From 1ae1b05fb19e0f374a676524f783a1162d406d2f Mon Sep 17 00:00:00 2001 From: ca1e Date: Wed, 22 Apr 2026 23:25:38 +0800 Subject: [PATCH 1/3] =?UTF-8?q?[cc=E6=8F=90=E4=BA=A4]=20feat:=20=E5=B0=86W?= =?UTF-8?q?PF=20AvalonEdit=E7=BC=96=E8=BE=91=E5=99=A8=E6=9B=BF=E6=8D=A2?= =?UTF-8?q?=E4=B8=BAAvalonia=20AvaloniaEdit=EF=BC=8C=E7=A7=BB=E9=99=A4UseW?= =?UTF-8?q?PF=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增ScriptEditorControl/ScriptEditorHost/EcsHighlightingLoader到Avalonia.Core - XSHD语法高亮文件从UI.Common迁移至Avalonia.Core - EasyConForm和MainForm改用WinFormsAvaloniaControlHost嵌入编辑器 - 集成SearchPanel搜索、CustomFoldingStrategy代码折叠、文件拖放 - 修复AvaloniaEdit 12 Load(string) stub问题(改用FileStream) - 修复编辑器焦点时菜单快捷键透传(移除ProcessCmdKey拦截) - 删除WPF相关代码:FindPanel、CodeCompletion、EcpCompletion --- src/EasyCon2.Avalonia.Core/AvaloniaRuntime.cs | 10 +- .../EasyCon2.Avalonia.Core.csproj | 5 + .../Editor/EcsHighlightingLoader.cs | 49 +++++ .../Editor/ScriptEditorControl.axaml | 11 + .../Editor/ScriptEditorControl.axaml.cs | 111 ++++++++++ .../Editor/ScriptEditorHost.cs | 13 ++ .../Resources/Scripts/Python-Mode.xshd | 104 ++++++++++ .../Resources/Scripts/ecp.xshd | 127 ++++++++++++ .../Resources/Scripts/lua.xshd | 156 ++++++++++++++ .../VPad/VPadService.cs | 5 +- src/EasyCon2/App/MainForm.Designer.cs | 25 +-- src/EasyCon2/App/MainForm.cs | 148 +++++-------- src/EasyCon2/EasyCon2.csproj | 8 - src/EasyCon2/EasyConForm.Designer.cs | 24 +-- src/EasyCon2/EasyConForm.Script.cs | 4 +- src/EasyCon2/EasyConForm.cs | 193 +++++++---------- src/EasyCon2/Forms/FindPanel.Designer.cs | 125 ----------- src/EasyCon2/Forms/FindPanel.cs | 58 ------ .../Helper/CodeCompletionController.cs | 194 ------------------ src/EasyCon2/Helper/CustomFoldingStrategy.cs | 25 +-- src/EasyCon2/Helper/EcpCompletionProvider.cs | 77 ------- src/EasyCon2/Helper/MouseTranslation.cs | 133 ++---------- src/EasyCon2/Models/EcpCompletionData.cs | 26 --- 23 files changed, 736 insertions(+), 895 deletions(-) create mode 100644 src/EasyCon2.Avalonia.Core/Editor/EcsHighlightingLoader.cs create mode 100644 src/EasyCon2.Avalonia.Core/Editor/ScriptEditorControl.axaml create mode 100644 src/EasyCon2.Avalonia.Core/Editor/ScriptEditorControl.axaml.cs create mode 100644 src/EasyCon2.Avalonia.Core/Editor/ScriptEditorHost.cs create mode 100644 src/EasyCon2.Avalonia.Core/Resources/Scripts/Python-Mode.xshd create mode 100644 src/EasyCon2.Avalonia.Core/Resources/Scripts/ecp.xshd create mode 100644 src/EasyCon2.Avalonia.Core/Resources/Scripts/lua.xshd delete mode 100644 src/EasyCon2/Forms/FindPanel.Designer.cs delete mode 100644 src/EasyCon2/Forms/FindPanel.cs delete mode 100644 src/EasyCon2/Helper/CodeCompletionController.cs delete mode 100644 src/EasyCon2/Helper/EcpCompletionProvider.cs delete mode 100644 src/EasyCon2/Models/EcpCompletionData.cs diff --git a/src/EasyCon2.Avalonia.Core/AvaloniaRuntime.cs b/src/EasyCon2.Avalonia.Core/AvaloniaRuntime.cs index 5a4052dd..531ff9a7 100644 --- a/src/EasyCon2.Avalonia.Core/AvaloniaRuntime.cs +++ b/src/EasyCon2.Avalonia.Core/AvaloniaRuntime.cs @@ -1,4 +1,5 @@ using Avalonia; +using Avalonia.Markup.Xaml.Styling; using Avalonia.Themes.Fluent; namespace EasyCon2.Avalonia.Core; @@ -16,6 +17,13 @@ public static void EnsureInitialized() .LogToTrace() .SetupWithoutStarting(); app.Instance.Styles.Add(new FluentTheme()); + + var editStyle = new StyleInclude(new Uri("avares://EasyCon2.Avalonia.Core")) + { + Source = new Uri("avares://AvaloniaEdit/Themes/Fluent/AvaloniaEdit.xaml") + }; + app.Instance.Styles.Add(editStyle); + _initialized = true; } -} \ No newline at end of file +} diff --git a/src/EasyCon2.Avalonia.Core/EasyCon2.Avalonia.Core.csproj b/src/EasyCon2.Avalonia.Core/EasyCon2.Avalonia.Core.csproj index a2df51a1..66e2b740 100644 --- a/src/EasyCon2.Avalonia.Core/EasyCon2.Avalonia.Core.csproj +++ b/src/EasyCon2.Avalonia.Core/EasyCon2.Avalonia.Core.csproj @@ -21,7 +21,12 @@ + + + + + diff --git a/src/EasyCon2.Avalonia.Core/Editor/EcsHighlightingLoader.cs b/src/EasyCon2.Avalonia.Core/Editor/EcsHighlightingLoader.cs new file mode 100644 index 00000000..468cbfa8 --- /dev/null +++ b/src/EasyCon2.Avalonia.Core/Editor/EcsHighlightingLoader.cs @@ -0,0 +1,49 @@ +using System.Reflection; +using System.Xml; +using AvaloniaEdit.Highlighting; +using AvaloniaEdit.Highlighting.Xshd; + +namespace EasyCon2.Avalonia.Core.Editor; + +public static class EcsHighlightingLoader +{ + private static bool _registered; + + public static void RegisterAll() + { + if (_registered) return; + + var asm = Assembly.GetExecutingAssembly(); + + RegisterFromResource(asm, "EasyCon2.Avalonia.Core.Resources.Scripts.ecp.xshd", + "ECScript", [".txt", ".ecs"]); + RegisterFromResource(asm, "EasyCon2.Avalonia.Core.Resources.Scripts.lua.xshd", + "Lua", [".lua"]); + RegisterFromResource(asm, "EasyCon2.Avalonia.Core.Resources.Scripts.Python-Mode.xshd", + "Python", [".py"]); + + _registered = true; + } + + public static IHighlightingDefinition? GetByExtension(string extension) + { + RegisterAll(); + return HighlightingManager.Instance.GetDefinitionByExtension(extension); + } + + public static IHighlightingDefinition? GetByName(string name) + { + RegisterAll(); + return HighlightingManager.Instance.GetDefinition(name); + } + + private static void RegisterFromResource(Assembly asm, string resourceName, + string name, string[] extensions) + { + using var stream = asm.GetManifestResourceStream(resourceName); + if (stream == null) return; + using var reader = XmlReader.Create(stream); + var definition = HighlightingLoader.Load(reader, HighlightingManager.Instance); + HighlightingManager.Instance.RegisterHighlighting(name, extensions, definition); + } +} diff --git a/src/EasyCon2.Avalonia.Core/Editor/ScriptEditorControl.axaml b/src/EasyCon2.Avalonia.Core/Editor/ScriptEditorControl.axaml new file mode 100644 index 00000000..664ffaaf --- /dev/null +++ b/src/EasyCon2.Avalonia.Core/Editor/ScriptEditorControl.axaml @@ -0,0 +1,11 @@ + + + + diff --git a/src/EasyCon2.Avalonia.Core/Editor/ScriptEditorControl.axaml.cs b/src/EasyCon2.Avalonia.Core/Editor/ScriptEditorControl.axaml.cs new file mode 100644 index 00000000..bc021a02 --- /dev/null +++ b/src/EasyCon2.Avalonia.Core/Editor/ScriptEditorControl.axaml.cs @@ -0,0 +1,111 @@ +using Avalonia.Controls; +using Avalonia.Input; +using AvaloniaEdit; +using AvaloniaEdit.Document; +using AvaloniaEdit.Highlighting; +using AvaloniaEdit.Search; + +namespace EasyCon2.Avalonia.Core.Editor; + +public partial class ScriptEditorControl : UserControl +{ + private readonly TextEditor _editor; + private readonly SearchPanel _searchPanel; + + public event EventHandler? EditorTextChanged; + public event Action? FileDropped; + + public TextEditor TextEditor => _editor; + + public string Text + { + get => _editor.Text; + set => _editor.Text = value; + } + + public bool IsModified + { + get => _editor.IsModified; + set => _editor.IsModified = value; + } + + public bool IsReadOnly + { + get => _editor.IsReadOnly; + set => _editor.IsReadOnly = value; + } + + public string? FileName + { + get => _editor.Document?.FileName; + set + { + if (_editor.Document != null) + _editor.Document.FileName = value; + } + } + + public IHighlightingDefinition? SyntaxHighlighting + { + get => _editor.SyntaxHighlighting; + set => _editor.SyntaxHighlighting = value; + } + + public int SelectionStart => _editor.SelectionStart; + public int SelectionLength => _editor.SelectionLength; + public string SelectedText => _editor.SelectedText; + + public ScriptEditorControl() + { + InitializeComponent(); + _editor = this.FindControl("Editor")!; + _editor.TextChanged += (_, _) => EditorTextChanged?.Invoke(this, EventArgs.Empty); + + SetupDragDrop(); + _searchPanel = SearchPanel.Install(_editor); + } + + public void OpenSearchPanel() => _searchPanel.Open(); + + private void SetupDragDrop() + { + DragDrop.SetAllowDrop(_editor, true); + DragDrop.AddDragOverHandler(_editor, OnDragOver); + DragDrop.AddDropHandler(_editor, OnDrop); + } + + private void OnDragOver(object? sender, DragEventArgs e) + { + e.DragEffects = e.DataTransfer.Contains(DataFormat.File) + ? DragDropEffects.Copy : DragDropEffects.None; + } + + private void OnDrop(object? sender, DragEventArgs e) + { + if (!e.DataTransfer.Contains(DataFormat.File)) return; + var files = e.DataTransfer.TryGetFiles(); + var path = files?.FirstOrDefault()?.Path.LocalPath; + if (path != null) + FileDropped?.Invoke(path); + } + + public void Load(string path) + { + using var stream = File.OpenRead(path); + _editor.Load(stream); + FileName = path; + } + public void Save(string path) + { + using var stream = File.Create(path); + _editor.Save(stream); + } + public void Clear() => _editor.Clear(); + public void ScrollToLine(int line) => _editor.ScrollToLine(line); + public void ScrollToHome() => _editor.ScrollToHome(); + public void Select(int offset, int length) => _editor.Select(offset, length); + + public IDocument Document => _editor.Document; + public TextDocument TextDocument => _editor.Document; + public AvaloniaEdit.Editing.TextArea TextArea => _editor.TextArea; +} diff --git a/src/EasyCon2.Avalonia.Core/Editor/ScriptEditorHost.cs b/src/EasyCon2.Avalonia.Core/Editor/ScriptEditorHost.cs new file mode 100644 index 00000000..eeaab5d7 --- /dev/null +++ b/src/EasyCon2.Avalonia.Core/Editor/ScriptEditorHost.cs @@ -0,0 +1,13 @@ +namespace EasyCon2.Avalonia.Core.Editor; + +public static class ScriptEditorHost +{ + public static ScriptEditorControl CreateControl() + { + AvaloniaRuntime.EnsureInitialized(); + EcsHighlightingLoader.RegisterAll(); + var control = new ScriptEditorControl(); + control.SyntaxHighlighting = EcsHighlightingLoader.GetByName("ECScript"); + return control; + } +} diff --git a/src/EasyCon2.Avalonia.Core/Resources/Scripts/Python-Mode.xshd b/src/EasyCon2.Avalonia.Core/Resources/Scripts/Python-Mode.xshd new file mode 100644 index 00000000..23091d63 --- /dev/null +++ b/src/EasyCon2.Avalonia.Core/Resources/Scripts/Python-Mode.xshd @@ -0,0 +1,104 @@ + + + + + + + + + + + + + TODO + FIXME + + + HACK + UNDONE + + + + + + \# + + + + ''' + ''' + + + """ + """ + + + + " + " + + + + + + ' + ' + + + + + + + and + as + assert + break + class + continue + def + del + elif + else + except + exec + False + finally + for + from + global + if + import + in + is + lambda + None + nonlocal + not + or + pass + print + raise + return + True + try + while + with + yield + async + await + + + + \b + [\d\w_]+ # an identifier + (?=\s*\() # followed by ( + + + \b0[xX][0-9a-fA-F]+ # hex number + ( \b\d+(\.[0-9]+)? #number with optional floating point + | \.[0-9]+ #or just starting with floating point + ) + ([eE][+-]?[0-9]+)? # optional exponent + + + \ No newline at end of file diff --git a/src/EasyCon2.Avalonia.Core/Resources/Scripts/ecp.xshd b/src/EasyCon2.Avalonia.Core/Resources/Scripts/ecp.xshd new file mode 100644 index 00000000..cc689827 --- /dev/null +++ b/src/EasyCon2.Avalonia.Core/Resources/Scripts/ecp.xshd @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + TODO + FIXME + + + HACK + UNDONE + + + + + + + + + " + " + + + ' + ' + + + + \$[\w]+ + \@[\w]+ + _[\w]+ + + + \b\d+(\.\d+)?([eE][+-]?\d+)? + + + IMPORT + FOR + TO + STEP + NEXT + BREAK + CONTINUE + WHILE + END + IF + ELIF + ELSE + ENDIF + FUNC + RETURN + ENDFUNC + CALL + and + or + + + + RAND + TIME + PRINT + ALERT + WAIT + AMIIBO + BEEP + + + + A + B + X + Y + L + R + ZL + ZR + PLUS + MINUS + LCLICK + RCLICK + HOME + CAPTURE + UP + UPRIGHT + RIGHT + DOWNRIGHT + DOWN + DOWNLEFT + LEFT + UPLEFT + LS + RS + + + + + \ No newline at end of file diff --git a/src/EasyCon2.Avalonia.Core/Resources/Scripts/lua.xshd b/src/EasyCon2.Avalonia.Core/Resources/Scripts/lua.xshd new file mode 100644 index 00000000..22151ce3 --- /dev/null +++ b/src/EasyCon2.Avalonia.Core/Resources/Scripts/lua.xshd @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + TODO + FIXME + + + HACK + UNDONE + + + + + + + + --- + + + + + + + + + --\[[=]*\[ + \][=]*] + + + + + -- + + + + " + " + + + + + + + + ' + ' + + + + + + + + \[[=]*\[ + \][=]*] + + + + true + false + + + + and + break + do + else + elseif + end + false + for + function + if + in + local + + not + or + repeat + return + then + true + until + while + using + continue + + + + break + return + + + + local + + + + nil + + + + + \b + [\d\w_]+ # an identifier + (?=\s*\() # followed by ( + + + \b + [\d\w_]+ # an identifier + (?=\s*\") # followed by " + + + \b + [\d\w_]+ # an identifier + (?=\s*\') # followed by ' + + + \b + [\d\w_]+ # an identifier + (?=\s*\{) # followed by { + + + \b + [\d\w_]+ # an identifier + (?=\s*\[) # followed by [ + + + + + \b0[xX][0-9a-fA-F]+ # hex number + | + ( \b\d+(\.[0-9]+)? #number with optional floating point + | \.[0-9]+ #or just starting with floating point + ) + ([eE][+-]?[0-9]+)? # optional exponent + + + + [?,.;()\[\]{}+\-/%*<>^+~!|&]+ + + + \ No newline at end of file diff --git a/src/EasyCon2.Avalonia.Core/VPad/VPadService.cs b/src/EasyCon2.Avalonia.Core/VPad/VPadService.cs index 0969707b..53f13830 100644 --- a/src/EasyCon2.Avalonia.Core/VPad/VPadService.cs +++ b/src/EasyCon2.Avalonia.Core/VPad/VPadService.cs @@ -80,8 +80,5 @@ private void RegisterEscapeKey() () => false); } - public void UnregisterAllKeys() - { - _keyBinder.UnregisterAllKeys(); - } + public void Deactivate() => Active = false; } \ No newline at end of file diff --git a/src/EasyCon2/App/MainForm.Designer.cs b/src/EasyCon2/App/MainForm.Designer.cs index cb65e5ea..e5bba229 100644 --- a/src/EasyCon2/App/MainForm.Designer.cs +++ b/src/EasyCon2/App/MainForm.Designer.cs @@ -1,3 +1,4 @@ +using Avalonia.Win32.Interoperability; using EasyCon2.Forms; namespace EasyCon2.App; @@ -47,7 +48,7 @@ private void InitializeComponent() menuItemAbout = new ToolStripMenuItem(); mainSplit = new SplitContainer(); contentPanel = new Panel(); - editorHost = new System.Windows.Forms.Integration.ElementHost(); + editorHost = new WinFormsAvaloniaControlHost(); logPanel = new Panel(); clsLogBtn = new Button(); logTxtBox = new RichLogBox(); @@ -80,7 +81,6 @@ private void InitializeComponent() btnCheckUpdate = new Button(); btnSource = new Button(); scriptTitleLabel = new Label(); - findPanel1 = new FindPanel(); sideBar = new Panel(); btnPageLog = new Button(); btnPageEditor = new Button(); @@ -361,7 +361,6 @@ private void InitializeComponent() contentPanel.Controls.Add(burnPanel); contentPanel.Controls.Add(settingsPanel); contentPanel.Controls.Add(scriptTitleLabel); - contentPanel.Controls.Add(findPanel1); contentPanel.Dock = DockStyle.Fill; contentPanel.Location = new Point(40, 0); contentPanel.Name = "contentPanel"; @@ -812,20 +811,7 @@ private void InitializeComponent() scriptTitleLabel.Text = "未命名脚本"; scriptTitleLabel.TextAlign = ContentAlignment.MiddleLeft; scriptTitleLabel.Visible = false; - // - // findPanel1 - // - findPanel1.Anchor = AnchorStyles.Top | AnchorStyles.Right; - findPanel1.BackColor = Color.Transparent; - findPanel1.BorderStyle = BorderStyle.FixedSingle; - findPanel1.Location = new Point(349, 17); - findPanel1.Name = "findPanel1"; - findPanel1.Replaced = null; - findPanel1.Size = new Size(250, 98); - findPanel1.TabIndex = 2; - findPanel1.Target = null; - findPanel1.Visible = false; - // + // // sideBar // sideBar.BackColor = Color.FromArgb(230, 229, 224); @@ -1292,7 +1278,7 @@ private void InitializeComponent() private ToolStripMenuItem menuItemAbout; // Editor - private System.Windows.Forms.Integration.ElementHost editorHost; + private WinFormsAvaloniaControlHost editorHost; private Label scriptTitleLabel; // Sidebar @@ -1339,9 +1325,6 @@ private void InitializeComponent() private RichLogBox logTxtBox; private Button clsLogBtn; - // Find - private FindPanel findPanel1; - // Status private StatusStrip statusStrip; private ToolStripStatusLabel toolStripStatusLabel1; diff --git a/src/EasyCon2/App/MainForm.cs b/src/EasyCon2/App/MainForm.cs index 46271e37..807958c7 100644 --- a/src/EasyCon2/App/MainForm.cs +++ b/src/EasyCon2/App/MainForm.cs @@ -3,6 +3,7 @@ using EasyCon.Script.Assembly; using EasyCon2.App.Models; using EasyCon2.App.Services; +using EasyCon2.Avalonia.Core.Editor; using EasyCon2.Avalonia.Core.VPad; using EasyCon2.Forms; using EasyCon2.Helper; @@ -10,10 +11,8 @@ using EasyCon2.Views; using EasyDevice; using EasyScript; -using ICSharpCode.AvalonEdit; -using ICSharpCode.AvalonEdit.Folding; -using ICSharpCode.AvalonEdit.Highlighting; -using ICSharpCode.AvalonEdit.Highlighting.Xshd; +using AvaloniaEdit.Folding; +using AvaloniaEdit.Highlighting; using System.Diagnostics; using System.IO; using System.Linq; @@ -21,7 +20,6 @@ using System.Net.Http; using System.Reflection; using System.Text.Json; -using System.Xml; using AvaColor = Avalonia.Media.Color; using AvaColors = Avalonia.Media.Colors; @@ -43,8 +41,7 @@ public partial class MainForm : Form, IOutputAdapter, IControllerAdapter private readonly AppState _state = new(); // Editor - private readonly TextEditor _textEditor = new(); - private CodeCompletionController? _completionController; + private ScriptEditorControl _textEditor; private FoldingManager? _foldingManager; private CustomFoldingStrategy? _foldingStrategy; private VPadService? _vpadService; @@ -80,45 +77,28 @@ public MainForm() private void InitEditor() { - _textEditor.ShowLineNumbers = true; - - // Syntax highlighting - var ecpHighlighting = HighlightingLoader.Load( - XmlReader.Create(new MemoryStream(Resources.ecp)), - HighlightingManager.Instance); - HighlightingManager.Instance.RegisterHighlighting("ECScript", [".txt", ".ecs"], ecpHighlighting); - - var luaHighlighting = HighlightingLoader.Load( - XmlReader.Create(new MemoryStream(Resources.lua)), - HighlightingManager.Instance); - HighlightingManager.Instance.RegisterHighlighting("Lua", [".lua"], luaHighlighting); - - var pyHighlighting = HighlightingLoader.Load( - XmlReader.Create(new MemoryStream(Resources.Python_Mode)), - HighlightingManager.Instance); - HighlightingManager.Instance.RegisterHighlighting("Python", [".py"], pyHighlighting); - - _textEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("ECScript"); - _textEditor.DragEnter += TextEditor_DragEnter; - _textEditor.Drop += TextEditor_DragDrop; - _textEditor.TextChanged += TextEditor_TextChanged; - - // Code completion - var completionProvider = new EcpCompletionProvider(_textEditor); - completionProvider.GetImgLabel += () => _captureService.LoadedLabels.Select(il => il.name); - _completionController = new CodeCompletionController( - _textEditor, completionProvider, _configService.Config.EnableAutoCompletion); + _textEditor = ScriptEditorHost.CreateControl(); + _textEditor.EditorTextChanged += TextEditor_TextChanged; + _textEditor.FileDropped += (path) => + { + try + { + if (!FileClose()) return; + FileOpen(path); + } + catch + { + MessageBox.Show("打开失败了,原因未知", "打开脚本"); + } + }; // Folding _foldingManager = FoldingManager.Install(_textEditor.TextArea); _foldingStrategy = new CustomFoldingStrategy(); - _foldingStrategy.UpdateFoldings(_foldingManager, _textEditor.Document); - - // Find panel - findPanel1.InitEditor(_textEditor); + _foldingStrategy.UpdateFoldings(_foldingManager, _textEditor.TextDocument); - // Host AvalonEdit in ElementHost - editorHost.Child = _textEditor; + // Host in Avalonia control host + editorHost.Content = _textEditor; } private void InitServices() @@ -289,7 +269,7 @@ private async void runStopBtn_Click(object sender, EventArgs e) // Compile first var externalGetters = _captureService.BuildExternalGetters(); var (success, errorLine, error) = _scriptService.Compile( - _textEditor.Text, _textEditor.Document.FileName, externalGetters); + _textEditor.Text, _textEditor.TextDocument.FileName, externalGetters); if (!success) { @@ -322,6 +302,7 @@ private async void runStopBtn_Click(object sender, EventArgs e) _state.ScriptStartTime = DateTime.Now; _state.ScriptRunning = true; + _vpadService?.Deactivate(); var pad = new GamePadAdapter(_deviceService.Device); _scriptService.Run(this, pad); @@ -340,7 +321,7 @@ private async void formatBtn_Click(object sender, EventArgs e) { var externalGetters = _captureService.BuildExternalGetters(); var (success, formatted, errorLine, error) = _scriptService.Format( - _textEditor.Text, _textEditor.Document.FileName, externalGetters); + _textEditor.Text, _textEditor.TextDocument.FileName, externalGetters); if (success) { @@ -555,7 +536,7 @@ private void btnFlash_Click(object sender, EventArgs e) // Compile var externalGetters = _captureService.BuildExternalGetters(); var (success, errorLine, error) = _scriptService.Compile( - _textEditor.Text, _textEditor.Document.FileName, externalGetters); + _textEditor.Text, _textEditor.TextDocument.FileName, externalGetters); if (!success) { MessageBox.Show(errorLine != null ? $"{errorLine}:{error}" : error, "编译出错"); @@ -631,7 +612,7 @@ private void btnGenFirmware_Click(object sender, EventArgs e) // Compile var externalGetters = _captureService.BuildExternalGetters(); var (success, errorLine, error) = _scriptService.Compile( - _textEditor.Text, _textEditor.Document.FileName, externalGetters); + _textEditor.Text, _textEditor.TextDocument.FileName, externalGetters); if (!success) { MessageBox.Show(errorLine != null ? $"{errorLine}:{error}" : error, "编译出错"); @@ -813,9 +794,8 @@ private bool FileOpen(string? path = null) _textEditor.Load(filePath); _textEditor.IsModified = false; - _textEditor.Document.FileName = filePath; - _textEditor.SyntaxHighlighting = HighlightingManager.Instance - .GetDefinitionByExtension(Path.GetExtension(filePath)); + _textEditor.FileName = filePath; + _textEditor.SyntaxHighlighting = EcsHighlightingLoader.GetByExtension(Path.GetExtension(filePath)); _state.CurrentFilePath = filePath; _state.IsModified = false; StatusShow("文件已打开"); @@ -836,7 +816,7 @@ private bool FileSave(bool asNew = false) if (dlg.ShowDialog() != DialogResult.OK) return false; _state.CurrentFilePath = dlg.FileName; - _textEditor.Document.FileName = dlg.FileName; + _textEditor.FileName = dlg.FileName; } _textEditor.Save(_state.CurrentFilePath); @@ -856,39 +836,18 @@ private bool FileClose() } _state.CurrentFilePath = null; _state.IsModified = false; - _textEditor.Document.FileName = null; + _textEditor.FileName = null; _textEditor.Clear(); _textEditor.IsModified = false; StatusShow("文件已关闭"); return true; } - private void TextEditor_DragEnter(object? sender, System.Windows.DragEventArgs e) - { - e.Effects = e.Data.GetDataPresent(DataFormats.FileDrop) - ? System.Windows.DragDropEffects.All - : System.Windows.DragDropEffects.None; - } - - private void TextEditor_DragDrop(object? sender, System.Windows.DragEventArgs e) - { - try - { - var path = (string[])e.Data.GetData(DataFormats.FileDrop, false); - if (!FileClose()) return; - FileOpen(path[0]); - } - catch - { - MessageBox.Show("打开失败了,原因未知", "打开脚本"); - } - } - private void TextEditor_TextChanged(object? sender, EventArgs e) { _state.IsModified = _textEditor.IsModified; if (_configService.Config.EnableAutoCompletion) - _foldingStrategy?.UpdateFoldings(_foldingManager!, _textEditor.Document); + _foldingStrategy?.UpdateFoldings(_foldingManager!, _textEditor.TextDocument); _scriptService.Reset(); } @@ -898,52 +857,41 @@ private void TextEditor_TextChanged(object? sender, EventArgs e) private void menuItemFindReplace_Click(object sender, EventArgs e) { - if (_textEditor.SelectedText.Length > 0) - findPanel1.Target = _textEditor.SelectedText; - findPanel1.Show(); - findPanel1.BringToFront(); + _textEditor.OpenSearchPanel(); } private void menuItemFindNext_Click(object sender, EventArgs e) { - if (_textEditor.SelectedText.Length > 0) - findPanel1.Target = _textEditor.SelectedText; - var index = findPanel1.Find(); - if (index == -1) - { - MessageBox.Show("到底了"); - return; - } - _textEditor.Select(index, findPanel1.Target!.Length); - _textEditor.ScrollToLine(_textEditor.Document.GetLineByOffset(index).LineNumber); + // SearchPanel 内部处理 F3 快捷键 } private void menuItemToggleComment_Click(object sender, EventArgs e) { int startOffset = _textEditor.SelectionStart; int endOffset = startOffset + _textEditor.SelectionLength; - var startLine = _textEditor.Document.GetLineByOffset(startOffset); - var endLine = _textEditor.Document.GetLineByOffset(endOffset); + var doc = _textEditor.TextDocument; + var startLine = doc.GetLineByOffset(startOffset); + var endLine = doc.GetLineByOffset(endOffset); var docomment = false; for (int lineNum = endLine.LineNumber; lineNum >= startLine.LineNumber; lineNum--) { - var line = _textEditor.Document.GetLineByNumber(lineNum); - if (Scripter.CanComment(_textEditor.Document.GetText(line))) + var line = doc.GetLineByNumber(lineNum); + if (Scripter.CanComment(doc.GetText(line))) { docomment = true; break; } } - using (_textEditor.Document.RunUpdate()) + using (doc.RunUpdate()) { for (int lineNum = endLine.LineNumber; lineNum >= startLine.LineNumber; lineNum--) { - var line = _textEditor.Document.GetLineByNumber(lineNum); - var text = _textEditor.Document.GetText(line); + var line = doc.GetLineByNumber(lineNum); + var text = doc.GetText(line); text = Scripter.ToggleComment(text, docomment); - _textEditor.Document.Replace(line, text); + doc.Replace(line, text); } } } @@ -956,8 +904,6 @@ private void chkAutoCompletion_CheckedChanged(object sender, EventArgs e) { _configService.Config.EnableAutoCompletion = chkAutoCompletion.Checked; _configService.Save(); - if (_completionController != null) - _completionController.EnableAutoCompletion = chkAutoCompletion.Checked; } private void chkFolding_CheckedChanged(object sender, EventArgs e) @@ -965,7 +911,7 @@ private void chkFolding_CheckedChanged(object sender, EventArgs e) _configService.Config.ShowControllerHelp = chkFolding.Checked; _configService.Save(); if (chkFolding.Checked) - _foldingStrategy?.UpdateFoldings(_foldingManager!, _textEditor.Document); + _foldingStrategy?.UpdateFoldings(_foldingManager!, _textEditor.TextDocument); else _foldingManager?.Clear(); } @@ -1183,11 +1129,11 @@ public void Alert(string message) #region Form Lifecycle - protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + protected override bool ProcessDialogKey(Keys keyData) { - if (keyData == Keys.Escape) - findPanel1.Hide(); - return base.ProcessCmdKey(ref msg, keyData); + if (editorHost.ContainsFocus) + return false; + return base.ProcessDialogKey(keyData); } private void MainForm_FormClosing(object sender, FormClosingEventArgs e) diff --git a/src/EasyCon2/EasyCon2.csproj b/src/EasyCon2/EasyCon2.csproj index 4fbef52d..19e5f54e 100644 --- a/src/EasyCon2/EasyCon2.csproj +++ b/src/EasyCon2/EasyCon2.csproj @@ -1,41 +1,33 @@ - WinExe warnings true - true net10.0-windows7.0 true ..\EasyCon2.UI.Common\Resources\Icons\favicon.ico app.manifest $(NoWarn);WFO1000 - - - - .\deps\libamiibo.dll - Always - diff --git a/src/EasyCon2/EasyConForm.Designer.cs b/src/EasyCon2/EasyConForm.Designer.cs index 295f55f8..029e459c 100644 --- a/src/EasyCon2/EasyConForm.Designer.cs +++ b/src/EasyCon2/EasyConForm.Designer.cs @@ -1,3 +1,5 @@ +using Avalonia.Win32.Interoperability; + namespace EasyCon2.Forms { partial class EasyConForm @@ -84,9 +86,8 @@ private void InitializeComponent() groupBox2 = new GroupBox(); genFwButton = new Button(); comboBoxBoardType = new ComboBox(); - findPanel1 = new FindPanel(); buttonShowController = new Button(); - editorHost = new System.Windows.Forms.Integration.ElementHost(); + editorHost = new WinFormsAvaloniaControlHost(); groupBox3 = new GroupBox(); ComPort = new ComboBox(); buttonSerialPortConnect = new Button(); @@ -601,20 +602,7 @@ private void InitializeComponent() comboBoxBoardType.Size = new Size(94, 28); comboBoxBoardType.TabIndex = 5; // - // findPanel1 - // - findPanel1.AccessibleName = "查找窗口"; - findPanel1.Anchor = AnchorStyles.Top | AnchorStyles.Right; - findPanel1.BackColor = Color.Transparent; - findPanel1.BorderStyle = BorderStyle.FixedSingle; - findPanel1.Location = new Point(184, 17); - findPanel1.Name = "findPanel1"; - findPanel1.Replaced = null; - findPanel1.Size = new Size(250, 98); - findPanel1.TabIndex = 1; - findPanel1.Target = null; - findPanel1.Visible = false; - // + // // buttonShowController // buttonShowController.AccessibleName = "打开虚拟手柄"; @@ -876,7 +864,6 @@ private void InitializeComponent() // scriptContainer // scriptContainer.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; - scriptContainer.Controls.Add(findPanel1); scriptContainer.Controls.Add(scriptTitleLabel); scriptContainer.Controls.Add(scriptPanel); scriptContainer.Location = new Point(313, 32); @@ -981,7 +968,7 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripMenuItem 采集卡类型ToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem 设置ToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem 显示调试信息ToolStripMenuItem; - private System.Windows.Forms.Integration.ElementHost editorHost; + private WinFormsAvaloniaControlHost editorHost; private ToolStripMenuItem 推送设置ToolStripMenuItem; private ToolStripMenuItem 代码自动补全ToolStripMenuItem; private ToolStripMenuItem 蓝牙ToolStripMenuItem; @@ -1001,7 +988,6 @@ private void InitializeComponent() private ToolStripMenuItem 編輯ToolStripMenuItem; private ToolStripMenuItem 查找替換ToolStripMenuItem; private ToolStripMenuItem 查找下一个ToolStripMenuItem; - private FindPanel findPanel1; private ToolStripMenuItem 显示折叠ToolStripMenuItem; private ToolStripMenuItem 注释取消注释ToolStripMenuItem; private ToolStripMenuItem 脚本语法ToolStripMenuItem; diff --git a/src/EasyCon2/EasyConForm.Script.cs b/src/EasyCon2/EasyConForm.Script.cs index a1ccdf01..69178fd3 100644 --- a/src/EasyCon2/EasyConForm.Script.cs +++ b/src/EasyCon2/EasyConForm.Script.cs @@ -33,7 +33,7 @@ private async Task ScriptCompile() })); try { - var diag = _program.Parse(textEditor.Text, textEditor.Document.FileName, externalGetters); + var diag = _program.Parse(scriptEditor.Text, scriptEditor.FileName, externalGetters); if (diag.HasErrors()) { var d1 = diag.Where(d => d.IsError).First(); @@ -76,7 +76,7 @@ private async void ScriptRun() } } - _vpadService?.UnregisterAllKeys(); + _vpadService?.Deactivate(); StatusShowLog("开始运行"); scriptRunning = true; diff --git a/src/EasyCon2/EasyConForm.cs b/src/EasyCon2/EasyConForm.cs index c53b079f..5172a2b2 100644 --- a/src/EasyCon2/EasyConForm.cs +++ b/src/EasyCon2/EasyConForm.cs @@ -1,16 +1,15 @@ using EasyCon.Core; using EasyCon.Core.Config; using EasyCon.Script.Assembly; +using EasyCon2.Avalonia.Core.Editor; using EasyCon2.Avalonia.Core.VPad; using EasyCon2.Helper; using Resources = EasyCon2.UI.Common.Properties.Resources; using EasyCon2.Views; using EasyDevice; using EasyScript; -using ICSharpCode.AvalonEdit; -using ICSharpCode.AvalonEdit.Folding; -using ICSharpCode.AvalonEdit.Highlighting; -using ICSharpCode.AvalonEdit.Highlighting.Xshd; +using AvaloniaEdit.Folding; +using AvaloniaEdit.Highlighting; using System.Diagnostics; using System.IO; using System.Media; @@ -18,7 +17,6 @@ using System.Reflection; using System.Text.Json; using System.Text.RegularExpressions; -using System.Xml; using AvaColor = Avalonia.Media.Color; using AvaColors = Avalonia.Media.Colors; @@ -28,8 +26,7 @@ public partial class EasyConForm : Form, IOutputAdapter, IControllerAdapter { private readonly string VER = Assembly.GetEntryAssembly()?.GetCustomAttribute() ?.InformationalVersion; - private readonly TextEditor textEditor = new(); - private CodeCompletionController _completionController; + private ScriptEditorControl scriptEditor; private VPadService? _vpadService; private NintendoSwitch NS = new(); @@ -47,9 +44,9 @@ public partial class EasyConForm : Form, IOutputAdapter, IControllerAdapter const string FirmwarePath = @"Firmware\"; private readonly string defaultName = "未命名脚本"; - private string fileName => textEditor.Document.FileName == null ? defaultName : Path.GetFileName(textEditor.Document.FileName); + private string fileName => scriptEditor.FileName == null ? defaultName : Path.GetFileName(scriptEditor.FileName); - private string curILPath => textEditor.Document.FileName == null ? "" : Path.Combine(Path.GetDirectoryName(textEditor.Document.FileName), "ImgLabel"); + private string curILPath => scriptEditor.FileName == null ? "" : Path.Combine(Path.GetDirectoryName(scriptEditor.FileName), "ImgLabel"); private readonly List captureTypes = []; @@ -60,13 +57,11 @@ public EasyConForm() LoadConfig(); } - protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + protected override bool ProcessDialogKey(Keys keyData) { - if (keyData == Keys.Escape) - { - findPanel1.Hide(); - } - return base.ProcessCmdKey(ref msg, keyData); + if (editorHost.ContainsFocus) + return false; + return base.ProcessDialogKey(keyData); } protected override void WndProc(ref Message m) @@ -88,8 +83,8 @@ protected override void WndProc(ref Message m) base.WndProc(ref m); } - private FoldingManager _foldingManager; - private CustomFoldingStrategy _foldingStrategy; + private FoldingManager? _foldingManager; + private CustomFoldingStrategy? _foldingStrategy; private void EasyConForm_Load(object sender, EventArgs e) { @@ -127,29 +122,26 @@ private void EasyConForm_FormClosing(object sender, FormClosingEventArgs e) private void InitEditor() { - textEditor.ShowLineNumbers = true; - var syntaxHighlighting = HighlightingLoader.Load(XmlReader.Create(new MemoryStream(Resources.ecp)), HighlightingManager.Instance); - HighlightingManager.Instance.RegisterHighlighting("ECScript", [".txt", ".ecs"], syntaxHighlighting); - var luaHighlighting = HighlightingLoader.Load(XmlReader.Create(new MemoryStream(Resources.lua)), HighlightingManager.Instance); - HighlightingManager.Instance.RegisterHighlighting("Lua", [".lua"], luaHighlighting); - var pyHighlighting = HighlightingLoader.Load(XmlReader.Create(new MemoryStream(Resources.Python_Mode)), HighlightingManager.Instance); - HighlightingManager.Instance.RegisterHighlighting("Python", [".py"], pyHighlighting); - textEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("ECScript"); - textEditor.DragEnter += new System.Windows.DragEventHandler(this.textBoxScript_DragEnter); - textEditor.Drop += new System.Windows.DragEventHandler(this.textBoxScript_DragDrop); - textEditor.TextChanged += new EventHandler(this.textBoxScript_TextChanged); - - var completionProvider = new EcpCompletionProvider(textEditor); - completionProvider.GetImgLabel += () => captureVideo.LoadedLabels.Select(il => il.name); - _completionController = new CodeCompletionController(textEditor, completionProvider, _config.EnableAutoCompletion); - - _foldingManager = FoldingManager.Install(textEditor.TextArea); - _foldingStrategy = new CustomFoldingStrategy(); - _foldingStrategy.UpdateFoldings(_foldingManager, textEditor.Document); + scriptEditor = ScriptEditorHost.CreateControl(); + scriptEditor.EditorTextChanged += textBoxScript_TextChanged; + scriptEditor.FileDropped += (path) => + { + try + { + if (!FileClose()) return; + FileOpen(path); + } + catch + { + MessageBox.Show("打开失败了,原因未知", "打开脚本"); + } + }; - findPanel1.InitEditor(textEditor); + _foldingManager = FoldingManager.Install(scriptEditor.TextArea); + _foldingStrategy = new CustomFoldingStrategy(); + _foldingStrategy.UpdateFoldings(_foldingManager, scriptEditor.TextDocument); - editorHost.Child = textEditor; + editorHost.Content = scriptEditor; } private void InitEvent() @@ -294,13 +286,13 @@ private void UpdateUI() Invoke(delegate { // script edited - scriptTitleLabel.Text = textEditor.IsModified ? $"{fileName}(已编辑)" : fileName; + scriptTitleLabel.Text = scriptEditor.IsModified ? $"{fileName}(已编辑)" : fileName; // update record script to text if (NS.recordState == RecordState.RECORD_START) { buttonRecord.Text = "停止录制"; - textEditor.Text = NS.GetRecordScript(); - this.textEditor.ScrollToHome(); + scriptEditor.Text = NS.GetRecordScript(); + scriptEditor.ScrollToHome(); } if (captureVideo.IsOpened) { @@ -377,7 +369,7 @@ private void StatusShowLog(string str) private void ScriptSelectLine(int index) { - textEditor.ScrollToLine(index); + scriptEditor.ScrollToLine(index); } private Board GetSelectedBoard() @@ -512,10 +504,10 @@ private bool FileOpen(string path = "") _currentFile = openFileDialog.FileName; } - textEditor.Load(_currentFile); - textEditor.IsModified = false; - textEditor.Document.FileName = _currentFile; - textEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinitionByExtension(Path.GetExtension(_currentFile)); + scriptEditor.Load(_currentFile); + scriptEditor.IsModified = false; + scriptEditor.FileName = _currentFile; + scriptEditor.SyntaxHighlighting = EcsHighlightingLoader.GetByExtension(Path.GetExtension(_currentFile)); StatusShowLog("文件已打开"); return true; } @@ -529,22 +521,22 @@ private bool FileSave(bool saveAs = false) saveFileDialog.Filter = "脚本文件 (*.txt,*.ecs)|*.txt;*.ecs|所有文件(*.*)|*.*"; saveFileDialog.FileName = "未命名脚本.txt"; - if (saveAs || textEditor.Document.FileName == null) + if (saveAs || scriptEditor.FileName == null) { if (saveFileDialog.ShowDialog() != DialogResult.OK) return false; - textEditor.Document.FileName = saveFileDialog.FileName; + scriptEditor.FileName = saveFileDialog.FileName; } - textEditor.Save(textEditor.Document.FileName); - textEditor.IsModified = false; + scriptEditor.Save(scriptEditor.FileName); + scriptEditor.IsModified = false; StatusShowLog("文件已保存"); return true; } private bool FileClose() { - if (textEditor.IsModified) + if (scriptEditor.IsModified) { var r = MessageBox.Show("文件已编辑,是否保存?", "", MessageBoxButtons.YesNoCancel); if (r == DialogResult.Cancel) @@ -555,9 +547,9 @@ private bool FileClose() return false; } } - textEditor.Document.FileName = null; - textEditor.Clear(); - textEditor.IsModified = false; + scriptEditor.FileName = null; + scriptEditor.Clear(); + scriptEditor.IsModified = false; StatusShowLog("文件已关闭"); return true; } @@ -600,8 +592,8 @@ private async void compileButton_Click(object sender, EventArgs e) // 格式化代码并确保逗号后面总是有空格 var formattedCode = _program.ToCode().Trim(); formattedCode = Regex.Replace(formattedCode, ",(?! )", ", "); - textEditor.Text = formattedCode; - textEditor.Select(0, 0); + scriptEditor.Text = formattedCode; + scriptEditor.Select(0, 0); } else { @@ -618,10 +610,10 @@ private void 执行ToolStripMenuItem_Click(object sender, EventArgs e) } } - private void textBoxScript_TextChanged(object sender, EventArgs e) + private void textBoxScript_TextChanged(object? sender, EventArgs e) { - if (显示折叠ToolStripMenuItem.Checked) - _foldingStrategy.UpdateFoldings(_foldingManager, textEditor.Document); + if (显示折叠ToolStripMenuItem.Checked && _foldingStrategy != null && _foldingManager != null) + _foldingStrategy.UpdateFoldings(_foldingManager, scriptEditor.TextDocument); ScriptReset(); } @@ -630,7 +622,7 @@ private void buttonScriptRunStop_Click(object sender, EventArgs e) runStopBtn.Enabled = false; if (!scriptRunning) { - if (textEditor.Document.FileName != null && textEditor.IsModified) + if (scriptEditor.FileName != null && scriptEditor.IsModified) { MessageBox.Show("您还没有保存脚本,请先保存后再运行"); } @@ -773,7 +765,7 @@ private void buttonRecord_Click(object sender, EventArgs e) } buttonRecord.Text = "停止录制"; buttonRecordPause.Enabled = true; - textEditor.IsEnabled = false; + scriptEditor.IsReadOnly = true; NS.StartRecord(); } else @@ -781,7 +773,7 @@ private void buttonRecord_Click(object sender, EventArgs e) Debug.Write("stop"); buttonRecord.Text = "录制脚本"; buttonRecordPause.Enabled = false; - textEditor.IsEnabled = true; + scriptEditor.IsReadOnly = false; NS.StopRecord(); } } @@ -795,33 +787,6 @@ private void buttonRecordPause_Click(object sender, EventArgs e) } } - private void textBoxScript_DragDrop(object sender, System.Windows.DragEventArgs e) - { - try - { - var path = (string[])e.Data.GetData(DataFormats.FileDrop, false); - if (!FileClose()) - return; - FileOpen(path[0]); - } - catch - { - MessageBox.Show("打开失败了,原因未知", "打开脚本"); - } - } - - private void textBoxScript_DragEnter(object sender, System.Windows.DragEventArgs e) - { - if (e.Data.GetDataPresent(DataFormats.FileDrop)) - { - e.Effects = System.Windows.DragDropEffects.All; - } - else - { - e.Effects = System.Windows.DragDropEffects.None; - } - } - #region EasyCon菜单功能 private void 新建ToolStripMenuItem_Click(object sender, EventArgs e) { @@ -856,25 +821,12 @@ private void 退出ToolStripMenuItem_Click(object sender, EventArgs e) private void 查找替換ToolStripMenuItem_Click(object sender, EventArgs e) { - if (textEditor.SelectedText.Length > 0) - findPanel1.Target = textEditor.SelectedText; - findPanel1.Show(); + scriptEditor.OpenSearchPanel(); } private void 查找下一个ToolStripMenuItem_Click(object sender, EventArgs e) { - if (textEditor.SelectedText.Length > 0) - findPanel1.Target = textEditor.SelectedText; - var index = findPanel1.Find(); - - if (index == -1) - { - MessageBox.Show("到底了"); - return; - } - - textEditor.Select(index, findPanel1.Target.Length); - textEditor.ScrollToLine(textEditor.Document.GetLineByOffset(index).LineNumber); + // SearchPanel 内部处理 F3 快捷键 } private void DeviceTypeItem_Click(object sender, EventArgs e) @@ -973,12 +925,8 @@ public void RefreshAlert() private void 代码自动补全ToolStripMenuItem_Click(object sender, EventArgs e) { var menu = (ToolStripMenuItem)sender; - // CheckOnClick已自动切换状态,直接读取当前值 _config.EnableAutoCompletion = menu.Checked; SaveConfig(); - - // 直接改变补全控制器的状态,无需重新初始化 - _completionController.EnableAutoCompletion = _config.EnableAutoCompletion; } private void 设备驱动配置ToolStripMenuItem_Click(object sender, EventArgs e) @@ -1064,42 +1012,39 @@ private void 显示折叠ToolStripMenuItem_Click(object sender, EventArgs e) var menu = (ToolStripMenuItem)sender; menu.Checked = !menu.Checked; if (menu.Checked) - _foldingStrategy.UpdateFoldings(_foldingManager, textEditor.Document); + _foldingStrategy?.UpdateFoldings(_foldingManager!, scriptEditor.TextDocument); else - _foldingManager.Clear(); + _foldingManager?.Clear(); } private void 注释取消注释ToolStripMenuItem_Click(object sender, EventArgs e) { - // 获取选择范围 - int startOffset = textEditor.SelectionStart; - int endOffset = startOffset + textEditor.SelectionLength; - - // 获取开始和结束行 - var startLine = textEditor.Document.GetLineByOffset(startOffset); - var endLine = textEditor.Document.GetLineByOffset(endOffset); - Debug.WriteLine($"选择范围:{startLine.LineNumber}-{endLine.LineNumber}"); + int startOffset = scriptEditor.SelectionStart; + int endOffset = startOffset + scriptEditor.SelectionLength; + var doc = scriptEditor.TextDocument; + var startLine = doc.GetLineByOffset(startOffset); + var endLine = doc.GetLineByOffset(endOffset); var docomment = false; for (int lineNum = endLine.LineNumber; lineNum >= startLine.LineNumber; lineNum--) { - var line = textEditor.Document.GetLineByNumber(lineNum); - if (Scripter.CanComment(textEditor.Document.GetText(line))) + var line = doc.GetLineByNumber(lineNum); + if (Scripter.CanComment(doc.GetText(line))) { docomment = true; break; } } - using (textEditor.Document.RunUpdate()) + using (doc.RunUpdate()) { for (int lineNum = endLine.LineNumber; lineNum >= startLine.LineNumber; lineNum--) { - var line = textEditor.Document.GetLineByNumber(lineNum); - var text = textEditor.Document.GetText(line); + var line = doc.GetLineByNumber(lineNum); + var text = doc.GetText(line); text = Scripter.ToggleComment(text, docomment); - textEditor.Document.Replace(line, text); + doc.Replace(line, text); } } } diff --git a/src/EasyCon2/Forms/FindPanel.Designer.cs b/src/EasyCon2/Forms/FindPanel.Designer.cs deleted file mode 100644 index 239477ce..00000000 --- a/src/EasyCon2/Forms/FindPanel.Designer.cs +++ /dev/null @@ -1,125 +0,0 @@ -namespace EasyCon2.Forms -{ - partial class FindPanel - { - /// - /// 必需的设计器变量。 - /// - private System.ComponentModel.IContainer components = null; - - /// - /// 清理所有正在使用的资源。 - /// - /// 如果应释放托管资源,为 true;否则为 false。 - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region 组件设计器生成的代码 - - /// - /// 设计器支持所需的方法 - 不要修改 - /// 使用代码编辑器修改此方法的内容。 - /// - private void InitializeComponent() - { - components = new System.ComponentModel.Container(); - searchBtn = new Button(); - searchTBox = new TextBox(); - bindingSource1 = new BindingSource(components); - replaceTBox = new TextBox(); - replaceBtn = new Button(); - closeBtn = new Button(); - ((System.ComponentModel.ISupportInitialize)bindingSource1).BeginInit(); - SuspendLayout(); - // - // searchBtn - // - searchBtn.FlatAppearance.BorderSize = 0; - searchBtn.FlatStyle = FlatStyle.Flat; - searchBtn.Location = new Point(228, 30); - searchBtn.Name = "searchBtn"; - searchBtn.Size = new Size(69, 35); - searchBtn.TabIndex = 0; - searchBtn.Text = "下一个"; - searchBtn.UseVisualStyleBackColor = true; - searchBtn.Click += searchBtn_Click; - // - // searchTBox - // - searchTBox.DataBindings.Add(new Binding("Text", bindingSource1, "Target", true)); - searchTBox.Location = new Point(20, 34); - searchTBox.Name = "searchTBox"; - searchTBox.Size = new Size(202, 27); - searchTBox.TabIndex = 1; - // - // bindingSource1 - // - bindingSource1.DataSource = this; - bindingSource1.Position = 0; - // - // replaceTBox - // - replaceTBox.DataBindings.Add(new Binding("Text", bindingSource1, "Replaced", true)); - replaceTBox.Location = new Point(20, 75); - replaceTBox.Name = "replaceTBox"; - replaceTBox.Size = new Size(202, 27); - replaceTBox.TabIndex = 2; - // - // replaceBtn - // - replaceBtn.FlatAppearance.BorderSize = 0; - replaceBtn.FlatStyle = FlatStyle.Flat; - replaceBtn.Location = new Point(228, 74); - replaceBtn.Name = "replaceBtn"; - replaceBtn.Size = new Size(69, 29); - replaceBtn.TabIndex = 3; - replaceBtn.Text = "替换"; - replaceBtn.UseVisualStyleBackColor = true; - replaceBtn.Click += replaceBtn_Click; - // - // closeBtn - // - closeBtn.BackColor = Color.Transparent; - closeBtn.FlatAppearance.BorderSize = 0; - closeBtn.FlatStyle = FlatStyle.Flat; - closeBtn.Location = new Point(278, -1); - closeBtn.Name = "closeBtn"; - closeBtn.Size = new Size(27, 29); - closeBtn.TabIndex = 4; - closeBtn.Text = "X"; - closeBtn.UseVisualStyleBackColor = false; - closeBtn.Click += closeBtn_Click; - // - // FindPanel - // - AutoScaleDimensions = new SizeF(9F, 20F); - AutoScaleMode = AutoScaleMode.Font; - BackColor = SystemColors.Control; - Controls.Add(closeBtn); - Controls.Add(replaceBtn); - Controls.Add(replaceTBox); - Controls.Add(searchTBox); - Controls.Add(searchBtn); - Name = "FindPanel"; - Size = new Size(305, 107); - ((System.ComponentModel.ISupportInitialize)bindingSource1).EndInit(); - ResumeLayout(false); - PerformLayout(); - } - - #endregion - - private Button searchBtn; - private TextBox searchTBox; - private TextBox replaceTBox; - private Button replaceBtn; - private Button closeBtn; - private BindingSource bindingSource1; - } -} diff --git a/src/EasyCon2/Forms/FindPanel.cs b/src/EasyCon2/Forms/FindPanel.cs deleted file mode 100644 index 1a67478a..00000000 --- a/src/EasyCon2/Forms/FindPanel.cs +++ /dev/null @@ -1,58 +0,0 @@ -using ICSharpCode.AvalonEdit; -using System.Diagnostics; - -namespace EasyCon2.Forms; - -public partial class FindPanel : UserControl -{ - public FindPanel() - { - InitializeComponent(); - } - - public void InitEditor(TextEditor _editor) - { - editor = _editor; - Target = editor.SelectedText; - Replaced = string.Empty; - } - - private TextEditor editor; - - public string Target { get; set; } - - public string Replaced { get; set; } - - private void closeBtn_Click(object sender, EventArgs e) - { - Hide(); - } - - private void searchBtn_Click(object sender, EventArgs e) - { - Debug.WriteLine(Target); - var index = Find(); - if (index == -1) - { - MessageBox.Show("到底了"); - return; - } - - editor.Select(index, Target.Length); - editor.ScrollToLine(editor.Document.GetLineByOffset(index).LineNumber); - } - - private void replaceBtn_Click(object sender, EventArgs e) - { - if (editor.SelectedText == Target) - { - editor.SelectedText = Replaced; - } - } - - public int Find() - { - if (Target?.Length == 0) return -1; - return editor.Text.IndexOf(Target, editor.SelectionStart + 1); - } -} \ No newline at end of file diff --git a/src/EasyCon2/Helper/CodeCompletionController.cs b/src/EasyCon2/Helper/CodeCompletionController.cs deleted file mode 100644 index fdd5f734..00000000 --- a/src/EasyCon2/Helper/CodeCompletionController.cs +++ /dev/null @@ -1,194 +0,0 @@ -using EasyCon2.Models; -using ICSharpCode.AvalonEdit; -using ICSharpCode.AvalonEdit.CodeCompletion; -using ICSharpCode.AvalonEdit.Document; -using System.Diagnostics; -using System.Windows.Input; - -namespace EasyCon2.Helper; - -public interface ICompletionProvider -{ - /// - /// 获取代码提示列表 - /// - Task> GetCompletions(ITextSource textSource, int offset, string cur); - - /// - /// 处理字符输入,决定是否触发代码提示 - /// - bool ShouldTriggerCompletion(char triggerChar, string currentLineText, int caretIndex); - - /// - /// 获取当前单词(用于过滤提示列表) - /// - string GetCurrentWord(TextDocument document, int offset); -} - -public class CodeCompletionController : IDisposable -{ - private readonly TextEditor _editor; - private readonly ICompletionProvider _completionProvider; - private bool _enableAutoCompletion; - private CompletionWindow _completionWindow; - private bool _isDisposed; - - /// - /// 获取或设置是否启用代码自动补全功能 - /// - public bool EnableAutoCompletion - { - get => _enableAutoCompletion; - set => _enableAutoCompletion = value; - } - - public CodeCompletionController(TextEditor editor, ICompletionProvider completionProvider, bool enableAutoCompletion = true) - { - _editor = editor ?? throw new ArgumentNullException(nameof(editor)); - _completionProvider = completionProvider; - _enableAutoCompletion = enableAutoCompletion; - - SetupEventHandlers(); - } - - private void SetupEventHandlers() - { - // 文本输入事件 - _editor.TextArea.TextEntering += OnTextEntering; - _editor.TextArea.TextEntered += OnTextEntered; - - // 键盘事件 - //_editor.TextArea.KeyDown += OnKeyDown; - - // 失去焦点时关闭提示窗口 - _editor.LostFocus += (s, e) => CloseCompletionWindow(); - } - - private async void OnTextEntered(object sender, TextCompositionEventArgs e) - { - if (!_enableAutoCompletion) return; - - var line = _editor.TextArea.Document.GetLineByNumber(_editor.TextArea.Caret.Line); - if (_completionProvider.ShouldTriggerCompletion(e.Text[0], - _editor.TextArea.Document.GetText(line.Offset, line.Length), - _editor.TextArea.Caret.Column)) - { - await ShowCompletionWindow(); - } - } - - private void OnTextEntering(object sender, TextCompositionEventArgs e) - { - if (_completionWindow != null && Keyboard.Modifiers == ModifierKeys.None) - { - // 如果用户输入空格、换行等,关闭提示窗口 - if (e.Text == " " || e.Text == "\t" || e.Text == "\n" || e.Text == "\r") - { - CloseCompletionWindow(); - } - } - } - - private void OnKeyDown(object sender, System.Windows.Input.KeyEventArgs e) - { - if (_completionWindow != null) - { - // Ctrl+Space 强制显示提示 - if (e.Key == Key.Space && Keyboard.Modifiers == ModifierKeys.Control) - { - e.Handled = true; - ShowCompletionWindow(); - } - // ESC 关闭提示窗口 - else if (e.Key == Key.Escape) - { - CloseCompletionWindow(); - e.Handled = true; - } - } - else - { - // Ctrl+Space 强制显示提示 - if (e.Key == Key.Space && Keyboard.Modifiers == ModifierKeys.Control) - { - e.Handled = true; - ShowCompletionWindow(); - } - } - } - - private async Task ShowCompletionWindow() - { - // 关闭已存在的窗口 - CloseCompletionWindow(); - - try - { - // 获取当前单词用于过滤 - var currentWord = _completionProvider.GetCurrentWord( - _editor.Document, - _editor.TextArea.Caret.Offset - ); - - // 获取提示列表 - var completions = await _completionProvider.GetCompletions( - _editor.Document, - _editor.TextArea.Caret.Offset, - currentWord); - - if (!completions.Any()) return; - - // 创建提示窗口 - _completionWindow = new CompletionWindow(_editor.TextArea) - { - CloseWhenCaretAtBeginning = true, - CloseAutomatically = true, - }; - - // 设置数据源 - var data = _completionWindow.CompletionList.CompletionData; - foreach (var c in completions) - { - data.Add(new EcpCompletionData(c.Text)); - } - - // 如果当前有单词,设置起始偏移量 - if (!string.IsNullOrEmpty(currentWord)) - { - _completionWindow.StartOffset = _editor.TextArea.Caret.Offset - currentWord.Length; - } - - // 设置事件处理 - _completionWindow.Closed += (s, e) => _completionWindow = null; - - // 显示窗口 - _completionWindow.Show(); - - // 设置初始选择 - if (!string.IsNullOrEmpty(currentWord)) - { - _completionWindow.CompletionList.SelectItem(currentWord); - } - } - catch (Exception ex) - { - Debug.WriteLine($"显示代码提示时出错: {ex.Message}"); - CloseCompletionWindow(); - } - } - - public void CloseCompletionWindow() - { - _completionWindow?.Close(); - _completionWindow = null; - } - - public void Dispose() - { - if (!_isDisposed) - { - CloseCompletionWindow(); - _isDisposed = true; - } - } -} \ No newline at end of file diff --git a/src/EasyCon2/Helper/CustomFoldingStrategy.cs b/src/EasyCon2/Helper/CustomFoldingStrategy.cs index e1ad8f4f..c99da795 100644 --- a/src/EasyCon2/Helper/CustomFoldingStrategy.cs +++ b/src/EasyCon2/Helper/CustomFoldingStrategy.cs @@ -1,22 +1,21 @@ -using ICSharpCode.AvalonEdit.Document; -using ICSharpCode.AvalonEdit.Folding; +using AvaloniaEdit.Document; +using AvaloniaEdit.Folding; namespace EasyCon2.Helper; public class CustomFoldingStrategy { - // 定义代码块开始和结束标记 private readonly Dictionary blockPairs = new() { { "if", "endif" }, { "for", "next" }, { "func", "endfunc" } }; + public IEnumerable CreateNewFoldings(TextDocument document, out int firstErrorOffset) { - firstErrorOffset = -1; // 没有语法错误,设为-1 + firstErrorOffset = -1; var newFoldings = new List(); - var startLines = new Stack<(int lineNumber, string blockType)>(); for (int i = 0; i < document.LineCount; i++) @@ -24,7 +23,6 @@ public IEnumerable CreateNewFoldings(TextDocument document, out int var line = document.GetText(document.Lines[i]); var trimmedLine = line.Trim().ToLower(); - // 检查是否是块开始 foreach (var pair in blockPairs) { if (trimmedLine.StartsWith(pair.Key)) @@ -34,7 +32,6 @@ public IEnumerable CreateNewFoldings(TextDocument document, out int } } - // 检查是否是块结束 if (startLines.Count > 0) { var currentBlock = startLines.Peek(); @@ -43,19 +40,9 @@ public IEnumerable CreateNewFoldings(TextDocument document, out int if (trimmedLine.StartsWith(endMarker) || trimmedLine == endMarker) { var startLine = startLines.Pop(); - - // 创建折叠区域 var startOffset = document.Lines[startLine.lineNumber].Offset; var endOffset = document.Lines[i].EndOffset; - // 跳过空行 - //while (i + 1 < document.Lines.Count && - // string.IsNullOrWhiteSpace(document.GetText(document.Lines[i + 1]))) - //{ - // i++; - // endOffset = document.Lines[i].EndOffset; - //} - newFoldings.Add(new NewFolding { StartOffset = startOffset, @@ -67,7 +54,6 @@ public IEnumerable CreateNewFoldings(TextDocument document, out int } } - // 按开始位置排序 newFoldings.Sort((a, b) => a.StartOffset.CompareTo(b.StartOffset)); return newFoldings; } @@ -80,9 +66,8 @@ private string GetFoldingName(TextDocument document, int lineNumber, string bloc public void UpdateFoldings(FoldingManager manager, TextDocument document) { - // 此方法可用于在文档变化后更新折叠区域 int firstErrorOffset; var newFoldings = CreateNewFoldings(document, out firstErrorOffset); manager.UpdateFoldings(newFoldings, firstErrorOffset); } -} \ No newline at end of file +} diff --git a/src/EasyCon2/Helper/EcpCompletionProvider.cs b/src/EasyCon2/Helper/EcpCompletionProvider.cs deleted file mode 100644 index 0e3323ce..00000000 --- a/src/EasyCon2/Helper/EcpCompletionProvider.cs +++ /dev/null @@ -1,77 +0,0 @@ -using EasyCon.Core; -using EasyCon2.Models; -using ICSharpCode.AvalonEdit; -using ICSharpCode.AvalonEdit.CodeCompletion; -using ICSharpCode.AvalonEdit.Document; - -namespace EasyCon2.Helper; - -public delegate IEnumerable GetImgLabel(); - -internal class EcpCompletionProvider(TextEditor textEditor) : ICompletionProvider -{ - private readonly TextEditor Editor = textEditor; - public GetImgLabel GetImgLabel; - - private readonly List _keywords = [ - "IMPORT", - "IF", "ELIF", "ELSE", "ENDIF", - "FOR", "TO", "NEXT", "BREAK", "CONTINUE", - "WHILE", "END", - "FUNC", "ENDFUNC", "CALL", "RETURN", - "PRINT", "ALERT", "TIME", "WAIT", - "A", "B", "X", "Y", "L", "R", "ZL", "ZR", - "PLUS", "MINUS", "HOME", "CAPTURE", - "LS", "RS", "LCLICK", "RCLICK", - "LEFT", "RIGHT", "UP", "DOWN", - "UPLEFT", "UPRIGHT", "DOWNLEFT", "DOWNRIGHT", - "RAND", "AMIIBO", "BEEP", - ]; - public async Task> GetCompletions(ITextSource textSource, int offset, string cur) - { - var completions = new List(); - - if (cur.StartsWith('@')) - { - var ilnames = GetImgLabel?.Invoke() ?? []; - foreach (var name in ilnames) - { - completions.Add(new EcpCompletionData($"@{name}")); - } - } - else if (cur.StartsWith('_') || cur.StartsWith('$')) - { - var tok = Scripter.GetTokens(Editor.Text, cur); - foreach (var name in tok) - { - completions.Add(new EcpCompletionData(name)); - } - } - else if (char.IsLetter(cur[0])) - { - var kw = _keywords.Where(ch => ch.StartsWith(cur, StringComparison.OrdinalIgnoreCase)); - foreach (var item in kw) - { - completions.Add(new EcpCompletionData(item)); - } - } - return completions; - } - - private bool IsWordPart(char c) - { - return char.IsLetterOrDigit(c) || "@$_".IndexOf(c) != -1; - } - - public string GetCurrentWord(TextDocument document, int offset) - { - int start = offset; - while (start > 0 && IsWordPart(document.GetCharAt(start - 1))) - { - start--; - } - return document.GetText(start, offset - start); - } - - public bool ShouldTriggerCompletion(char triggerChar, string currentLineText, int caretIndex) => true; -} \ No newline at end of file diff --git a/src/EasyCon2/Helper/MouseTranslation.cs b/src/EasyCon2/Helper/MouseTranslation.cs index 7112e7c6..42ed0590 100644 --- a/src/EasyCon2/Helper/MouseTranslation.cs +++ b/src/EasyCon2/Helper/MouseTranslation.cs @@ -2,53 +2,23 @@ namespace EasyCon2.Helper { public class MouseTranslation { - //Sensitivity - public System.Windows.Point mc_sensitivity = new System.Windows.Point(0.5, 0.5); - - //Sensitivity with Sigmoid - public System.Windows.Point mc_sensitivity2 = new System.Windows.Point(0.5, 0.5); - - //Mouse Delta Start Threshold - public System.Windows.Point mc_mouse_delta_start_threshold = new System.Windows.Point(0.0, 0.0); - - //Delta Sensitivity Sigmoid Constant, -1.0 < k < 1.0 - public System.Windows.Point mc_delta_sensitivity_sigmoid_constant = new System.Windows.Point(-0.5, -0.5); - - //Initial Delta - public System.Windows.Point mc_delta_initial = new System.Windows.Point(128 * 0.5, 128 * 0.5); - - //Delta-Stopping Threshold, 0.0 <= k <= 1.0 - public System.Windows.Point mc_delta_stop_threshold = new System.Windows.Point(128 * 0.49, 128 * 0.49); - - //Delta Damping Origin - public System.Windows.Point mc_delta_damping_origin = new System.Windows.Point(128 * 0.4, 128 * 0.4); - - //Delta Damping, 0.0 <= k <= 1.0 - public System.Windows.Point mc_delta_damping = new System.Windows.Point(0.0, 0.0); - - //Delta Damping with Sigmoid, 0.0 <= k <= 1.0 - public System.Windows.Point mc_delta_damping2 = new System.Windows.Point(0.1, 0.1); - - //Delta Damping with Sigmoid Constant, -1.0 < k < 1.0 - public System.Windows.Point mc_delta_damping_sigmoid_constant = new System.Windows.Point(0.5, 0.5); - - //Maximum Delta - public System.Windows.Point mc_delta_max = new System.Windows.Point(128 * 2.0, 128 * 2.0); - - //system.threadExecutionInterval = 1000.0 / mc_framerate - - //Mouse Camera Init - //bool mc_enabled = false; // Toggle - public System.Windows.Point mc_delta = new System.Windows.Point(); // Mouse Camera Delta - - //System.Windows.Point mc_screen_half = new System.Windows.Point(); // Screen Half - public System.Windows.Point mc_mouse_delta = new System.Windows.Point(); // Mouse Delta - public System.Windows.Point mc_mouse = new System.Windows.Point(); // Mouse Position + public (double X, double Y) mc_sensitivity = (0.5, 0.5); + public (double X, double Y) mc_sensitivity2 = (0.5, 0.5); + public (double X, double Y) mc_mouse_delta_start_threshold = (0.0, 0.0); + public (double X, double Y) mc_delta_sensitivity_sigmoid_constant = (-0.5, -0.5); + public (double X, double Y) mc_delta_initial = (128 * 0.5, 128 * 0.5); + public (double X, double Y) mc_delta_stop_threshold = (128 * 0.49, 128 * 0.49); + public (double X, double Y) mc_delta_damping_origin = (128 * 0.4, 128 * 0.4); + public (double X, double Y) mc_delta_damping = (0.0, 0.0); + public (double X, double Y) mc_delta_damping2 = (0.1, 0.1); + public (double X, double Y) mc_delta_damping_sigmoid_constant = (0.5, 0.5); + public (double X, double Y) mc_delta_max = (128 * 2.0, 128 * 2.0); + public (double X, double Y) mc_delta = (0, 0); + public (double X, double Y) mc_mouse_delta = (0, 0); + public (double X, double Y) mc_mouse = (0, 0); public MouseTranslation() { - //Damping Initialization - mc_delta_damping.X = 0; mc_delta_damping.X = 1.0 - mc_delta_damping.X; mc_delta_damping.Y = 1.0 - mc_delta_damping.Y; mc_delta_damping2.X = 1.0 - mc_delta_damping2.X; @@ -62,37 +32,26 @@ private double Sigmoid_Tunable(double k, double x) public System.Drawing.Point Translate(System.Drawing.Point mouse, System.Drawing.Point center) { - // Mouse Delta - // 鼠标偏移 - mc_mouse = new System.Windows.Point(mouse.X, mouse.Y); - mc_mouse_delta = new System.Windows.Point(mc_mouse.X - center.X, mc_mouse.Y - center.Y); + mc_mouse = (mouse.X, mouse.Y); + mc_mouse_delta = (mc_mouse.X - center.X, mc_mouse.Y - center.Y); - // 初始化偏移 - // Initial Delta if (mc_delta.X == 0.0 && Math.Abs(mc_mouse_delta.X) > mc_mouse_delta_start_threshold.X) mc_delta.X = mc_delta_initial.X * Math.Abs(mc_mouse_delta.X) / mc_mouse_delta.X; if (mc_delta.Y == 0.0 && Math.Abs(mc_mouse_delta.Y) > mc_mouse_delta_start_threshold.Y) mc_delta.Y = mc_delta_initial.Y * Math.Abs(mc_mouse_delta.Y) / mc_mouse_delta.Y; - // 计算镜头偏移 - // Mouse Camera Delta if (mc_delta.X != 0.0) mc_delta.X += mc_mouse_delta.X * (mc_sensitivity.X + mc_sensitivity2.X * Math.Abs(Sigmoid_Tunable(mc_delta_sensitivity_sigmoid_constant.X, mc_delta.X / mc_delta_max.X))); if (mc_delta.Y != 0.0) mc_delta.Y += mc_mouse_delta.Y * (mc_sensitivity.Y + mc_sensitivity2.Y * Math.Abs(Sigmoid_Tunable(mc_delta_sensitivity_sigmoid_constant.Y, mc_delta.Y / mc_delta_max.Y))); - // Delta Limit - // 偏移限制 if (Math.Abs(mc_delta.X) > mc_delta_max.X) mc_delta.X = Math.Abs(mc_delta.X) / mc_delta.X * mc_delta_max.X; if (Math.Abs(mc_delta.Y) > mc_delta_max.Y) mc_delta.Y = Math.Abs(mc_delta.Y) / mc_delta.Y * mc_delta_max.Y; - // Mouse Camera Delta to Axis - //Console.WriteLine((int)(mc_delta.X+128) + ", " + (int)(mc_delta.Y + 128)); - System.Drawing.Point translation = new System.Drawing.Point((int)(mc_delta.X + 128), (int)(mc_delta.Y + 128)); + var translation = new System.Drawing.Point((int)(mc_delta.X + 128), (int)(mc_delta.Y + 128)); - // Delta Damping if (mc_delta.X != 0) { var dt_x = mc_delta_damping_origin.X * Math.Abs(mc_delta.X) / mc_delta.X; @@ -103,67 +62,11 @@ public System.Drawing.Point Translate(System.Drawing.Point mouse, System.Drawing var dt_y = mc_delta_damping_origin.Y * Math.Abs(mc_delta.Y) / mc_delta.Y; mc_delta.Y = (mc_delta.Y - dt_y) * (mc_delta_damping.Y + mc_delta_damping2.Y * Math.Abs(Sigmoid_Tunable(mc_delta_damping_sigmoid_constant.Y, mc_delta.Y / mc_delta_max.Y))) * 0.5 + dt_y; } - // Delta Stopping if (Math.Abs(mc_delta.X) < mc_delta_stop_threshold.X) mc_delta.X = 0.0; if (Math.Abs(mc_delta.Y) < mc_delta_stop_threshold.Y) mc_delta.Y = 0.0; return translation; } - - //this will need to be rewritten to use raw input instead of hacky WinForms input... - //public void MouseUpdate() - //{ - // //SwitchInputSink sink, Panel panel1 - // if (!firstTime) - // { - // Point m_translation = mouseTranslation.Translate(Mouse.Position, center); - // var x = m_translation.X; - // var y = m_translation.Y; - - // if (x > 255) - // { - // Mouse.Position = new Point(Mouse.Position.X - (x - 255), Mouse.Position.Y); - // x = 255; - // } - // else if (x < 0) - // { - // Mouse.Position = new Point(Mouse.Position.X - x, Mouse.Position.Y); - // x = 0; - // } - // if (y > 255) - // { - // Mouse.Position = new Point(Mouse.Position.X, Mouse.Position.Y - (y - 255)); - // y = 255; - // } - // else if (y < 0) - // { - // Mouse.Position = new Point(Mouse.Position.X, Mouse.Position.Y - y); - // y = 0; - // } - - // if (last.X != x) - // { - // Console.WriteLine(x + ", " + y); - // sink.Update(InputFrame.ParseInputString("RX=" + x)); - // } - - // if (last.Y != y) - // { - // Console.WriteLine(x + ", " + y); - // sink.Update(InputFrame.ParseInputString("RY=" + y)); - // } - // Mouse.Position = center; - // last = new Point(x, y); - - // } - // else - // { - // firstTime = false; - // //Mouse.Position = new Point(panel1.PointToScreen(Point.Empty).X + 128, panel1.PointToScreen(Point.Empty).Y + 128); - // center = Mouse.Position; - // } - //} - } -} \ No newline at end of file +} diff --git a/src/EasyCon2/Models/EcpCompletionData.cs b/src/EasyCon2/Models/EcpCompletionData.cs deleted file mode 100644 index 5708a793..00000000 --- a/src/EasyCon2/Models/EcpCompletionData.cs +++ /dev/null @@ -1,26 +0,0 @@ -using ICSharpCode.AvalonEdit.CodeCompletion; -using ICSharpCode.AvalonEdit.Document; -using ICSharpCode.AvalonEdit.Editing; -using System.Windows.Media; - -namespace EasyCon2.Models; - -internal class EcpCompletionData : ICompletionData -{ - public EcpCompletionData(string text) { Text = text; } - - public ImageSource Image => null; - - public string Text { get; private set; } - - public object Content { get { return this.Text; } } - - public object Description { get { return $"描述:{Text}"; } } - - public double Priority => 0.9; - - public void Complete(TextArea textArea, ISegment completionSegment, EventArgs insertionRequestEventArgs) - { - textArea.Document.Replace(completionSegment, this.Text); - } -} \ No newline at end of file From 0fddb93ec9e70e4671851f97dec6124db057e374 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 22 Apr 2026 15:29:13 +0000 Subject: [PATCH 2/3] style: auto-fix dotnet format [skip ci] --- src/EasyCon2.Avalonia.Core/AvaloniaRuntime.cs | 2 +- src/EasyCon2.Avalonia.Core/Editor/EcsHighlightingLoader.cs | 6 +++--- .../Editor/ScriptEditorControl.axaml.cs | 2 +- src/EasyCon2.Avalonia.Core/Editor/ScriptEditorHost.cs | 2 +- src/EasyCon2/App/MainForm.cs | 6 +++--- src/EasyCon2/EasyConForm.cs | 6 +++--- src/EasyCon2/Forms/CaptureVideoForm.cs | 2 +- src/EasyCon2/Forms/ESPConfig.cs | 2 +- src/EasyCon2/Helper/CustomFoldingStrategy.cs | 2 +- src/EasyCon2/Helper/MouseTranslation.cs | 2 +- src/EasyCon2/ResourceHelper.cs | 4 ++-- 11 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/EasyCon2.Avalonia.Core/AvaloniaRuntime.cs b/src/EasyCon2.Avalonia.Core/AvaloniaRuntime.cs index 531ff9a7..1193c393 100644 --- a/src/EasyCon2.Avalonia.Core/AvaloniaRuntime.cs +++ b/src/EasyCon2.Avalonia.Core/AvaloniaRuntime.cs @@ -26,4 +26,4 @@ public static void EnsureInitialized() _initialized = true; } -} +} \ No newline at end of file diff --git a/src/EasyCon2.Avalonia.Core/Editor/EcsHighlightingLoader.cs b/src/EasyCon2.Avalonia.Core/Editor/EcsHighlightingLoader.cs index 468cbfa8..8c2a9939 100644 --- a/src/EasyCon2.Avalonia.Core/Editor/EcsHighlightingLoader.cs +++ b/src/EasyCon2.Avalonia.Core/Editor/EcsHighlightingLoader.cs @@ -1,7 +1,7 @@ -using System.Reflection; -using System.Xml; using AvaloniaEdit.Highlighting; using AvaloniaEdit.Highlighting.Xshd; +using System.Reflection; +using System.Xml; namespace EasyCon2.Avalonia.Core.Editor; @@ -46,4 +46,4 @@ private static void RegisterFromResource(Assembly asm, string resourceName, var definition = HighlightingLoader.Load(reader, HighlightingManager.Instance); HighlightingManager.Instance.RegisterHighlighting(name, extensions, definition); } -} +} \ No newline at end of file diff --git a/src/EasyCon2.Avalonia.Core/Editor/ScriptEditorControl.axaml.cs b/src/EasyCon2.Avalonia.Core/Editor/ScriptEditorControl.axaml.cs index bc021a02..1ea51f42 100644 --- a/src/EasyCon2.Avalonia.Core/Editor/ScriptEditorControl.axaml.cs +++ b/src/EasyCon2.Avalonia.Core/Editor/ScriptEditorControl.axaml.cs @@ -108,4 +108,4 @@ public void Save(string path) public IDocument Document => _editor.Document; public TextDocument TextDocument => _editor.Document; public AvaloniaEdit.Editing.TextArea TextArea => _editor.TextArea; -} +} \ No newline at end of file diff --git a/src/EasyCon2.Avalonia.Core/Editor/ScriptEditorHost.cs b/src/EasyCon2.Avalonia.Core/Editor/ScriptEditorHost.cs index eeaab5d7..257a90b8 100644 --- a/src/EasyCon2.Avalonia.Core/Editor/ScriptEditorHost.cs +++ b/src/EasyCon2.Avalonia.Core/Editor/ScriptEditorHost.cs @@ -10,4 +10,4 @@ public static ScriptEditorControl CreateControl() control.SyntaxHighlighting = EcsHighlightingLoader.GetByName("ECScript"); return control; } -} +} \ No newline at end of file diff --git a/src/EasyCon2/App/MainForm.cs b/src/EasyCon2/App/MainForm.cs index 807958c7..aa09b675 100644 --- a/src/EasyCon2/App/MainForm.cs +++ b/src/EasyCon2/App/MainForm.cs @@ -1,3 +1,5 @@ +using AvaloniaEdit.Folding; +using AvaloniaEdit.Highlighting; using EasyCon.Core; using EasyCon.Core.Config; using EasyCon.Script.Assembly; @@ -7,12 +9,9 @@ using EasyCon2.Avalonia.Core.VPad; using EasyCon2.Forms; using EasyCon2.Helper; -using Resources = EasyCon2.UI.Common.Properties.Resources; using EasyCon2.Views; using EasyDevice; using EasyScript; -using AvaloniaEdit.Folding; -using AvaloniaEdit.Highlighting; using System.Diagnostics; using System.IO; using System.Linq; @@ -22,6 +21,7 @@ using System.Text.Json; using AvaColor = Avalonia.Media.Color; using AvaColors = Avalonia.Media.Colors; +using Resources = EasyCon2.UI.Common.Properties.Resources; namespace EasyCon2.App; diff --git a/src/EasyCon2/EasyConForm.cs b/src/EasyCon2/EasyConForm.cs index 5172a2b2..7e701cd1 100644 --- a/src/EasyCon2/EasyConForm.cs +++ b/src/EasyCon2/EasyConForm.cs @@ -1,15 +1,14 @@ +using AvaloniaEdit.Folding; +using AvaloniaEdit.Highlighting; using EasyCon.Core; using EasyCon.Core.Config; using EasyCon.Script.Assembly; using EasyCon2.Avalonia.Core.Editor; using EasyCon2.Avalonia.Core.VPad; using EasyCon2.Helper; -using Resources = EasyCon2.UI.Common.Properties.Resources; using EasyCon2.Views; using EasyDevice; using EasyScript; -using AvaloniaEdit.Folding; -using AvaloniaEdit.Highlighting; using System.Diagnostics; using System.IO; using System.Media; @@ -19,6 +18,7 @@ using System.Text.RegularExpressions; using AvaColor = Avalonia.Media.Color; using AvaColors = Avalonia.Media.Colors; +using Resources = EasyCon2.UI.Common.Properties.Resources; namespace EasyCon2.Forms { diff --git a/src/EasyCon2/Forms/CaptureVideoForm.cs b/src/EasyCon2/Forms/CaptureVideoForm.cs index 365879de..d98ef3dc 100644 --- a/src/EasyCon2/Forms/CaptureVideoForm.cs +++ b/src/EasyCon2/Forms/CaptureVideoForm.cs @@ -1,11 +1,11 @@ using EasyCon.Capture; using EasyCon2.Helper; -using Resources = EasyCon2.UI.Common.Properties.Resources; using OpenCvSharp.Extensions; using System.Diagnostics; using System.Drawing.Drawing2D; using System.IO; using Mat = OpenCvSharp.Mat; +using Resources = EasyCon2.UI.Common.Properties.Resources; namespace EasyCon2.Forms { diff --git a/src/EasyCon2/Forms/ESPConfig.cs b/src/EasyCon2/Forms/ESPConfig.cs index 143d8c41..bd5d4517 100644 --- a/src/EasyCon2/Forms/ESPConfig.cs +++ b/src/EasyCon2/Forms/ESPConfig.cs @@ -1,6 +1,5 @@ using EasyCapture; using EasyCon2.Models; -using Resources = EasyCon2.UI.Common.Properties.Resources; using EasyDevice; using LibAmiibo.Data; using LibAmiibo.Data.Figurine; @@ -10,6 +9,7 @@ using System.Security.Cryptography; using System.Text; using System.Text.Json; +using Resources = EasyCon2.UI.Common.Properties.Resources; namespace EasyCon2.Forms { diff --git a/src/EasyCon2/Helper/CustomFoldingStrategy.cs b/src/EasyCon2/Helper/CustomFoldingStrategy.cs index c99da795..c405c14b 100644 --- a/src/EasyCon2/Helper/CustomFoldingStrategy.cs +++ b/src/EasyCon2/Helper/CustomFoldingStrategy.cs @@ -70,4 +70,4 @@ public void UpdateFoldings(FoldingManager manager, TextDocument document) var newFoldings = CreateNewFoldings(document, out firstErrorOffset); manager.UpdateFoldings(newFoldings, firstErrorOffset); } -} +} \ No newline at end of file diff --git a/src/EasyCon2/Helper/MouseTranslation.cs b/src/EasyCon2/Helper/MouseTranslation.cs index 42ed0590..137ea006 100644 --- a/src/EasyCon2/Helper/MouseTranslation.cs +++ b/src/EasyCon2/Helper/MouseTranslation.cs @@ -69,4 +69,4 @@ public System.Drawing.Point Translate(System.Drawing.Point mouse, System.Drawing return translation; } } -} +} \ No newline at end of file diff --git a/src/EasyCon2/ResourceHelper.cs b/src/EasyCon2/ResourceHelper.cs index a94c9f69..0afb2320 100644 --- a/src/EasyCon2/ResourceHelper.cs +++ b/src/EasyCon2/ResourceHelper.cs @@ -1,5 +1,5 @@ -using System.IO; using EasyCon2.UI.Common.Properties; +using System.IO; namespace EasyCon2; @@ -7,4 +7,4 @@ internal static class ResourceHelper { public static Bitmap Clrlog => new(new MemoryStream(Resources.clrlog)); public static Icon CaptureVideoIcon => new(new MemoryStream(Resources.CaptureVideo)); -} +} \ No newline at end of file From db4cf082bf4dbfb218f315bb10a20768be53cdf4 Mon Sep 17 00:00:00 2001 From: ca1e Date: Thu, 23 Apr 2026 00:17:35 +0800 Subject: [PATCH 3/3] =?UTF-8?q?[cc=E6=8F=90=E4=BA=A4]=20feat:=20=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E4=BB=A3=E7=A0=81=E8=A1=A5=E5=85=A8=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=B9=B6=E4=BF=AE=E5=A4=8D=E8=8F=9C=E5=8D=95=E5=BF=AB=E6=8D=B7?= =?UTF-8?q?=E9=94=AE=E9=80=8F=E4=BC=A0=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增CodeCompletionController/EcpCompletionProvider/EcpCompletionData - 支持关键词、变量($)/常量(_)/图片标签(@)补全,Ctrl+Space手动触发 - 集成到ScriptEditorControl,暴露EnableAutoCompletion开关和SetImgLabelProvider - EasyConForm和MainForm接入补全并绑定配置开关 - 修复菜单快捷键在编辑器焦点时不透传的问题(移除ProcessCmdKey拦截) - 修复AvaloniaEdit 12 Load(string) stub问题 - CodeCompletionController改为internal,移除多余GetImgLabel委托类型 --- .github/workflows/ci-dev.yml | 37 ++---- .../Editor/CodeCompletionController.cs | 125 ++++++++++++++++++ .../Editor/EcpCompletionData.cs | 26 ++++ .../Editor/EcpCompletionProvider.cs | 73 ++++++++++ .../Editor/ScriptEditorControl.axaml.cs | 20 +++ src/EasyCon2/App/MainForm.cs | 5 + src/EasyCon2/EasyConForm.cs | 4 + 7 files changed, 265 insertions(+), 25 deletions(-) create mode 100644 src/EasyCon2.Avalonia.Core/Editor/CodeCompletionController.cs create mode 100644 src/EasyCon2.Avalonia.Core/Editor/EcpCompletionData.cs create mode 100644 src/EasyCon2.Avalonia.Core/Editor/EcpCompletionProvider.cs diff --git a/.github/workflows/ci-dev.yml b/.github/workflows/ci-dev.yml index af9e8051..58f82b85 100644 --- a/.github/workflows/ci-dev.yml +++ b/.github/workflows/ci-dev.yml @@ -1,6 +1,10 @@ name: CI Develop on: + workflow_run: + workflows: ["Auto Fix Format"] + types: + - completed # 在工作流完成时触发 pull_request: branches: [dev] paths-ignore: @@ -10,15 +14,9 @@ on: - 'LICENSE' - 'README*' - 'CHANGELOG*' - push: + merge_group: branches: [dev] - paths-ignore: - - '**.md' - - 'docs/**' - - '.github/**/*.md' - - 'LICENSE' - - 'README*' - - 'CHANGELOG*' + types: [checks_requested] concurrency: group: ci-dev-${{ github.ref }} @@ -34,26 +32,11 @@ env: jobs: # ======================================== - # 1. 代码格式检查 - # ======================================== - format: - runs-on: windows-latest - steps: - - uses: actions/checkout@v6 - - - name: 安装 .NET SDK - uses: actions/setup-dotnet@v5 - with: - dotnet-version: ${{ env.DOTNET_VERSION }} - - - name: 检查代码格式 - run: dotnet format --verify-no-changes --verbosity diagnostic - - # ======================================== - # 2. 构建 + 测试 (Debug) + # 1. 构建 + 测试 (Debug) # ======================================== build-and-test: runs-on: windows-latest + if: ${{ github.event.workflow_run.conclusion == 'success' }} needs: format steps: - uses: actions/checkout@v6 @@ -62,6 +45,7 @@ jobs: uses: actions/setup-dotnet@v5 with: dotnet-version: ${{ env.DOTNET_VERSION }} + ref: ${{ github.event.workflow_run.head_branch }} - name: 缓存 NuGet 包 uses: actions/cache@v4 @@ -73,6 +57,9 @@ jobs: - name: 还原 NuGet 包 run: dotnet restore + - name: Build (Debug) + run: dotnet build --no-restore --configuration Debug + - name: 运行 EasyCon 测试 shell: cmd run: ci\test.bat Debug diff --git a/src/EasyCon2.Avalonia.Core/Editor/CodeCompletionController.cs b/src/EasyCon2.Avalonia.Core/Editor/CodeCompletionController.cs new file mode 100644 index 00000000..8eb5860c --- /dev/null +++ b/src/EasyCon2.Avalonia.Core/Editor/CodeCompletionController.cs @@ -0,0 +1,125 @@ +using Avalonia.Input; +using AvaloniaEdit; +using AvaloniaEdit.CodeCompletion; +using System.Diagnostics; + +namespace EasyCon2.Avalonia.Core.Editor; + +internal class CodeCompletionController : IDisposable +{ + private readonly TextEditor _editor; + private readonly ICompletionProvider _completionProvider; + private bool _enableAutoCompletion; + private CompletionWindow _completionWindow; + private bool _isDisposed; + + public bool EnableAutoCompletion + { + get => _enableAutoCompletion; + set => _enableAutoCompletion = value; + } + + public CodeCompletionController(TextEditor editor, ICompletionProvider completionProvider, bool enableAutoCompletion = true) + { + _editor = editor ?? throw new ArgumentNullException(nameof(editor)); + _completionProvider = completionProvider; + _enableAutoCompletion = enableAutoCompletion; + + _editor.TextArea.TextEntering += OnTextEntering; + _editor.TextArea.TextEntered += OnTextEntered; + _editor.TextArea.KeyDown += OnKeyDown; + _editor.LostFocus += (_, _) => CloseCompletionWindow(); + } + + private async void OnTextEntered(object sender, TextInputEventArgs e) + { + if (!_enableAutoCompletion || string.IsNullOrEmpty(e.Text)) return; + + var line = _editor.TextArea.Document.GetLineByNumber(_editor.TextArea.Caret.Line); + if (_completionProvider.ShouldTriggerCompletion(e.Text[0], + _editor.TextArea.Document.GetText(line.Offset, line.Length), + _editor.TextArea.Caret.Column)) + { + await ShowCompletionWindow(); + } + } + + private void OnTextEntering(object sender, TextInputEventArgs e) + { + if (_completionWindow != null && e.Text is " " or "\t" or "\n" or "\r") + CloseCompletionWindow(); + } + + private void OnKeyDown(object sender, KeyEventArgs e) + { + if (e.Key == Key.Space && e.KeyModifiers.HasFlag(KeyModifiers.Control)) + { + e.Handled = true; + _ = ShowCompletionWindow(); + } + else if (e.Key == Key.Escape && _completionWindow != null) + { + CloseCompletionWindow(); + e.Handled = true; + } + } + + private async Task ShowCompletionWindow() + { + CloseCompletionWindow(); + + try + { + var currentWord = _completionProvider.GetCurrentWord( + _editor.Document, + _editor.TextArea.Caret.Offset + ); + + var completions = await _completionProvider.GetCompletions( + _editor.Document, + _editor.TextArea.Caret.Offset, + currentWord); + + if (!completions.Any()) return; + + _completionWindow = new CompletionWindow(_editor.TextArea) + { + CloseWhenCaretAtBeginning = true, + CloseAutomatically = true, + }; + + var data = _completionWindow.CompletionList.CompletionData; + foreach (var c in completions) + data.Add(c); + + if (!string.IsNullOrEmpty(currentWord)) + _completionWindow.StartOffset = _editor.TextArea.Caret.Offset - currentWord.Length; + + _completionWindow.Closed += (_, _) => _completionWindow = null; + _completionWindow.Show(); + + if (!string.IsNullOrEmpty(currentWord)) + _completionWindow.CompletionList.SelectItem(currentWord); + } + catch (Exception ex) + { + Debug.WriteLine($"显示代码提示时出错: {ex.Message}"); + CloseCompletionWindow(); + } + } + + public void CloseCompletionWindow() + { + _completionWindow?.Close(); + _completionWindow = null; + } + + public void Dispose() + { + if (!_isDisposed) + { + CloseCompletionWindow(); + _isDisposed = true; + } + } +} \ No newline at end of file diff --git a/src/EasyCon2.Avalonia.Core/Editor/EcpCompletionData.cs b/src/EasyCon2.Avalonia.Core/Editor/EcpCompletionData.cs new file mode 100644 index 00000000..6f5e6509 --- /dev/null +++ b/src/EasyCon2.Avalonia.Core/Editor/EcpCompletionData.cs @@ -0,0 +1,26 @@ +using Avalonia.Media; +using AvaloniaEdit.CodeCompletion; +using AvaloniaEdit.Document; +using AvaloniaEdit.Editing; + +namespace EasyCon2.Avalonia.Core.Editor; + +internal class EcpCompletionData : ICompletionData +{ + public EcpCompletionData(string text) { Text = text; } + + public IImage Image => null; + + public string Text { get; } + + public object Content => Text; + + public object Description => $"描述:{Text}"; + + public double Priority => 0.9; + + public void Complete(TextArea textArea, ISegment completionSegment, EventArgs insertionRequestEventArgs) + { + textArea.Document.Replace(completionSegment, Text); + } +} \ No newline at end of file diff --git a/src/EasyCon2.Avalonia.Core/Editor/EcpCompletionProvider.cs b/src/EasyCon2.Avalonia.Core/Editor/EcpCompletionProvider.cs new file mode 100644 index 00000000..fcd0988f --- /dev/null +++ b/src/EasyCon2.Avalonia.Core/Editor/EcpCompletionProvider.cs @@ -0,0 +1,73 @@ +using AvaloniaEdit; +using AvaloniaEdit.CodeCompletion; +using AvaloniaEdit.Document; +using EasyCon.Core; + +namespace EasyCon2.Avalonia.Core.Editor; + +public interface ICompletionProvider +{ + Task> GetCompletions(ITextSource textSource, int offset, string cur); + bool ShouldTriggerCompletion(char triggerChar, string currentLineText, int caretIndex); + string GetCurrentWord(TextDocument document, int offset); +} + +internal class EcpCompletionProvider(TextEditor textEditor) : ICompletionProvider +{ + private readonly TextEditor Editor = textEditor; + public Func> GetImgLabel; + + private readonly List _keywords = + [ + "IMPORT", + "IF", "ELIF", "ELSE", "ENDIF", + "FOR", "TO", "NEXT", "BREAK", "CONTINUE", + "WHILE", "END", + "FUNC", "ENDFUNC", "CALL", "RETURN", + "PRINT", "ALERT", "TIME", "WAIT", + "A", "B", "X", "Y", "L", "R", "ZL", "ZR", + "PLUS", "MINUS", "HOME", "CAPTURE", + "LS", "RS", "LCLICK", "RCLICK", + "LEFT", "RIGHT", "UP", "DOWN", + "UPLEFT", "UPRIGHT", "DOWNLEFT", "DOWNRIGHT", + "RAND", "AMIIBO", "BEEP", + ]; + + public Task> GetCompletions(ITextSource textSource, int offset, string cur) + { + var completions = new List(); + + if (cur.StartsWith('@')) + { + var ilnames = GetImgLabel?.Invoke() ?? []; + foreach (var name in ilnames) + completions.Add(new EcpCompletionData($"@{name}")); + } + else if (cur.StartsWith('_') || cur.StartsWith('$')) + { + var tok = Scripter.GetTokens(Editor.Text, cur); + foreach (var name in tok) + completions.Add(new EcpCompletionData(name)); + } + else if (cur.Length > 0 && char.IsLetter(cur[0])) + { + var kw = _keywords.Where(ch => ch.StartsWith(cur, StringComparison.OrdinalIgnoreCase)); + foreach (var item in kw) + completions.Add(new EcpCompletionData(item)); + } + + return Task.FromResult>(completions); + } + + public string GetCurrentWord(TextDocument document, int offset) + { + int start = offset; + while (start > 0 && IsWordPart(document.GetCharAt(start - 1))) + start--; + return document.GetText(start, offset - start); + } + + public bool ShouldTriggerCompletion(char triggerChar, string currentLineText, int caretIndex) => true; + + private static bool IsWordPart(char c) => char.IsLetterOrDigit(c) || "@$_".IndexOf(c) != -1; +} \ No newline at end of file diff --git a/src/EasyCon2.Avalonia.Core/Editor/ScriptEditorControl.axaml.cs b/src/EasyCon2.Avalonia.Core/Editor/ScriptEditorControl.axaml.cs index 1ea51f42..4bff52e3 100644 --- a/src/EasyCon2.Avalonia.Core/Editor/ScriptEditorControl.axaml.cs +++ b/src/EasyCon2.Avalonia.Core/Editor/ScriptEditorControl.axaml.cs @@ -11,12 +11,20 @@ public partial class ScriptEditorControl : UserControl { private readonly TextEditor _editor; private readonly SearchPanel _searchPanel; + private CodeCompletionController _completionController; + private EcpCompletionProvider _completionProvider; public event EventHandler? EditorTextChanged; public event Action? FileDropped; public TextEditor TextEditor => _editor; + public bool EnableAutoCompletion + { + get => _completionController?.EnableAutoCompletion ?? false; + set { if (_completionController != null) _completionController.EnableAutoCompletion = value; } + } + public string Text { get => _editor.Text; @@ -63,6 +71,18 @@ public ScriptEditorControl() SetupDragDrop(); _searchPanel = SearchPanel.Install(_editor); + InitCompletion(); + } + + private void InitCompletion() + { + _completionProvider = new EcpCompletionProvider(_editor); + _completionController = new CodeCompletionController(_editor, _completionProvider); + } + + public void SetImgLabelProvider(Func> provider) + { + _completionProvider.GetImgLabel = provider; } public void OpenSearchPanel() => _searchPanel.Open(); diff --git a/src/EasyCon2/App/MainForm.cs b/src/EasyCon2/App/MainForm.cs index aa09b675..c6edb263 100644 --- a/src/EasyCon2/App/MainForm.cs +++ b/src/EasyCon2/App/MainForm.cs @@ -97,6 +97,10 @@ private void InitEditor() _foldingStrategy = new CustomFoldingStrategy(); _foldingStrategy.UpdateFoldings(_foldingManager, _textEditor.TextDocument); + // Completion + _textEditor.SetImgLabelProvider(() => _captureService.LoadedLabels.Select(il => il.name)); + _textEditor.EnableAutoCompletion = _configService.Config.EnableAutoCompletion; + // Host in Avalonia control host editorHost.Content = _textEditor; } @@ -903,6 +907,7 @@ private void menuItemToggleComment_Click(object sender, EventArgs e) private void chkAutoCompletion_CheckedChanged(object sender, EventArgs e) { _configService.Config.EnableAutoCompletion = chkAutoCompletion.Checked; + _textEditor.EnableAutoCompletion = chkAutoCompletion.Checked; _configService.Save(); } diff --git a/src/EasyCon2/EasyConForm.cs b/src/EasyCon2/EasyConForm.cs index 7e701cd1..5f9255c6 100644 --- a/src/EasyCon2/EasyConForm.cs +++ b/src/EasyCon2/EasyConForm.cs @@ -141,6 +141,9 @@ private void InitEditor() _foldingStrategy = new CustomFoldingStrategy(); _foldingStrategy.UpdateFoldings(_foldingManager, scriptEditor.TextDocument); + scriptEditor.SetImgLabelProvider(() => captureVideo.LoadedLabels.Select(il => il.name)); + scriptEditor.EnableAutoCompletion = _config.EnableAutoCompletion; + editorHost.Content = scriptEditor; } @@ -926,6 +929,7 @@ private void 代码自动补全ToolStripMenuItem_Click(object sender, EventArgs { var menu = (ToolStripMenuItem)sender; _config.EnableAutoCompletion = menu.Checked; + scriptEditor.EnableAutoCompletion = menu.Checked; SaveConfig(); }