From 7d2c7d84efd6cfb2201dc7240ba396da2e283e18 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jean-Philippe=20Bruy=C3=A8re?= Date: Thu, 7 Oct 2021 05:59:09 +0000 Subject: [PATCH] wip --- CrowEditBase/CrowEditBase.csproj | 4 +- CrowEditBase/src/Compiler/SourceDocument.cs | 30 +- CrowEditBase/src/Compiler/SyntaxAnalyser.cs | 14 + CrowEditBase/src/Compiler/SyntaxNode.cs | 102 ++++- CrowEditBase/src/Compiler/Token.cs | 4 +- CrowEditBase/src/Compiler/Tokenizer.cs | 52 +++ CrowEditBase/src/Editor.cs | 61 +-- CrowEditBase/src/SourceEditor.cs | 397 ++++++++++++------ CrowEditBase/ui/sourceEditor.itmp | 4 +- .../src/StyleParsing/StyleTokenizer.cs | 41 +- .../src/StyleParsing/SyntaxAnalyser.cs | 17 +- .../src/StyleParsing/SyntaxNodes.cs | 12 +- .../CENetcoreDbgPlugin.csproj | 6 +- .../src/NetcoreDbgService.cs | 2 +- .../src/NetcoredbgDebugger.cs | 22 +- .../ui/winConfiguration.crow | 2 +- .../CENetcoreDbgPlugin/ui/winDebugging.crow | 16 + .../CENetcoreDbgPlugin/ui/winSolution.crow | 4 - plugins/CERoslynPlugin/src/ConsoleLogger.cs | 8 +- plugins/CERoslynPlugin/src/MSBuildProject.cs | 2 + plugins/CERoslynPlugin/src/SolutionProject.cs | 6 + .../CEXmlPlugin/src/Parsing/SyntaxAnalyser.cs | 93 ++-- .../CEXmlPlugin/src/Parsing/SyntaxNodes.cs | 33 +- .../CEXmlPlugin/src/Parsing/XmlTokenizer.cs | 60 +-- src/CrowEdit.cs | 3 +- ui/windows/winFileExplorer.crow | 8 +- ui/windows/winSyntaxExplorer.crow | 51 +++ 27 files changed, 688 insertions(+), 366 deletions(-) create mode 100644 plugins/CENetcoreDbgPlugin/ui/winDebugging.crow delete mode 100644 plugins/CENetcoreDbgPlugin/ui/winSolution.crow create mode 100644 ui/windows/winSyntaxExplorer.crow diff --git a/CrowEditBase/CrowEditBase.csproj b/CrowEditBase/CrowEditBase.csproj index 2e5c287..c19e01a 100644 --- a/CrowEditBase/CrowEditBase.csproj +++ b/CrowEditBase/CrowEditBase.csproj @@ -15,7 +15,7 @@ - - + + diff --git a/CrowEditBase/src/Compiler/SourceDocument.cs b/CrowEditBase/src/Compiler/SourceDocument.cs index 25d5396..58a6f11 100644 --- a/CrowEditBase/src/Compiler/SourceDocument.cs +++ b/CrowEditBase/src/Compiler/SourceDocument.cs @@ -16,10 +16,13 @@ namespace CrowEditBase } protected Token[] tokens; protected SyntaxNode RootNode; + protected LineCollection lines; protected Token currentToken; protected SyntaxNode currentNode; public Token[] Tokens => tokens; + public SyntaxNode SyntaxRootNode => RootNode; + public LineCollection Lines => lines; public Token FindTokenIncludingPosition (int pos) { if (pos == 0 || tokens == null || tokens.Length == 0) return default; @@ -27,12 +30,27 @@ namespace CrowEditBase return idx == 0 ? tokens[0] : idx < 0 ? tokens[~idx - 1] : tokens[idx - 1]; } - public SyntaxNode FindNodeIncludingPosition (int pos) { + public int FindTokenIndexIncludingPosition (int pos) { + if (pos == 0 || tokens == null || tokens.Length == 0) + return default; + int idx = Array.BinarySearch (tokens, 0, tokens.Length, new Token () {Start = pos}); + + return idx == 0 ? 0 : idx < 0 ? ~idx - 1 : idx - 1; + } + /// + /// if outermost is true, return oldest ancestor exept root node, useful for folding. + /// + public SyntaxNode FindNodeIncludingPosition (int pos, bool outerMost = false) { if (RootNode == null) return null; if (!RootNode.Contains (pos)) return null; - return RootNode.FindNodeIncludingPosition (pos); + SyntaxNode sn = RootNode.FindNodeIncludingPosition (pos); + if (outerMost) { + while (sn.Parent != RootNode && sn.StartToken.Start == sn.Parent.StartToken.Start) + sn = sn.Parent; + } + return sn; } public T FindNodeIncludingPosition (int pos) { if (RootNode == null) @@ -77,14 +95,14 @@ namespace CrowEditBase tokens = tokenizer.Tokenize (Source); SyntaxAnalyser syntaxAnalyser = CreateSyntaxAnalyser (); - Stopwatch sw = Stopwatch.StartNew (); + //Stopwatch sw = Stopwatch.StartNew (); syntaxAnalyser.Process (); - sw.Stop(); + //sw.Stop(); RootNode = syntaxAnalyser.Root; - Console.WriteLine ($"Syntax Analysis done in {sw.ElapsedMilliseconds}(ms) {sw.ElapsedTicks}(ticks)"); + /*Console.WriteLine ($"Syntax Analysis done in {sw.ElapsedMilliseconds}(ms) {sw.ElapsedTicks}(ticks)"); foreach (SyntaxException ex in syntaxAnalyser.Exceptions) - Console.WriteLine ($"{ex}"); + Console.WriteLine ($"{ex}");*/ /*foreach (Token t in Tokens) Console.WriteLine ($"{t,-40} {Source.AsSpan(t.Start, t.Length).ToString()}"); diff --git a/CrowEditBase/src/Compiler/SyntaxAnalyser.cs b/CrowEditBase/src/Compiler/SyntaxAnalyser.cs index d3a7f74..d48fc08 100644 --- a/CrowEditBase/src/Compiler/SyntaxAnalyser.cs +++ b/CrowEditBase/src/Compiler/SyntaxAnalyser.cs @@ -22,5 +22,19 @@ namespace CrowEditBase this.source = source; } public abstract void Process (); + protected SyntaxNode currentNode; + + /// + /// set current node endToken and line count and set current to current.parent. + /// + /// The final token of this node + /// the endline number of this node + protected void storeCurrentNode (Token endToken, int endLine) { + currentNode.EndToken = endToken; + currentNode.EndLine = endLine; + currentNode = currentNode.Parent; + } + protected void setCurrentNodeEndLine (int endLine) + => currentNode.EndLine = endLine; } } \ No newline at end of file diff --git a/CrowEditBase/src/Compiler/SyntaxNode.cs b/CrowEditBase/src/Compiler/SyntaxNode.cs index 8128d60..bee3622 100644 --- a/CrowEditBase/src/Compiler/SyntaxNode.cs +++ b/CrowEditBase/src/Compiler/SyntaxNode.cs @@ -4,22 +4,114 @@ using System; using System.Collections.Generic; using System.Linq; +using Crow.Text; namespace CrowEditBase { + public abstract class SyntaxRootNode : SyntaxNode { + protected readonly SourceDocument source; + public SyntaxRootNode (SourceDocument source) + : base (0, source.Tokens.FirstOrDefault (), source.Tokens.LastOrDefault ()) { + this.source = source; + } + public override SyntaxRootNode Root => this; + public override bool IsFoldable => false; + public override SyntaxNode NextSiblingOrParentsNextSibling => null; + } public class SyntaxNode { public SyntaxNode Parent { get; private set; } + public int StartLine { get; private set; } + public virtual int LineCount => lineCount; + public virtual bool IsComplete => EndToken.HasValue; + public virtual bool IsFoldable => Parent.StartLine != StartLine && lineCount > 1; + public virtual SyntaxRootNode Root => Parent.Root; + + List children = new List (); + public IEnumerable Children => children; + public bool HasChilds => children.Count > 0; + public SyntaxNode NextSibling { + get { + if (Parent != null) { + int idx = Parent.children.IndexOf (this); + if (idx < Parent.children.Count - 1) + return Parent.children[idx + 1]; + } + return null; + } + } + public SyntaxNode PreviousSibling { + get { + if (Parent != null) { + int idx = Parent.children.IndexOf (this); + if (idx > 0) + return Parent.children[idx - 1]; + } + return null; + } + } + /*public virtual int lineCount2 { + get { + SyntaxNode ns = NextSibling; + if (ns == null) + return Parent.StartLine + Parent.lineCount2 - StartLine; + return ns.StartLine - StartLine; + } + }*/ + /*public virtual int FoldedLineCount { + get { + + } + }*/ + public virtual SyntaxNode NextSiblingOrParentsNextSibling + => NextSibling ?? Parent.NextSiblingOrParentsNextSibling; + public IEnumerable FoldableNodes { + get { + if (IsFoldable) + yield return this; + foreach (SyntaxNode n in Children) { + foreach (SyntaxNode folds in n.FoldableNodes) + yield return folds; + } + } + } + public virtual int FoldedLineCount { + get { + if (isFolded) + return lineCount; + int tmp = 0; + if (HasChilds) { + foreach (SyntaxNode n in children.Where (c => c.IsFoldable)) + tmp += n.FoldedLineCount; + } + return tmp; + } + } public readonly Token StartToken; public Token? EndToken { get; set; } - public SyntaxNode (Token tokStart, Token? tokEnd = null) { + public SyntaxNode (int startLine, Token tokStart, Token? tokEnd = null) { + StartLine = startLine; StartToken = tokStart; EndToken = tokEnd; } + internal bool isFolded; + internal int lineCount; + public int EndLine { + internal set { + lineCount = value - StartLine + 1; + } + get => StartLine + lineCount - 1; + } + public TextSpan Span { + get { + /*if (HasChilds) { + return new TextSpan (children.First().Span.Start, children.Last().Span.End) + }*/ + return new TextSpan (StartToken.Start, EndToken.HasValue ? EndToken.Value.End : StartToken.End); - public virtual bool IsComplete => EndToken.HasValue; - + } + } public SyntaxNode AddChild (SyntaxNode child) { children.Add (child); child.Parent = this; @@ -45,16 +137,14 @@ namespace CrowEditBase return this is T tt ? tt : default; } - public virtual SyntaxNode Root => Parent.Root; public bool Contains (int pos) => EndToken.HasValue ? StartToken.Start <= pos && EndToken.Value.End >= pos : false; - public void Dump (int level = 0) { Console.WriteLine ($"{new string('\t', level)}{this}"); foreach (SyntaxNode node in children) node.Dump (level + 1); } - public override string ToString() => $"{this.GetType().Name}: {StartToken} -> {EndToken}"; + public override string ToString() => $"{this.GetType().Name}: lines:({StartLine},{LineCount}) tokens:{StartToken} -> {EndToken}"; } } \ No newline at end of file diff --git a/CrowEditBase/src/Compiler/Token.cs b/CrowEditBase/src/Compiler/Token.cs index 26fd21c..a1a86d0 100644 --- a/CrowEditBase/src/Compiler/Token.cs +++ b/CrowEditBase/src/Compiler/Token.cs @@ -31,13 +31,13 @@ namespace CrowEditBase Type = type; Start = start; Length = length; - } + } public int CompareTo([AllowNull] Token other) => Start - other.Start; public bool Equals([AllowNull] Token other) => Type == other.Type && Start == other.Start && Length == other.Length; - public override bool Equals(object obj) + public override bool Equals(object obj) => obj is Token other ? Equals (other) : false; public override int GetHashCode() => HashCode.Combine (Type, Start, Length); public override string ToString() => $"{Type}:{Start},{Length};"; diff --git a/CrowEditBase/src/Compiler/Tokenizer.cs b/CrowEditBase/src/Compiler/Tokenizer.cs index d01640f..80b9d24 100644 --- a/CrowEditBase/src/Compiler/Tokenizer.cs +++ b/CrowEditBase/src/Compiler/Tokenizer.cs @@ -2,6 +2,8 @@ // // This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) using System; +using System.Collections.Generic; +using Crow.Text; namespace CrowEditBase { @@ -12,8 +14,58 @@ namespace CrowEditBase } } public abstract class Tokenizer { + protected List Toks; + protected int startOfTok; public Tokenizer () {} public abstract Token[] Tokenize (string source); + /// + /// First method to call in tokenizers to init parsing variables + /// + /// + protected virtual SpanCharReader initParsing (string source) { + startOfTok = 0; + Toks = new List(100); + return new SpanCharReader(source); + } + /// + /// Add token delimited from 'startOfTok' to current reader position. + /// + /// + /// + protected void addTok (ref SpanCharReader reader, Enum tokType) { + if (reader.CurrentPosition == startOfTok) + return; + Toks.Add (new Token((TokenType)tokType, startOfTok, reader.CurrentPosition)); + startOfTok = reader.CurrentPosition; + } + protected virtual void skipWhiteSpaces (ref SpanCharReader reader) { + while(!reader.EndOfSpan) { + switch (reader.Peak) { + case '\x85': + case '\x2028': + case '\xA': + reader.Read(); + addTok (ref reader, TokenType.LineBreak); + break; + case '\xD': + reader.Read(); + if (reader.IsNextCharIn ('\xA', '\x85')) + reader.Read(); + addTok (ref reader, TokenType.LineBreak); + break; + case '\x20': + case '\x9': + char c = reader.Read(); + while (reader.TryPeak (c)) + reader.Read(); + addTok (ref reader, c == '\x20' ? TokenType.WhiteSpace : TokenType.Tabulation); + break; + default: + return; + } + } + } + } } diff --git a/CrowEditBase/src/Editor.cs b/CrowEditBase/src/Editor.cs index 2388a0c..180866e 100644 --- a/CrowEditBase/src/Editor.cs +++ b/CrowEditBase/src/Editor.cs @@ -105,8 +105,8 @@ namespace Crow NotifyValueChanged ("CurrentColumn", CurrentColumn); CMDCopy.CanExecute = CMDCut.CanExecute = !SelectionIsEmpty; - } - } + } + } public virtual int CurrentLine { get => currentLoc.HasValue ? currentLoc.Value.Line : 0; set { @@ -117,7 +117,7 @@ namespace Crow CMDCopy.CanExecute = CMDCut.CanExecute = !SelectionIsEmpty; } - } + } public virtual int CurrentColumn { get => currentLoc.HasValue ? currentLoc.Value.Column < 0 ? 0 : currentLoc.Value.Column : 0; set { @@ -192,7 +192,7 @@ namespace Crow if (loc.Line == 0) return false; CurrentLoc = new CharLocation (loc.Line - 1, lines[loc.Line - 1].Length); - }else + }else CurrentLoc = new CharLocation (loc.Line, loc.Column - 1); return true; } @@ -226,7 +226,7 @@ namespace Crow CurrentLoc = new CharLocation (newLine, targetColumn); return true; - } + } protected int visibleLines => (int)((double)ClientRectangle.Height / (fe.Ascent + fe.Descent)); public void GotoWordStart(){ int pos = lines.GetAbsolutePosition (CurrentLoc.Value); @@ -253,7 +253,7 @@ namespace Crow 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++) { @@ -261,9 +261,9 @@ namespace Crow if (!lb.SequenceEqual (LineBreak)) { mixedLineBreak = true; break; - } + } } - } + } protected void getLines () { if (lines == null) @@ -308,20 +308,22 @@ namespace Crow } return new TextSpan (lines.GetAbsolutePosition (selStart), lines.GetAbsolutePosition (selEnd)); } - } + } public string SelectedText { get { TextSpan selection = Selection; return selection.IsEmpty ? "" : _text.AsSpan (selection.Start, selection.Length).ToString (); } - } + } public bool SelectionIsEmpty => selectionStart.HasValue ? Selection.IsEmpty : true; + protected virtual int lineCount => lines.Count; + protected virtual void measureTextBounds (Context gr) { fe = gr.FontExtents; te = new TextExtents (); - cachedTextSize.Height = (int)Math.Ceiling ((fe.Ascent + fe.Descent) * Math.Max (1, lines.Count)); + cachedTextSize.Height = (int)Math.Ceiling ((fe.Ascent + fe.Descent) * Math.Max (1, lineCount)); TextExtents tmp = default; int longestLine = 0; @@ -465,16 +467,17 @@ namespace Crow gr.Translate (ScrollX, ScrollY); } + protected int getLineIndex (Point mouseLocalPos) => + (int)Math.Min (Math.Max (0, Math.Floor ((mouseLocalPos.Y + ScrollY)/ (fe.Ascent + fe.Descent))), lines.Count - 1); + protected int getVisualLineIndex (Point mouseLocalPos) => + (int)Math.Min (Math.Max (0, Math.Floor (mouseLocalPos.Y / (fe.Ascent + fe.Descent))), visibleLines - 1); + protected virtual void updateHoverLocation (Point mouseLocalPos) { - int hoverLine = (int)Math.Min (Math.Max (0, Math.Floor ((mouseLocalPos.Y + ScrollY)/ (fe.Ascent + fe.Descent))), lines.Count - 1); - int scrollLine = (int)Math.Ceiling((double)ScrollY / (fe.Ascent + fe.Descent)); - /*if (hoverLine > scrollLine + visibleLines) - ScrollY = (int)((double)(hoverLine - visibleLines) * (fe.Ascent + fe.Descent));*/ + int hoverLine = getLineIndex (mouseLocalPos); NotifyValueChanged("MouseY", mouseLocalPos.Y + ScrollY); NotifyValueChanged("ScrollY", ScrollY); NotifyValueChanged("VisibleLines", visibleLines); NotifyValueChanged("HoverLine", hoverLine); - NotifyValueChanged("ScrollLine", hoverLine); hoverLoc = new CharLocation (hoverLine, -1, mouseLocalPos.X + ScrollX); using (Context gr = new Context (IFace.surf)) { setFontForContext (gr); @@ -483,6 +486,8 @@ namespace Crow } protected virtual bool cancelLinePrint (double lineHeght, double y, int clientHeight) => false; RectangleD? textCursor = null; + protected virtual int visualCurrentLine => CurrentLoc.Value.Line; + public virtual bool DrawCursor (Context ctx, out Rectangle rect) { if (CurrentLoc == null) { rect = default; @@ -501,8 +506,8 @@ namespace Crow } - int lineHeight = (int)(fe.Ascent + fe.Descent); - textCursor = computeTextCursor (new RectangleD (CurrentLoc.Value.VisualCharXPosition, CurrentLoc.Value.Line * lineHeight, 1.0, lineHeight)); + double lineHeight = fe.Ascent + fe.Descent; + textCursor = computeTextCursor (new RectangleD (CurrentLoc.Value.VisualCharXPosition, lineHeight * visualCurrentLine, 1.0, lineHeight)); if (textCursor == null) { rect = default; @@ -535,7 +540,7 @@ namespace Crow //int encodedBytes = Crow.Text.Encoding2.ToUtf8 (curLine.Slice (0, loc.Column), bytes); #if DEBUG if (loc.Column > curLine.Length) { - System.Diagnostics.Debug.WriteLine ($"loc.Column: {loc.Column} curLine.Length:{curLine.Length}"); + System.Diagnostics.Debug.WriteLine ($"loc.Column: {loc.Column} curLine.Length:{curLine.Length}"); loc.Column = curLine.Length; } #endif @@ -620,7 +625,7 @@ namespace Crow if (!textMeasureIsUpToDate) { lock (linesMutex) measureTextBounds (gr); - } + } if (ClipToClientRect) { gr.Save (); @@ -653,13 +658,13 @@ namespace Crow base.onUnfocused (sender, e); RegisterForRedraw (); }*/ - public override void onMouseEnter (object sender, MouseMoveEventArgs e) { - base.onMouseEnter (sender, e); + public override void onMouseEnter (object sender, MouseMoveEventArgs e) { + base.onMouseEnter (sender, e); + if (!Focusable) + return; HasFocus = true; - if (Focusable) - IFace.MouseCursor = MouseCursor.ibeam; } - public override void onMouseMove (object sender, MouseMoveEventArgs e) + public override void onMouseMove (object sender, MouseMoveEventArgs e) { base.onMouseMove (sender, e); mouseMove (e); @@ -669,8 +674,8 @@ namespace Crow base.onMouseWheel(sender, e); mouseMove (e); } - void mouseMove (MouseEventArgs e) { - updateHoverLocation (ScreenPointToLocal (IFace.MousePosition)); + protected virtual void mouseMove (MouseEventArgs e) { + updateHoverLocation (ScreenPointToLocal (e.Position)); if (HasFocus && IFace.IsDown (MouseButton.Left)) { CurrentLoc = hoverLoc; @@ -681,7 +686,7 @@ namespace Crow } public override void onMouseDown (object sender, MouseButtonEventArgs e) { - if (e.Button == Glfw.MouseButton.Left) { + if (!e.Handled && e.Button == Glfw.MouseButton.Left) { targetColumn = -1; if (HasFocus) { if (!IFace.Shift) diff --git a/CrowEditBase/src/SourceEditor.cs b/CrowEditBase/src/SourceEditor.cs index 2193c3b..6e9b473 100644 --- a/CrowEditBase/src/SourceEditor.cs +++ b/CrowEditBase/src/SourceEditor.cs @@ -9,6 +9,8 @@ using Crow.Drawing; using System.Collections; using CrowEditBase; using static CrowEditBase.CrowEditBase; +using System.Collections.Generic; +using System.Linq; namespace Crow { @@ -108,10 +110,123 @@ namespace Crow } hideOverlay (); } + + const int leftMarginGap = 5;//gap between margin start and numbering + const int leftMarginRightGap = 3;//gap between items in margin and text + const int foldSize = 9;//folding rectangles size + const int foldMargin = 9; + + int leftMargin; + bool mouseIsInMargin, mouseIsInFoldRect; + + void updateMargin () { + leftMargin = leftMarginGap; + if (App.PrintLineNumbers) + leftMargin += (int)Math.Ceiling((double)lines.Count.ToString().Length * fe.MaxXAdvance) + 6; + if (App.FoldingEnabled) + leftMargin += foldMargin; + leftMargin += leftMarginRightGap; + //updateVisibleColumns (); + } + public override int measureRawSize(LayoutingType lt) + { + DbgLogger.StartEvent(DbgEvtType.GOMeasure, this, lt); + try { + if ((bool)lines?.IsEmpty) + getLines (); + + updateMargin (); + + if (!textMeasureIsUpToDate) { + using (Context gr = new Context (IFace.surf)) { + setFontForContext (gr); + measureTextBounds (gr); + } + } + return Margin * 2 + (lt == LayoutingType.Height ? cachedTextSize.Height : cachedTextSize.Width + leftMargin); + } finally { + DbgLogger.EndEvent(DbgEvtType.GOMeasure); + } + } public override void onMouseDown (object sender, MouseButtonEventArgs e) { hideOverlay (); + if (mouseIsInMargin) { + if (e.Button == MouseButton.Left && mouseIsInFoldRect) { + SyntaxNode curNode = getFoldFromLine (hoverLoc.Value.Line); + if (curNode != null) { + curNode.isFolded = !curNode.isFolded; + textMeasureIsUpToDate = false; + RegisterForRedraw(); + } + } + + e.Handled = true; + } else if (!e.Handled && HasFocus && e.Button == MouseButton.Left) + visualLine = hoverVisualLine; + base.onMouseDown (sender, e); } + SyntaxNode getFoldFromLine (int line) { + if (!(Document is SourceDocument doc)) + return null; + IEnumerable folds = doc.SyntaxRootNode.FoldableNodes; + if (folds == null) + return null; + return folds.FirstOrDefault (n => n.StartLine == line); + } + protected override void mouseMove (MouseEventArgs e) { + Point mLoc = ScreenPointToLocal (e.Position); + if (mLoc.X < leftMargin - leftMarginRightGap) { + mouseIsInMargin = true; + IFace.MouseCursor = MouseCursor.arrow; + } else { + if (mouseIsInFoldRect) + RegisterForRedraw(); + mouseIsInMargin = mouseIsInFoldRect = false; + IFace.MouseCursor = MouseCursor.ibeam; + } + + updateHoverLocation (mLoc); + + if (mouseIsInMargin) { + double lineHeight = fe.Ascent + fe.Descent; + Rectangle rFold = new Rectangle (leftMargin - foldMargin - leftMarginRightGap, + (int)(lineHeight * hoverVisualLine + lineHeight / 2.0 - foldSize / 2.0) - ScrollY, foldSize, foldSize); + mouseIsInFoldRect = rFold.ContainsOrIsEqual (mLoc); + RegisterForRedraw(); + return; + } + + + if (HasFocus && IFace.IsDown (MouseButton.Left)) { + CurrentLoc = hoverLoc; + autoAdjustScroll = true; + IFace.forceTextCursor = true; + RegisterForRedraw (); + } + } + int hoverVisualLine, visualLine; + protected override void updateHoverLocation (Point mouseLocalPos) { + hoverVisualLine = getLineIndex (mouseLocalPos); + int hoverLine = hoverVisualLine + countFoldedLinesUntil (hoverVisualLine); + NotifyValueChanged("MouseY", mouseLocalPos.Y + ScrollY); + NotifyValueChanged("ScrollY", ScrollY); + NotifyValueChanged("VisibleLines", visibleLines); + NotifyValueChanged("HoverLine", hoverLine); + if (mouseIsInMargin) { + if (hoverLoc.HasValue) + hoverLoc = new CharLocation (hoverLine, hoverLoc.Value.Column, hoverLoc.Value.VisualCharXPosition); + else + hoverLoc = new CharLocation (hoverLine, 0, 0); + return; + } + hoverLoc = new CharLocation (hoverLine, -1, mouseLocalPos.X + ScrollX - leftMargin); + using (Context gr = new Context (IFace.surf)) { + setFontForContext (gr); + updateLocation (gr, ClientRectangle.Width, ref hoverLoc); + } + } + public override void onKeyDown(object sender, KeyEventArgs e) { TextSpan selection = Selection; @@ -175,61 +290,24 @@ namespace Crow return; } + if (e.Key == Key.F3 && Document is SourceDocument doc) { + doc.SyntaxRootNode?.Dump(); + } + base.onKeyDown(sender, e); } - const int leftMarginGap = 3;//gap between margin start and numbering - const int leftMarginRightGap = 3;//gap between items in margin and text - const int foldSize = 9;//folding rectangles size - const int foldMargin = 9; - - int leftMargin; - void updateMargin () { - leftMargin = leftMarginGap; - if (App.PrintLineNumbers) - leftMargin += (int)Math.Ceiling((double)lines.Count.ToString().Length * fe.MaxXAdvance) + 6; - if (App.FoldingEnabled) - leftMargin += foldMargin; - if (leftMargin > 0) - leftMargin += leftMarginRightGap; - //updateVisibleColumns (); - } - public override int measureRawSize(LayoutingType lt) - { - DbgLogger.StartEvent(DbgEvtType.GOMeasure, this, lt); - try { - if ((bool)lines?.IsEmpty) - getLines (); - updateMargin (); - if (!textMeasureIsUpToDate) { - using (Context gr = new Context (IFace.surf)) { - setFontForContext (gr); - measureTextBounds (gr); - } - } - return Margin * 2 + (lt == LayoutingType.Height ? cachedTextSize.Height : cachedTextSize.Width + leftMargin); - } finally { - DbgLogger.EndEvent(DbgEvtType.GOMeasure); - } - } - protected override void updateHoverLocation (Point mouseLocalPos) { - int hoverLine = (int)Math.Min (Math.Max (0, Math.Floor ((mouseLocalPos.Y + ScrollY)/ (fe.Ascent + fe.Descent))), lines.Count - 1); - int scrollLine = (int)Math.Ceiling((double)ScrollY / (fe.Ascent + fe.Descent)); - /*if (hoverLine > scrollLine + visibleLines) - ScrollY = (int)((double)(hoverLine - visibleLines) * (fe.Ascent + fe.Descent));*/ - NotifyValueChanged("MouseY", mouseLocalPos.Y + ScrollY); - NotifyValueChanged("ScrollY", ScrollY); - NotifyValueChanged("VisibleLines", visibleLines); - NotifyValueChanged("HoverLine", hoverLine); - NotifyValueChanged("ScrollLine", hoverLine); - hoverLoc = new CharLocation (hoverLine, -1, mouseLocalPos.X + ScrollX - leftMargin); - using (Context gr = new Context (IFace.surf)) { - setFontForContext (gr); - updateLocation (gr, ClientRectangle.Width, ref hoverLoc); + protected override int lineCount + { + get { + if (!(Document is SourceDocument doc)) + return base.lineCount; + return lines.Count - countFoldedLinesUntil (lines.Count); } } + protected override int visualCurrentLine => visualLine; protected override void updateMaxScrolls (LayoutingType layout) { updateMargin(); Rectangle cb = ClientRectangle; @@ -247,12 +325,43 @@ namespace Crow } } + int countFoldedLinesUntil (int visualLine) { + if (!(Document is SourceDocument doc)) + return 0; + int foldedLines = 0; + IEnumerator nodeEnum = doc.SyntaxRootNode.FoldableNodes.GetEnumerator (); + if (!nodeEnum.MoveNext()) + return 0; + + int l = 0; + while (l < visualLine + foldedLines) { + if (nodeEnum.Current.StartLine == l) { + if (nodeEnum.Current.isFolded) { + foldedLines += nodeEnum.Current.lineCount - 1; + SyntaxNode nextNode = nodeEnum.Current.NextSiblingOrParentsNextSibling; + if (nextNode == null || !nodeEnum.MoveNext()) + return foldedLines; + + while (nodeEnum.Current.StartLine < nextNode.StartLine) { + if (!nodeEnum.MoveNext()) + return foldedLines; + } + + } else if (!nodeEnum.MoveNext()) + return foldedLines; + } + l ++; + } + //Console.WriteLine ($"visualLine: {visualLine} foldedLines: {foldedLines}"); + return foldedLines; + } + protected override void drawContent (Context gr) { if (!(Document is SourceDocument doc)) { base.drawContent (gr); return; } - //lock(TokenMutex) { + doc.EnterReadLock (); try { if (doc.Tokens == null || doc.Tokens.Length == 0) { @@ -260,6 +369,13 @@ namespace Crow return; } + setFontForContext (gr); + + if (!textMeasureIsUpToDate) { + lock (linesMutex) + measureTextBounds (gr); + } + double lineHeight = fe.Ascent + fe.Descent; updateMargin (); @@ -270,58 +386,55 @@ namespace Crow Rectangle cb = ClientRectangle; RectangleD marginRect = new RectangleD (cb.X + ScrollX, cb.Y, leftMargin - leftMarginRightGap, lineHeight); - /*gr.SetSource (App.MarginBackground); - gr.Rectangle (marginRect); - gr.Fill ();*/ cb.Left += leftMargin; 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 (overlay != null && overlay.IsVisible) { - Point p = new Point((int)currentLoc.Value.VisualCharXPosition - ScrollX, (int)(lineHeight * (currentLoc.Value.Line + 1) - ScrollY)); - if (p.Y < 0 || p.X < 0) - hideOverlay (); - else { - p += ScreenCoordinates (Slot).TopLeft; - overlay.Left = p.X; - overlay.Top = p.Y; - } + if (currentLoc?.Column < 0) { + updateLocation (gr, cb.Width, ref currentLoc); + NotifyValueChanged ("CurrentColumn", CurrentColumn); + } else + updateLocation (gr, cb.Width, ref currentLoc); + + if (overlay != null && overlay.IsVisible) { + Point p = new Point((int)currentLoc.Value.VisualCharXPosition - ScrollX, (int)(lineHeight * (currentLoc.Value.Line + 1) - ScrollY)); + if (p.Y < 0 || p.X < 0) + hideOverlay (); + else { + p += ScreenCoordinates (Slot).TopLeft; + overlay.Left = p.X; + overlay.Top = p.Y; } - if (selectionStart.HasValue) { - updateLocation (gr, cb.Width, ref selectionStart); - if (CurrentLoc.Value != selectionStart.Value) - selectionNotEmpty = true; + } + 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; } - 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; - } - } else - IFace.forceTextCursor = true; - //} + } else + IFace.forceTextCursor = true; + double spacePixelWidth = gr.TextExtents (" ").XAdvance; - int x = 0, y = 0; - double pixX = cb.Left; + int x = 0; + double pixX = cb.Left, + pixY = cb.Top; Foreground.SetAsSource (IFace, gr); gr.Translate (-ScrollX, -ScrollY); @@ -331,32 +444,39 @@ namespace Crow TextExtents extents; int tokPtr = 0; Token tok = doc.Tokens[tokPtr]; - bool multilineToken = false; ReadOnlySpan buff = sourceBytes; + SyntaxNode curNode = null; - for (int i = 0; i < lines.Count; i++) { + IEnumerator nodeEnum = doc.SyntaxRootNode.FoldableNodes.GetEnumerator (); + bool notEndOfNodes = nodeEnum.MoveNext(); + + int l = 0; + while (l < lines.Count) { //if (!cancelLinePrint (lineHeight, lineHeight * y, cb.Height)) { - double pixY = lineHeight * y + cb.Top; - if (multilineToken) { - if (tok.End < lines[i].End)//last incomplete line of multiline token - buff = sourceBytes.Slice (lines[i].Start, tok.End - lines[i].Start); - else//print full line - buff = sourceBytes.Slice (lines[i].Start, lines[i].Length); + bool foldable = false; + if (notEndOfNodes && nodeEnum.Current.StartLine == l) { + curNode = nodeEnum.Current; + notEndOfNodes = nodeEnum.MoveNext(); + if (curNode.isFolded) { + SyntaxNode nextNode = curNode.NextSiblingOrParentsNextSibling; + if (nextNode == null) + notEndOfNodes = false; + else { + while (notEndOfNodes && nodeEnum.Current.StartLine < nextNode.StartLine) + notEndOfNodes = nodeEnum.MoveNext(); + } + } + foldable = true; } - while (tok.Start < lines[i].End) { - if (!multilineToken) { - if (tok.End > lines[i].End) {//first line of multiline - multilineToken = true; - buff = sourceBytes.Slice (tok.Start, lines[i].End - tok.Start); - } else - buff = sourceBytes.Slice (tok.Start, tok.Length); + //buff = sourceBytes.Slice (lines[l].Start, lines[l].Length); - gr.SetSource(doc.GetColorForToken (tok.Type)); - } + while (tok.Start < lines[l].End) { + buff = sourceBytes.Slice (tok.Start, tok.Length); + gr.SetSource (doc.GetColorForToken (tok.Type)); int size = buff.Length * 4 + 1; if (bytes.Length < size) @@ -373,13 +493,6 @@ namespace Crow x += buff.Length; } - if (multilineToken) { - if (tok.End < lines[i].End)//last incomplete line of multiline token - multilineToken = false; - else - break; - } - if (++tokPtr >= doc.Tokens.Length) break; tok = doc.Tokens[tokPtr]; @@ -389,19 +502,19 @@ namespace Crow if (selectionNotEmpty) { RectangleD selRect = lineRect; - if (i >= selStart.Line && i <= selEnd.Line) { + if (l >= selStart.Line && l <= selEnd.Line) { if (selStart.Line == selEnd.Line) { selRect.X += selStart.VisualCharXPosition; selRect.Width = selEnd.VisualCharXPosition - selStart.VisualCharXPosition; - } else if (i == selStart.Line) { + } else if (l == selStart.Line) { selRect.X += selStart.VisualCharXPosition; selRect.Width -= selStart.VisualCharXPosition - 10.0; - } else if (i == selEnd.Line) + } else if (l == selEnd.Line) selRect.Width = selEnd.VisualCharXPosition - selRect.X + cb.X; else selRect.Width += 10.0; - buff = sourceBytes.Slice(lines[i].Start, lines[i].Length); + buff = sourceBytes.Slice(lines[l].Start, lines[l].Length); int size = buff.Length * 4 + 1; if (bytes.Length < size) bytes = size > 512 ? new byte[size] : stackalloc byte[size]; @@ -426,11 +539,10 @@ namespace Crow } //Draw line numbering - int curLine = i; if (printLineNumbers){ marginRect.Y = lineRect.Y; - string strLN = (curLine+1).ToString (); + string strLN = (l+1).ToString (); gr.SetSource (marginBG); gr.Rectangle (marginRect); gr.Fill(); @@ -439,19 +551,48 @@ namespace Crow gr.ShowText (strLN); gr.Fill (); } + //draw fold + if (foldable) { + Rectangle rFld = new Rectangle (cb.X - leftMarginGap - foldMargin, + (int)(marginRect.Y + lineHeight / 2.0 - foldSize / 2.0), foldSize, foldSize); + + gr.Rectangle (rFld); + if (hoverLoc.HasValue && l == hoverLoc.Value.Line && mouseIsInFoldRect) + gr.SetSource (Colors.LightBlue); + else + gr.SetSource (Colors.White); + gr.Fill(); + gr.SetSource (Colors.Black); + gr.Rectangle (rFld, 1.0); + if (curNode.isFolded) { + gr.MoveTo (rFld.Center.X + 0.5, rFld.Y + 2); + gr.LineTo (rFld.Center.X + 0.5, rFld.Bottom - 2); + } - if (!multilineToken) { - if (++tokPtr >= doc.Tokens.Length) - break; - tok = doc.Tokens[tokPtr]; + gr.MoveTo (rFld.Left + 2, rFld.Center.Y + 0.5); + gr.LineTo (rFld.Right - 2, rFld.Center.Y + 0.5); + gr.Stroke (); } + if (++tokPtr >= doc.Tokens.Length) + break; + tok = doc.Tokens[tokPtr]; + x = 0; pixX = cb.Left; + pixY += lineHeight; - y++; - - + if (foldable && curNode.isFolded) { + TextSpan ns = curNode.Span; + l = curNode.StartLine + curNode.LineCount; + while (tok.End <= lines[l].Start) { + if (++tokPtr >= doc.Tokens.Length) + break; + tok = doc.Tokens[tokPtr]; + } + //tokPtr = doc.FindTokenIndexIncludingPosition (lines[l].Start); + } else + l ++; /* } else if (tok2.Type == TokenType.Tabulation) { int spaceRounding = x % tabSize; int spaces = spaceRounding == 0 ? diff --git a/CrowEditBase/ui/sourceEditor.itmp b/CrowEditBase/ui/sourceEditor.itmp index b1cc0b2..19f10af 100644 --- a/CrowEditBase/ui/sourceEditor.itmp +++ b/CrowEditBase/ui/sourceEditor.itmp @@ -13,7 +13,7 @@ CursorRatio="{../tb.ChildWidthRatio}" Maximum="{../tb.MaxScrollX}" /> - +