// Internal for testing internal static bool IsAcceptableInsertion(SourceChange change) { if (!change.IsInsert) { return(false); } if (ContainsInvalidContent(change)) { return(false); } return(true); }
public override IReadOnlyList <RazorCompletionItem> GetCompletionItems(RazorSyntaxTree syntaxTree, TagHelperDocumentContext tagHelperDocumentContext, SourceSpan location) { if (syntaxTree is null) { throw new ArgumentNullException(nameof(syntaxTree)); } if (tagHelperDocumentContext is null) { throw new ArgumentNullException(nameof(tagHelperDocumentContext)); } if (!FileKinds.IsComponent(syntaxTree.Options.FileKind)) { // Directive attributes are only supported in components return(Array.Empty <RazorCompletionItem>()); } var change = new SourceChange(location, string.Empty); var owner = syntaxTree.Root.LocateOwner(change); if (owner == null) { return(Array.Empty <RazorCompletionItem>()); } if (!TryGetAttributeInfo(owner, out var attributeName, out var attributeNameLocation, out _, out _)) { // Either we're not in an attribute or the attribute is so malformed that we can't provide proper completions. return(Array.Empty <RazorCompletionItem>()); } if (!attributeNameLocation.IntersectsWith(location.AbsoluteIndex)) { // We're trying to retrieve completions on a portion of the name that is not supported (such as a parameter). return(Array.Empty <RazorCompletionItem>()); } if (!TryGetElementInfo(owner.Parent.Parent, out var containingTagName, out var attributes)) { // This should never be the case, it means that we're operating on an attribute that doesn't have a tag. return(Array.Empty <RazorCompletionItem>()); } // At this point we've determined that completions have been requested for the name portion of the selected attribute. var completionItems = GetAttributeCompletions(attributeName, containingTagName, attributes, tagHelperDocumentContext); return(completionItems); }
public override IEnumerable <IModelChange> Invert() { for (int i = NestedChanges.Count - 1; i >= 0; i--) { foreach (var inverted in NestedChanges[i].Invert()) { yield return(inverted); } } foreach (var invertedSource in SourceChange.Invert()) { yield return(invertedSource); } }
public override IReadOnlyList <RazorCompletionItem> GetCompletionItems(RazorSyntaxTree syntaxTree, TagHelperDocumentContext tagHelperDocumentContext, SourceSpan location) { if (syntaxTree is null) { throw new ArgumentNullException(nameof(syntaxTree)); } if (tagHelperDocumentContext is null) { throw new ArgumentNullException(nameof(tagHelperDocumentContext)); } var change = new SourceChange(location, string.Empty); var owner = syntaxTree.Root.LocateOwner(change); if (owner == null) { Debug.Fail("Owner should never be null."); return(Array.Empty <RazorCompletionItem>()); } var parent = owner.Parent; if (_htmlFactsService.TryGetElementInfo(parent, out var containingTagNameToken, out var attributes) && containingTagNameToken.Span.IntersectsWith(location.AbsoluteIndex)) { var stringifiedAttributes = _tagHelperFactsService.StringifyAttributes(attributes); var elementCompletions = GetElementCompletions(parent, containingTagNameToken.Content, stringifiedAttributes, tagHelperDocumentContext); return(elementCompletions); } if (_htmlFactsService.TryGetAttributeInfo( parent, out containingTagNameToken, out var prefixLocation, out var selectedAttributeName, out var selectedAttributeNameLocation, out attributes) && (selectedAttributeName == null || selectedAttributeNameLocation?.IntersectsWith(location.AbsoluteIndex) == true || (prefixLocation?.IntersectsWith(location.AbsoluteIndex) ?? false))) { var stringifiedAttributes = _tagHelperFactsService.StringifyAttributes(attributes); var attributeCompletions = GetAttributeCompletions(parent, containingTagNameToken.Content, selectedAttributeName, stringifiedAttributes, tagHelperDocumentContext); return(attributeCompletions); } // Invalid location for TagHelper completions. return(Array.Empty <RazorCompletionItem>()); }
// Internal for testing internal static bool IsAcceptableDeletion(SyntaxNode target, SourceChange change) { if (!change.IsDelete) { return(false); } if (ModifiesInvalidContent(target, change)) { return(false); } return(true); }
public static SyntaxNode LocateOwner(this SyntaxNode node, SourceChange change) { if (node == null) { throw new ArgumentNullException(nameof(node)); } if (change.Span.AbsoluteIndex < node.Position) { // Early escape for cases where changes overlap multiple spans // In those cases, the span will return false, and we don't want to search the whole tree // So if the current span starts after the change, we know we've searched as far as we need to return(null); } if (IsSpanKind(node)) { var editHandler = node.GetSpanContext()?.EditHandler ?? SpanEditHandler.CreateDefault(); return(editHandler.OwnsChange(node, change) ? node : null); } SyntaxNode owner = null; IEnumerable <SyntaxNode> children = null; if (node is MarkupStartTagSyntax startTag) { children = startTag.Children; } else if (node is MarkupEndTagSyntax endTag) { children = endTag.Children; } else { children = node.ChildNodes(); } foreach (var child in children) { owner = LocateOwner(child, change); if (owner != null) { break; } } return(owner); }
private void TextBuffer_OnChanged(object sender, TextContentChangedEventArgs args) { _dispatcher.AssertForegroundThread(); if (args.Changes.Count > 0) { // Idle timers are used to track provisional changes. Provisional changes only last for a single text change. After that normal // partial parsing rules apply (stop the timer). StopIdleTimer(); } var snapshot = args.After; if (!args.TextChangeOccurred(out var changeInformation)) { // Ensure we get a parse for latest snapshot. QueueChange(null, snapshot); return; } var change = new SourceChange(changeInformation.firstChange.OldPosition, changeInformation.oldText.Length, changeInformation.newText); var result = PartialParseResultInternal.Rejected; RazorSyntaxTree partialParseSyntaxTree = null; using (_parser.SynchronizeMainThreadState()) { // Check if we can partial-parse if (_partialParser != null && _parser.IsIdle) { (result, partialParseSyntaxTree) = _partialParser.Parse(change); } } // If partial parsing failed or there were outstanding parser tasks, start a full reparse if ((result & PartialParseResultInternal.Rejected) == PartialParseResultInternal.Rejected) { QueueChange(change, snapshot); } else { TryUpdateLatestParsedSyntaxTreeSnapshot(partialParseSyntaxTree, snapshot); } if ((result & PartialParseResultInternal.Provisional) == PartialParseResultInternal.Provisional) { StartIdleTimer(); } }
public override IReadOnlyList <RazorCompletionItem> GetCompletionItems(RazorCompletionContext context, SourceSpan location) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (context.TagHelperDocumentContext is null) { throw new ArgumentNullException(nameof(context.TagHelperDocumentContext)); } if (!FileKinds.IsComponent(context.SyntaxTree.Options.FileKind)) { // Directive attribute parameters are only supported in components return(Array.Empty <RazorCompletionItem>()); } var change = new SourceChange(location, string.Empty); var owner = context.SyntaxTree.Root.LocateOwner(change); if (owner is null) { return(Array.Empty <RazorCompletionItem>()); } if (!TryGetAttributeInfo(owner, out _, out var attributeName, out _, out var parameterName, out var parameterNameLocation)) { // Either we're not in an attribute or the attribute is so malformed that we can't provide proper completions. return(Array.Empty <RazorCompletionItem>()); } if (!parameterNameLocation.IntersectsWith(location.AbsoluteIndex)) { // We're trying to retrieve completions on a portion of the name that is not supported (such as the name, i.e., |@bind|:format). return(Array.Empty <RazorCompletionItem>()); } if (!TryGetElementInfo(owner.Parent.Parent, out var containingTagName, out var attributes)) { // This should never be the case, it means that we're operating on an attribute that doesn't have a tag. return(Array.Empty <RazorCompletionItem>()); } var completions = GetAttributeParameterCompletions(attributeName, parameterName, containingTagName, attributes, context.TagHelperDocumentContext); return(completions); }
Document GetChangedDocument(Document originalDocument, SourceChangeList changes) { string originalString = originalDocument.Text.Source; StringBuilder sb = new StringBuilder(); int lastPos = 0; for (int i = 0, n = changes.Count; i < n; i++) { SourceChange sc = changes[i]; sb.Append(originalString, lastPos, sc.SourceContext.StartPos - lastPos); sb.Append(sc.ChangedText); lastPos = sc.SourceContext.EndPos; } sb.Append(originalString, lastPos, originalString.Length - lastPos); return(this.compiler.CreateDocument(this.compilationUnit.SourceContext.Document.Name, 1, sb.ToString())); }
private PartialParseResult HandleInsertion(Span target, char previousChar, SourceChange change) { // What are we inserting after? if (previousChar == '.') { return(HandleInsertionAfterDot(target, change)); } else if (ParserHelpers.IsIdentifierPart(previousChar) || previousChar == ')' || previousChar == ']') { return(HandleInsertionAfterIdPart(target, change)); } else { return(PartialParseResult.Rejected); } }
private PartialParseResult HandleDeletion(Span target, char previousChar, SourceChange change) { // What's left after deleting? if (previousChar == '.') { return(TryAcceptChange(target, change, PartialParseResult.Accepted | PartialParseResult.Provisional)); } else if (ParserHelpers.IsIdentifierPart(previousChar)) { return(TryAcceptChange(target, change)); } else { return(PartialParseResult.Rejected); } }
public virtual EditResult ApplyChange(SyntaxNode target, SourceChange change, bool force) { var result = PartialParseResultInternal.Accepted; if (!force) { result = CanAcceptChange(target, change); } // If the change is accepted then apply the change if ((result & PartialParseResultInternal.Accepted) == PartialParseResultInternal.Accepted) { return(new EditResult(result, UpdateSpan(target, change))); } return(new EditResult(result, target)); }
[InlineData(9, 5, " Space")] // "Some Name Space" public void CanAcceptChange_RejectsWhitespaceChanges(int index, int length, string newText) { // Arrange var factory = new SpanFactory(); var directiveTokenHandler = new TestDirectiveTokenEditHandler(); var target = factory.Span(SpanKindInternal.Code, "Some Namespace", markup: false) .With(directiveTokenHandler) .Accepts(AcceptedCharactersInternal.NonWhiteSpace); var sourceChange = new SourceChange(index, length, newText); // Act var result = directiveTokenHandler.CanAcceptChange(target, sourceChange); // Assert Assert.Equal(PartialParseResult.Rejected, result); }
public override bool TryResolveInsertion(Position position, FormattingContext context, out TextEdit edit, out InsertTextFormat format) { if (position is null) { throw new ArgumentNullException(nameof(position)); } if (context is null) { throw new ArgumentNullException(nameof(context)); } var syntaxTree = context.CodeDocument.GetSyntaxTree(); var absoluteIndex = position.GetAbsoluteIndex(context.SourceText); var change = new SourceChange(absoluteIndex, 0, string.Empty); var owner = syntaxTree.Root.LocateOwner(change); if (!IsAtEnterRuleLocation(owner)) { format = default; edit = default; return(false); } // We're currently at: // <someTag> // |</someTag> context.SourceText.GetLineAndOffset(owner.SpanStart, out var lineNumber, out _); var existingIndentation = context.Indentations[lineNumber].ExistingIndentation; var existingIndentationString = context.GetIndentationString(existingIndentation); var increasedIndentationString = context.GetIndentationLevelString(indentationLevel: 1); var innerIndentationString = string.Concat(increasedIndentationString, existingIndentationString); // We mark start position at the beginning of the line in order to remove any pre-existing whitespace. var startPosition = new Position(position.Line, 0); format = InsertTextFormat.Snippet; edit = new TextEdit() { NewText = $"{innerIndentationString}$0{Environment.NewLine}{existingIndentationString}", Range = new Range(startPosition, position) }; return(true); }
protected virtual SpanBuilder UpdateSpan(Span target, SourceChange change) { var newContent = change.GetEditedContent(target); var newSpan = new SpanBuilder(target); newSpan.ClearTokens(); foreach (var token in Tokenizer(newContent)) { newSpan.Accept(token); } if (target.Next != null) { var newEnd = SourceLocationTracker.CalculateNewLocation(target.Start, newContent); target.Next.ChangeStart(newEnd); } return(newSpan); }
[InlineData(9, 5, " Space")] // "Some Name Space" public void CanAcceptChange_RejectsWhitespaceChanges(int index, int length, string newText) { // Arrange var directiveTokenHandler = new TestDirectiveTokenEditHandler(); directiveTokenHandler.AcceptedCharacters = AcceptedCharactersInternal.NonWhitespace; var target = GetSyntaxNode(directiveTokenHandler, "Some Namespace"); var sourceChange = new SourceChange(index, length, newText); // Act var result = directiveTokenHandler.CanAcceptChange(target, sourceChange); // Assert Assert.Equal(PartialParseResultInternal.Rejected, result); }
/// <summary> /// Make generic change to running assembly using EnC. /// </summary> /// <param name="change">Description of change.</param> public void MakeChange(SourceChange change) { switch (change.MemberKind) { case SourceChangeMember.Method: if (change.MemberAction == SourceChangeAction.BodyChanged) { ChangeMethod((IMethod)change.OldEntity); } else if (change.MemberAction == SourceChangeAction.Created && change.NewEntity.IsPrivate && !change.NewEntity.IsVirtual) { AddMethod((IMethod)change.NewEntity); } else { throw new TranslatingException("This kind of change is not supported by this version of EnC."); } break; case SourceChangeMember.Property: if (change.MemberAction == SourceChangeAction.BodyChanged) { ChangeProperty((IProperty)change.OldEntity, ((BodyChange)change).isGetter); } else { throw new TranslatingException("This kind of change is not supported by this version of EnC."); } break; case SourceChangeMember.Field: if (change.MemberAction == SourceChangeAction.Created) { addField((IField)change.NewEntity); } else { throw new TranslatingException("This kind of change is not supported by this version of EnC."); } break; default: throw new TranslatingException("This kind of change is not supported by this version of EnC."); } }
protected override PartialParseResultInternal CanAcceptChange(SyntaxNode target, SourceChange change) { if (AcceptedCharacters == AcceptedCharactersInternal.NonWhitespace) { var originalText = change.GetOriginalText(target); var editedContent = change.GetEditedContent(target); if (!ContainsWhitespace(originalText) && !ContainsWhitespace(editedContent)) { // Did not modify whitespace, directive format should be the same. // Return provisional so extensible IR/code gen pieces can see the full directive text // once the user stops editing the document. return(PartialParseResultInternal.Accepted | PartialParseResultInternal.Provisional); } } return(PartialParseResultInternal.Rejected); }
public void GetOrigninalText_SpanIsOwner_ReturnsContent_ZeroLengthSpan() { // Arrange var builder = SyntaxListBuilder <SyntaxToken> .Create(); builder.Add(SyntaxFactory.Token(SyntaxKind.Marker, "Hello, ")); builder.Add(SyntaxFactory.Token(SyntaxKind.Marker, "World")); var node = SyntaxFactory.MarkupTextLiteral(builder.ToList()).CreateRed(null, 13); var change = new SourceChange(15, 0, "heyo"); // Act var result = change.GetOriginalText(node); // Act Assert.Equal(string.Empty, result); }
public void GetOffSet_SpanIsOwner_ReturnsOffset() { // Arrange var builder = SyntaxListBuilder <SyntaxToken> .Create(); builder.Add(SyntaxFactory.Token(SyntaxKind.Marker, "Hello, ")); builder.Add(SyntaxFactory.Token(SyntaxKind.Marker, "World")); var node = SyntaxFactory.MarkupTextLiteral(builder.ToList()).CreateRed(null, 13); var change = new SourceChange(15, 2, "heyo"); // Act var result = change.GetOffset(node); // Act Assert.Equal(2, result); }
public void GetEditedContent_SyntaxNode_ReturnsNewContent() { // Arrange var builder = SyntaxListBuilder <SyntaxToken> .Create(); builder.Add(SyntaxFactory.Token(SyntaxKind.Marker, "Hello, ")); builder.Add(SyntaxFactory.Token(SyntaxKind.Marker, "World")); var node = SyntaxFactory.MarkupTextLiteral(builder.ToList()).CreateRed(); var change = new SourceChange(2, 2, "heyo"); // Act var result = change.GetEditedContent(node); // Act Assert.Equal("Heheyoo, World", result); }
public override IReadOnlyList <RazorCompletionItem> GetCompletionItems(RazorSyntaxTree syntaxTree, TagHelperDocumentContext tagHelperDocumentContext, SourceSpan location) { if (!FileKinds.IsComponent(syntaxTree.Options.FileKind)) { // Directive attributes are only supported in components return(Array.Empty <RazorCompletionItem>()); } var change = new SourceChange(location, string.Empty); var owner = syntaxTree.Root.LocateOwner(change); if (owner == null) { return(Array.Empty <RazorCompletionItem>()); } var attribute = owner.Parent; if (attribute is MarkupMiscAttributeContentSyntax) { // This represents a tag when there's no attribute content <InputText | />. return(Completions); } if (!TryGetAttributeInfo(owner, out var prefixLocation, out var attributeName, out var attributeNameLocation, out _, out _)) { return(Array.Empty <RazorCompletionItem>()); } if (attributeNameLocation.IntersectsWith(location.AbsoluteIndex) && attributeName.StartsWith("@", StringComparison.Ordinal)) { // The transition is already provided for the attribute name return(Array.Empty <RazorCompletionItem>()); } if (!IsValidCompletionPoint(location, prefixLocation, attributeNameLocation)) { // Not operating in the attribute name area return(Array.Empty <RazorCompletionItem>()); } // This represents a tag when there's no attribute content <InputText | />. return(Completions); }
public void GetOffSet_SpanIsNotOwnerOfChange_ThrowsException() { // Arrange var builder = SyntaxListBuilder <SyntaxToken> .Create(); builder.Add(SyntaxFactory.Token(SyntaxKind.Marker, "Hello, ")); builder.Add(SyntaxFactory.Token(SyntaxKind.Marker, "World")); var node = SyntaxFactory.MarkupTextLiteral(builder.ToList()).CreateRed(null, 13); var change = new SourceChange(12, 2, "heyo"); var expected = $"The node '{node}' is not the owner of change '{change}'."; // Act & Assert var exception = Assert.Throws <InvalidOperationException>(() => { change.GetOffset(node); }); Assert.Equal(expected, exception.Message); }
protected override PartialParseResultInternal CanAcceptChange(Span target, SourceChange change) { if (IsAcceptableDeletion(target, change)) { return(PartialParseResultInternal.Accepted); } if (IsAcceptableReplacement(target, change)) { return(PartialParseResultInternal.Accepted); } if (IsAcceptableInsertion(change)) { return(PartialParseResultInternal.Accepted); } return(PartialParseResultInternal.Rejected); }
private bool AtDirectiveCompletionPoint(RazorSyntaxTree syntaxTree, CompletionContext context) { if (TryGetRazorSnapshotPoint(context, out var razorSnapshotPoint)) { var change = new SourceChange(razorSnapshotPoint.Position, 0, string.Empty); var owner = syntaxTree.Root.LocateOwner(change); if (owner.ChunkGenerator is ExpressionChunkGenerator && owner.Symbols.All(IsDirectiveCompletableSymbol) && // Do not provide IntelliSense for explicit expressions. Explicit expressions will usually look like: // [@] [(] [DateTime.Now] [)] owner.Parent?.Children.Count > 1 && owner.Parent.Children[1] == owner) { return(true); } } return(false); }
// Internal for testing internal static bool IsAcceptableReplacement(SyntaxNode target, SourceChange change) { if (!change.IsReplace) { return(false); } if (ContainsInvalidContent(change)) { return(false); } if (ModifiesInvalidContent(target, change)) { return(false); } return(true); }
private static bool IsPartOfHtmlTag(FormattingContext context, int position) { var syntaxTree = context.CodeDocument.GetSyntaxTree(); var change = new SourceChange(position, 0, string.Empty); var owner = syntaxTree.Root.LocateOwner(change); if (owner == null) { // Can't determine owner of this position. return(false); } // E.g, (| is position) // // `<p csharpattr="|Variable">` - true // return(owner.AncestorsAndSelf().Any( n => n is MarkupStartTagSyntax || n is MarkupTagHelperStartTagSyntax || n is MarkupEndTagSyntax || n is MarkupTagHelperEndTagSyntax)); }
private PartialParseResultInternal GetPartialParseResult(SourceChange change) { var result = PartialParseResultInternal.Rejected; // Try the last change owner if (_lastChangeOwner != null) { var editHandler = _lastChangeOwner.GetSpanContext()?.EditHandler ?? SpanEditHandler.CreateDefault(); if (editHandler.OwnsChange(_lastChangeOwner, change)) { var editResult = editHandler.ApplyChange(_lastChangeOwner, change); result = editResult.Result; if ((editResult.Result & PartialParseResultInternal.Rejected) != PartialParseResultInternal.Rejected) { ReplaceLastChangeOwner(editResult.EditedNode); } } return(result); } // Locate the span responsible for this change _lastChangeOwner = ModifiedSyntaxTreeRoot.LocateOwner(change); if (_lastResultProvisional) { // Last change owner couldn't accept this, so we must do a full reparse result = PartialParseResultInternal.Rejected; } else if (_lastChangeOwner != null) { var editHandler = _lastChangeOwner.GetSpanContext()?.EditHandler ?? SpanEditHandler.CreateDefault(); var editResult = editHandler.ApplyChange(_lastChangeOwner, change); result = editResult.Result; if ((editResult.Result & PartialParseResultInternal.Rejected) != PartialParseResultInternal.Rejected) { ReplaceLastChangeOwner(editResult.EditedNode); } } return(result); }
private async Task <TagHelperBinding> GetOriginTagHelperBindingAsync(DocumentSnapshot documentSnapshot, RazorCodeDocument codeDocument, Position position) { var sourceText = await documentSnapshot.GetTextAsync().ConfigureAwait(false); var linePosition = new LinePosition((int)position.Line, (int)position.Character); var hostDocumentIndex = sourceText.Lines.GetPosition(linePosition); var location = new SourceLocation(hostDocumentIndex, (int)position.Line, (int)position.Character); var change = new SourceChange(location.AbsoluteIndex, length: 0, newText: string.Empty); var syntaxTree = codeDocument.GetSyntaxTree(); if (syntaxTree?.Root is null) { return(null); } var owner = syntaxTree.Root.LocateOwner(change); if (owner is null) { return(null); } var node = owner.Ancestors().FirstOrDefault(n => n.Kind == SyntaxKind.MarkupTagHelperStartTag); if (node == null || !(node is MarkupTagHelperStartTagSyntax tagHelperStartTag)) { return(null); } if (!tagHelperStartTag.Name.Span.Contains(location.AbsoluteIndex)) { return(null); } if (!(tagHelperStartTag?.Parent is MarkupTagHelperElementSyntax tagHelperElement)) { return(null); } return(tagHelperElement.TagHelperInfo.BindingResult); }
// Internal for testing internal static bool IsAcceptableInsertionInBalancedParenthesis(Span target, SourceChange change) { if (!change.IsInsert) { return(false); } if (change.NewText.IndexOfAny(new[] { '(', ')' }) >= 0) { // Insertions of parenthesis aren't handled by us. If someone else wants to accept it, they can. return(false); } if (IsInsideParenthesis(change.Span.AbsoluteIndex, target.Tokens)) { return(true); } return(false); }
SourceChangeList ConstructSourceChangeList(){ SourceChangeList result = new SourceChangeList(); int lastPos = 0; do{ int beforeLine = this.lineCounter; string beforeText = this.ReadString(); if (this.inputLine != "after") throw new MalformedSuiteException("Line "+this.lineCounter+": Expected an 'after' line"); string afterText = this.ReadString(); SourceChange sc = new SourceChange(); sc.ChangedText = afterText; sc.SourceContext = this.compilationUnit.SourceContext; int beforePos = this.currentDocument.Text.Source.IndexOf(beforeText, lastPos); if (beforePos < 0) throw new MalformedSuiteException("Line "+beforeLine+": before text not found"); sc.SourceContext.StartPos = beforePos; sc.SourceContext.EndPos = lastPos = beforePos + beforeText.Length; result.Add(sc); }while (this.inputLine == "before"); return result; }