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);
        }
Exemple #2
0
        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);
        }
Exemple #5
0
        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);
        }
Exemple #6
0
        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());
Exemple #10
0
        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());
Exemple #12
0
        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;
Exemple #15
0
        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;
Exemple #24
0
 public static bool IsPossibleExpressionCompletionContext(XmlSpineParser parser)
 => parser.IsInAttributeValue() || parser.IsInText() || parser.IsRootFree();
 internal static bool MaybeDocType(this XmlSpineParser parser) => XmlRootState.MaybeDocType(parser.GetContext());