//FIXME: make this smarter, it's very simple right now
        public virtual int?GetDesiredIndentation(ITextSnapshotLine line)
        {
            if (!XmlBackgroundParser.TryGetParser(textView.TextBuffer, out var parser))
            {
                return(null);
            }

            var indentSize = options.GetIndentSize();
            var tabSize    = options.GetTabSize();

            //calculate the delta between the previous line's expected and actual indent
            int?previousIndentDelta = null;

            // find a preceding non-empty line so we don't get confused by blank lines with virtual indents
            var previousLine = GetPreviousNonEmptyLine(line);

            if (previousLine != null)
            {
                var previousExpectedIndent = GetLineExpectedIndent(previousLine, parser, indentSize);
                var previousActualIndent   = GetLineActualIndent(previousLine, tabSize);
                previousIndentDelta = previousActualIndent - previousExpectedIndent;
            }

            var indent = GetLineExpectedIndent(line, parser, indentSize);

            // if the previous non-blank line was in the same state and had a different indent than
            // expected, the user has manually corrected it, so re-apply the same delta to this line.
            if (previousIndentDelta.HasValue)
            {
                indent = Math.Max(0, indent + previousIndentDelta.Value);
            }

            return(indent);
        }
예제 #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));
        }
        bool ExecuteCommandCore(EditorCommandArgs args, CommandExecutionContext context, Operation operation)
        {
            ITextView   textView   = args.TextView;
            ITextBuffer textBuffer = args.SubjectBuffer;

            if (!XmlBackgroundParser.TryGetParser(textBuffer, out var parser))
            {
                return(false);
            }

            var xmlParseResult    = parser.GetOrProcessAsync(textBuffer.CurrentSnapshot, default).Result;
            var xmlDocumentSyntax = xmlParseResult.XDocument;

            if (xmlDocumentSyntax == null)
            {
                return(false);
            }

            string description = operation.ToString();

            var editorOperations     = editorOperationsFactoryService.GetEditorOperations(textView);
            var multiSelectionBroker = textView.GetMultiSelectionBroker();
            var selectedSpans        = multiSelectionBroker.AllSelections.Select(selection => selection.Extent);

            using (context.OperationContext.AddScope(allowCancellation: false, description: description)) {
                ITextUndoHistory undoHistory = undoHistoryRegistry.RegisterHistory(textBuffer);

                using (ITextUndoTransaction undoTransaction = undoHistory.CreateTransaction(description)) {
                    switch (operation)
                    {
                    case Operation.Comment:
                        CommentSelection(textBuffer, selectedSpans, xmlDocumentSyntax, editorOperations, multiSelectionBroker);
                        break;

                    case Operation.Uncomment:
                        UncommentSelection(textBuffer, selectedSpans, xmlDocumentSyntax);
                        break;

                    case Operation.Toggle:
                        ToggleCommentSelection(textBuffer, selectedSpans, xmlDocumentSyntax, editorOperations, multiSelectionBroker);
                        break;
                    }

                    undoTransaction.Complete();
                }
            }

            return(true);
        }
        protected virtual int GetLineExpectedIndent(ITextSnapshotLine line, XmlBackgroundParser parser, int indentSize)
        {
            //create a lightweight tree parser, which will actually close nodes
            var spineParser = parser.GetSpineParser(line.Start);
            var startNodes  = spineParser.Spine.ToList();
            var startState  = spineParser.CurrentState;

            startNodes.Reverse();

            //advance the parser to the end of the line
            for (int i = line.Start.Position; i < line.End.Position; i++)
            {
                spineParser.Push(line.Snapshot[i]);
            }

            var endNodes = spineParser.Spine.ToList();

            endNodes.Reverse();

            //count the number of elements in the stack at the start of the line
            //which were not closed by the end of the line
            int depth = 0;

            //first node is the xdocument, skip it
            for (int i = 1; i < startNodes.Count; i++)
            {
                if (i == endNodes.Count || !(startNodes[i] is XElement) || startNodes[i] != endNodes[i])
                {
                    break;
                }
                depth++;
            }

            //if inside a tag state, indent a level further
            while (startState != null)
            {
                if (startState is XmlTagState)
                {
                    depth++;
                }
                startState = startState.Parent;
            }

            int indent = indentSize * depth;

            return(indent);
        }
예제 #5
0
        GetHighlightsAsync(SnapshotPoint caretLocation, CancellationToken token)
        {
            if (!XmlBackgroundParser.TryGetParser(TextView.TextBuffer, out var parser))
            {
                return(Empty);
            }

            var spine = parser.GetSpineParser(caretLocation);

            if (!(spine.CurrentState is XmlNameState) || !(spine.CurrentState.Parent is XmlTagState || spine.CurrentState.Parent is XmlClosingTagState))
            {
                return(Empty);
            }

            var parseResult = await parser.GetOrProcessAsync(caretLocation.Snapshot, token).ConfigureAwait(false);

            var node = parseResult.XDocument.RootElement.FindAtOffset(caretLocation.Position);

            if (node is XElement element)
            {
                if (element.ClosingTag is XClosingTag closingTag && closingTag.IsNamed)
                {
                    return(CreateResult(element.NameSpan, closingTag.NameSpan));
                }
            }
            else if (node is XClosingTag closingTag)
            {
                var matchingElement = parseResult.XDocument.AllDescendentNodes
                                      .OfType <XElement> ()
                                      .FirstOrDefault(t => t.ClosingTag == closingTag);
                if (matchingElement != null && matchingElement.IsNamed)
                {
                    return(CreateResult(closingTag.NameSpan, matchingElement.NameSpan));
                }
            }
            return(Empty);

            (SnapshotSpan sourceSpan, ImmutableArray <(ITextMarkerTag kind, SnapshotSpan location)> highlights)
            CreateResult(TextSpan source, TextSpan target)
            => (
                new SnapshotSpan(caretLocation.Snapshot, source.Start, source.Length),
                ImmutableArray <(ITextMarkerTag kind, SnapshotSpan location)> .Empty
                .Add((
                         MatchingTagHighlightTag.Instance,
                         new SnapshotSpan(caretLocation.Snapshot, target.Start, target.Length)))
                );
        }
        public MSBuildBackgroundParser(
            ITextBuffer buffer,
            IRuntimeInformation runtimeInformation,
            MSBuildSchemaProvider schemaProvider,
            ITaskMetadataBuilder taskMetadataBuilder)
        {
            RuntimeInformation  = runtimeInformation ?? throw new ArgumentNullException(nameof(runtimeInformation));
            SchemaProvider      = schemaProvider ?? throw new ArgumentNullException(nameof(schemaProvider));
            TaskMetadataBuilder = taskMetadataBuilder ?? throw new ArgumentNullException(nameof(taskMetadataBuilder));

            XmlParser = XmlBackgroundParser.GetParser(buffer);
            XmlParser.ParseCompleted += XmlParseCompleted;

            if (buffer.Properties.TryGetProperty <ITextDocument> (typeof(ITextDocument), out var doc))
            {
                filepath = doc.FilePath;
                doc.FileActionOccurred += OnFileAction;
            }
        }
        public void ExecuteCommand(TypeCharCommandArgs args, Action nextCommandHandler, CommandExecutionContext executionContext)
        {
            var openingPoint = args.TextView.Caret.Position.BufferPosition;

            // short circuit on the easy checks
            ITextSnapshot snapshot = openingPoint.Snapshot;

            if (!IsQuoteChar(args.TypedChar) ||
                !IsBraceCompletionEnabled(args.TextView) ||
                !args.TextView.Selection.IsEmpty ||
                !XmlBackgroundParser.TryGetParser(snapshot.TextBuffer, out var parser))
            {
                nextCommandHandler();
                return;
            }

            // overtype
            if (snapshot.Length > openingPoint.Position && snapshot[openingPoint] == args.TypedChar)
            {
                var  spine      = parser.GetSpineParser(openingPoint);
                bool isOverType =
                    // in a quoted value typing its quote char over the end quote
                    spine.GetAttributeValueDelimiter() == args.TypedChar
                    // typing a quote after after the attribute's equals sign
                    || spine.IsExpectingAttributeQuote();
                if (isOverType)
                {
                    using (var edit = args.SubjectBuffer.CreateEdit()) {
                        edit.Replace(openingPoint.Position, 1, args.TypedChar.ToString());
                        edit.Apply();
                    }
                    args.TextView.Caret.MoveTo(new SnapshotPoint(args.SubjectBuffer.CurrentSnapshot, openingPoint.Position + 1));
                    return;
                }
            }

            // auto insertion of matching quote
            if (snapshot.Length == openingPoint.Position || !IsQuoteChar(snapshot[openingPoint]))
            {
                var spine = parser.GetSpineParser(openingPoint);
                // if we're in a state where we expect an attribute value quote
                // and we're able to walk the parser to the end of of the line without completing the attribute
                // and without ending in an incomplete attribute value, then it's reasonable to auto insert a matching quote
                if (spine.IsExpectingAttributeQuote())
                {
                    var att = (XAttribute)spine.Spine.Peek();
                    if (AdvanceParserUntilConditionOrEol(spine, snapshot, p => att.Value != null, 1000) && att.Value == null && !(spine.CurrentState is XmlAttributeValueState))
                    {
                        using (var edit = args.SubjectBuffer.CreateEdit()) {
                            //TODO create an undo transition between the two chars
                            edit.Insert(openingPoint.Position, new string (args.TypedChar, 2));
                            edit.Apply();
                        }
                        args.TextView.Caret.MoveTo(new SnapshotPoint(args.SubjectBuffer.CurrentSnapshot, openingPoint.Position + 1));
                        return;
                    }
                }
            }

            nextCommandHandler();
            return;
        }
예제 #8
0
 public StructureTagger(ITextBuffer buffer, StructureTaggerProvider provider)
 {
     this.buffer   = buffer;
     this.provider = provider;
     parser        = XmlBackgroundParser.GetParser(buffer);
 }
예제 #9
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));
        }
예제 #10
0
        void InsertCloseTag(TypeCharCommandArgs args, CommandExecutionContext executionContext)
        {
            if (!XmlBackgroundParser.TryGetParser(args.SubjectBuffer, out var parser))
            {
                return;
            }

            var multiSelectionBroker = args.TextView.GetMultiSelectionBroker();

            if (multiSelectionBroker.HasMultipleSelections)
            {
                return;
            }

            var position    = args.TextView.Caret.Position.BufferPosition;
            var spineParser = parser.GetSpineParser(position);
            var el          = spineParser.Spine.Peek() as XElement;

            if (el == null || !el.IsEnded || !el.IsNamed || el.Span.End != position.Position)
            {
                return;
            }

            el = (XElement)spineParser.Spine.Peek();
            spineParser.AdvanceUntilClosed(el, position.Snapshot);

            // also check for orphaned closing tags in this element's parent
            // this is not as accurate as the tree parse we did above as it uses
            // the last parse result, which might be a little stale
            var lastParseResult = parser.LastOutput;

            if (lastParseResult == null)
            {
                return;
            }
            if (el.Parent != null)
            {
                if (lastParseResult.XDocument.FindAtOffset(el.Parent.Span.Start + 1) is XContainer parent)
                {
                    foreach (var n in parent.Nodes)
                    {
                        if (n is XClosingTag)
                        {
                            return;
                        }
                    }
                }
            }

            // When the completion handler triggers a new completion session immediately after
            // committing, it created a tracking ApplicableToSpan. When we make our edit, the
            // tracking spand expands to include it, so when the new completion session is
            // committed, it erases our edit.
            //
            // Since we cannot update the ApplicableToSpan at this point, we explicitly dismiss and
            // re-trigger completion instead.

            var completionSession = CompletionBroker.GetSession(args.TextView);

            if (completionSession != null)
            {
                completionSession.Dismiss();
            }

            var bufferEdit = args.SubjectBuffer.CreateEdit();

            bufferEdit.Insert(position, $"</{el.Name.FullName}>");
            bufferEdit.Apply();
            args.TextView.Caret.MoveTo(new SnapshotPoint(args.SubjectBuffer.CurrentSnapshot, position));

            if (completionSession != null)
            {
                var trigger = new CompletionTrigger(
                    CompletionTriggerReason.Insertion, args.SubjectBuffer.CurrentSnapshot, '>');
                var location = args.TextView.Caret.Position.BufferPosition;
                var token    = executionContext.OperationContext.UserCancellationToken;

                completionSession = CompletionBroker.GetSession(args.TextView);
                if (completionSession == null)
                {
                    completionSession = CompletionBroker.TriggerCompletion(args.TextView, trigger, location, token);
                }

                completionSession?.OpenOrUpdate(trigger, location, token);
            }

            return;
        }
예제 #11
0
 public XmlSyntaxValidationTagger(ITextBuffer buffer, JoinableTaskContext joinableTaskContext)
 {
     parser = XmlBackgroundParser.GetParser(buffer);
     parser.ParseCompleted   += ParseCompleted;
     this.joinableTaskContext = joinableTaskContext;
 }
예제 #12
0
        protected virtual int GetLineExpectedIndent(ITextSnapshotLine line, XmlBackgroundParser parser, int indentSize)
        {
            var snapshot = line.Snapshot;

            //create a lightweight tree parser, which will actually close nodes
            var spineParser = parser.GetSpineParser(line.Start);
            var startNodes  = spineParser.Spine.ToList();
            var startState  = spineParser.CurrentState;

            startNodes.Reverse();

            //advance the parser to the end of the line
            for (int i = line.Start.Position; i < line.End.Position; i++)
            {
                spineParser.Push(snapshot[i]);
            }

            var endNodes = spineParser.Spine.ToList();

            endNodes.Reverse();

            //count the number of elements in the stack at the start of the line
            //which were not closed by the end of the line
            int depth = 0;

            //special case if the line starts with something else than a closing tag,
            //treat it as content and don't take the remaining closing tags on the
            //current line into account
            bool startsWithClosingTag       = false;
            var  firstNonWhitespacePosition = line.GetFirstNonWhitespacePosition();

            if (firstNonWhitespacePosition is int first &&
                line.End > first + 1 &&
                snapshot[first] == '<' &&
                snapshot[first + 1] == '/')
            {
                startsWithClosingTag = true;
            }

            //first node is the xdocument, skip it
            for (int i = 1; i < startNodes.Count; i++)
            {
                if (!(startNodes[i] is XElement))
                {
                    continue;
                }

                if (startsWithClosingTag)
                {
                    if (i == endNodes.Count || startNodes[i] != endNodes[i])
                    {
                        break;
                    }
                }

                depth++;
            }

            //if inside a tag state, indent a level further
            while (startState != null)
            {
                if (startState is XmlTagState)
                {
                    depth++;
                }
                startState = startState.Parent;
            }

            int indent = indentSize * depth;

            return(indent);
        }