static bool AdvanceParserUntilConditionOrEol(XmlSpineParser parser, ITextSnapshot snapshot, Func <XmlParser, bool> condition, int maxChars) { if (parser.Position == snapshot.Length) { return(true); } int maxPos = Math.Min(snapshot.Length, Math.Max(parser.Position + maxChars, maxChars)); while (parser.Position < maxPos) { char c = snapshot[parser.Position]; if (c == '\r' || c == '\n') { return(true); } parser.Push(c); if (condition(parser)) { return(true); } } return(false); }
public SnapshotSpan GetSpanOfEnclosing(SnapshotSpan activeSpan) { if (!XmlBackgroundParser.TryGetParser(activeSpan.Snapshot.TextBuffer, out var parser)) { return(codeNavigator.GetSpanOfEnclosing(activeSpan)); } // use last parse if it's up to date, which is most likely will be // else use a spine from the end of the selection and update as needed var lastParse = parser.LastOutput; List <XObject> nodePath; XmlSpineParser spine = null; if (lastParse != null && lastParse.TextSnapshot.Version.VersionNumber == activeSpan.Snapshot.Version.VersionNumber) { var n = lastParse.XDocument.FindAtOrBeforeOffset(activeSpan.Start.Position); nodePath = n.GetPath(); } else { spine = parser.GetSpineParser(activeSpan.Start); nodePath = spine.AdvanceToNodeEndAndGetNodePath(activeSpan.Snapshot); } // this is a little odd because it was ported from MonoDevelop, where it has to maintain its own stack of state // for contract selection. it describes the current semantic selection as a node path, the index of the node in that path // that's selected, and the kind of selection that node has. int selectedNodeIndex = nodePath.Count; SelectionLevel selectionLevel = default; // keep on expanding the selection until we find one that contains the current selection but is a little bigger while (ExpandSelection(nodePath, spine, activeSpan, ref selectedNodeIndex, ref selectionLevel)) { var selectionSpan = GetSelectionSpan(activeSpan.Snapshot, nodePath, ref selectedNodeIndex, ref selectionLevel); if (selectionSpan is TextSpan s && s.Start <= activeSpan.Start && s.End >= activeSpan.End && s.Length > activeSpan.Length) { var selectionSnapshotSpan = new SnapshotSpan(activeSpan.Snapshot, s.Start, s.Length); // if we're in content, the code navigator may be able to make a useful smaller expansion if (selectionLevel == SelectionLevel.Content) { var codeNavigatorSpan = codeNavigator.GetSpanOfEnclosing(activeSpan); if (selectionSnapshotSpan.Contains(codeNavigatorSpan)) { return(codeNavigatorSpan); } } return(selectionSnapshotSpan); } } return(codeNavigator.GetSpanOfEnclosing(activeSpan)); }
public static string GetIncompleteValue(this XmlSpineParser spineAtCaret, ITextSnapshot snapshot) { int caretPosition = spineAtCaret.Position; var node = spineAtCaret.Spine.Peek(); int valueStart; if (node is XText t) { valueStart = t.Span.Start; } else if (node is XElement el && el.IsEnded) { valueStart = el.Span.End; }
public static string GetAttributeOrElementValueToCaret(this XmlSpineParser spineAtCaret, SnapshotPoint caretPosition) { int currentPosition = caretPosition.Position; int lineStart = caretPosition.Snapshot.GetLineFromPosition(currentPosition).Start.Position; int expressionStart = currentPosition - spineAtCaret.CurrentStateLength; if (spineAtCaret.GetAttributeValueDelimiter().HasValue) { expressionStart += 1; } int start = Math.Max(expressionStart, lineStart); var expression = caretPosition.Snapshot.GetText(start, currentPosition - start); return(expression); }
public void TriggerTests(object[] args) { //first arg is the doc string doc = (string)args[0]; XmlTriggerReason reason; char typedChar; //next arg can be typed char or a trigger reason //this would make a nice switch expression w/c#8 if (args[1] is XmlTriggerReason r) { reason = r; typedChar = '\0'; } else if (args[1] is char c) { reason = XmlTriggerReason.TypedChar; typedChar = c; } else { reason = XmlTriggerReason.Invocation; typedChar = '\0'; } //expected trigger will be last unless length is provided, then it's penultimate var expectedTrigger = args[args.Length - 1] is XmlCompletionTrigger ? args[args.Length - 1] : args[args.Length - 2]; //length is optional, but if provided it's always last int expectedLength = args[args.Length - 1] as int? ?? 0; if (typedChar != '\0') { doc += typedChar; } var spine = new XmlSpineParser(new XmlRootState()); spine.Parse(doc); var result = XmlCompletionTriggering.GetTrigger(spine, reason, typedChar); Assert.AreEqual(expectedTrigger, result.kind); Assert.AreEqual(expectedLength, result.length); }
public XmlSpineParser GetSpineParser(SnapshotPoint point) { XmlSpineParser parser = null; var prevParse = LastOutput; if (prevParse != null) { var startPos = Math.Min(point.Position, MaximumCompatiblePosition(prevParse.TextSnapshot, point.Snapshot)); if (startPos > 0) { var obj = prevParse.XDocument.FindAtOrBeforeOffset(startPos); // check for null as there may not be a node before startPos if (obj != null) { var state = StateMachine.TryRecreateState(obj, startPos); if (state != null) { LoggingService.LogDebug($"XML parser recovered {state.Position}/{point.Position} state"); parser = new XmlSpineParser(state, StateMachine); } } } } if (parser == null) { LoggingService.LogDebug($"XML parser failed to recover any state"); parser = new XmlSpineParser(StateMachine); } var end = Math.Min(point.Position, point.Snapshot.Length); for (int i = parser.Position; i < end; i++) { parser.Push(point.Snapshot[i]); } return(parser); }
public static bool IsTagFree(this XmlSpineParser parser) => XmlTagState.IsFree(parser.GetContext());
//FIXME: the length should do a readahead to capture the whole token public static (XmlCompletionTrigger kind, int length) GetTrigger(XmlSpineParser parser, XmlTriggerReason reason, char typedCharacter) { bool isExplicit = reason == XmlTriggerReason.Invocation; bool isTypedChar = reason == XmlTriggerReason.TypedChar; bool isBackspace = reason == XmlTriggerReason.Backspace; if (isTypedChar) { Debug.Assert(typedCharacter != '\0'); } var context = parser.GetContext(); // explicit invocation in element name if (isExplicit && context.CurrentState is XmlNameState && context.CurrentState.Parent is XmlTagState) { int length = context.CurrentStateLength; return(XmlCompletionTrigger.Element, length); } //auto trigger after < in free space if ((isTypedChar || isBackspace) && XmlRootState.MaybeTag(context)) { return(XmlCompletionTrigger.Element, 0); } //auto trigger after typing first char after < or fist char of attribute if (isTypedChar && context.CurrentStateLength == 1 && context.CurrentState is XmlNameState && XmlChar.IsFirstNameChar(typedCharacter)) { if (context.CurrentState.Parent is XmlTagState) { return(XmlCompletionTrigger.Element, 1); } if (context.CurrentState.Parent is XmlAttributeState) { return(XmlCompletionTrigger.Attribute, 1); } } // trigger on explicit invocation after < if (isExplicit && XmlRootState.MaybeTag(context)) { return(XmlCompletionTrigger.Element, 0); } //doctype/cdata completion, explicit trigger after <! or type ! after < if ((isExplicit || typedCharacter == '!') && XmlRootState.MaybeCDataOrCommentOrDocType(context)) { return(XmlCompletionTrigger.DeclarationOrCDataOrComment, 2); } //explicit trigger in existing doctype if (isExplicit && (XmlRootState.MaybeDocType(context) || context.Nodes.Peek() is XDocType)) { int length = context.CurrentState is XmlRootState ? context.CurrentStateLength : context.Position - ((XDocType)context.Nodes.Peek()).Span.Start; return(XmlCompletionTrigger.DocType, length); } //explicit trigger in attribute name if (isExplicit && context.CurrentState is XmlNameState && context.CurrentState.Parent is XmlAttributeState) { return(XmlCompletionTrigger.Attribute, context.CurrentStateLength); } //typed space or explicit trigger in tag if ((isExplicit || typedCharacter == ' ') && XmlTagState.IsFree(context)) { return(XmlCompletionTrigger.Attribute, 0); } //attribute value completion if (XmlAttributeValueState.GetDelimiterChar(context).HasValue) { //auto trigger on quote regardless if (context.CurrentStateLength == 1) { return(XmlCompletionTrigger.AttributeValue, 0); } if (isExplicit) { return(XmlCompletionTrigger.AttributeValue, context.CurrentStateLength - 1); } } //entity completion if (context.CurrentState is XmlTextState || context.CurrentState is XmlAttributeValueState) { if (typedCharacter == '&') { return(XmlCompletionTrigger.Entity, 0); } var text = parser.GetContext().KeywordBuilder; if (isBackspace && text[text.Length - 1] == '&') { return(XmlCompletionTrigger.Entity, 0); } if (isExplicit) { for (int i = 0; i < text.Length; i++) { var c = text[text.Length - i - 1]; if (c == '&') { return(XmlCompletionTrigger.Entity, i); } if (!XmlChar.IsNameChar(c)) { break; } } } } //explicit invocation in free space if (isExplicit && ( context.CurrentState is XmlTextState || XmlRootState.IsFree(context) )) { return(XmlCompletionTrigger.ElementWithBracket, 0); } return(XmlCompletionTrigger.None, 0); }
public static bool IsExpectingAttributeQuote(this XmlSpineParser parser) => XmlAttributeState.IsExpectingQuote(parser.GetContext());
public SnapshotSpan GetSpanOfEnclosing(SnapshotSpan activeSpan) { if (!XmlBackgroundParser.TryGetParser(activeSpan.Snapshot.TextBuffer, out var parser)) { return(xmlNavigator.GetSpanOfEnclosing(activeSpan)); } // use last parse if it's up to date, which is most likely will be // else use a spine from the end of the selection and update as needed var lastParse = parser.LastOutput; List <XObject> nodePath; XmlSpineParser spine = null; if (lastParse != null && lastParse.TextSnapshot.Version.VersionNumber == activeSpan.Snapshot.Version.VersionNumber) { var n = lastParse.XDocument.FindAtOrBeforeOffset(activeSpan.Start.Position); nodePath = n.GetPath(); } else { spine = parser.GetSpineParser(activeSpan.Start); nodePath = spine.AdvanceToNodeEndAndGetNodePath(activeSpan.Snapshot); } if (nodePath.Count > 0) { var leaf = nodePath[nodePath.Count - 1]; if (leaf is XAttribute || leaf is XText) { var syntax = MSBuildElementSyntax.Get(nodePath); if (syntax != null) { int offset; string text; bool isCondition = false; if (leaf is XText t) { offset = t.Span.Start; text = t.Text; } else { var att = (XAttribute)leaf; offset = att.ValueOffset; text = att.Value; isCondition = true; } var expr = isCondition ? ExpressionParser.ParseCondition(text, offset) : ExpressionParser.Parse(text, ExpressionOptions.ItemsMetadataAndLists, offset); var expansion = Expand(activeSpan, expr, out var isText); if (expansion is SnapshotSpan expandedSpan) { if (isText) { var xmlNavigatorSpan = xmlNavigator.GetSpanOfEnclosing(activeSpan); if (expandedSpan.Contains(xmlNavigatorSpan)) { return(xmlNavigatorSpan); } } return(expandedSpan); } } } } return(xmlNavigator.GetSpanOfEnclosing(activeSpan)); }
public static char?GetAttributeValueDelimiter(this XmlSpineParser parser) => XmlAttributeValueState.GetDelimiterChar(parser.GetContext());
bool ExpandSelection(List <XObject> nodePath, XmlSpineParser spine, SnapshotSpan activeSpan, ref int index, ref SelectionLevel level) { if (index - 1 < 0) { return(false); } //if an index is selected, we may need to transition level rather than transitioning index if (index < nodePath.Count) { var current = nodePath[index]; if (current is XElement element) { switch (level) { case SelectionLevel.Self: if (spine != null && !spine.AdvanceUntilClosed(element, activeSpan.Snapshot, 5000)) { return(false); } if (!element.IsSelfClosing) { level = SelectionLevel.OuterElement; return(true); } break; case SelectionLevel.Content: level = SelectionLevel.OuterElement; return(true); case SelectionLevel.Name: level = SelectionLevel.Self; return(true); case SelectionLevel.Attributes: level = SelectionLevel.Self; return(true); } } else if (current is XAttribute) { switch (level) { case SelectionLevel.Name: case SelectionLevel.Content: level = SelectionLevel.Self; return(true); } } else if (level == SelectionLevel.Name) { level = SelectionLevel.Self; return(true); } else if (level == SelectionLevel.Document) { return(false); } } //advance up the node path index--; var newNode = nodePath[index]; //determine the starting selection level for the new node if (newNode is XDocument) { level = SelectionLevel.Document; return(true); } if (spine != null && !spine.AdvanceUntilEnded(newNode, activeSpan.Snapshot, 5000)) { return(false); } bool ContainsSelection(TextSpan span) => activeSpan.Start >= span.Start && activeSpan.End <= span.End; if (ContainsSelection(newNode.Span)) { if ((newNode as INamedXObject)?.NameSpan is TextSpan nr && ContainsSelection(nr)) { level = SelectionLevel.Name; return(true); } if (newNode is XAttribute attribute) { var valRegion = attribute.ValueSpan; if (ContainsSelection(valRegion)) { level = SelectionLevel.Content; return(true); } } if (newNode is XText) { level = SelectionLevel.Content; return(true); } if (newNode is XElement xElement && xElement.Attributes.Count > 1) { if (xElement.GetAttributesSpan() is TextSpan attsSpan && ContainsSelection(attsSpan)) { level = SelectionLevel.Attributes; return(true); } } level = SelectionLevel.Self; return(true); } if (spine != null && !spine.AdvanceUntilClosed(newNode, activeSpan.Snapshot, 5000)) { return(false); } if (newNode is XElement el && el.ClosingTag != null) { if (el.IsSelfClosing) { level = SelectionLevel.Self; return(true); } if (ContainsSelection((TextSpan)el.InnerSpan)) { level = SelectionLevel.Content; return(true); } level = SelectionLevel.OuterElement; return(true); } level = SelectionLevel.Self; return(true); }
/// <summary> /// Gets the node path at the parser position without changing the parser state, ensuring that the deepest node has a complete name. /// </summary> /// <param name="parser">A spine parser. Its state will not be modified.</param> /// <param name="snapshot">The text snapshot corresponding to the parser.</param> public static List <XObject> GetNodePath(this XmlSpineParser parser, ITextSnapshot snapshot) => parser.GetNodePath(new SnapshotTextSource(snapshot));
public static bool IsInText(this XmlSpineParser parser) => parser.GetContext().CurrentState is XmlTextState;
public static MSBuildResolveResult Resolve( XmlSpineParser spineParser, ITextSource textSource, MSBuildDocument context, IFunctionTypeProvider functionTypeProvider, CancellationToken cancellationToken = default) { int offset = spineParser.Position; var nodePath = spineParser.AdvanceToNodeEndAndGetNodePath(textSource); nodePath.ConnectParents(); //need to look up element by walking how the path, since at each level, if the parent has special children, //then that gives us information to identify the type of its children MSBuildElementSyntax languageElement = null; MSBuildAttributeSyntax languageAttribute = null; XElement el = null; XAttribute att = null; foreach (var node in nodePath) { if (node is XAttribute xatt && xatt.Name.Prefix == null) { att = xatt; languageAttribute = languageElement?.GetAttribute(att.Name.Name); break; } //if children of parent is known to be arbitrary data, don't go into it if (languageElement != null && languageElement.ValueKind == MSBuildValueKind.Data) { break; } //code completion is forgiving, all we care about best guess resolve for deepest child if (node is XElement xel && xel.Name.Prefix == null) { el = xel; languageElement = MSBuildElementSyntax.Get(el.Name.Name, languageElement); if (languageElement != null) { continue; } } if (node is XText) { continue; } if (node is XClosingTag ct && ct == el.ClosingTag) { continue; } languageElement = null; } if (languageElement == null) { return(null); } var rr = new MSBuildResolveResult { ElementSyntax = languageElement, AttributeSyntax = languageAttribute, Element = el, Attribute = att }; var rv = new MSBuildResolveVisitor(offset, rr, functionTypeProvider); try { rv.Run(el, languageElement, textSource, context, token: cancellationToken); } catch (OperationCanceledException) when(cancellationToken.IsCancellationRequested) { // callers always have to handle the possibility this returns null // so this means callers don't need to handle cancellation exceptions explciitly return(null); } return(rr); }
internal static bool MaybeComment(this XmlSpineParser parser) => XmlRootState.MaybeComment(parser.GetContext());
/// <summary> /// Gets the XML name at the parser's position. /// </summary> /// <param name="spine">A spine parser. It will not be modified.</param> /// <param name="snapshot">The text snapshot corresponding to the parser.</param> /// <returns></returns> public static XName GetCompleteName(this XmlSpineParser parser, ITextSnapshot snapshot, int maximumReadahead = DEFAULT_READAHEAD_LIMIT) => parser.GetCompleteName(new SnapshotTextSource(snapshot), maximumReadahead);
public static bool MaybeTag(this XmlSpineParser parser) => XmlRootState.MaybeTag(parser.GetContext());
public static bool IsRootFree(this XmlSpineParser parser) => XmlRootState.IsFree(parser.GetContext());
internal static bool MaybeCDataOrCommentOrDocType(this XmlSpineParser parser) => XmlRootState.IsNotFree(parser.GetContext());
/// <summary> /// Advances the parser to end the node at the current position and gets that node's path. /// </summary> /// <param name="parser">A spine parser. Its state will be modified.</param> /// <param name="snapshot">The text snapshot corresponding to the parser.</param> /// <returns></returns> public static List <XObject> AdvanceToNodeEndAndGetNodePath(this XmlSpineParser parser, ITextSnapshot snapshot, int maximumReadahead = DEFAULT_READAHEAD_LIMIT) => parser.AdvanceToNodeEndAndGetNodePath(new SnapshotTextSource(snapshot), maximumReadahead);
/// <summary> /// Advances the parser until the specified object is ended. /// </summary> /// <param name="parser">A spine parser. Its state will be modified.</param> /// <param name="ob">The object to complete</param> /// <param name="snapshot"></param> /// <param name="snapshot">The text snapshot corresponding to the parser.</param> /// <param name="maximumReadahead">Maximum number of characters to advance before giving up.</param> /// <returns>Whether the object was successfully completed</returns> public static bool AdvanceUntilEnded(this XmlSpineParser parser, XObject ob, ITextSnapshot snapshot, int maximumReadahead = DEFAULT_READAHEAD_LIMIT) => parser.AdvanceUntilEnded(ob, new SnapshotTextSource(snapshot), maximumReadahead);
public static bool IsInAttributeValue(this XmlSpineParser parser) => parser.GetContext().CurrentState is XmlAttributeValueState;
public static bool IsPossibleExpressionCompletionContext(XmlSpineParser parser) => parser.IsInAttributeValue() || parser.IsInText() || parser.IsRootFree();
internal static bool MaybeDocType(this XmlSpineParser parser) => XmlRootState.MaybeDocType(parser.GetContext());