From 509e413a43950985b31bc1d4e2cc9b48fb699b38 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jean-Philippe=20Bruy=C3=A8re?= Date: Wed, 3 Nov 2021 14:19:48 +0100 Subject: [PATCH] source only in document, no more in editor->simplification --- CrowEditBase/src/Compiler/SourceDocument.cs | 6 +- CrowEditBase/src/Document.cs | 2 + CrowEditBase/src/Editor.cs | 645 ++++++++---------- CrowEditBase/src/SourceEditor.cs | 169 ++--- CrowEditBase/src/TextDocument.cs | 155 ++++- plugins/CECrowPlugin/src/ImlDocument.cs | 4 +- plugins/CECrowPlugin/src/StyleDocument.cs | 2 +- .../CEXmlPlugin/src/Parsing/XmlDocument.cs | 2 +- 8 files changed, 547 insertions(+), 438 deletions(-) diff --git a/CrowEditBase/src/Compiler/SourceDocument.cs b/CrowEditBase/src/Compiler/SourceDocument.cs index 420141e..2f092b6 100644 --- a/CrowEditBase/src/Compiler/SourceDocument.cs +++ b/CrowEditBase/src/Compiler/SourceDocument.cs @@ -17,7 +17,6 @@ namespace CrowEditBase } protected Token[] tokens; protected SyntaxNode RootNode; - protected LineCollection lines; protected Token currentToken => currentTokenIndex < 0 ? default : tokens[currentTokenIndex]; SyntaxNode currentNode; public SyntaxNode CurrentNode { @@ -131,7 +130,8 @@ namespace CrowEditBase return true; } - internal void updateCurrentTokAndNode (int pos) { + internal void updateCurrentTokAndNode (CharLocation loc) { + int pos = lines.GetAbsolutePosition(loc); if (tokens.Length > 0) { currentTokenIndex = FindTokenIndexIncludingPosition (pos); CurrentNode = FindNodeIncludingSpan (currentToken.Span); @@ -154,7 +154,7 @@ namespace CrowEditBase } protected abstract Tokenizer CreateTokenizer (); protected abstract SyntaxAnalyser CreateSyntaxAnalyser (); - public abstract IList GetSuggestions (int pos); + public abstract IList GetSuggestions (CharLocation loc); /// /// complete current token with selected item from the suggestion overlay. diff --git a/CrowEditBase/src/Document.cs b/CrowEditBase/src/Document.cs index 8b3343e..5aa101c 100644 --- a/CrowEditBase/src/Document.cs +++ b/CrowEditBase/src/Document.cs @@ -31,6 +31,8 @@ namespace CrowEditBase protected ReaderWriterLockSlim editorRWLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); public void EnterReadLock () => editorRWLock.EnterReadLock (); public void ExitReadLock () => editorRWLock.ExitReadLock (); + public void EnterWriteLock () => editorRWLock.EnterWriteLock (); + public void ExitWriteLock () => editorRWLock.ExitWriteLock (); public abstract bool TryGetState (object client, out T state); public abstract void RegisterClient (object client); diff --git a/CrowEditBase/src/Editor.cs b/CrowEditBase/src/Editor.cs index f55ce22..435c2f3 100644 --- a/CrowEditBase/src/Editor.cs +++ b/CrowEditBase/src/Editor.cs @@ -25,8 +25,6 @@ namespace Crow initCommands (); - getLines(); - Thread t = new Thread (backgroundThreadFunc); t.IsBackground = true; t.Start (); @@ -78,7 +76,7 @@ namespace Crow } #region Label - protected string _text = ""; + int targetColumn = -1;//handle line changes with long->short->long line length sequence. protected CharLocation? hoverLoc = null; @@ -124,18 +122,18 @@ namespace Crow /// /// Absolute character position in text. public void SetCursorPosition (int position) { - CharLocation loc = lines.GetLocation (position); - loc.Column = Math.Min (loc.Column, lines[loc.Line].Length); + CharLocation loc = document.GetLocation (position); + loc.Column = Math.Min (loc.Column, document.GetLine (loc.Line).Length); CurrentLoc = loc; } Color selForeground, selBackground; - protected LineCollection lines; + protected bool textMeasureIsUpToDate = false; - protected object linesMutex = new object (); - protected string LineBreak = null; + //protected object linesMutex = new object (); + protected Size cachedTextSize = default (Size); - protected bool mixedLineBreak = false; + protected FontExtents fe; protected TextExtents te; @@ -185,7 +183,7 @@ namespace Crow if (loc.Line == 0) return false; int newLine = getAbsoluteLineIndexFromVisualLineMove (loc.Line, -1); - CurrentLoc = new CharLocation (newLine, lines[newLine].Length); + CurrentLoc = new CharLocation (newLine, document.GetLine (newLine).Length); }else CurrentLoc = new CharLocation (loc.Line, loc.Column - 1); return true; @@ -193,8 +191,8 @@ namespace Crow public bool MoveRight () { targetColumn = -1; CharLocation loc = CurrentLoc.Value; - if (loc.Column == lines[loc.Line].Length) { - if (loc.Line == lines.Count - 1) + if (loc.Column == document.GetLine (loc.Line).Length) { + if (loc.Line == document.LinesCount - 1) return false; CurrentLoc = new CharLocation ( getAbsoluteLineIndexFromVisualLineMove (loc.Line, 1), 0); @@ -209,65 +207,20 @@ namespace Crow if (newLine == loc.Line) return false; - if (loc.Column > lines[newLine].Length) { + if (loc.Column > document.GetLine (newLine).Length) { if (targetColumn < 0) targetColumn = loc.Column; - CurrentLoc = new CharLocation (newLine, lines[newLine].Length); + CurrentLoc = new CharLocation (newLine, document.GetLine (newLine).Length); } else if (targetColumn < 0) CurrentLoc = new CharLocation (newLine, loc.Column); - else if (targetColumn > lines[newLine].Length) - CurrentLoc = new CharLocation (newLine, lines[newLine].Length); + else if (targetColumn > document.GetLine (newLine).Length) + CurrentLoc = new CharLocation (newLine, document.GetLine (newLine).Length); else CurrentLoc = new CharLocation (newLine, targetColumn); return true; } - public void GotoWordStart(){ - int pos = lines.GetAbsolutePosition (CurrentLoc.Value); - //skip white spaces - while (pos > 0 && !char.IsLetterOrDigit (_text[pos-1])) - pos--; - while (pos > 0 && char.IsLetterOrDigit (_text[pos-1])) - pos--; - CurrentLoc = lines.GetLocation (pos); - } - public void GotoWordEnd(){ - int pos = lines.GetAbsolutePosition (CurrentLoc.Value); - //skip white spaces - while (pos < _text.Length -1 && !char.IsLetterOrDigit (_text[pos])) - pos++; - while (pos < _text.Length - 1 && char.IsLetterOrDigit (_text[pos])) - pos++; - CurrentLoc = lines.GetLocation (pos); - } - protected void detectLineBreak () { - mixedLineBreak = false; - - if (lines.Count == 0 || lines[0].LineBreakLength == 0) { - LineBreak = Environment.NewLine; - return; - } - LineBreak = _text.GetLineBreak (lines[0]).ToString (); - for (int i = 1; i < lines.Count; i++) { - ReadOnlySpan lb = _text.GetLineBreak (lines[i]); - if (!lb.SequenceEqual (LineBreak)) { - mixedLineBreak = true; - break; - } - } - } - protected void getLines () { - if (lines == null) - lines = new LineCollection (10); - else - lines.Clear (); - - if (string.IsNullOrEmpty (_text)) - lines.Add (new TextLine (0, 0, 0)); - else - lines.Update (_text); - } /// /// Current Selected text span. May be used to set current position, or current selection. /// @@ -276,8 +229,8 @@ namespace Crow if (value.IsEmpty) selectionStart = null; else - selectionStart = lines.GetLocation (value.Start); - CurrentLoc = lines.GetLocation (value.End); + selectionStart = document.GetLocation (value.Start); + CurrentLoc = document.GetLocation (value.End); } get { if (CurrentLoc == null) @@ -298,13 +251,13 @@ namespace Crow selEnd = CurrentLoc.Value; } } - return new TextSpan (lines.GetAbsolutePosition (selStart), lines.GetAbsolutePosition (selEnd)); + return new TextSpan (document.GetAbsolutePosition (selStart), document.GetAbsolutePosition (selEnd)); } } public string SelectedText { get { TextSpan selection = Selection; - return selection.IsEmpty ? "" : _text.AsSpan (selection.Start, selection.Length).ToString (); + return selection.IsEmpty ? "" : document.GetText (selection).ToString (); } } public bool SelectionIsEmpty => selectionStart.HasValue ? Selection.IsEmpty : true; @@ -315,33 +268,40 @@ namespace Crow /// /// total line count /// - protected virtual int visualLineCount => lines.Count; + protected virtual int visualLineCount => document.LinesCount; protected virtual void measureTextBounds (Context gr) { fe = gr.FontExtents; te = new TextExtents (); - cachedTextSize.Height = (int)Math.Ceiling (lineHeight * Math.Max (1, visualLineCount)); - - TextExtents tmp = default; - int longestLine = 0; - for (int i = 0; i < lines.Count; i++) { - if (lines[i].LengthInPixel < 0) { - if (lines[i].Length == 0) - lines.UpdateLineLengthInPixel (i, 0);// (int)Math.Ceiling (fe.MaxXAdvance); - else { - gr.TextExtents (_text.GetLine (lines[i]), App.TabulationSize, out tmp); - lines.UpdateLineLengthInPixel (i, (int)Math.Ceiling (tmp.XAdvance)); + document.EnterReadLock(); + try { + + cachedTextSize.Height = (int)Math.Ceiling (lineHeight * Math.Max (1, visualLineCount)); + + TextExtents tmp = default; + int longestLine = 0; + for (int i = 0; i < document.LinesCount; i++) { + TextLine l = document.GetLine (i); + if (l.LengthInPixel < 0) { + if (l.Length == 0) + l.LengthInPixel = 0;// (int)Math.Ceiling (fe.MaxXAdvance); + else { + gr.TextExtents (document.GetText (l), App.TabulationSize, out tmp); + l.LengthInPixel = (int)Math.Ceiling (tmp.XAdvance); + } } + if (l.LengthInPixel > document.GetLine (longestLine).LengthInPixel) + longestLine = i; } - if (lines[i].LengthInPixel > lines[longestLine].LengthInPixel) - longestLine = i; - } - cachedTextSize.Width = lines[longestLine].LengthInPixel; - textMeasureIsUpToDate = true; + cachedTextSize.Width = document.GetLine (longestLine).LengthInPixel; + textMeasureIsUpToDate = true; - updateMaxScrolls (LayoutingType.Height); - updateMaxScrolls (LayoutingType.Width); + updateMaxScrolls (LayoutingType.Height); + updateMaxScrolls (LayoutingType.Width); + } finally { + document.ExitReadLock (); + } } protected virtual void drawContent (Context gr) { gr.Translate (-ScrollX, -ScrollY); @@ -353,114 +313,120 @@ namespace Crow CharLocation selStart = default, selEnd = default; bool selectionNotEmpty = false; - //if (HasFocus) { - if (currentLoc?.Column < 0) { - updateLocation (gr, cb.Width, ref currentLoc); - NotifyValueChanged ("CurrentColumn", CurrentColumn); - } else - updateLocation (gr, cb.Width, ref currentLoc); - if (selectionStart.HasValue) { - updateLocation (gr, cb.Width, ref selectionStart); - if (CurrentLoc.Value != selectionStart.Value) - selectionNotEmpty = true; - } - if (selectionNotEmpty) { - if (CurrentLoc.Value.Line < selectionStart.Value.Line) { - selStart = CurrentLoc.Value; - selEnd = selectionStart.Value; - } else if (CurrentLoc.Value.Line > selectionStart.Value.Line) { - selStart = selectionStart.Value; - selEnd = CurrentLoc.Value; - } else if (CurrentLoc.Value.Column < selectionStart.Value.Column) { - selStart = CurrentLoc.Value; - selEnd = selectionStart.Value; - } else { - selStart = selectionStart.Value; - selEnd = CurrentLoc.Value; + document.EnterReadLock(); + try { + //if (HasFocus) { + if (currentLoc?.Column < 0) { + updateLocation (gr, cb.Width, ref currentLoc); + NotifyValueChanged ("CurrentColumn", CurrentColumn); + } else + updateLocation (gr, cb.Width, ref currentLoc); + if (selectionStart.HasValue) { + updateLocation (gr, cb.Width, ref selectionStart); + if (CurrentLoc.Value != selectionStart.Value) + selectionNotEmpty = true; } - } else - IFace.forceTextCursor = true; - //} - - if (!string.IsNullOrEmpty (_text)) { - Foreground?.SetAsSource (IFace, gr); - - TextExtents extents; - Span bytes = stackalloc byte[128]; - double y = 0; - - for (int i = 0; i < lines.Count; i++) { - if (!cancelLinePrint (lineHeight, y, cb.Height)) { - int encodedBytes = -1; - if (lines[i].Length > 0) { - int size = lines[i].Length * 4 + 1; - if (bytes.Length < size) - bytes = size > 512 ? new byte[size] : stackalloc byte[size]; - - encodedBytes = Crow.Text.Encoding.ToUtf8 (_text.GetLine (lines[i]), bytes); - bytes[encodedBytes++] = 0; - - if (lines[i].LengthInPixel < 0) { - gr.TextExtents (bytes.Slice (0, encodedBytes), out extents); - lines.UpdateLineLengthInPixel (i, (int)extents.XAdvance); - } + if (selectionNotEmpty) { + if (CurrentLoc.Value.Line < selectionStart.Value.Line) { + selStart = CurrentLoc.Value; + selEnd = selectionStart.Value; + } else if (CurrentLoc.Value.Line > selectionStart.Value.Line) { + selStart = selectionStart.Value; + selEnd = CurrentLoc.Value; + } else if (CurrentLoc.Value.Column < selectionStart.Value.Column) { + selStart = CurrentLoc.Value; + selEnd = selectionStart.Value; + } else { + selStart = selectionStart.Value; + selEnd = CurrentLoc.Value; } - - RectangleD lineRect = new RectangleD ( - (int)cb.X, - y + cb.Top, lines[i].LengthInPixel, lineHeight); - - if (encodedBytes > 0) { - gr.MoveTo (lineRect.X, lineRect.Y + fe.Ascent); - gr.ShowText (bytes.Slice (0, encodedBytes)); - } - /********** DEBUG TextLineCollection ************* - gr.SetSource (Colors.Red); - gr.SetFontSize (9); - gr.MoveTo (700, lineRect.Y + fe.Ascent); - gr.ShowText ($"({lines[i].Start}, {lines[i].End}, {lines[i].EndIncludingLineBreak})"); - gr.SetFontSize (Font.Size); - Foreground.SetAsSource (IFace, gr); - ********** DEBUG TextLineCollection *************/ - - if (selectionNotEmpty) { - RectangleD selRect = lineRect; - - if (i >= selStart.Line && i <= selEnd.Line) { - if (selStart.Line == selEnd.Line) { - selRect.X = selStart.VisualCharXPosition + cb.X; - selRect.Width = selEnd.VisualCharXPosition - selStart.VisualCharXPosition; - } else if (i == selStart.Line) { - double newX = selStart.VisualCharXPosition + cb.X; - selRect.Width -= (newX - selRect.X) - 10.0; - selRect.X = newX; - } else if (i == selEnd.Line) { - selRect.Width = selEnd.VisualCharXPosition - selRect.X + cb.X; - } else - selRect.Width += 10.0; - } else { - y += lineHeight; - continue; + } else + IFace.forceTextCursor = true; + //} + + if (document.Lenght > 0) { + Foreground?.SetAsSource (IFace, gr); + + TextExtents extents; + Span bytes = stackalloc byte[128]; + double y = 0; + + for (int i = 0; i < document.LinesCount; i++) { + if (!cancelLinePrint (lineHeight, y, cb.Height)) { + int encodedBytes = -1; + TextLine l = document.GetLine (i); + if (l.Length > 0) { + int size = l.Length * 4 + 1; + if (bytes.Length < size) + bytes = size > 512 ? new byte[size] : stackalloc byte[size]; + + encodedBytes = Crow.Text.Encoding.ToUtf8 (document.GetText (l), bytes); + bytes[encodedBytes++] = 0; + + if (l.LengthInPixel < 0) { + gr.TextExtents (bytes.Slice (0, encodedBytes), out extents); + l.LengthInPixel = (int)extents.XAdvance; + } } - gr.SetSource (selBackground); - gr.Rectangle (selRect); - if (encodedBytes < 0) - gr.Fill (); - else { - gr.FillPreserve (); - gr.Save (); - gr.Clip (); - gr.SetSource (SelectionForeground); + RectangleD lineRect = new RectangleD ( + (int)cb.X, + y + cb.Top, l.LengthInPixel, lineHeight); + + if (encodedBytes > 0) { gr.MoveTo (lineRect.X, lineRect.Y + fe.Ascent); gr.ShowText (bytes.Slice (0, encodedBytes)); - gr.Restore (); } + /********** DEBUG TextLineCollection ************* + gr.SetSource (Colors.Red); + gr.SetFontSize (9); + gr.MoveTo (700, lineRect.Y + fe.Ascent); + gr.ShowText ($"({lines[i].Start}, {lines[i].End}, {lines[i].EndIncludingLineBreak})"); + gr.SetFontSize (Font.Size); Foreground.SetAsSource (IFace, gr); + ********** DEBUG TextLineCollection *************/ + + if (selectionNotEmpty) { + RectangleD selRect = lineRect; + + if (i >= selStart.Line && i <= selEnd.Line) { + if (selStart.Line == selEnd.Line) { + selRect.X = selStart.VisualCharXPosition + cb.X; + selRect.Width = selEnd.VisualCharXPosition - selStart.VisualCharXPosition; + } else if (i == selStart.Line) { + double newX = selStart.VisualCharXPosition + cb.X; + selRect.Width -= (newX - selRect.X) - 10.0; + selRect.X = newX; + } else if (i == selEnd.Line) { + selRect.Width = selEnd.VisualCharXPosition - selRect.X + cb.X; + } else + selRect.Width += 10.0; + } else { + y += lineHeight; + continue; + } + + gr.SetSource (selBackground); + gr.Rectangle (selRect); + if (encodedBytes < 0) + gr.Fill (); + else { + gr.FillPreserve (); + gr.Save (); + gr.Clip (); + gr.SetSource (SelectionForeground); + gr.MoveTo (lineRect.X, lineRect.Y + fe.Ascent); + gr.ShowText (bytes.Slice (0, encodedBytes)); + gr.Restore (); + } + Foreground.SetAsSource (IFace, gr); + } } + y += lineHeight; } - y += lineHeight; } + } finally { + document.ExitReadLock (); } gr.Translate (ScrollX, ScrollY); @@ -493,13 +459,13 @@ namespace Crow } if (!CurrentLoc.Value.HasVisualX) { setFontForContext (ctx); - lock (linesMutex) { - if (currentLoc?.Column < 0) { - updateLocation (ctx, ClientRectangle.Width, ref currentLoc); - NotifyValueChanged ("CurrentColumn", CurrentColumn); - } else - updateLocation (ctx, ClientRectangle.Width, ref currentLoc); - } + + if (currentLoc?.Column < 0) { + updateLocation (ctx, ClientRectangle.Width, ref currentLoc); + NotifyValueChanged ("CurrentColumn", CurrentColumn); + } else + updateLocation (ctx, ClientRectangle.Width, ref currentLoc); + textCursor = null; } @@ -510,6 +476,7 @@ namespace Crow return false; } //} + Rectangle c = ScreenCoordinates (textCursor.Value + Slot.Position + ClientRectangle.Position); ctx.ResetClip (); Foreground.SetAsSource (IFace, ctx, c); @@ -528,8 +495,8 @@ namespace Crow //Console.WriteLine ($"updateLocation: {loc} text:{_text.Length}"); if (loc.HasVisualX) return; - TextLine ls = lines[loc.Line]; - ReadOnlySpan curLine = _text.GetLine (ls); + TextLine ls = document.GetLine (loc.Line); + ReadOnlySpan curLine = document.GetText (ls); double cPos = 0; if (loc.Column >= 0) { @@ -581,7 +548,7 @@ namespace Crow base.OnLayoutChanges (layoutType); updateMaxScrolls (layoutType); } - public override bool UpdateLayout (LayoutingType layoutType) { + /*public override bool UpdateLayout (LayoutingType layoutType) { if ((LayoutingType.Sizing | layoutType) != LayoutingType.None) { if (!System.Threading.Monitor.TryEnter (linesMutex)) return false; @@ -592,14 +559,11 @@ namespace Crow } finally { System.Threading.Monitor.Exit (linesMutex); } - } + }*/ public override int measureRawSize(LayoutingType lt) { DbgLogger.StartEvent(DbgEvtType.GOMeasure, this, lt); try { - if ((bool)lines?.IsEmpty) - getLines (); - if (!textMeasureIsUpToDate) { using (Context gr = new Context (IFace.surf)) { setFontForContext (gr); @@ -611,7 +575,10 @@ namespace Crow DbgLogger.EndEvent(DbgEvtType.GOMeasure); } } - + public override void Paint (Context ctx) { + base.Paint (ctx); + IFace.forceTextCursor = true; + } protected override void onDraw (Context gr) { //base.onDraw (gr); @@ -619,8 +586,7 @@ namespace Crow setFontForContext (gr); if (!textMeasureIsUpToDate) { - lock (linesMutex) - measureTextBounds (gr); + measureTextBounds (gr); } if (ClipToClientRect) { @@ -629,8 +595,7 @@ namespace Crow gr.Clip (); } - lock (linesMutex) - drawContent (gr); + drawContent (gr); if (ClipToClientRect) gr.Restore (); @@ -708,128 +673,144 @@ namespace Crow if (e.Button != MouseButton.Left || !HasFocus) return; - GotoWordStart (); - selectionStart = CurrentLoc; - GotoWordEnd (); + selectionStart = document.GetWordStart (CurrentLoc.Value); + CurrentLoc = document.GetWordEnd (CurrentLoc.Value); RegisterForRedraw (); } #endregion #region Keyboard handling + public override void onKeyPress (object sender, KeyPressEventArgs e) { + base.onKeyPress (sender, e); + + TextSpan selection = Selection; + update (new TextChange (selection.Start, selection.Length, e.KeyChar.ToString ())); + + /*Insert (e.KeyChar.ToString()); + + SelRelease = -1; + SelBegin = new Point(CurrentColumn, SelBegin.Y); + + RegisterForGraphicUpdate();*/ + } public override void onKeyDown (object sender, KeyEventArgs e) { Key key = e.Key; TextSpan selection = Selection; - switch (key) { - case Key.Backspace: - if (selection.IsEmpty) { - if (selection.Start == 0) - return; - if (CurrentLoc.Value.Column == 0) { - int lbLength = lines[CurrentLoc.Value.Line - 1].LineBreakLength; - update (new TextChange (selection.Start - lbLength, lbLength, "")); - }else - update (new TextChange (selection.Start - 1, 1, "")); - } else - update (new TextChange (selection.Start, selection.Length, "")); - break; - case Key.Delete: - if (selection.IsEmpty) { - if (selection.Start == _text.Length) - return; - if (CurrentLoc.Value.Column >= lines[CurrentLoc.Value.Line].Length) - update (new TextChange (selection.Start, lines[CurrentLoc.Value.Line].LineBreakLength, "")); + + /*document.EnterReadLock(); + try {*/ + switch (key) { + case Key.Backspace: + if (selection.IsEmpty) { + if (selection.Start == 0) + return; + if (CurrentLoc.Value.Column == 0) { + int lbLength = document.GetLine (CurrentLoc.Value.Line - 1).LineBreakLength; + update (new TextChange (selection.Start - lbLength, lbLength, "")); + }else + update (new TextChange (selection.Start - 1, 1, "")); + } else + update (new TextChange (selection.Start, selection.Length, "")); + break; + case Key.Delete: + if (selection.IsEmpty) { + if (selection.Start == document.Lenght) + return; + if (CurrentLoc.Value.Column >= document.GetLine (CurrentLoc.Value.Line).Length) + update (new TextChange (selection.Start, document.GetLine (CurrentLoc.Value.Line).LineBreakLength, "")); + else + update (new TextChange (selection.Start, 1, "")); + } else { + if (e.Modifiers == Modifier.Shift) + IFace.Clipboard = SelectedText; + update (new TextChange (selection.Start, selection.Length, "")); + } + break; + case Key.Insert: + if (e.Modifiers.HasFlag (Modifier.Shift)) + Paste (); + else if (e.Modifiers.HasFlag (Modifier.Control)) + Copy (); + break; + case Key.KeypadEnter: + case Key.Enter: + update (new TextChange (selection.Start, selection.Length, document.GetLineBreak ())); + break; + case Key.Escape: + selectionStart = null; + CurrentLoc = document.GetLocation (selection.Start); + RegisterForRedraw (); + break; + case Key.Tab: + update (new TextChange (selection.Start, selection.Length, App.IndentWithSpace ? new string(' ', App.TabulationSize) : "\t")); + break; + case Key.PageUp: + checkShift (e); + LineMove (-visibleLines); + RegisterForRedraw (); + break; + case Key.PageDown: + checkShift (e); + LineMove (visibleLines); + RegisterForRedraw (); + break; + case Key.Home: + targetColumn = -1; + checkShift (e); + if (e.Modifiers.HasFlag (Modifier.Control)) + CurrentLoc = new CharLocation (0, 0); else - update (new TextChange (selection.Start, 1, "")); - } else { - if (e.Modifiers == Modifier.Shift) - IFace.Clipboard = SelectedText; - update (new TextChange (selection.Start, selection.Length, "")); - } - break; - case Key.Insert: - if (e.Modifiers.HasFlag (Modifier.Shift)) - Paste (); - else if (e.Modifiers.HasFlag (Modifier.Control)) - Copy (); - break; - case Key.KeypadEnter: - case Key.Enter: - if (string.IsNullOrEmpty (LineBreak)) - detectLineBreak (); - update (new TextChange (selection.Start, selection.Length, LineBreak)); - break; - case Key.Escape: - selectionStart = null; - CurrentLoc = lines.GetLocation (selection.Start); - RegisterForRedraw (); - break; - case Key.Tab: - update (new TextChange (selection.Start, selection.Length, App.IndentWithSpace ? new string(' ', App.TabulationSize) : "\t")); - break; - case Key.PageUp: - checkShift (e); - LineMove (-visibleLines); - RegisterForRedraw (); - break; - case Key.PageDown: - checkShift (e); - LineMove (visibleLines); - RegisterForRedraw (); - break; - case Key.Home: - targetColumn = -1; - checkShift (e); - if (e.Modifiers.HasFlag (Modifier.Control)) - CurrentLoc = new CharLocation (0, 0); - else - CurrentLoc = new CharLocation (CurrentLoc.Value.Line, 0); - RegisterForRedraw (); - break; - case Key.End: - checkShift (e); - int l = e.Modifiers.HasFlag (Modifier.Control) ? lines.Count - 1 : CurrentLoc.Value.Line; - CurrentLoc = new CharLocation (l, lines[l].Length); - RegisterForRedraw (); - break; - case Key.Left: - checkShift (e); - if (e.Modifiers.HasFlag (Modifier.Control)) - GotoWordStart (); - else - MoveLeft (); - RegisterForRedraw (); - break; - case Key.Right: - checkShift (e); - if (e.Modifiers.HasFlag (Modifier.Control)) - GotoWordEnd (); - else - MoveRight (); - RegisterForRedraw (); - break; - case Key.Up: - checkShift (e); - LineMove (-1); - RegisterForRedraw (); - break; - case Key.Down: - checkShift (e); - LineMove (1); - RegisterForRedraw (); - break; - case Key.A: - if (e.Modifiers.HasFlag (Modifier.Control)) { - selectionStart = new CharLocation (0, 0); - CurrentLoc = new CharLocation (lines.Count - 1, lines[lines.Count - 1].Length); + CurrentLoc = new CharLocation (CurrentLoc.Value.Line, 0); + RegisterForRedraw (); + break; + case Key.End: + checkShift (e); + int l = e.Modifiers.HasFlag (Modifier.Control) ? document.LinesCount - 1 : CurrentLoc.Value.Line; + CurrentLoc = new CharLocation (l, document.GetLine (l).Length); + RegisterForRedraw (); + break; + case Key.Left: + checkShift (e); + if (e.Modifiers.HasFlag (Modifier.Control)) + CurrentLoc = document.GetWordStart (CurrentLoc.Value); + else + MoveLeft (); + RegisterForRedraw (); + break; + case Key.Right: + checkShift (e); + if (e.Modifiers.HasFlag (Modifier.Control)) + CurrentLoc = document.GetWordEnd (CurrentLoc.Value); + else + MoveRight (); + RegisterForRedraw (); + break; + case Key.Up: + checkShift (e); + LineMove (-1); + RegisterForRedraw (); + break; + case Key.Down: + checkShift (e); + LineMove (1); + RegisterForRedraw (); + break; + case Key.A: + if (e.Modifiers.HasFlag (Modifier.Control)) { + selectionStart = new CharLocation (0, 0); + CurrentLoc = document.EndLocation; + } + break; + default: + base.onKeyDown (sender, e); + return; } - break; - default: - base.onKeyDown (sender, e); - return; - } - autoAdjustScroll = true; - IFace.forceTextCursor = true; - e.Handled = true; + autoAdjustScroll = true; + IFace.forceTextCursor = true; + e.Handled = true; + /*} finally { + document.ExitReadLock (); + }*/ } #endregion #endregion @@ -898,48 +879,20 @@ namespace Crow update (new TextChange (selection.Start, selection.Length, IFace.Clipboard)); } - #region Keyboard handling - public override void onKeyPress (object sender, KeyPressEventArgs e) { - base.onKeyPress (sender, e); - - TextSpan selection = Selection; - update (new TextChange (selection.Start, selection.Length, e.KeyChar.ToString ())); - - /*Insert (e.KeyChar.ToString()); - - SelRelease = -1; - SelBegin = new Point(CurrentColumn, SelBegin.Y); - - RegisterForGraphicUpdate();*/ - } - #endregion - protected void update (TextChange change) { - lock (linesMutex) { - ReadOnlySpan src = _text.AsSpan (); - Span tmp = stackalloc char[src.Length + (change.ChangedText.Length - change.Length)]; - //Console.WriteLine ($"{Text.Length,-4} {change.Start,-4} {change.Length,-4} {change.ChangedText.Length,-4} tmp:{tmp.Length,-4}"); - src.Slice (0, change.Start).CopyTo (tmp); - change.ChangedText.AsSpan ().CopyTo (tmp.Slice (change.Start)); - src.Slice (change.End).CopyTo (tmp.Slice (change.Start + change.ChangedText.Length)); - - _text = tmp.ToString (); - lines.Update (change); - //lines.Update (_text); - selectionStart = null; + OnTextChanged (this, new TextChangeEventArgs (change)); - CurrentLoc = lines.GetLocation (change.Start + change.ChangedText.Length); - textMeasureIsUpToDate = false; - IFace.forceTextCursor = true; - } + selectionStart = null; + CurrentLoc = document.GetLocation (change.Start + change.ChangedText.Length); - OnTextChanged (this, new TextChangeEventArgs (change)); + textMeasureIsUpToDate = false; + IFace.forceTextCursor = true; + autoAdjustScroll = true; RegisterForGraphicUpdate (); } #endregion - } } \ No newline at end of file diff --git a/CrowEditBase/src/SourceEditor.cs b/CrowEditBase/src/SourceEditor.cs index ceac278..9121ac9 100644 --- a/CrowEditBase/src/SourceEditor.cs +++ b/CrowEditBase/src/SourceEditor.cs @@ -64,7 +64,7 @@ namespace Crow base.OnTextChanged(sender, e); if (Document is SourceDocument srcdoc) - srcdoc.updateCurrentTokAndNode (lines.GetAbsolutePosition(CurrentLoc.Value)); + srcdoc.updateCurrentTokAndNode (CurrentLoc.Value); if (!disableSuggestions && HasFocus) tryGetSuggestions (); @@ -83,7 +83,7 @@ namespace Crow protected void tryGetSuggestions () { if (currentLoc.HasValue && Document is SourceDocument srcDoc) { - IList suggs = srcDoc.GetSuggestions (lines.GetAbsolutePosition (CurrentLoc.Value)); + IList suggs = srcDoc.GetSuggestions (CurrentLoc.Value); if (suggs != null && suggs.Count == 1 && ( (suggs[0] is System.Reflection.MemberInfo mi && mi.Name == srcDoc.CurrentTokenString) || (suggs[0].ToString() == srcDoc.CurrentTokenString) @@ -164,7 +164,7 @@ namespace Crow void updateMargin () { leftMargin = leftMarginGap; if (App.PrintLineNumbers) - leftMargin += (int)Math.Ceiling((double)lines.Count.ToString().Length * fe.MaxXAdvance) + 6; + leftMargin += (int)Math.Ceiling((double)(Document == null ? 1 : Document.LinesCount.ToString().Length) * fe.MaxXAdvance) + 6; if (App.FoldingEnabled) leftMargin += foldMargin; leftMargin += leftMarginRightGap; @@ -183,7 +183,7 @@ namespace Crow fold = fold.Parent; fold?.UnfoldToTheTop(); if (Document is SourceDocument doc) - doc.updateCurrentTokAndNode (lines.GetAbsolutePosition(currentLoc.Value)); + doc.updateCurrentTokAndNode (currentLoc.Value); } NotifyValueChanged ("CurrentLine", CurrentLine); NotifyValueChanged ("CurrentColumn", CurrentColumn); @@ -194,9 +194,6 @@ namespace Crow { DbgLogger.StartEvent(DbgEvtType.GOMeasure, this, lt); try { - if ((bool)lines?.IsEmpty) - getLines (); - updateMargin (); if (!textMeasureIsUpToDate) { @@ -286,85 +283,89 @@ namespace Crow { TextSpan selection = Selection; - if (SelectionIsEmpty) { - if (suggestionsActive) { - switch (e.Key) { - case Key.Escape: - hideOverlay (); - return; - case Key.Left: - case Key.Right: - hideOverlay (); - break; - case Key.End: - case Key.Home: - case Key.Down: - case Key.Up: - case Key.PageDown: - case Key.PageUp: - overlay.onKeyDown (this, e); - return; - case Key.Tab: - case Key.Enter: - case Key.KeypadEnter: - completeToken (); + /*Document.EnterReadLock(); + try {*/ + if (SelectionIsEmpty) { + if (suggestionsActive) { + switch (e.Key) { + case Key.Escape: + hideOverlay (); + return; + case Key.Left: + case Key.Right: + hideOverlay (); + break; + case Key.End: + case Key.Home: + case Key.Down: + case Key.Up: + case Key.PageDown: + case Key.PageUp: + overlay.onKeyDown (this, e); + return; + case Key.Tab: + case Key.Enter: + case Key.KeypadEnter: + completeToken (); + return; + } + } else if (e.Key == Key.Space && e.Modifiers.HasFlag (Modifier.Control)) { + tryGetSuggestions (); return; } - } else if (e.Key == Key.Space && e.Modifiers.HasFlag (Modifier.Control)) { - tryGetSuggestions (); - return; - } - } else if (e.Key == Key.Tab && !selection.IsEmpty) { - int lineStart = lines.GetLocation (selection.Start).Line; - CharLocation locEnd = lines.GetLocation (selection.End); - int lineEnd = locEnd.Column == 0 ? Math.Max (0, locEnd.Line - 1) : locEnd.Line; - - disableSuggestions = true; - - if ( e.Modifiers == Modifier.Shift) { - for (int l = lineStart; l <= lineEnd; l++) { - if (_text[lines[l].Start] == '\t') - update (new TextChange (lines[l].Start, 1, "")); - else if (Char.IsWhiteSpace (_text[lines[l].Start])) { - int i = 1; - while (i < lines[l].Length && i < App.TabulationSize && Char.IsWhiteSpace (_text[i])) - i++; - update (new TextChange (lines[l].Start, i, "")); + } else if (e.Key == Key.Tab && !selection.IsEmpty) { + int lineStart = Document.GetLocation (selection.Start).Line; + CharLocation locEnd = Document.GetLocation (selection.End); + int lineEnd = locEnd.Column == 0 ? Math.Max (0, locEnd.Line - 1) : locEnd.Line; + + disableSuggestions = true; + + if ( e.Modifiers == Modifier.Shift) { + for (int l = lineStart; l <= lineEnd; l++) { + TextLine li = Document.GetLine (l); + if (Document.GetChar (li.Start) == '\t') + update (new TextChange (li.Start, 1, "")); + else if (Char.IsWhiteSpace (Document.GetChar (li.Start))) { + int i = 1; + while (i < li.Length && i < App.TabulationSize && Char.IsWhiteSpace (Document.GetChar (i))) + i++; + update (new TextChange (li.Start, i, "")); + } } - } - }else{ - for (int l = lineStart; l <= lineEnd; l++) - update (new TextChange (lines[l].Start, 0, "\t")); - } + }else{ + for (int l = lineStart; l <= lineEnd; l++) + update (new TextChange (Document.GetLine (l).Start, 0, "\t")); + } - selectionStart = new CharLocation (lineStart, 0); - CurrentLoc = new CharLocation (lineEnd, lines[lineEnd].Length); + selectionStart = new CharLocation (lineStart, 0); + CurrentLoc = new CharLocation (lineEnd, Document.GetLine (lineEnd).Length); - disableSuggestions = false; + disableSuggestions = false; - return; - } - if (Document is SourceDocument doc) { - switch (e.Key) { - case Key.F3: - doc.SyntaxRootNode?.Dump(); - break; - case Key.Enter: - case Key.KeypadEnter: - //doc.updateCurrentTokAndNode (Selection.Start); - Console.WriteLine ($"*** Current Token: {doc.CurrentToken} Current Node: {doc.CurrentNode}"); - if (string.IsNullOrEmpty (LineBreak)) - detectLineBreak (); - update (new TextChange (selection.Start, selection.Length, LineBreak)); - autoAdjustScroll = true; - IFace.forceTextCursor = true; - e.Handled = true; - return; + return; + } + if (Document is SourceDocument doc) { + switch (e.Key) { + case Key.F3: + doc.SyntaxRootNode?.Dump(); + break; + case Key.Enter: + case Key.KeypadEnter: + //doc.updateCurrentTokAndNode (Selection.Start); + Console.WriteLine ($"*** Current Token: {doc.CurrentToken} Current Node: {doc.CurrentNode}"); + update (new TextChange (selection.Start, selection.Length, Document.GetLineBreak ())); + autoAdjustScroll = true; + IFace.forceTextCursor = true; + e.Handled = true; + return; + } } - } - base.onKeyDown(sender, e); + base.onKeyDown(sender, e); + /*} finally { + Document.ExitReadLock (); + }*/ } @@ -461,7 +462,7 @@ namespace Crow get { if (!(Document is SourceDocument doc)) return base.visualLineCount; - return lines.Count - countFoldedLinesUntil (lines.Count); + return Document.LinesCount - countFoldedLinesUntil (Document.LinesCount); } } protected override int visualCurrentLine => CurrentLoc.HasValue ? getVisualLine (CurrentLoc.Value.Line) : 0; @@ -524,7 +525,7 @@ namespace Crow bool printLineNumbers = App.PrintLineNumbers; Color marginBG = App.MarginBackground; Color marginFG = Colors.Ivory; - double lineNumWidth = gr.TextExtents (lines.Count.ToString()).Width; + double lineNumWidth = gr.TextExtents (Document.LinesCount.ToString()).Width; Rectangle cb = ClientRectangle; RectangleD marginRect = new RectangleD (cb.X + ScrollX, cb.Y, leftMargin - leftMarginRightGap, lineHeight); @@ -546,9 +547,9 @@ namespace Crow if (CurrentNode != null) { TextSpan nodeSpan = CurrentNode.Span; - nodeStart = lines.GetLocation (nodeSpan.Start); + nodeStart = Document.GetLocation (nodeSpan.Start); updateLocation (gr, cb.Width, ref nodeStart); - nodeEnd = lines.GetLocation (nodeSpan.End); + nodeEnd = Document.GetLocation (nodeSpan.End); updateLocation (gr, cb.Width, ref nodeEnd); } #if DEBUG_NODES @@ -623,7 +624,7 @@ namespace Crow bool notEndOfNodes = nodeEnum.MoveNext(); int l = 0; - while (l < lines.Count) { + while (l < Document.LinesCount) { //if (!cancelLinePrint (lineHeight, lineHeight * y, cb.Height)) { bool foldable = false; @@ -644,7 +645,7 @@ namespace Crow //buff = sourceBytes.Slice (lines[l].Start, lines[l].Length); - while (tok.Start < lines[l].End) { + while (tok.Start < Document.GetLine (l).End) { buff = sourceBytes.Slice (tok.Start, tok.Length); gr.SetSource (doc.GetColorForToken (tok.Type)); @@ -728,7 +729,7 @@ namespace Crow if (foldable && curNode.isFolded) { TextSpan ns = curNode.Span; l = curNode.StartLine + curNode.LineCount; - while (tok.End <= lines[l].Start) { + while (tok.End <= Document.GetLine (l).Start) { if (++tokPtr >= doc.Tokens.Length) break; tok = doc.Tokens[tokPtr]; diff --git a/CrowEditBase/src/TextDocument.cs b/CrowEditBase/src/TextDocument.cs index 26828d1..dfc3146 100644 --- a/CrowEditBase/src/TextDocument.cs +++ b/CrowEditBase/src/TextDocument.cs @@ -20,6 +20,8 @@ namespace CrowEditBase string source, origSource; System.Text.Encoding encoding = System.Text.Encoding.UTF8; + protected bool mixedLineBreak = false; + protected string lineBreak = null; public string Source { get => source; @@ -27,11 +29,15 @@ namespace CrowEditBase if (source == value) return; source = value; + + getLines(); + NotifyValueChanged (source); NotifyValueChanged ("IsDirty", IsDirty); CMDSave.CanExecute = IsDirty; } } + protected LineCollection lines; public override bool IsDirty => origSource != source; /// dictionnary of object per document client, when not null, client must reload content of document. @@ -180,7 +186,9 @@ namespace CrowEditBase 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)); - Source = tmp.ToString (); + source = tmp.ToString (); + + lines.Update (change); } protected void applyTextChange (TextChange change, object triggeringEditor = null) { editorRWLock.EnterWriteLock (); @@ -199,5 +207,150 @@ namespace CrowEditBase protected void onTextChanged (object sender, TextChangeEventArgs e) { applyTextChange (e.Change, sender); } + protected void getLines () { + editorRWLock.EnterWriteLock (); + if (lines == null) + lines = new LineCollection (10); + else + lines.Clear (); + + if (string.IsNullOrEmpty (source)) + lines.Add (new TextLine (0, 0, 0)); + else + lines.Update (source); + editorRWLock.ExitWriteLock (); + } + public string GetLineBreak () { + editorRWLock.EnterReadLock (); + try { + if (string.IsNullOrEmpty (lineBreak)) { + mixedLineBreak = false; + + if (lines.Count == 0 || lines[0].LineBreakLength == 0) + lineBreak = Environment.NewLine; + else { + lineBreak = source.GetLineBreak (lines[0]).ToString (); + + for (int i = 1; i < lines.Count; i++) { + ReadOnlySpan lb = source.GetLineBreak (lines[i]); + if (!lb.SequenceEqual (lineBreak)) { + mixedLineBreak = true; + break; + } + } + } + } + return lineBreak; + } finally { + editorRWLock.ExitReadLock(); + } + } + public CharLocation GetLocation (int absolutePosition) { + editorRWLock.EnterReadLock (); + try { + return lines.GetLocation (absolutePosition); + } finally { + editorRWLock.ExitReadLock(); + } + } + public int GetAbsolutePosition (CharLocation loc) { + editorRWLock.EnterReadLock (); + try { + return lines.GetAbsolutePosition (loc); + } finally { + editorRWLock.ExitReadLock(); + } + } + public CharLocation EndLocation { + get { + editorRWLock.EnterReadLock (); + try { + return new CharLocation (lines.Count - 1, lines[lines.Count - 1].Length); + } finally { + editorRWLock.ExitReadLock(); + } + } + } + public int LinesCount { + get { + editorRWLock.EnterReadLock (); + try { + return lines.Count; + } finally { + editorRWLock.ExitReadLock(); + } + } + } + public int Lenght { + get { + editorRWLock.EnterReadLock (); + try { + return source.Length; + } finally { + editorRWLock.ExitReadLock(); + } + } + } + public TextLine GetLine (int index) { + editorRWLock.EnterReadLock (); + try { + return lines[index]; + } finally { + editorRWLock.ExitReadLock(); + } + } + public ReadOnlySpan GetText (TextLine line) { + editorRWLock.EnterReadLock (); + try { + return source.GetLine (line); + } finally { + editorRWLock.ExitReadLock(); + } + } + public ReadOnlySpan GetText (TextSpan span) { + editorRWLock.EnterReadLock (); + try { + return source.AsSpan (span.Start, span.Length); + } finally { + editorRWLock.ExitReadLock(); + } + } + public char GetChar (int pos){ + editorRWLock.EnterReadLock (); + try { + return source[pos]; + } finally { + editorRWLock.ExitReadLock(); + } + } + + public virtual CharLocation GetWordStart (CharLocation loc) { + editorRWLock.EnterReadLock (); + try { + int pos = lines.GetAbsolutePosition (loc); + //skip white spaces + while (pos > 0 && !char.IsLetterOrDigit (source[pos-1])) + pos--; + while (pos > 0 && char.IsLetterOrDigit (source[pos-1])) + pos--; + return lines.GetLocation (pos); + } finally { + editorRWLock.ExitReadLock(); + } + } + public virtual CharLocation GetWordEnd (CharLocation loc) { + editorRWLock.EnterReadLock (); + try { + int pos = lines.GetAbsolutePosition (loc); + //skip white spaces + while (pos < Lenght - 1 && !char.IsLetterOrDigit (source[pos])) + pos++; + while (pos < Lenght - 1 && char.IsLetterOrDigit (source[pos])) + pos++; + return lines.GetLocation (pos); + } finally { + editorRWLock.ExitReadLock(); + } + } } } \ No newline at end of file diff --git a/plugins/CECrowPlugin/src/ImlDocument.cs b/plugins/CECrowPlugin/src/ImlDocument.cs index 53375b9..5dde2dc 100644 --- a/plugins/CECrowPlugin/src/ImlDocument.cs +++ b/plugins/CECrowPlugin/src/ImlDocument.cs @@ -47,10 +47,10 @@ namespace CECrowPlugin Type crowType = IML.Instantiator.GetWidgetTypeFromName (crowTypeName); return crowType.GetMember (memberName, BindingFlags.Public | BindingFlags.Instance).FirstOrDefault (); } - public override IList GetSuggestions (int pos) { + public override IList GetSuggestions (CharLocation loc) { if (tokens.Length == 0) return null; - IList sugs = base.GetSuggestions (pos); + IList sugs = base.GetSuggestions (loc); if (sugs != null) return sugs; diff --git a/plugins/CECrowPlugin/src/StyleDocument.cs b/plugins/CECrowPlugin/src/StyleDocument.cs index d13216f..31e1337 100644 --- a/plugins/CECrowPlugin/src/StyleDocument.cs +++ b/plugins/CECrowPlugin/src/StyleDocument.cs @@ -24,7 +24,7 @@ namespace CECrowPlugin protected override Tokenizer CreateTokenizer() => new StyleTokenizer (); protected override SyntaxAnalyser CreateSyntaxAnalyser() => new StyleSyntaxAnalyser (this); - public override IList GetSuggestions (int pos) { + public override IList GetSuggestions (CharLocation loc) { /*currentToken = FindTokenIncludingPosition (pos); currentNode = FindNodeIncludingPosition (pos);*/ return null; diff --git a/plugins/CEXmlPlugin/src/Parsing/XmlDocument.cs b/plugins/CEXmlPlugin/src/Parsing/XmlDocument.cs index a9d9830..edb10d3 100644 --- a/plugins/CEXmlPlugin/src/Parsing/XmlDocument.cs +++ b/plugins/CEXmlPlugin/src/Parsing/XmlDocument.cs @@ -32,7 +32,7 @@ namespace CrowEdit.Xml protected override Tokenizer CreateTokenizer() => new XmlTokenizer (); protected override SyntaxAnalyser CreateSyntaxAnalyser() => new XmlSyntaxAnalyser (this); - public override IList GetSuggestions (int pos) { + public override IList GetSuggestions (CharLocation loc) { /*currentToken = FindTokenIncludingPosition (pos); currentNode = FindNodeIncludingPosition (pos);*/ return null; -- 2.47.3