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 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); }
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)); }
/// <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);