From 4e46ad20005a1a677d110b1a9358b35760402e4a Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jean-Philippe=20Bruy=C3=A8re?= Date: Sun, 6 Jul 2025 00:46:26 +0200 Subject: [PATCH] debug xml/iml parsing/suggestions/autocomplete --- CrowEditBase/src/Compiler/SourceDocument.cs | 8 +- .../Compiler/SyntaxNodes/SingleTokenSyntax.cs | 6 +- .../src/Compiler/SyntaxNodes/SyntaxNode.cs | 4 + CrowEditBase/src/SourceEditor.cs | 12 +- .../src/Parsing/IML/ImlDocument.cs | 33 +-- .../src/Parsing/IML/ImlTokenizer.cs | 2 + .../src/Parsing/CsharpSyntaxWalkerBridge.cs | 5 +- .../CEXmlPlugin/src/Parsing/XmlDocument.cs | 254 ++++-------------- .../src/Parsing/XmlSyntaxAnalyser.cs | 39 +-- .../CEXmlPlugin/src/Parsing/XmlSyntaxNodes.cs | 36 ++- .../CEXmlPlugin/src/Parsing/XmlTokenizer.cs | 2 +- 11 files changed, 143 insertions(+), 258 deletions(-) diff --git a/CrowEditBase/src/Compiler/SourceDocument.cs b/CrowEditBase/src/Compiler/SourceDocument.cs index cee7da6..258e186 100644 --- a/CrowEditBase/src/Compiler/SourceDocument.cs +++ b/CrowEditBase/src/Compiler/SourceDocument.cs @@ -135,11 +135,11 @@ namespace CrowEditBase if (tokType.HasFlag (TokenType.Punctuation)) return Colors.DarkGrey; if (tokType.HasFlag (TokenType.WhiteSpace)) - return Colors.Gainsboro; - if (tokType.HasFlag (TokenType.Trivia)) return Colors.Silver; + if (tokType.HasFlag (TokenType.Trivia)) + return Colors.DimGrey; if (tokType == TokenType.Keyword) - return Colors.DarkSlateBlue; + return Colors.Blue; return Colors.Red; } public virtual string GetTokenTypeString (TokenType tokenType) => tokenType.ToString(); @@ -153,6 +153,8 @@ namespace CrowEditBase cancelSource = new CancellationTokenSource(); backgroundCompilationTask = Task.Run(()=>parseAssync(cancelSource.Token)); + //TODO: to do + await backgroundCompilationTask; //CurrentNode?.ExpandToTheTop(); diff --git a/CrowEditBase/src/Compiler/SyntaxNodes/SingleTokenSyntax.cs b/CrowEditBase/src/Compiler/SyntaxNodes/SingleTokenSyntax.cs index 56c486f..2df8fe7 100644 --- a/CrowEditBase/src/Compiler/SyntaxNodes/SingleTokenSyntax.cs +++ b/CrowEditBase/src/Compiler/SyntaxNodes/SingleTokenSyntax.cs @@ -1,6 +1,8 @@ // Copyright (c) 2021-2025 Bruyère Jean-Philippe // // This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) +using Drawing2D; + namespace CrowEditBase { public class SingleTokenSyntax : SyntaxNode { @@ -21,10 +23,10 @@ namespace CrowEditBase public override bool IsSimilar(object other) { return other is SingleTokenSyntax sts ? - sts.Type == Type : other is TokenType tt ? Type == tt : false; - + sts.Type == Type : (int)other == (int)Type; } #endregion + public static implicit operator TokenType (SingleTokenSyntax sts) => sts == null ? TokenType.Unknown : sts.Type; } } \ No newline at end of file diff --git a/CrowEditBase/src/Compiler/SyntaxNodes/SyntaxNode.cs b/CrowEditBase/src/Compiler/SyntaxNodes/SyntaxNode.cs index bb639d6..102721e 100644 --- a/CrowEditBase/src/Compiler/SyntaxNodes/SyntaxNode.cs +++ b/CrowEditBase/src/Compiler/SyntaxNodes/SyntaxNode.cs @@ -66,6 +66,10 @@ namespace CrowEditBase return null; } } + public bool NextSiblingIs(TokenType tokType) + => NextSibling is SingleTokenSyntax sts && sts.token.Type == tokType; + public bool PreviousSiblingIs(TokenType tokType) + => PreviousSibling is SingleTokenSyntax sts && sts.token.Type == tokType; protected Token getTokenByIndex (int idx) => Root.GetTokenByIndex(idx); diff --git a/CrowEditBase/src/SourceEditor.cs b/CrowEditBase/src/SourceEditor.cs index 43a31e1..a7553ce 100644 --- a/CrowEditBase/src/SourceEditor.cs +++ b/CrowEditBase/src/SourceEditor.cs @@ -171,16 +171,20 @@ namespace CrowEditBase "); overlay.DataSource = this; overlay.Loaded += (sender, arg) => (sender as ListBox).SelectedIndex = 0; - } else + } else { + overlay.DataSource = this; overlay.IsVisible = true; + } overlay.RegisterForLayouting(LayoutingType.Sizing); } } void hideOverlay () { if (overlay == null) return; - lock(App.UpdateMutex) + lock(App.UpdateMutex) { overlay.IsVisible = false; + overlay.DataSource = null; + } } void completeToken () { disableSuggestions = true; @@ -464,7 +468,6 @@ namespace CrowEditBase return; } } - base.onKeyDown(sender, e); /*} finally { Document.ExitReadLock (); @@ -837,7 +840,7 @@ namespace CrowEditBase gr.Stroke (); } - if (++tokPtr >= sourceDocument.Tokens.Length) + if (tokPtr >= sourceDocument.Tokens.Length) break; tok = sourceDocument.Tokens[tokPtr]; @@ -902,6 +905,7 @@ namespace CrowEditBase updateCurrentTokAndNode(); + hideOverlay(); if (!disableSuggestions &&!disableTextChangedEvent && HasFocus) tryGetSuggestions (); diff --git a/plugins/CECrowPlugin/src/Parsing/IML/ImlDocument.cs b/plugins/CECrowPlugin/src/Parsing/IML/ImlDocument.cs index 11ac338..34a684f 100644 --- a/plugins/CECrowPlugin/src/Parsing/IML/ImlDocument.cs +++ b/plugins/CECrowPlugin/src/Parsing/IML/ImlDocument.cs @@ -46,7 +46,7 @@ namespace CECrowPlugin return crowType.GetMember (memberName, BindingFlags.Public | BindingFlags.Instance).FirstOrDefault (); }*/ - protected override IEnumerable getElementNameSuggestions(string curName, TextChange change) + protected override IEnumerable getElementNameSuggestions(string curName, TextChange change, int finalPositionOffset = 0) { CrowService srv = App.GetService(); if (srv == null || !srv.IsRunning) @@ -62,13 +62,12 @@ namespace CECrowPlugin widgetTypes = widgetTypes.Where(t=>t.Name.StartsWith(curName, StringComparison.OrdinalIgnoreCase)); curNameLength = curName.Length; } - int endPosOffset = change.HasNewText ? -change.ChangedText.Length : 0; return widgetTypes.Select (t => new WidgetSuggestion(t, - new TextChange(change.Start, change.Length, t.Name + change.ChangedText), endPosOffset)); + new TextChange(change.Start, change.Length, t.Name + change.ChangedText), finalPositionOffset)); } - protected override IEnumerable getAttributeNameSuggestions(string eltName, string curName, TextChange change) { - int endPosOffset = change.HasNewText ? -1 : 0; + protected override IEnumerable getAttributeNameSuggestions(string eltName, string curName, TextChange change, int finalPositionOffset = 0) { + //int endPosOffset = change.HasNewText ? -1 : 0; var members = App.GetService()?.GetAllCrowTypeMembers(eltName); if (members != null) { @@ -77,7 +76,7 @@ namespace CECrowPlugin var suggs = members?.Where(m=>m.MemberType == MemberTypes.Property)?.Select(p => new CrowPropertySuggestion(p as PropertyInfo, - new TextChange(change.Start, change.Length, p.Name + change.ChangedText), endPosOffset)); + new TextChange(change.Start, change.Length, p.Name + change.ChangedText), finalPositionOffset)); foreach (var tmp in suggs.Where(s=>s.Category == "Divers")) yield return tmp; @@ -92,46 +91,38 @@ namespace CECrowPlugin } } - protected override IEnumerable getAttributeValueSuggestions(string eltName, string attribName, string attribValue, TextChange change) { + protected override IEnumerable getAttributeValueSuggestions(string eltName, string attribName, string attribValue, TextChange change, int finalPositionOffset = 0) { MemberInfo mi = App.GetService()?.GetAllCrowTypeMembers(eltName)?.Where(m=>m.Name.Equals(attribName, StringComparison.Ordinal)).FirstOrDefault(); if (mi is PropertyInfo pi) { if (pi.Name == "Style") return App.Styling.Keys .Where (s => s.StartsWith (attribValue, StringComparison.OrdinalIgnoreCase)) .Select(s=>new Suggestion(s, - new TextChange(change.Start, change.Length, s + change.ChangedText))); + new TextChange(change.Start, change.Length, s + change.ChangedText), finalPositionOffset)); if (pi.PropertyType.IsEnum) return Enum.GetNames (pi.PropertyType) .Where (s => s.StartsWith (attribValue, StringComparison.OrdinalIgnoreCase)) .Select(s=>new Suggestion(s, - new TextChange(change.Start, change.Length, s + change.ChangedText))); + new TextChange(change.Start, change.Length, s + change.ChangedText), finalPositionOffset)); if (pi.PropertyType == typeof(bool)) return (new string[] {"true", "false"}). Where (s => s.StartsWith (attribValue, StringComparison.OrdinalIgnoreCase)) .Select(s=>new Suggestion(s, - new TextChange(change.Start, change.Length, s + change.ChangedText))); + new TextChange(change.Start, change.Length, s + change.ChangedText), finalPositionOffset)); if (pi.PropertyType.Name == "Measure") return (new string[] {"Stretched", "Fit"}). Where (s => s.StartsWith (attribValue, StringComparison.OrdinalIgnoreCase)) .Select(s=>new Suggestion(s, - new TextChange(change.Start, change.Length, s + change.ChangedText))); + new TextChange(change.Start, change.Length, s + change.ChangedText), finalPositionOffset)); if (pi.PropertyType.Name == "Fill") return EnumsNET.Enums.GetValues () .Where (s => s.ToString().StartsWith (attribValue, StringComparison.OrdinalIgnoreCase)) .Select(c=>new ColorSuggestion(c, - new TextChange(change.Start, change.Length, c + change.ChangedText))); + new TextChange(change.Start, change.Length, c + change.ChangedText), finalPositionOffset)); } return null; } - public override IList GetSuggestions (int absoluteTextPos, int currentTokenIndex, SyntaxNode CurrentNode, CharLocation loc) { - Token tok = GetTokenByIndex(currentTokenIndex); - Console.Write($"{absoluteTextPos}({tok.Span}){tok.GetTokenType()}"); - if (currentTokenIndex > 0) - Console.Write($" prev:{GetTokenByIndex(currentTokenIndex-1).GetTokenType()}"); - if (currentTokenIndex < Tokens.Length - 1) - Console.Write($" next:{GetTokenByIndex(currentTokenIndex+1).GetTokenType()}"); - Console.WriteLine($" node:{CurrentNode} ({loc})"); - + public override IList GetSuggestions (int absoluteTextPos, int currentTokenIndex, SyntaxNode CurrentNode, CharLocation loc) { IList sugs = base.GetSuggestions (absoluteTextPos, currentTokenIndex, CurrentNode, loc); if (sugs != null) return sugs; diff --git a/plugins/CECrowPlugin/src/Parsing/IML/ImlTokenizer.cs b/plugins/CECrowPlugin/src/Parsing/IML/ImlTokenizer.cs index 378e9f3..dc62fe3 100644 --- a/plugins/CECrowPlugin/src/Parsing/IML/ImlTokenizer.cs +++ b/plugins/CECrowPlugin/src/Parsing/IML/ImlTokenizer.cs @@ -139,6 +139,8 @@ namespace CECrowPlugin } reader.Read (); } + if (reader.EndOfSpan)//occurs when no closing quote + addTok(ref reader, XmlTokenType.AttributeValue); } } diff --git a/plugins/CERoslynPlugin/src/Parsing/CsharpSyntaxWalkerBridge.cs b/plugins/CERoslynPlugin/src/Parsing/CsharpSyntaxWalkerBridge.cs index c276670..7918f78 100644 --- a/plugins/CERoslynPlugin/src/Parsing/CsharpSyntaxWalkerBridge.cs +++ b/plugins/CERoslynPlugin/src/Parsing/CsharpSyntaxWalkerBridge.cs @@ -47,10 +47,9 @@ namespace CERoslynPlugin VisitLeadingTrivia (token); Microsoft.CodeAnalysis.Text.TextSpan fs = token.Span; - /*if (SyntaxFacts.IsLiteralExpression (token.Kind ())) + if (SyntaxFacts.IsLiteralExpression (token.Kind ())) addMultilineToken(token.ToString(), token.Span, (TokenType)token.RawKind); - else*/ - if (fs.Length == 0) + else if (fs.Length == 0) Debug.WriteLine($"Empty token: {token}"); else { Microsoft.CodeAnalysis.Text.TextSpan span = token.Span; diff --git a/plugins/CEXmlPlugin/src/Parsing/XmlDocument.cs b/plugins/CEXmlPlugin/src/Parsing/XmlDocument.cs index 43a3d2d..eaac3c1 100644 --- a/plugins/CEXmlPlugin/src/Parsing/XmlDocument.cs +++ b/plugins/CEXmlPlugin/src/Parsing/XmlDocument.cs @@ -11,6 +11,8 @@ using System; using Crow; using System.Linq; using System.Diagnostics; +using System.Xml; +using System.Text; namespace CrowEdit.Xml { @@ -20,22 +22,25 @@ namespace CrowEdit.Xml protected override SyntaxAnalyser CreateSyntaxAnalyser() => new XmlSyntaxAnalyser (ImmutableBufferCopy); public override string GetTokenTypeString (TokenType tokenType) => ((XmlTokenType)tokenType).ToString(); - protected virtual IEnumerable getElementNameSuggestions(string curName, TextChange change) => null; - protected virtual IEnumerable getAttributeNameSuggestions(string eltName, string attribName, TextChange change) => null; - protected virtual IEnumerable getAttributeValueSuggestions(string eltName, string attribName, string attribValue, TextChange change) => null; + protected virtual IEnumerable getElementNameSuggestions(string curName, TextChange change, int finalPositionOffset = 0) => null; + protected virtual IEnumerable getAttributeNameSuggestions(string eltName, string attribName, TextChange change, int finalPositionOffset = 0) => null; + protected virtual IEnumerable getAttributeValueSuggestions(string eltName, string attribName, string attribValue, TextChange change, int finalPositionOffset = 0) => null; public override IList GetSuggestions (int absoluteTextPos, int currentTokenIndex, SyntaxNode CurrentNode, CharLocation loc) { - Console.WriteLine($"absPos:{absoluteTextPos} tokIdx:{currentTokenIndex} node:{CurrentNode} charLoc:{loc}"); Token tok = GetTokenByIndex(currentTokenIndex); + Token nextTok = currentTokenIndex < Tokens.Length - 1 ? GetTokenByIndex(currentTokenIndex + 1) : null; + //Console.WriteLine($"absPos:{absoluteTextPos} {tok.GetTokenType()} tokIdx:{currentTokenIndex} node:{CurrentNode} parent:{CurrentNode?.Parent} charLoc:{loc}"); XmlTokenType tokType = tok.GetTokenType(); if (CurrentNode is SingleTokenSyntax sts) { XmlTokenType tk = sts.token.GetTokenType(); + if (CurrentNode.Parent is ElementTagSyntax ets) { if (ets is ElementEndTagSyntax eets) { if (eets.Parent is ElementSyntax es) { string name = es.StartTag?.Name; - if (!string.IsNullOrEmpty(name)){ + string eltName = sts.AsText(); + if (!string.IsNullOrEmpty(name) && !string.Equals(name, eltName)){ Suggestion sug = new Suggestion(name); - if (tk == XmlTokenType.ElementName && name.StartsWith(sts.AsText(), StringComparison.OrdinalIgnoreCase)) + if (tk == XmlTokenType.ElementName && name.StartsWith(eltName, StringComparison.OrdinalIgnoreCase)) sug.Change = new TextChange (tok.Start, tok.Length, sug.Caption); else if (tk == XmlTokenType.EndElementOpen) sug.Change = new TextChange (tok.End, 0, sug.Caption); @@ -46,209 +51,65 @@ namespace CrowEdit.Xml return new List([sug]); } } - } else { - string txtEnd = ets.HasClosingToken ? "" : ">"; - if (tk == XmlTokenType.ElementOpen) { - return getElementNameSuggestions("", new TextChange(tok.End, 0, txtEnd)).ToList(); + } else {//startTag or empty element + StringBuilder txtEnd = new StringBuilder(10); + int offset = 0; + if (nextTok?.GetTokenType() != XmlTokenType.WhiteSpace) + txtEnd.Append(" "); + if (!ets.HasClosingToken) { + txtEnd.Append(">"); + offset = -1; } - if (tk == XmlTokenType.ElementName) { - return getElementNameSuggestions(sts.AsText(), new TextChange(tok.Start, tok.Length, txtEnd)).ToList(); + List sugs; + if (tk == XmlTokenType.ElementOpen) { + sugs = getElementNameSuggestions("", new TextChange(tok.End, 0, txtEnd.ToString()), offset).ToList(); + }else if (tk == XmlTokenType.ElementName) { + sugs = getElementNameSuggestions(sts.AsText(), new TextChange(tok.Start, tok.Length, txtEnd.ToString()), offset).ToList(); + } else { + return null; } - } - } - } - /* - if (tok.Start != absoluteTextPos //middle of edited tok - && currentTokenIndex >= CurrentNode?.Root.TokenCount - 1) //occurs when curTok is last tok of text - { - return null; - } - Token prevTok = GetTokenByIndex(currentTokenIndex-1); - - if (CurrentNode is ElementEndTagSyntax eltEndTag) { - //sug = corresponding eltName - if (!tok.Is(XmlTokenType.ElementName) && - eltEndTag.Parent is ElementSyntax elt && - elt.StartTag?.Name != null) { - - Suggestion sug = new Suggestion(elt.StartTag.Name); - string curEndName = null; - if (prevTok.Is(XmlTokenType.ElementName) && !elt.StartTag.Name.Equals(curEndName, StringComparison.Ordinal)) { - curEndName = root.GetTokenString(prevTok); - sug.Change = new TextChange (prevTok.Start, prevTok.Length, sug.Caption); - } else if (prevTok.Is(XmlTokenType.EndElementOpen)) { - curEndName = ""; - sug.Change = new TextChange (prevTok.End, 0, sug.Caption); - } else - return null; - - //if (!tok.Is(XmlTokenType.ClosingSign)) - if (!eltEndTag.close.HasValue) - sug.Change.ChangedText += ">"; - - if (elt.StartTag.Name.StartsWith (curEndName, StringComparison.OrdinalIgnoreCase)) - return new List ([sug]); - } - } else if (CurrentNode is ElementStartTagSyntax eltStartTag) { - if (!tok.Is(XmlTokenType.ElementName)) { - TextChange change = default; - - if (prevTok.Is(XmlTokenType.ElementName)) - change = new TextChange (prevTok.Start, prevTok.Length); - else if (prevTok.Is(XmlTokenType.ElementOpen)) - change = new TextChange (prevTok.End, 0); - else if (eltStartTag.name.HasValue) { - string attribName = ""; - if (prevTok.Type.HasFlag(TokenType.Trivia) || - (tok.Type.HasFlag(TokenType.Trivia) && GetTokenByIndex(currentTokenIndex+1).Type.HasFlag(TokenType.Trivia)))//attribute - change = new TextChange(tok.Start, 0); - else if (prevTok.Is(XmlTokenType.AttributeName)) { - change = new TextChange(prevTok.Start, prevTok.Length); - attribName = prevTok.AsString(source); - } else + if (sugs.Count == 1 && sugs[0].Caption == sts.AsText()) return null; - if (!tok.Is(XmlTokenType.EqualSign)) - change.ChangedText += "=\"\""; - return getAttributeNameSuggestions(eltStartTag.Name, attribName, change).ToList(); - } else - return null; - - //if (!tok.Is(XmlTokenType.ClosingSign)) - if (!eltStartTag.close.HasValue) - change.ChangedText = ">"; - - return getElementNameSuggestions(eltStartTag.Name, change).ToList(); - - } - } else if (CurrentNode is AttributeSyntax attrib && - attrib.Parent is ElementStartTagSyntax eltStart && - eltStart.name.HasValue) { - - if (prevTok.Is(XmlTokenType.AttributeName)) { - TextChange change = new TextChange(prevTok.Start, prevTok.Length); - if (!tok.Is(XmlTokenType.EqualSign)) - change.ChangedText += "=\"\""; - return getAttributeNameSuggestions(eltStart.Name, attrib.Name, change).ToList(); - } else if (attrib.name.HasValue) { - if (prevTok.Is(XmlTokenType.AttributeValueOpen)) { - return getAttributeValueSuggestions(eltStart.Name, attrib.Name, "", - tok.Is(XmlTokenType.AttributeValueClose) ? - new TextChange(prevTok.End, 0) - : new TextChange(prevTok.End, 0, "\""))?.ToList(); - } else if (prevTok.Is(XmlTokenType.AttributeValue) && attrib.valueTok.HasValue) { - return getAttributeValueSuggestions(eltStart.Name, attrib.Name, attrib.Value, - tok.Is(XmlTokenType.AttributeValueClose) ? - new TextChange(prevTok.Start, prevTok.Length) - : new TextChange(tok.Start, tok.Length, "\""))?.ToList(); + return sugs; } + } else if (CurrentNode.Parent is AttributeSyntax atts) { - } - - *if (tokType == XmlTokenType.AttributeName) { - if (attrib.ValueToken.HasValue) { - change = new TextChange (tok.Start, tok.Length, selectedSugg); - newSelection = new TextSpan( - attrib.ValueToken.Value.Start + change.CharDiff + 1, - attrib.ValueToken.Value.End + change.CharDiff - 1 - ); + if (tokType == XmlTokenType.WhiteSpace) { + Console.WriteLine($"*** {tk}"); + //return getAttributeNameSuggestions(atts.Name, "", new TextChange(tok.End, 0)).ToList(); } else { - change = new TextChange (tok.Start, tok.Length, selectedSugg + "=\"\""); - newSelection = TextSpan.FromStartAndLength (tok.Start + selectedSugg.Length + 2); - } - } else { - int offset = 1; - if (!attrib.valueClose.HasValue) { - selectedSugg += root.GetTokenStringByIndex(attrib.valueClose.Value); - offset = 0; - } - if (tokType == XmlTokenType.AttributeValueOpen) - change = new TextChange (tok.End, 0, selectedSugg); - else if (tokType == XmlTokenType.AttributeValue) - change = new TextChange (tok.Start, tok.Length, selectedSugg); - newSelection = TextSpan.FromStartAndLength (change.End2 + offset); - }* - }*/ - - return null; - } - /* - public override bool TryCompleteToken (Suggestion suggestion) { - newSelection = null; - change = default; - - string selectedSugg = suggestion?.ToString (); - - if (selectedSugg == null) - return false; - - XmlTokenType tokType = tok.GetTokenType(); - - if (tokType.HasFlag(XmlTokenType.WhiteSpace)) { - - if (typeof(ElementTagSyntax).IsAssignableFrom(node?.GetType())) { - ElementTagSyntax ets = node as ElementTagSyntax; - if (ets.name.HasValue) { - change = new TextChange (tok.End, 0, selectedSugg + "=\"\""); - newSelection = TextSpan.FromStartAndLength(change.End2 - 1); - } else - change = new TextChange (tok.End, 0, selectedSugg + " "); - } else { - change = new TextChange (tok.End, 0, selectedSugg); - } - } else if (tokType == XmlTokenType.EndElementOpen) { - change = new TextChange (tok.End, 0, selectedSugg + ">"); - } else if (tokType == XmlTokenType.ElementName) { - if (node is ElementEndTagSyntax) - change = new TextChange (tok.Start, tok.Length, selectedSugg + ">"); - else { - change = new TextChange (tok.Start, tok.Length, selectedSugg + ">"); - newSelection = TextSpan.FromStartAndLength (change.End2 - 1); - } - } else if (node is AttributeSyntax attrib) { - - } else if (tokType == XmlTokenType.ElementOpen) { - change = new TextChange (tok.End, 0, selectedSugg + " "); - } else - change = new TextChange (tok.Start, tok.Length, selectedSugg); - - return true; - + if (atts.Parent is ElementTagSyntax et) { + if (tokType == XmlTokenType.AttributeName) { + string txtEnd = sts.NextSiblingIs(XmlTokenType.EqualSign) ? "" : "=\"\""; + List sugs = getAttributeNameSuggestions(et.Name, atts.Name, new TextChange(tok.Start, tok.Length, txtEnd), txtEnd.Length > 0 ? -1 : 0).ToList(); + if (sugs.Count == 0 || (sugs.Count == 1 && sugs[0].Caption == sts.AsText())) + return null; - if (tok.GetTokenType() == XmlTokenType.ElementOpen || - tok.GetTokenType() == XmlTokenType.WhiteSpace || - tok.GetTokenType() == XmlTokenType.AttributeValueOpen) { - change = new TextChange (tok.End, 0, selectedSugg); - return true; - } - if (tok.GetTokenType() == XmlTokenType.AttributeName && CurrentNode is AttributeSyntax attrib) { - if (attrib.ValueToken.HasValue) { - change = new TextChange (tok.Start, tok.Length, selectedSugg); - newSelection = new TextSpan( - attrib.ValueToken.Value.Start + change.CharDiff + 1, - attrib.ValueToken.Value.End + change.CharDiff - 1 - ); - } else { - change = new TextChange (tok.Start, tok.Length, selectedSugg + "=\"\""); - newSelection = TextSpan.FromStartAndLength (tok.Start + selectedSugg.Length + 2); + return sugs; + } else if (atts.HasName) { + if(tokType == XmlTokenType.AttributeValueOpen) { + return getAttributeValueSuggestions(et.Name, atts.Name, "", new TextChange(tok.End, 0))?.ToList(); + } else if (tokType == XmlTokenType.AttributeValue) { + return getAttributeValueSuggestions(et.Name, atts.Name, CurrentNode.AsText(), new TextChange(tok.Start, tok.Length))?.ToList(); + } + } + } else { + System.Diagnostics.Debugger.Break(); + } + } } - return true; + } else if (CurrentNode is ElementTagSyntax ts) { + string txtEnd = nextTok?.GetTokenType() == XmlTokenType.EqualSign ? "" : "=\"\""; + return getAttributeNameSuggestions(ts.Name, "", new TextChange(tok.End, 0, txtEnd), txtEnd.Length > 0 ? -1 : 0).ToList(); } - - change = new TextChange (tok.Start, tok.Length, selectedSugg); - return true; - }*/ + return null; + } public override Color GetColorForToken(Token token) { TokenType tokType = token.Type; XmlTokenType xmlTokType = (XmlTokenType)tokType; - if (xmlTokType.HasFlag (XmlTokenType.Punctuation)) - return Colors.DarkGrey; - if (tokType.HasFlag (TokenType.WhiteSpace)) - return Colors.Silver; - if (xmlTokType.HasFlag (XmlTokenType.Trivia)) - return Colors.DimGrey; - else if (xmlTokType == XmlTokenType.ElementName) + if (xmlTokType == XmlTokenType.ElementName) return Colors.Green; if (xmlTokType == XmlTokenType.AttributeName) return Colors.Blue; @@ -258,9 +119,8 @@ namespace CrowEdit.Xml return Colors.Black; if (xmlTokType == XmlTokenType.PI_Target) return Colors.DarkSlateBlue; - return Colors.Red; + return base.GetColorForToken(token); } - //protected bool previousTokHasFlag(XmlTokenType flag) => previousToken.HasValue && previousToken.Value.Type.HasFlag(flag); } } \ No newline at end of file diff --git a/plugins/CEXmlPlugin/src/Parsing/XmlSyntaxAnalyser.cs b/plugins/CEXmlPlugin/src/Parsing/XmlSyntaxAnalyser.cs index e678241..9832636 100644 --- a/plugins/CEXmlPlugin/src/Parsing/XmlSyntaxAnalyser.cs +++ b/plugins/CEXmlPlugin/src/Parsing/XmlSyntaxAnalyser.cs @@ -19,6 +19,8 @@ namespace CrowEdit.Xml tok.Type = (TokenType)type; } public static bool Is(this Token tok, XmlTokenType type) => (XmlTokenType)tok.Type == type; + public static bool NextSiblingIs(this SyntaxNode sts, XmlTokenType tt) => sts.NextSiblingIs((TokenType)tt); + public static bool PreviousSiblingIs(this SyntaxNode sts, XmlTokenType tt) => sts.PreviousSiblingIs((TokenType)tt); } public class XmlSyntaxAnalyser : SyntaxAnalyser { public XmlSyntaxAnalyser (ReadOnlyTextBuffer document) : base (document) {} @@ -32,10 +34,10 @@ namespace CrowEdit.Xml break; case XmlTokenType.BlockCommentStart: MultiNodeSyntax bc = new CommentTriviaSyntax(true); - bc.AddChild(new SingleTokenSyntax(Read())); + bc.AddChild(new XMLSingleTokenSyntax(Read())); while(tryPeek(out Token tok)) { if (tok.Type == TokenType.BlockCommentEnd) { - bc.AddChild(new SingleTokenSyntax(Read())); + bc.AddChild(new XMLSingleTokenSyntax(Read())); break; } if (tok.Type == TokenType.LineBreak) { @@ -43,7 +45,7 @@ namespace CrowEdit.Xml return true; Read(); } else { - bc.AddChild(new SingleTokenSyntax(Read())); + bc.AddChild(new XMLSingleTokenSyntax(Read())); } } currentNode.AddChild(bc); @@ -56,28 +58,29 @@ namespace CrowEdit.Xml return !EOF; } - bool accept(MultiNodeSyntax node, Enum tokenType) { - if (EOF) + bool accept(MultiNodeSyntax node, Enum tokenType, bool skipTrivia = true, bool skipLineBreaks = true) { + if (skipTrivia && !skipTriviaAndComments(node, skipLineBreaks)) return false; if (Peek().Type == (TokenType)tokenType) { - node.AddChild(new SingleTokenSyntax(Read())); + node.AddChild(new XMLSingleTokenSyntax(Read())); return true; } return false; } - public virtual void ProcessAttributeValueSyntax(AttributeSyntax attrib) { + /*public virtual void ProcessAttributeValueSyntax(AttributeSyntax attrib) { //attrib.valueTok = tokIdx - attrib.TokenIndexBase; - } + }*/ AttributeSyntax processNode(AttributeSyntax attrib) { - if (accept(attrib, XmlTokenType.EqualSign)) - if (accept(attrib, XmlTokenType.AttributeValueOpen)) - if(accept(attrib, XmlTokenType.AttributeValue)) - accept(attrib, XmlTokenType.AttributeValueClose); + if (accept(attrib, XmlTokenType.EqualSign, true)) + if (accept(attrib, XmlTokenType.AttributeValueOpen, true)) { + accept(attrib, XmlTokenType.AttributeValue); + accept(attrib, XmlTokenType.AttributeValueClose); + } return attrib; } ElementEndTagSyntax processNode(ElementEndTagSyntax et) { if (accept(et, XmlTokenType.ElementName)) - accept(et, XmlTokenType.ClosingSign); + accept(et, XmlTokenType.ClosingSign, true); return et; } ProcessingInstructionSyntax processNode(ProcessingInstructionSyntax pi) { @@ -85,7 +88,7 @@ namespace CrowEdit.Xml pi.AddChild(new PITargetSyntax(Read())); while (skipTriviaAndComments(pi, false)) { if (Peek().Is(XmlTokenType.PI_End)) { - pi.AddChild(new SingleTokenSyntax(Read())); + pi.AddChild(new XMLSingleTokenSyntax(Read())); break; } if (Peek().Is(XmlTokenType.AttributeName)) @@ -127,7 +130,13 @@ namespace CrowEdit.Xml if (accept (start, XmlTokenType.ClosingSign)) { elt = processElement(new ElementSyntax(start)); break; - } + } + //wouldd be better in tokenizer, and break on unexpected tok + if (Peek().Is(XmlTokenType.ElementOpen) || Peek().Is(XmlTokenType.EndElementOpen)) { + start.AddChild(new UnexpectedTokenSyntax(Read())); + break; + } + if (Peek().Is(XmlTokenType.AttributeName)) start.AddChild(processNode(new AttributeSyntax(Read()))); else diff --git a/plugins/CEXmlPlugin/src/Parsing/XmlSyntaxNodes.cs b/plugins/CEXmlPlugin/src/Parsing/XmlSyntaxNodes.cs index e877d53..3404522 100644 --- a/plugins/CEXmlPlugin/src/Parsing/XmlSyntaxNodes.cs +++ b/plugins/CEXmlPlugin/src/Parsing/XmlSyntaxNodes.cs @@ -12,32 +12,33 @@ namespace CrowEdit.Xml public class XMLRootSyntax : SyntaxRootNode { public XMLRootSyntax (ReadOnlyTextBuffer buff, Token[] tokens) : base (buff, tokens) { } } + public class ProcessingInstructionSyntax : MultiNodeSyntax { // public override bool IsComplete => base.IsComplete & name.HasValue & PIClose.HasValue; public ProcessingInstructionSyntax (Token openTok){ - AddChild(new SingleTokenSyntax(openTok)); + AddChild(new XMLSingleTokenSyntax(openTok)); } } - public class PITargetSyntax : SingleTokenSyntax { + public class PITargetSyntax : XMLSingleTokenSyntax { // public override bool IsComplete => base.IsComplete & name.HasValue & PIClose.HasValue; public PITargetSyntax (Token target) : base(target) { } } - + public class XMLSingleTokenSyntax : SingleTokenSyntax { + public XMLSingleTokenSyntax(Token tok) : base(tok) { } + public static implicit operator XmlTokenType (XMLSingleTokenSyntax sts) => sts == null ? XmlTokenType.Unknown : (XmlTokenType)sts.Type; + } public abstract class ElementTagSyntax : MultiNodeSyntax { // public override bool IsComplete => base.IsComplete & name.HasValue & close.HasValue; protected ElementTagSyntax (){} protected ElementTagSyntax (Token openTok) { - AddChild(new SingleTokenSyntax(openTok)); + AddChild(new XMLSingleTokenSyntax(openTok)); } public override bool IsComplete => ChildSequenceIs(); public string Name => Children.ElementAtOrDefault(1) is SingleTokenSyntax sts && sts.token.GetTokenType() == XmlTokenType.ElementName ? sts.AsText(): ""; public abstract bool HasClosingToken { get; } } - /*public class ElementNameSyntax : SingleTokenSyntax { - public ElementNameSyntax(Token name) : base(name) {} - }*/ public class ElementStartTagSyntax : ElementTagSyntax { public ElementStartTagSyntax (Token openTok) : base(openTok) {} public override bool HasClosingToken => Children.LastOrDefault() is SingleTokenSyntax sts && sts.token.GetTokenType() == XmlTokenType.ClosingSign; @@ -47,13 +48,12 @@ namespace CrowEdit.Xml public ElementEndTagSyntax (Token openTok) : base(openTok) {} public override bool HasClosingToken => Children.LastOrDefault() is SingleTokenSyntax sts && sts.token.GetTokenType() == XmlTokenType.ClosingSign; } - public class EmptyElementSyntax : ElementTagSyntax { public EmptyElementSyntax (ElementStartTagSyntax startNode) { foreach (var child in startNode.Children) AddChild(child); } - public override bool HasClosingToken => Children.LastOrDefault() is SingleTokenSyntax sts && sts.token.GetTokenType() == XmlTokenType.EmptyElementClosing; + public override bool HasClosingToken => HasChilds && Children.LastOrDefault().IsSimilar(XmlTokenType.EmptyElementClosing); //public override bool IsComplete => base.IsComplete && StartTag != null; } @@ -66,10 +66,22 @@ namespace CrowEdit.Xml public ElementStartTagSyntax StartTag => Children.ElementAtOrDefault(0) as ElementStartTagSyntax; public ElementEndTagSyntax EndTag => Children.LastOrDefault() as ElementEndTagSyntax; } - public class AttributeSyntax : MultiNodeSyntax { - //public override bool IsComplete => base.IsComplete & name.HasValue & equal.HasValue & valueTok.HasValue & valueOpen.HasValue & valueClose.HasValue; + public class AttributeSyntax : MultiNodeSyntax { + public override bool IsComplete => HasName && HasEquals; public AttributeSyntax(Token name) { - AddChild (new SingleTokenSyntax(name)); + AddChild (new XMLSingleTokenSyntax(name)); + } + public string Name => Children.FirstOrDefault() is SingleTokenSyntax sts && + sts.token.GetTokenType() == XmlTokenType.AttributeName ? sts.AsText(): ""; + public bool HasName => HasChilds && Children.First().IsSimilar(XmlTokenType.AttributeName); + public bool HasEquals => HasChilds && + ((HasName && Children.First().NextSiblingIs(XmlTokenType.EqualSign)) || + (!HasName && Children.First().IsSimilar(XmlTokenType.EqualSign))); + + } + public class AttributeValueSyntax : MultiNodeSyntax { + public AttributeValueSyntax(Token openTok) { + AddChild(new XMLSingleTokenSyntax(openTok)); } } } \ No newline at end of file diff --git a/plugins/CEXmlPlugin/src/Parsing/XmlTokenizer.cs b/plugins/CEXmlPlugin/src/Parsing/XmlTokenizer.cs index c7fb88f..6fbf00d 100644 --- a/plugins/CEXmlPlugin/src/Parsing/XmlTokenizer.cs +++ b/plugins/CEXmlPlugin/src/Parsing/XmlTokenizer.cs @@ -195,7 +195,7 @@ namespace CrowEdit.Xml break; } } - + addTok (ref reader, XmlTokenType.Unknown); return Toks.ToArray(); } -- 2.47.3