//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); }
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); }
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; }
public StructureTagger(ITextBuffer buffer, StructureTaggerProvider provider) { this.buffer = buffer; this.provider = provider; parser = XmlBackgroundParser.GetParser(buffer); }
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)); }
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; }
public XmlSyntaxValidationTagger(ITextBuffer buffer, JoinableTaskContext joinableTaskContext) { parser = XmlBackgroundParser.GetParser(buffer); parser.ParseCompleted += ParseCompleted; this.joinableTaskContext = joinableTaskContext; }
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); }