From 64a35fd0980856809115e5878b1e748ed08306f8 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jean-Philippe=20Bruy=C3=A8re?= Date: Mon, 15 Feb 2021 14:55:01 +0100 Subject: [PATCH] simple editor with TextBox control --- Crow.Coding/Crow.Coding.csproj | 6 +- CrowEdit.csproj | 15 ++- CrowEdit.sln | 34 +++-- OpenTK.dll.config | 25 ---- clean.sh | 2 + packages.config | 5 - src/CrowEdit.cs | 235 +++++++++++++++------------------ src/Extensions.cs | 14 ++ ui/EditorOptions.crow | 1 + ui/main.crow | 35 ++--- 10 files changed, 175 insertions(+), 197 deletions(-) delete mode 100644 OpenTK.dll.config create mode 100644 clean.sh delete mode 100644 packages.config create mode 100644 src/Extensions.cs diff --git a/Crow.Coding/Crow.Coding.csproj b/Crow.Coding/Crow.Coding.csproj index 5057a88..11bb5de 100644 --- a/Crow.Coding/Crow.Coding.csproj +++ b/Crow.Coding/Crow.Coding.csproj @@ -11,10 +11,6 @@ - + - - - - \ No newline at end of file diff --git a/CrowEdit.csproj b/CrowEdit.csproj index 5f3d623..afab445 100644 --- a/CrowEdit.csproj +++ b/CrowEdit.csproj @@ -1,21 +1,22 @@  - netcoreapp3.1 + netcoreapp3.1 WinExe false - + - + - - + + - + + - + \ No newline at end of file diff --git a/CrowEdit.sln b/CrowEdit.sln index 2c7ab77..7c1fa37 100644 --- a/CrowEdit.sln +++ b/CrowEdit.sln @@ -1,29 +1,37 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrowEdit", "CrowEdit.csproj", "{AAA67D93-458E-4DD7-9CDA-4EC7F73D47FF}" +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30711.63 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CrowEdit", "CrowEdit.csproj", "{AAA67D93-458E-4DD7-9CDA-4EC7F73D47FF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Crow.Coding", "Crow.Coding\Crow.Coding.csproj", "{78842EE4-8A2F-4C75-AEC6-C95F15AD3994}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Crow.Coding", "Crow.Coding\Crow.Coding.csproj", "{78842EE4-8A2F-4C75-AEC6-C95F15AD3994}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Crow", "..\crow\Crow\Crow.csproj", "{0D18388D-8DDB-43DE-A608-0C65F0AEC4E0}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Global", "Global", "{3118E6C1-2180-4364-9A17-511D546E18CD}" + ProjectSection(SolutionItems) = preProject + Directory.Build.props = Directory.Build.props + README.md = README.md + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Release|Any CPU = Release|Any CPU Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {78842EE4-8A2F-4C75-AEC6-C95F15AD3994}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {78842EE4-8A2F-4C75-AEC6-C95F15AD3994}.Debug|Any CPU.Build.0 = Debug|Any CPU - {78842EE4-8A2F-4C75-AEC6-C95F15AD3994}.Release|Any CPU.ActiveCfg = Release|Any CPU - {78842EE4-8A2F-4C75-AEC6-C95F15AD3994}.Release|Any CPU.Build.0 = Release|Any CPU {AAA67D93-458E-4DD7-9CDA-4EC7F73D47FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AAA67D93-458E-4DD7-9CDA-4EC7F73D47FF}.Debug|Any CPU.Build.0 = Debug|Any CPU {AAA67D93-458E-4DD7-9CDA-4EC7F73D47FF}.Release|Any CPU.ActiveCfg = Release|Any CPU {AAA67D93-458E-4DD7-9CDA-4EC7F73D47FF}.Release|Any CPU.Build.0 = Release|Any CPU - {0D18388D-8DDB-43DE-A608-0C65F0AEC4E0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0D18388D-8DDB-43DE-A608-0C65F0AEC4E0}.Release|Any CPU.Build.0 = Release|Any CPU - {0D18388D-8DDB-43DE-A608-0C65F0AEC4E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0D18388D-8DDB-43DE-A608-0C65F0AEC4E0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {78842EE4-8A2F-4C75-AEC6-C95F15AD3994}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {78842EE4-8A2F-4C75-AEC6-C95F15AD3994}.Debug|Any CPU.Build.0 = Debug|Any CPU + {78842EE4-8A2F-4C75-AEC6-C95F15AD3994}.Release|Any CPU.ActiveCfg = Release|Any CPU + {78842EE4-8A2F-4C75-AEC6-C95F15AD3994}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {121E26CF-4A0F-4E74-BC0F-82BABEFDE8BF} EndGlobalSection EndGlobal diff --git a/OpenTK.dll.config b/OpenTK.dll.config deleted file mode 100644 index 7098d39..0000000 --- a/OpenTK.dll.config +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/clean.sh b/clean.sh new file mode 100644 index 0000000..08b3c61 --- /dev/null +++ b/clean.sh @@ -0,0 +1,2 @@ +#!/bin/bash +rm -fr build && find . -iname bin -o -iname obj -o -iname packages -o -iname build | xargs rm -rf diff --git a/packages.config b/packages.config deleted file mode 100644 index 26caf83..0000000 --- a/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/CrowEdit.cs b/src/CrowEdit.cs index 970aa4b..734b647 100644 --- a/src/CrowEdit.cs +++ b/src/CrowEdit.cs @@ -6,6 +6,7 @@ using System; using Crow; using System.IO; using System.Collections.Generic; +using Crow.Text; namespace CrowEdit { @@ -15,30 +16,59 @@ namespace CrowEdit CMDUndo, CMDRedo, CMDCut, CMDCopy, CMDPaste, CMDHelp, CMDAbout, CMDOptions; const string _defaultFileName = "unnamed.txt"; - string _text = "", _origText=""; - bool isDirty = false; - public new bool IsDirty { get { return _text != _origText; } } - - List undoStack = new List(); - List redoStack = new List(); - - public string Text { - get { return _text; } + string source = ""; + int dirtyUndoLevel;// + public new bool IsDirty { get { return undoStack.Count != dirtyUndoLevel; } } + public string Source { + get => source; set { - if (_text == value) + if (source == value) return; - undoStack.Add (_text); + source = value; + NotifyValueChanged (source); + } + } + public CommandGroup EditorCommands => new CommandGroup (CMDUndo, CMDRedo, CMDCut, CMDCopy, CMDPaste, CMDSave, CMDSaveAs); + + Stack undoStack = new Stack (); + Stack redoStack = new Stack (); + + void undo () { + if (undoStack.TryPop (out TextChange tch)) { + redoStack.Push (tch.Inverse (source)); + CMDRedo.CanExecute = true; + apply (tch); + editor.SetCursorPosition (tch.End + tch.ChangedText.Length); + } + if (undoStack.Count == 0) + CMDUndo.CanExecute = false; + } + void redo () { + if (redoStack.TryPop (out TextChange tch)) { + undoStack.Push (tch.Inverse (source)); CMDUndo.CanExecute = true; - redoStack.Clear (); - CMDRedo.CanExecute = false; - _text = value; - NotifyValueChanged (_text); - NotifyValueChanged ("IsDirty", IsDirty); + apply (tch); + editor.SetCursorPosition (tch.End + tch.ChangedText.Length); } + if (redoStack.Count == 0) + CMDRedo.CanExecute = false; + } + bool disableTextChangedEvent = false; + void apply (TextChange change) { + Span tmp = stackalloc char[source.Length + (change.ChangedText.Length - change.Length)]; + ReadOnlySpan src = source.AsSpan (); + src.Slice (0, change.Start).CopyTo (tmp); + if (!string.IsNullOrEmpty(change.ChangedText)) + change.ChangedText.AsSpan ().CopyTo (tmp.Slice (change.Start)); + src.Slice (change.End).CopyTo (tmp.Slice (change.Start + change.ChangedText.Length)); + disableTextChangedEvent = true; + Source = tmp.ToString (); + disableTextChangedEvent = false; + NotifyValueChanged ("IsDirty", IsDirty); } public string CurrentDir { - get { return Configuration.Global.Get("CurrentDir"); } + get => Configuration.Global.Get("CurrentDir"); set { if (CurrentDir == value) return; @@ -47,13 +77,12 @@ namespace CrowEdit } } public string CurrentFilePath { - get { return Configuration.Global.Get ("CurrentFilePath"); } + get => Configuration.Global.Get ("CurrentFilePath"); set { if (CurrentFilePath == value) return; Configuration.Global.Set ("CurrentFilePath", value); NotifyValueChanged (CurrentFilePath); - } } public string CurFileName { @@ -62,9 +91,8 @@ namespace CrowEdit public string CurFileDir { get => string.IsNullOrEmpty (CurrentFilePath) ? CurrentDir : Path.GetDirectoryName (CurrentFilePath); } - public bool ShowLeftPane { - get { return Configuration.Global.Get ("ShowLeftPane"); } + get => Configuration.Global.Get ("ShowLeftPane"); set { if (ShowLeftPane == value) return; @@ -72,8 +100,17 @@ namespace CrowEdit NotifyValueChanged (ShowLeftPane); } } + public bool ReopenLastFile { + get => Configuration.Global.Get ("ReopenLastFile"); + set { + if (ReopenLastFile == value) + return; + Configuration.Global.Set ("ReopenLastFile", value); + NotifyValueChanged (ReopenLastFile); + } + } - void initCommands(){ + void initCommands (){ CMDNew = new Command(new Action(() => onNewFile())) { Caption = "New", Icon = new SvgPicture("#CrowEdit.ui.icons.blank-file.svg")}; CMDOpen = new Command(new Action(() => openFileDialog())) { Caption = "Open...", Icon = new SvgPicture("#CrowEdit.ui.icons.outbox.svg")}; CMDSave = new Command(new Action(() => saveFileDialog())) { Caption = "Save", Icon = new SvgPicture("#CrowEdit.ui.icons.inbox.svg"), CanExecute = false}; @@ -94,43 +131,10 @@ namespace CrowEdit mb.Yes += (sender, e) => newFile(); } else newFile (); - } - void undo(){ - string step = undoStack [undoStack.Count -1]; - redoStack.Add (_text); - CMDRedo.CanExecute = true; - undoStack.RemoveAt(undoStack.Count -1); - - _text = step; - - NotifyValueChanged ("Text", (object)_text); - NotifyValueChanged ("IsDirty", IsDirty); - - if (undoStack.Count == 0) - CMDUndo.CanExecute = false; - } - void redo(){ - string step = redoStack [redoStack.Count -1]; - undoStack.Add (_text); - CMDUndo.CanExecute = true; - redoStack.RemoveAt(redoStack.Count -1); - _text = step; - NotifyValueChanged ("Text", (object)_text); - NotifyValueChanged ("IsDirty", IsDirty); - - if (redoStack.Count == 0) - CMDRedo.CanExecute = false; - } - void openOptionsDialog(){ - Widget ed = this.FindByName("editor"); - Load ("#CrowEdit.ui.EditorOptions.crow").DataSource = ed; - } - void openFileDialog(){ - Load ("#CrowEdit.ui.openFile.crow").DataSource = this; - } - void saveFileDialog(){ - Load ("#CrowEdit.ui.saveFile.crow").DataSource = this; - } + } + void openOptionsDialog() => Load ("#CrowEdit.ui.EditorOptions.crow").DataSource = this; + void openFileDialog() => Load ("#CrowEdit.ui.openFile.crow").DataSource = this; + void saveFileDialog() => Load ("#CrowEdit.ui.saveFile.crow").DataSource = this; void openFileDialog_OkClicked (object sender, EventArgs e) { FileDialog fd = sender as FileDialog; @@ -141,22 +145,19 @@ namespace CrowEdit void saveFileDialog_OkClicked (object sender, EventArgs e) { FileDialog fd = sender as FileDialog; - if (string.IsNullOrEmpty (fd.SelectedFile)) - return; - CurrentFilePath = Path.Combine (fd.SelectedDirectory, fd.SelectedFile); - -// using (StreamWriter sr = new StreamWriter (fd.SelectedFile)) { -// sr.Write(_text); -// } - _origText = _text; - - NotifyValueChanged ("IsDirty", false); - } + return; + save (fd.SelectedFile, fd.SelectedDirectory); + } void onTextChanged (object sender, TextChangeEventArgs e) { - //System.Diagnostics.Debug.WriteLine ("text changed"); - NotifyValueChanged ("IsDirty", IsDirty); + if (disableTextChangedEvent) + return; + undoStack.Push (e.Change.Inverse(source)); + redoStack.Clear (); + CMDUndo.CanExecute = true; + CMDRedo.CanExecute = false; + apply (e.Change); } void goUpDirClick (object sender, MouseButtonEventArgs e) { @@ -185,90 +186,74 @@ namespace CrowEdit } void newFile () { CurrentFilePath = Path.Combine (CurFileDir, _defaultFileName); - _origText = _text = ""; - NotifyValueChanged ("Text", (object)_text); - NotifyValueChanged ("IsDirty", false); - redoStack.Clear (); - undoStack.Clear (); - CMDRedo.CanExecute = false; - CMDUndo.CanExecute = false; + disableTextChangedEvent = true; + Source = ""; + disableTextChangedEvent = false; + resetUndoRedo (); } void openFile (string filePath, string directory) { CurrentFilePath = Path.Combine(directory, filePath); - //reloadFromFile (); - - redoStack.Clear (); - undoStack.Clear (); - CMDRedo.CanExecute = false; - CMDUndo.CanExecute = false; + reloadFromFile (); + resetUndoRedo (); } - /***temp***/ - string source; - public string Source { - get => source; - set { - if (source == value) - return; - source = value; - NotifyValueChanged (source); + void save (string filePath, string directory) { + CurrentFilePath = Path.Combine (directory, filePath); + using (StreamWriter sr = new StreamWriter (CurrentFilePath)) { + sr.Write (source); } + dirtyUndoLevel = undoStack.Count; + + NotifyValueChanged ("IsDirty", IsDirty); } + void reloadFromFile () { + disableTextChangedEvent = true; if (File.Exists (CurrentFilePath)) { using (Stream s = new FileStream (CurrentFilePath, FileMode.Open)) { using (StreamReader sr = new StreamReader (s)) Source = sr.ReadToEnd (); } } + disableTextChangedEvent = false; + resetUndoRedo (); } - /********************************/ - [STAThread] + void resetUndoRedo () { + undoStack.Clear (); + redoStack.Clear (); + CMDUndo.CanExecute = false; + CMDRedo.CanExecute = false; + dirtyUndoLevel = 0; + } static void Main () { using (CrowEdit win = new CrowEdit ()) win.Run (); } - public CrowEdit () - : base(800, 600) - {} + public CrowEdit () : base(800, 600) {} protected override void OnInitialized () { base.OnInitialized (); if (CurrentDir == null) CurrentDir = Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments); - - this.ValueChanged += CrowEdit_ValueChanged; + initCommands (); Load ("#CrowEdit.ui.main.crow").DataSource = this; - } + editor = FindByName ("tb") as TextBox; - /*void textView_KeyDown (object sender, Crow.KeyEventArgs e) - { - if (e.Control) { - if (e.Key == Key.W) { - if (e.Shift) - CMDRedo.Execute (); - else - CMDUndo.Execute (); - } - } - }*/ + if (ReopenLastFile) + reloadFromFile (); + } + TextBox editor; + void textView_KeyDown (object sender, Crow.KeyEventArgs e) { + if (Ctrl && e.Key == Glfw.Key.W) { + if (Shift) + CMDRedo.Execute (); + else + CMDUndo.Execute (); - void CrowEdit_ValueChanged (object sender, ValueChangeEventArgs e) - { - if (e.MemberName == "IsDirty" && isDirty != (bool)e.NewValue) { - isDirty = (bool)e.NewValue; - if (isDirty) { - CMDSave.CanExecute = true; - CMDSaveAs.CanExecute = true; - }else{ - CMDSave.CanExecute = false; - CMDSaveAs.CanExecute = false; - } } } - } } diff --git a/src/Extensions.cs b/src/Extensions.cs new file mode 100644 index 0000000..6cd0e85 --- /dev/null +++ b/src/Extensions.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Crow.Text; + +namespace CrowEdit +{ + public static class Extensions + { + public static TextChange Inverse (this TextChange tch, string src) + => new TextChange (tch.Start, string.IsNullOrEmpty (tch.ChangedText) ? 0 : tch.ChangedText.Length, + tch.Length == 0 ? "" : src.AsSpan (tch.Start, tch.Length).ToString()); + } +} diff --git a/ui/EditorOptions.crow b/ui/EditorOptions.crow index 81e529b..96869c9 100644 --- a/ui/EditorOptions.crow +++ b/ui/EditorOptions.crow @@ -2,5 +2,6 @@ + \ No newline at end of file diff --git a/ui/main.crow b/ui/main.crow index 1695b3f..ee1fe96 100755 --- a/ui/main.crow +++ b/ui/main.crow @@ -33,10 +33,7 @@ - - + + + + + + - -- 2.47.3