From: Jean-Philippe Bruyère Date: Tue, 25 Feb 2025 10:18:05 +0000 (+0100) Subject: TextBuffer class X-Git-Url: https://git.osiis.dedyn.io/?a=commitdiff_plain;h=82597f4b26a9135bfee58b5d49637ad3a99cfaf6;p=jp%2Fcrowedit.git TextBuffer class --- diff --git a/.gitignore b/.gitignore index 78de650..3521011 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ CrowDesignAssembly/ netcoredbg/ +testfiles/ #Autosave files *~ diff --git a/CrowEditBase/src/Compiler/SourceDocument.cs b/CrowEditBase/src/Compiler/SourceDocument.cs index 48bfd65..c2ffdb5 100644 --- a/CrowEditBase/src/Compiler/SourceDocument.cs +++ b/CrowEditBase/src/Compiler/SourceDocument.cs @@ -111,8 +111,7 @@ namespace CrowEditBase SyntaxNode changedNode = RootNode.FindNodeIncludingSpan (TextSpan.FromStartAndLength (change.Start, change.ChangedText.Length)); - tokens = tokenizer.Tokenize (Source); - + tokens = tokenizer.Tokenize (buffer.Span); syntaxAnalyser.Process (); @@ -193,7 +192,7 @@ namespace CrowEditBase protected bool previousTokHasFlag(TokenType flag) => previousToken.HasValue && previousToken.Value.Type.HasFlag(flag); void parse () { Tokenizer tokenizer = CreateTokenizer (); - tokens = tokenizer?.Tokenize (Source); + tokens = tokenizer?.Tokenize (source); SyntaxAnalyser syntaxAnalyser = CreateSyntaxAnalyser (); Stopwatch sw = Stopwatch.StartNew (); syntaxAnalyser?.Process (); diff --git a/CrowEditBase/src/Compiler/SyntaxNode.cs b/CrowEditBase/src/Compiler/SyntaxNode.cs index 4d28970..0d41864 100644 --- a/CrowEditBase/src/Compiler/SyntaxNode.cs +++ b/CrowEditBase/src/Compiler/SyntaxNode.cs @@ -21,11 +21,13 @@ namespace CrowEditBase public override SyntaxNode NextSiblingOrParentsNextSibling => null; public override void UnfoldToTheTop() {} public string GetTokenStringByIndex (int idx) => - idx >= 0 && idx < source.Tokens.Length ? source.Tokens[idx].AsString (source.Source) : null; + idx >= 0 && idx < source.Tokens.Length ? Root.GetText(source.Tokens[idx].Span).ToString() : null; public Token GetTokenByIndex (int idx) => idx >= 0 && idx < source.Tokens.Length ? source.Tokens[idx] : default; public ReadOnlySpan GetText(TextSpan span) => source.GetText(span); + public string GetTokenString(Token tok) => + source.GetText(tok.Span).ToString(); } public class SyntaxNode : CrowEditComponent { internal SyntaxNode () {} diff --git a/CrowEditBase/src/Compiler/Tokenizer.cs b/CrowEditBase/src/Compiler/Tokenizer.cs index 06c33ca..ee3f2d6 100644 --- a/CrowEditBase/src/Compiler/Tokenizer.cs +++ b/CrowEditBase/src/Compiler/Tokenizer.cs @@ -18,12 +18,12 @@ namespace CrowEditBase protected int startOfTok; public Tokenizer () {} - public abstract Token[] Tokenize (string source); + public abstract Token[] Tokenize (ReadOnlySpan source); /// /// First method to call in tokenizers to init parsing variables /// /// - protected virtual SpanCharReader initParsing (string source) { + protected virtual SpanCharReader initParsing (ReadOnlySpan source) { startOfTok = 0; Toks = new List(100); return new SpanCharReader(source); diff --git a/CrowEditBase/src/SourceEditor.cs b/CrowEditBase/src/SourceEditor.cs index a1aac8a..7a77750 100644 --- a/CrowEditBase/src/SourceEditor.cs +++ b/CrowEditBase/src/SourceEditor.cs @@ -254,7 +254,7 @@ namespace Crow using (IContext gr = IFace.Backend.CreateContext (IFace.MainSurface)) { gr.SelectFontFace (Font.Name, Font.Slant, Font.Wheight); gr.SetFontSize (Font.Size); - updateLocation (gr, ClientRectangle.Width, ref hoverLoc); + updateLocation (gr, ref hoverLoc); } #if DEBUG_NODES if (Document is SourceDocument doc) { @@ -605,7 +605,7 @@ namespace Crow Foreground.SetAsSource (IFace, gr); gr.Translate (-ScrollX, -ScrollY); - ReadOnlySpan sourceBytes = doc.Source.AsSpan(); + ReadOnlySpan sourceBytes = doc.source; Span bytes = stackalloc byte[128]; TextExtents extents; int tokPtr = 0; diff --git a/CrowEditBase/src/TextDocument.cs b/CrowEditBase/src/TextDocument.cs index a786dc5..d64ce27 100644 --- a/CrowEditBase/src/TextDocument.cs +++ b/CrowEditBase/src/TextDocument.cs @@ -9,37 +9,72 @@ using System.IO; using Crow; using Crow.Text; using static CrowEditBase.CrowEditBase; +using System.Collections.Immutable; +using System.Reflection.Metadata; namespace CrowEditBase { + public class TextBuffer { + static int bufferExpension = 100; + int lenght; + Memory buffer; + ReadOnlyMemory origBuffer; + public Span Span => buffer.Span.Slice(0, lenght); + public bool IsEmpty => lenght == 0; + public bool IsDirty => origBuffer.Span.Equals(buffer.Span, StringComparison.Ordinal); + public void ResetDirtyState () { + origBuffer = buffer.ToArray(); + } + public TextBuffer(ReadOnlySpan origText) { + lenght = origText.Length; + buffer = new char[lenght + bufferExpension]; + origText.CopyTo(buffer.Span); + } + public void Update (TextChange change) { + ReadOnlySpan orig = buffer.Span; + char[] newBuff = null; + Span tmp; + if (buffer.Length < lenght + change.CharDiff) { + newBuff = new char[lenght + change.CharDiff + bufferExpension]; + tmp = newBuff; + orig.Slice(0, change.Start).CopyTo(tmp); + if (change.CharDiff == 0) + orig.Slice(change.End, lenght - change.End).CopyTo(tmp.Slice(change.End)); + } else + tmp = buffer.Span; + + if (change.CharDiff != 0) + orig.Slice(change.End, lenght - change.End).CopyTo(tmp.Slice(change.End2)); + if (!string.IsNullOrEmpty (change.ChangedText)) + change.ChangedText.AsSpan ().CopyTo (tmp.Slice (change.Start)); + + if (newBuff != null) + buffer = newBuff; + lenght += change.CharDiff; + } + } public class TextDocument : Document { public TextDocument (string fullPath, string editorPath = "default") : base (fullPath, editorPath) { reloadFromFile (); } - string source, origSource; + protected TextBuffer buffer; + public ReadOnlySpan source => buffer.Span; + System.Text.Encoding encoding = System.Text.Encoding.UTF8; protected bool mixedLineBreak = false; protected string lineBreak = null; - public string Source { - get => source; - set { - if (source == value) - return; - source = value; - getLines(); + + +// NotifyValueChanged ("IsDirty", IsDirty); +// CMDSave.CanExecute = IsDirty; - NotifyValueChanged (source); - NotifyValueChanged ("IsDirty", IsDirty); - CMDSave.CanExecute = IsDirty; - } - } protected LineCollection lines; - public override bool IsDirty => origSource != source; + public override bool IsDirty => buffer.IsDirty; /// dictionnary of object per document client, when not null, client must reload content of document. Dictionary> registeredClients = new Dictionary>(); public override bool TryGetState(object client, out T state) { @@ -85,9 +120,9 @@ namespace CrowEditBase protected override void writeToDisk () { using (Stream s = new FileStream(FullPath, FileMode.Create)) { using (StreamWriter sw = new StreamWriter (s, encoding)) - sw.Write (source); + sw.Write (buffer.Span); } - origSource = source; + buffer.ResetDirtyState(); NotifyValueChanged ("IsDirty", IsDirty); CMDSave.CanExecute = IsDirty; } @@ -95,14 +130,14 @@ namespace CrowEditBase { using (Stream s = new FileStream (FullPath, FileMode.Open)) { using (StreamReader sr = new StreamReader (s)) { - Source = origSource = sr.ReadToEnd (); + buffer = new TextBuffer(sr.ReadToEnd ()); encoding = sr.CurrentEncoding; } } } protected override void initNewFile() { - Source = origSource = ""; + buffer = new TextBuffer(""); } protected override void reloadFromFile () { editorRWLock.EnterWriteLock (); @@ -178,14 +213,7 @@ namespace CrowEditBase protected bool disableTextChangedEvent = false; protected virtual 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)); - source = tmp.ToString (); - + buffer.Update(change); lines.Update (change); NotifyValueChanged ("IsDirty", IsDirty); @@ -215,7 +243,7 @@ namespace CrowEditBase else lines.Clear (); - if (string.IsNullOrEmpty (source)) + if (buffer.IsEmpty) lines.Add (new TextLine (0, 0, 0)); else lines.Update (source); @@ -274,6 +302,8 @@ namespace CrowEditBase } public int LinesCount { get { + if (lines == null) + getLines(); editorRWLock.EnterReadLock (); try { return lines.Count; @@ -311,7 +341,7 @@ namespace CrowEditBase public ReadOnlySpan GetText (TextSpan span) { editorRWLock.EnterReadLock (); try { - return source.AsSpan (span.Start, span.Length); + return source.Slice (span.Start, span.Length); } finally { editorRWLock.ExitReadLock(); } diff --git a/CrowEditBase/ui/sourceEditor.itmp b/CrowEditBase/ui/sourceEditor.itmp index 321014a..ffeb398 100644 --- a/CrowEditBase/ui/sourceEditor.itmp +++ b/CrowEditBase/ui/sourceEditor.itmp @@ -1,42 +1,42 @@ - - - - - + + + + + + + + - - - - + diff --git a/plugins/CECrowPlugin/src/ImlDocument.cs b/plugins/CECrowPlugin/src/ImlDocument.cs index fad6b30..9d4ddc3 100644 --- a/plugins/CECrowPlugin/src/ImlDocument.cs +++ b/plugins/CECrowPlugin/src/ImlDocument.cs @@ -72,7 +72,7 @@ namespace CECrowPlugin if (tok.GetTokenType() == XmlTokenType.ElementOpen) return new List (allWidgetNames); if (tok.GetTokenType() == XmlTokenType.ElementName) - return allWidgetNames.Where (s => s.StartsWith (tok.AsString (Source), StringComparison.OrdinalIgnoreCase)).ToList (); + return allWidgetNames.Where (s => s.StartsWith (RootNode.Root.GetTokenString(tok), StringComparison.OrdinalIgnoreCase)).ToList (); if ((tok.Type.HasFlag(TokenType.WhiteSpace) || previousTokHasFlag(TokenType.WhiteSpace)) && tryCast(CurrentNode, out ElementTagSyntax ets)) { if (ets.name.HasValue) @@ -85,7 +85,7 @@ namespace CECrowPlugin if (!string.IsNullOrEmpty (eltTag.Name)) { if (tok.GetTokenType() == XmlTokenType.AttributeName) { return getAllCrowTypeMembers (eltTag.Name) - .Where (s => s.Name.StartsWith (tok.AsString (Source), StringComparison.OrdinalIgnoreCase)).ToList (); + .Where (s => s.Name.StartsWith (RootNode.Root.GetTokenString(tok), StringComparison.OrdinalIgnoreCase)).ToList (); } else if (!string.IsNullOrEmpty (attribNode.Name)) { if (tok.GetTokenType() == XmlTokenType.AttributeValue) { MemberInfo mi = getCrowTypeMember ( @@ -93,19 +93,19 @@ namespace CECrowPlugin if (mi is PropertyInfo pi) { if (pi.Name == "Style") return App.Styling.Keys - .Where (s => s.StartsWith (tok.AsString (Source), StringComparison.OrdinalIgnoreCase)).ToList (); + .Where (s => s.StartsWith (RootNode.Root.GetTokenString(tok), StringComparison.OrdinalIgnoreCase)).ToList (); if (pi.PropertyType.IsEnum) return Enum.GetNames (pi.PropertyType) - .Where (s => s.StartsWith (tok.AsString (Source), StringComparison.OrdinalIgnoreCase)).ToList (); + .Where (s => s.StartsWith (RootNode.Root.GetTokenString(tok), StringComparison.OrdinalIgnoreCase)).ToList (); if (pi.PropertyType == typeof(bool)) return (new string[] {"true", "false"}). - Where (s => s.StartsWith (tok.AsString (Source), StringComparison.OrdinalIgnoreCase)).ToList (); + Where (s => s.StartsWith (RootNode.Root.GetTokenString(tok), StringComparison.OrdinalIgnoreCase)).ToList (); if (pi.PropertyType == typeof (Measure)) return (new string[] {"Stretched", "Fit"}). - Where (s => s.StartsWith (tok.AsString (Source), StringComparison.OrdinalIgnoreCase)).ToList (); + Where (s => s.StartsWith (RootNode.Root.GetTokenString(tok), StringComparison.OrdinalIgnoreCase)).ToList (); if (pi.PropertyType == typeof (Fill)) return EnumsNET.Enums.GetValues () - .Where (s => s.ToString().StartsWith (tok.AsString (Source), StringComparison.OrdinalIgnoreCase)).ToList (); + .Where (s => s.ToString().StartsWith (RootNode.Root.GetTokenString(tok), StringComparison.OrdinalIgnoreCase)).ToList (); } } else if (tok.GetTokenType() == XmlTokenType.AttributeValueOpen) { MemberInfo mi = getCrowTypeMember ( diff --git a/plugins/CECrowPlugin/src/Parsing/Styling/StyleTokenizer.cs b/plugins/CECrowPlugin/src/Parsing/Styling/StyleTokenizer.cs index 823bb86..9275dd2 100644 --- a/plugins/CECrowPlugin/src/Parsing/Styling/StyleTokenizer.cs +++ b/plugins/CECrowPlugin/src/Parsing/Styling/StyleTokenizer.cs @@ -55,7 +55,7 @@ namespace CECrowPlugin.Style { return false; } - public override Token[] Tokenize (string source) { + public override Token[] Tokenize (ReadOnlySpan source) { SpanCharReader reader = initParsing (source); curState = States.classNames; diff --git a/plugins/CEEbnfPlugin/src/Parsing/EbnfTokenizer.cs b/plugins/CEEbnfPlugin/src/Parsing/EbnfTokenizer.cs index f852d3b..039dca5 100644 --- a/plugins/CEEbnfPlugin/src/Parsing/EbnfTokenizer.cs +++ b/plugins/CEEbnfPlugin/src/Parsing/EbnfTokenizer.cs @@ -51,7 +51,7 @@ namespace CrowEdit.Ebnf public static bool IsValidHexDigit (char c) => char.IsDigit (c) || (c > 64 && c < 71) || (c > 96 && c < 103); - public override Token[] Tokenize (string source) { + public override Token[] Tokenize (ReadOnlySpan source) { SpanCharReader reader = new SpanCharReader(source); startOfTok = 0; diff --git a/plugins/CERoslynPlugin/src/CSDocument.cs b/plugins/CERoslynPlugin/src/CSDocument.cs index e9ebb21..efe786d 100644 --- a/plugins/CERoslynPlugin/src/CSDocument.cs +++ b/plugins/CERoslynPlugin/src/CSDocument.cs @@ -36,7 +36,7 @@ namespace CERoslynPlugin CSharpSyntaxTree tree; public CSDocument (string fullPath, string editorPath) : base (fullPath, editorPath) { - tree = (CSharpSyntaxTree)CSharpSyntaxTree.ParseText (Source, CSharpParseOptions.Default); + tree = (CSharpSyntaxTree)CSharpSyntaxTree.ParseText (source.ToString(), CSharpParseOptions.Default); var root = tree.GetRoot(); /*foreach (SyntaxKind v in Enum.GetValues().OrderBy(k=>(uint)k)) { Console.WriteLine($"{v,50} {(((uint)v) ).ToString("B16") } {(((uint)v) ).ToString("X4") }"); diff --git a/plugins/CERoslynPlugin/src/CSTokenizer.cs b/plugins/CERoslynPlugin/src/CSTokenizer.cs index 3a219a0..0644175 100644 --- a/plugins/CERoslynPlugin/src/CSTokenizer.cs +++ b/plugins/CERoslynPlugin/src/CSTokenizer.cs @@ -68,9 +68,9 @@ namespace CERoslynPlugin } } - public override Token[] Tokenize(string source) + public override Token[] Tokenize(ReadOnlySpan source) { - var tree = CSharpSyntaxTree.ParseText(source); + var tree = CSharpSyntaxTree.ParseText(source.ToString()); CsharpSyntaxWalkerBridge bridge = new CsharpSyntaxWalkerBridge(); bridge.Visit(tree.GetRoot()); //SpanCharReader reader = new SpanCharReader(source); diff --git a/plugins/CEXmlPlugin/src/Parsing/XmlDocument.cs b/plugins/CEXmlPlugin/src/Parsing/XmlDocument.cs index 09ef509..0d1fa30 100644 --- a/plugins/CEXmlPlugin/src/Parsing/XmlDocument.cs +++ b/plugins/CEXmlPlugin/src/Parsing/XmlDocument.cs @@ -33,7 +33,7 @@ namespace CrowEdit.Xml CurrentNode is ElementEndTagSyntax eltEndTag && !eltEndTag.IsComplete) { ElementSyntax es = eltEndTag.Parent as ElementSyntax; if (es?.StartTag.name != null) - return new List (new string[] {tokens[es.StartTag.name.Value].AsString(Source)}); + return new List (new string[] {es.StartTag.Name}); } return null; } @@ -84,7 +84,7 @@ namespace CrowEdit.Xml } else { int offset = 1; if (!attrib.valueClose.HasValue) { - selectedSugg += tokens[attrib.valueClose.Value].AsString(Source); + selectedSugg += RootNode.Root.GetTokenStringByIndex(attrib.valueClose.Value); offset = 0; } if (tokType == XmlTokenType.AttributeValueOpen) diff --git a/plugins/CEXmlPlugin/src/Parsing/XmlTokenizer.cs b/plugins/CEXmlPlugin/src/Parsing/XmlTokenizer.cs index 46f287e..c7fb88f 100644 --- a/plugins/CEXmlPlugin/src/Parsing/XmlTokenizer.cs +++ b/plugins/CEXmlPlugin/src/Parsing/XmlTokenizer.cs @@ -64,7 +64,7 @@ namespace CrowEdit.Xml reader.Read (); } } - public override Token[] Tokenize (string source) { + public override Token[] Tokenize (ReadOnlySpan source) { SpanCharReader reader = initParsing (source); int curObjectLevel = 0;