From b9abac630a003e39da13fac2bc869b37cc6cd381 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Fri, 19 Jul 2024 06:13:15 -0700 Subject: [PATCH 01/64] Factoring out AutoInsertService --- .../AutoClosingTagOnAutoInsertProvider.cs | 270 ++++++++++++++++++ .../AutoInsert/AutoInsertService.cs | 62 ++++ .../CloseTextTagOnAutoInsertProvider.cs | 68 +++++ .../AutoInsert/IAutoInsertService.cs | 21 ++ .../AutoInsert/IOnAutoInsertProvider.cs | 15 + .../AutoInsert/InsertTextEdit.cs | 10 + 6 files changed, 446 insertions(+) create mode 100644 src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs create mode 100644 src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs create mode 100644 src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs create mode 100644 src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs create mode 100644 src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs create mode 100644 src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/InsertTextEdit.cs diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs new file mode 100644 index 00000000000..26c7f45629a --- /dev/null +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs @@ -0,0 +1,270 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using System; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.AspNetCore.Razor.Language.Syntax; +using Microsoft.CodeAnalysis.Razor.Logging; +using Microsoft.CodeAnalysis.Razor.ProjectSystem; +using Microsoft.CodeAnalysis.Razor.Workspaces; +using Microsoft.VisualStudio.LanguageServer.Protocol; + +using RazorSyntaxNode = Microsoft.AspNetCore.Razor.Language.Syntax.SyntaxNode; + +namespace Microsoft.CodeAnalysis.Razor.AutoInsert; + +internal sealed class AutoClosingTagOnAutoInsertProvider : IOnAutoInsertProvider +{ + // From http://dev.w3.org/html5/spec/Overview.html#elements-0 + private static readonly ImmutableHashSet s_voidElements = ImmutableHashSet.Create(StringComparer.OrdinalIgnoreCase, + "area", + "base", + "br", + "col", + "command", + "embed", + "hr", + "img", + "input", + "keygen", + "link", + "meta", + "menuitem", + "param", + "source", + "track", + "wbr" + ); + private static readonly ImmutableHashSet s_voidElementsCaseSensitive = s_voidElements.WithComparer(StringComparer.Ordinal); + + private readonly ILogger _logger; + + public AutoClosingTagOnAutoInsertProvider(ILoggerFactory loggerFactory) + { + if (loggerFactory is null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + _logger = loggerFactory.GetOrCreateLogger(); + } + + public string TriggerCharacter => ">"; + + public async ValueTask TryResolveInsertionAsync(Position position, IDocumentSnapshot documentSnapshot, bool autoClosingTagsOption) + { + if (!(autoClosingTagsOption + && documentSnapshot.TryGetText(out var sourceText) + && position.TryGetAbsoluteIndex(sourceText, _logger, out var afterCloseAngleIndex) + && await TryResolveAutoClosingBehaviorAsync(documentSnapshot, afterCloseAngleIndex) + .ConfigureAwait(false) is { } tagNameWithClosingBehavior)) + { + return default; + } + + if (tagNameWithClosingBehavior.AutoClosingBehavior == AutoClosingBehavior.EndTag) + { + var formatForEndTag = InsertTextFormat.Snippet; + var editForEndTag = new TextEdit() + { + NewText = $"$0", + Range = new Range { Start = position, End = position }, + }; + + return new InsertTextEdit(editForEndTag, formatForEndTag); + } + + Debug.Assert(tagNameWithClosingBehavior.AutoClosingBehavior == AutoClosingBehavior.SelfClosing); + + var format = InsertTextFormat.Plaintext; + + // Need to replace the `>` with ' />$0' or '/>$0' depending on if there's prefixed whitespace. + var insertionText = char.IsWhiteSpace(sourceText[afterCloseAngleIndex - 2]) ? "/" : " /"; + var insertionPosition = new Position(position.Line, position.Character - 1); + var edit = new TextEdit() + { + NewText = insertionText, + Range = new Range + { + Start = insertionPosition, + End = insertionPosition + } + }; + + return new InsertTextEdit(edit, format); + } + + private static async ValueTask TryResolveAutoClosingBehaviorAsync(IDocumentSnapshot documentSnapshot, int afterCloseAngleIndex) + { + var codeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); + var syntaxTree = codeDocument.GetSyntaxTree(); + var closeAngle = syntaxTree.Root.FindToken(afterCloseAngleIndex - 1); + + if (closeAngle.Parent is MarkupStartTagSyntax + { + ForwardSlash: null, + Parent: MarkupElementSyntax htmlElement + } startTag) + { + var unescapedTagName = startTag.Name.Content; + var autoClosingBehavior = InferAutoClosingBehavior(unescapedTagName, caseSensitive: false); + + if (autoClosingBehavior == AutoClosingBehavior.EndTag && !CouldAutoCloseParentOrSelf(unescapedTagName, htmlElement)) + { + // Auto-closing behavior is end-tag; however, we already have and end-tag therefore we don't need to do anything! + return default; + } + + // Finally capture the entire tag name with the potential escape operator. + var name = startTag.GetTagNameWithOptionalBang(); + return new TagNameWithClosingBehavior(name, autoClosingBehavior); + } + + if (closeAngle.Parent is MarkupTagHelperStartTagSyntax + { + ForwardSlash: null, + Parent: MarkupTagHelperElementSyntax { TagHelperInfo.BindingResult: var binding } tagHelperElement + } startTagHelper) + { + var name = startTagHelper.Name.Content; + + if (!TryGetTagHelperAutoClosingBehavior(binding, out var autoClosingBehavior)) + { + autoClosingBehavior = InferAutoClosingBehavior(name, caseSensitive: true); + } + + if (autoClosingBehavior == AutoClosingBehavior.EndTag && !CouldAutoCloseParentOrSelf(name, tagHelperElement)) + { + // Auto-closing behavior is end-tag; however, we already have and end-tag therefore we don't need to do anything! + return default; + } + + return new TagNameWithClosingBehavior(name, autoClosingBehavior); + } + + return default; + } + + private static AutoClosingBehavior InferAutoClosingBehavior(string name, bool caseSensitive) + { + var voidElements = caseSensitive ? s_voidElementsCaseSensitive : s_voidElements; + + if (voidElements.Contains(name)) + { + return AutoClosingBehavior.SelfClosing; + } + + return AutoClosingBehavior.EndTag; + } + + private static bool TryGetTagHelperAutoClosingBehavior(TagHelperBinding bindingResult, out AutoClosingBehavior autoClosingBehavior) + { + var resolvedTagStructure = TagStructure.Unspecified; + + foreach (var descriptor in bindingResult.Descriptors) + { + var tagMatchingRules = bindingResult.Mappings[descriptor]; + foreach (var tagMatchingRule in tagMatchingRules) + { + if (tagMatchingRule.TagStructure == TagStructure.Unspecified) + { + // The current tag matching rule isn't specified so it should never be used as the resolved tag structure since it + // says it doesn't have an opinion. + } + else if (tagMatchingRule.TagStructure == TagStructure.NormalOrSelfClosing) + { + // We have a rule that indicates it can be normal or self-closing, that always wins because + // it's all encompassing. Meaning, even if all previous rules indicate "no children" and at least + // one says it supports children we render the tag as having the potential to have children. + autoClosingBehavior = AutoClosingBehavior.EndTag; + return true; + } + else + { + resolvedTagStructure = tagMatchingRule.TagStructure; + } + } + } + + Debug.Assert(resolvedTagStructure != TagStructure.NormalOrSelfClosing, "Normal tag structure should already have been preferred"); + + if (resolvedTagStructure == TagStructure.WithoutEndTag) + { + autoClosingBehavior = AutoClosingBehavior.SelfClosing; + return true; + } + + autoClosingBehavior = default; + return false; + } + + private static bool CouldAutoCloseParentOrSelf(string currentTagName, RazorSyntaxNode node) + { + do + { + string? potentialStartTagName = null; + RazorSyntaxNode? endTag = null; + if (node is MarkupTagHelperElementSyntax parentTagHelper) + { + potentialStartTagName = parentTagHelper.StartTag?.Name.Content ?? parentTagHelper.EndTag?.Name.Content; + endTag = parentTagHelper.EndTag; + } + else if (node is MarkupElementSyntax parentElement) + { + potentialStartTagName = parentElement.StartTag?.Name.Content ?? parentElement.EndTag?.Name.Content; + endTag = parentElement.EndTag; + } + + var isNonTagStructure = potentialStartTagName is null; + if (isNonTagStructure) + { + // We don't want to look outside of our immediate parent for potential parents that we could auto-close because + // auto-closing one of those parents wouldn't actually auto-close them. For instance: + // + //
+ // @if (true) + // { + //
|
+ // } + // + // If we re-type the `>` in the inner-div we don't want to add another
because it would be out of scope + // for the parent
+ return false; + } + + if (string.Equals(potentialStartTagName, currentTagName, StringComparison.Ordinal)) + { + // Tag names equal, if the parent is missing an end-tag it could apply to that + // i.e.
|
+ if (endTag is null) + { + return true; + } + + // Has an end-tag; however, it could be another level of parent which is OK lets keep going up + } + else + { + // Different tag name, can't apply + return false; + } + + node = node.Parent; + } while (node is not null); + + return false; + } + + private enum AutoClosingBehavior + { + EndTag, + SelfClosing, + } + + private record struct TagNameWithClosingBehavior(string TagName, AutoClosingBehavior AutoClosingBehavior) + { + } +} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs new file mode 100644 index 00000000000..2c51e812621 --- /dev/null +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs @@ -0,0 +1,62 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.AspNetCore.Razor.PooledObjects; +using Microsoft.CodeAnalysis.Razor.ProjectSystem; +using Microsoft.CodeAnalysis.Razor.Workspaces; +using Microsoft.VisualStudio.LanguageServer.Protocol; +using StreamJsonRpc; + +namespace Microsoft.CodeAnalysis.Razor.AutoInsert; + +internal class AutoInsertService(IEnumerable onAutoInsertProviders) : IAutoInsertService +{ + private readonly IEnumerable _onAutoInsertProviders = onAutoInsertProviders; + + // This gets called just once + public IEnumerable TriggerCharacters => _onAutoInsertProviders.Select((provider) => provider.TriggerCharacter) + + public async ValueTask TryResolveInsertionAsync( + IDocumentSnapshot documentSnapshot, + Position position, + string character, + bool autoCloseTags) + { + using var applicableProviders = new PooledArrayBuilder(); + foreach (var provider in _onAutoInsertProviders) + { + if (provider.TriggerCharacter == character) + { + applicableProviders.Add(provider); + } + } + + if (applicableProviders.Count == 0) + { + // There's currently a bug in the LSP platform where other language clients OnAutoInsert trigger characters influence every language clients trigger characters. + // To combat this we need to preemptively return so we don't try having our providers handle characters that they can't. + return null; + } + + foreach (var provider in applicableProviders) + { + var insertTextEdit = await provider.TryResolveInsertionAsync( + position, + documentSnapshot, + autoCloseTags + ).ConfigureAwait(false); + + if (insertTextEdit is not null) + { + return insertTextEdit; + } + } + + // No provider could handle the text edit. + return null; + } +} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs new file mode 100644 index 00000000000..b83000f217e --- /dev/null +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs @@ -0,0 +1,68 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.AspNetCore.Razor.Language.Legacy; +using Microsoft.AspNetCore.Razor.Language.Syntax; +using Microsoft.CodeAnalysis.Razor.Logging; +using Microsoft.CodeAnalysis.Razor.ProjectSystem; +using Microsoft.CodeAnalysis.Razor.Workspaces; +using Microsoft.VisualStudio.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.Razor.AutoInsert; + +internal sealed class CloseTextTagOnAutoInsertProvider(ILoggerFactory loggerFactory) : IOnAutoInsertProvider +{ + private readonly ILogger _logger = loggerFactory.GetOrCreateLogger(); + + public string TriggerCharacter => ">"; + + public async ValueTask TryResolveInsertionAsync(Position position, IDocumentSnapshot documentSnapshot, bool autoClosingTags) + { + if (!(autoClosingTags + && await IsAtTextTagAsync(documentSnapshot, position, _logger).ConfigureAwait(false))) + { + return default; + } + + // This is a text tag. + var format = InsertTextFormat.Snippet; + var edit = new TextEdit() + { + NewText = $"$0", + Range = new Range { Start = position, End = position }, + }; + + return new InsertTextEdit(edit, format); + } + + private static async ValueTask IsAtTextTagAsync(IDocumentSnapshot documentSnapshot, Position position, ILogger logger) + { + var codeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); + var syntaxTree = codeDocument.GetSyntaxTree(); + + if (!(documentSnapshot.TryGetText(out var sourceText) + && position.TryGetAbsoluteIndex(sourceText, logger, out var absoluteIndex))) + { + return false; + } + + var owner = syntaxTree.Root.FindToken(absoluteIndex - 1); + // Make sure the end tag doesn't already exist + if (owner?.Parent is MarkupStartTagSyntax + { + IsMarkupTransition: true, + Parent: MarkupElementSyntax { EndTag: null } + } startTag) + { + Debug.Assert(string.Equals(startTag.Name.Content, SyntaxConstants.TextTagName, StringComparison.Ordinal), "MarkupTransition that is not a tag."); + + return true; + } + + return false; + } +} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs new file mode 100644 index 00000000000..cfd1d2d3e85 --- /dev/null +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs @@ -0,0 +1,21 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Razor.ProjectSystem; +using Microsoft.VisualStudio.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.Razor.AutoInsert; + +internal interface IAutoInsertService +{ + public IEnumerable TriggerCharacters { get; } + + ValueTask TryResolveInsertionAsync( + IDocumentSnapshot documentSnapshot, + Position position, + string character, + bool autoCloseTags + ); +} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs new file mode 100644 index 00000000000..fc3ca957a6e --- /dev/null +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Razor.ProjectSystem; +using Microsoft.VisualStudio.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.Razor.AutoInsert; + +internal interface IOnAutoInsertProvider +{ + string TriggerCharacter { get; } + + public ValueTask TryResolveInsertionAsync(Position position, IDocumentSnapshot documentSnapshot, bool autoClosingTagsOption); +} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/InsertTextEdit.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/InsertTextEdit.cs new file mode 100644 index 00000000000..e86a2a26a24 --- /dev/null +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/InsertTextEdit.cs @@ -0,0 +1,10 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.VisualStudio.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.Razor.AutoInsert; + +internal record struct InsertTextEdit(TextEdit TextEdit, InsertTextFormat InsertTextFormat) +{ +} From 330a6704a6ce9518851d89ff75bab87fcb65fdea Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Fri, 19 Jul 2024 06:58:57 -0700 Subject: [PATCH 02/64] Switch non-cohost endpoint to use new AutoInsertService --- .../AutoClosingTagOnAutoInsertProvider.cs | 259 ------------------ .../CloseTextTagOnAutoInsertProvider.cs | 71 ----- .../AutoInsert/IOnAutoInsertProvider.cs | 16 -- .../AutoInsert/OnAutoInsertEndpoint.cs | 47 ++-- .../AutoInsert/AutoInsertService.cs | 5 +- 5 files changed, 17 insertions(+), 381 deletions(-) delete mode 100644 src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs delete mode 100644 src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/CloseTextTagOnAutoInsertProvider.cs delete mode 100644 src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/IOnAutoInsertProvider.cs diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs deleted file mode 100644 index ae288e14de1..00000000000 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs +++ /dev/null @@ -1,259 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root for license information. - -using System; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using Microsoft.AspNetCore.Razor.Language; -using Microsoft.AspNetCore.Razor.Language.Syntax; -using Microsoft.AspNetCore.Razor.LanguageServer.Formatting; -using Microsoft.CodeAnalysis.Razor.Formatting; -using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.LanguageServer.Protocol; - -namespace Microsoft.AspNetCore.Razor.LanguageServer.AutoInsert; - -internal sealed class AutoClosingTagOnAutoInsertProvider(RazorLSPOptionsMonitor optionsMonitor) : IOnAutoInsertProvider -{ - // From http://dev.w3.org/html5/spec/Overview.html#elements-0 - private static readonly ImmutableHashSet s_voidElements = ImmutableHashSet.Create(StringComparer.OrdinalIgnoreCase, - "area", - "base", - "br", - "col", - "command", - "embed", - "hr", - "img", - "input", - "keygen", - "link", - "meta", - "menuitem", - "param", - "source", - "track", - "wbr" - ); - - private static readonly ImmutableHashSet s_voidElementsCaseSensitive = s_voidElements.WithComparer(StringComparer.Ordinal); - - private readonly RazorLSPOptionsMonitor _optionsMonitor = optionsMonitor; - - public string TriggerCharacter => ">"; - - public bool TryResolveInsertion(Position position, FormattingContext context, [NotNullWhen(true)] out TextEdit? edit, out InsertTextFormat format) - { - if (!_optionsMonitor.CurrentValue.AutoClosingTags) - { - format = default; - edit = default; - return false; - } - - if (!context.SourceText.TryGetAbsoluteIndex(position, out var afterCloseAngleIndex)) - { - format = default; - edit = default; - return false; - } - - if (!TryResolveAutoClosingBehavior(context, afterCloseAngleIndex, out var tagName, out var autoClosingBehavior)) - { - format = default; - edit = default; - return false; - } - - if (autoClosingBehavior == AutoClosingBehavior.EndTag) - { - format = InsertTextFormat.Snippet; - edit = VsLspFactory.CreateTextEdit(position, $"$0"); - - return true; - } - - Debug.Assert(autoClosingBehavior == AutoClosingBehavior.SelfClosing); - - format = InsertTextFormat.Plaintext; - - // Need to replace the `>` with ' />$0' or '/>$0' depending on if there's prefixed whitespace. - var insertionText = char.IsWhiteSpace(context.SourceText[afterCloseAngleIndex - 2]) ? "/" : " /"; - edit = VsLspFactory.CreateTextEdit(position.Line, position.Character - 1, insertionText); - - return true; - } - - private static bool TryResolveAutoClosingBehavior(FormattingContext context, int afterCloseAngleIndex, [NotNullWhen(true)] out string? name, out AutoClosingBehavior autoClosingBehavior) - { - var syntaxTree = context.CodeDocument.GetSyntaxTree(); - var closeAngle = syntaxTree.Root.FindToken(afterCloseAngleIndex - 1); - - if (closeAngle.Parent is MarkupStartTagSyntax - { - ForwardSlash: null, - Parent: MarkupElementSyntax htmlElement - } startTag) - { - var unescapedTagName = startTag.Name.Content; - autoClosingBehavior = InferAutoClosingBehavior(unescapedTagName, caseSensitive: false); - - if (autoClosingBehavior == AutoClosingBehavior.EndTag && !CouldAutoCloseParentOrSelf(unescapedTagName, htmlElement)) - { - // Auto-closing behavior is end-tag; however, we already have and end-tag therefore we don't need to do anything! - autoClosingBehavior = default; - name = null; - return false; - } - - // Finally capture the entire tag name with the potential escape operator. - name = startTag.GetTagNameWithOptionalBang(); - return true; - } - - if (closeAngle.Parent is MarkupTagHelperStartTagSyntax - { - ForwardSlash: null, - Parent: MarkupTagHelperElementSyntax { TagHelperInfo.BindingResult: var binding } tagHelperElement - } startTagHelper) - { - name = startTagHelper.Name.Content; - - if (!TryGetTagHelperAutoClosingBehavior(binding, out autoClosingBehavior)) - { - autoClosingBehavior = InferAutoClosingBehavior(name, caseSensitive: true); - } - - if (autoClosingBehavior == AutoClosingBehavior.EndTag && !CouldAutoCloseParentOrSelf(name, tagHelperElement)) - { - // Auto-closing behavior is end-tag; however, we already have and end-tag therefore we don't need to do anything! - autoClosingBehavior = default; - name = null; - return false; - } - - return true; - } - - autoClosingBehavior = default; - name = null; - return false; - } - - private static AutoClosingBehavior InferAutoClosingBehavior(string name, bool caseSensitive) - { - var voidElements = caseSensitive ? s_voidElementsCaseSensitive : s_voidElements; - - if (voidElements.Contains(name)) - { - return AutoClosingBehavior.SelfClosing; - } - - return AutoClosingBehavior.EndTag; - } - - private static bool TryGetTagHelperAutoClosingBehavior(TagHelperBinding bindingResult, out AutoClosingBehavior autoClosingBehavior) - { - var resolvedTagStructure = TagStructure.Unspecified; - - foreach (var descriptor in bindingResult.Descriptors) - { - var tagMatchingRules = bindingResult.Mappings[descriptor]; - foreach (var tagMatchingRule in tagMatchingRules) - { - if (tagMatchingRule.TagStructure == TagStructure.Unspecified) - { - // The current tag matching rule isn't specified so it should never be used as the resolved tag structure since it - // says it doesn't have an opinion. - } - else if (tagMatchingRule.TagStructure == TagStructure.NormalOrSelfClosing) - { - // We have a rule that indicates it can be normal or self-closing, that always wins because - // it's all encompassing. Meaning, even if all previous rules indicate "no children" and at least - // one says it supports children we render the tag as having the potential to have children. - autoClosingBehavior = AutoClosingBehavior.EndTag; - return true; - } - else - { - resolvedTagStructure = tagMatchingRule.TagStructure; - } - } - } - - Debug.Assert(resolvedTagStructure != TagStructure.NormalOrSelfClosing, "Normal tag structure should already have been preferred"); - - if (resolvedTagStructure == TagStructure.WithoutEndTag) - { - autoClosingBehavior = AutoClosingBehavior.SelfClosing; - return true; - } - - autoClosingBehavior = default; - return false; - } - - private static bool CouldAutoCloseParentOrSelf(string currentTagName, SyntaxNode node) - { - do - { - string? potentialStartTagName = null; - RazorSyntaxNode? endTag = null; - if (node is MarkupTagHelperElementSyntax parentTagHelper) - { - potentialStartTagName = parentTagHelper.StartTag?.Name.Content ?? parentTagHelper.EndTag?.Name.Content; - endTag = parentTagHelper.EndTag; - } - else if (node is MarkupElementSyntax parentElement) - { - potentialStartTagName = parentElement.StartTag?.Name.Content ?? parentElement.EndTag?.Name.Content; - endTag = parentElement.EndTag; - } - - var isNonTagStructure = potentialStartTagName is null; - if (isNonTagStructure) - { - // We don't want to look outside of our immediate parent for potential parents that we could auto-close because - // auto-closing one of those parents wouldn't actually auto-close them. For instance: - // - //
- // @if (true) - // { - //
|
- // } - // - // If we re-type the `>` in the inner-div we don't want to add another
because it would be out of scope - // for the parent
- return false; - } - - if (string.Equals(potentialStartTagName, currentTagName, StringComparison.Ordinal)) - { - // Tag names equal, if the parent is missing an end-tag it could apply to that - // i.e.
|
- if (endTag is null) - { - return true; - } - - // Has an end-tag; however, it could be another level of parent which is OK lets keep going up - } - else - { - // Different tag name, can't apply - return false; - } - - node = node.Parent; - } while (node is not null); - - return false; - } - - private enum AutoClosingBehavior - { - EndTag, - SelfClosing, - } -} diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/CloseTextTagOnAutoInsertProvider.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/CloseTextTagOnAutoInsertProvider.cs deleted file mode 100644 index f6ff6ff95e1..00000000000 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/CloseTextTagOnAutoInsertProvider.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root for license information. - -using System; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using Microsoft.AspNetCore.Razor.Language; -using Microsoft.AspNetCore.Razor.Language.Legacy; -using Microsoft.AspNetCore.Razor.Language.Syntax; -using Microsoft.AspNetCore.Razor.LanguageServer.Formatting; -using Microsoft.CodeAnalysis.Razor.Formatting; -using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.LanguageServer.Protocol; - -namespace Microsoft.AspNetCore.Razor.LanguageServer.AutoInsert; - -internal sealed class CloseTextTagOnAutoInsertProvider(RazorLSPOptionsMonitor optionsMonitor) : IOnAutoInsertProvider -{ - private readonly RazorLSPOptionsMonitor _optionsMonitor = optionsMonitor; - - public string TriggerCharacter => ">"; - - public bool TryResolveInsertion(Position position, FormattingContext context, [NotNullWhen(true)] out TextEdit? edit, out InsertTextFormat format) - { - if (!_optionsMonitor.CurrentValue.AutoClosingTags) - { - // We currently only support auto-closing tags our onType formatter. - format = default; - edit = default; - return false; - } - - if (!IsAtTextTag(context, position)) - { - format = default; - edit = default; - return false; - } - - // This is a text tag. - format = InsertTextFormat.Snippet; - edit = VsLspFactory.CreateTextEdit(position, $"$0"); - - return true; - } - - private static bool IsAtTextTag(FormattingContext context, Position position) - { - var syntaxTree = context.CodeDocument.GetSyntaxTree(); - - if (!context.SourceText.TryGetAbsoluteIndex(position, out var absoluteIndex)) - { - return false; - } - - var owner = syntaxTree.Root.FindToken(absoluteIndex - 1); - // Make sure the end tag doesn't already exist - if (owner?.Parent is MarkupStartTagSyntax - { - IsMarkupTransition: true, - Parent: MarkupElementSyntax { EndTag: null } - } startTag) - { - Debug.Assert(string.Equals(startTag.Name.Content, SyntaxConstants.TextTagName, StringComparison.Ordinal), "MarkupTransition that is not a tag."); - - return true; - } - - return false; - } -} diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/IOnAutoInsertProvider.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/IOnAutoInsertProvider.cs deleted file mode 100644 index d45bfe02d8d..00000000000 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/IOnAutoInsertProvider.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root for license information. - -using System.Diagnostics.CodeAnalysis; -using Microsoft.AspNetCore.Razor.LanguageServer.Formatting; -using Microsoft.CodeAnalysis.Razor.Formatting; -using Microsoft.VisualStudio.LanguageServer.Protocol; - -namespace Microsoft.AspNetCore.Razor.LanguageServer.AutoInsert; - -internal interface IOnAutoInsertProvider -{ - string TriggerCharacter { get; } - - bool TryResolveInsertion(Position position, FormattingContext context, [NotNullWhen(true)] out TextEdit? edit, out InsertTextFormat format); -} diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs index cbcef4ece99..e4a3f7da33f 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs @@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Razor.LanguageServer.Hosting; using Microsoft.AspNetCore.Razor.PooledObjects; using Microsoft.AspNetCore.Razor.Threading; +using Microsoft.CodeAnalysis.Razor.AutoInsert; using Microsoft.CodeAnalysis.Razor.DocumentMapping; using Microsoft.CodeAnalysis.Razor.Formatting; using Microsoft.CodeAnalysis.Razor.Logging; @@ -26,7 +27,7 @@ internal class OnAutoInsertEndpoint( LanguageServerFeatureOptions languageServerFeatureOptions, IDocumentMappingService documentMappingService, IClientConnection clientConnection, - IEnumerable onAutoInsertProvider, + IAutoInsertService autoInsertService, RazorLSPOptionsMonitor optionsMonitor, IAdhocWorkspaceFactory workspaceFactory, IRazorFormattingService razorFormattingService, @@ -40,7 +41,7 @@ internal class OnAutoInsertEndpoint( private readonly RazorLSPOptionsMonitor _optionsMonitor = optionsMonitor; private readonly IAdhocWorkspaceFactory _workspaceFactory = workspaceFactory; private readonly IRazorFormattingService _razorFormattingService = razorFormattingService; - private readonly List _onAutoInsertProviders = onAutoInsertProvider.ToList(); + private readonly IAutoInsertService _autoInsertService = autoInsertService; protected override string CustomMessageTarget => CustomMessageNames.RazorOnAutoInsertEndpointName; @@ -53,7 +54,7 @@ internal class OnAutoInsertEndpoint( public void ApplyCapabilities(VSInternalServerCapabilities serverCapabilities, VSInternalClientCapabilities clientCapabilities) { - var triggerCharacters = _onAutoInsertProviders.Select(provider => provider.TriggerCharacter); + var triggerCharacters = _autoInsertService.TriggerCharacters; if (_languageServerFeatureOptions.SingleServerSupport) { @@ -84,36 +85,20 @@ public void ApplyCapabilities(VSInternalServerCapabilities serverCapabilities, V var character = request.Character; - using var applicableProviders = new PooledArrayBuilder(); - foreach (var provider in _onAutoInsertProviders) - { - if (provider.TriggerCharacter == character) - { - applicableProviders.Add(provider); - } - } + var insertTextEdit = await _autoInsertService.TryResolveInsertionAsync( + documentContext.Snapshot, + request.Position, + character, + _optionsMonitor.CurrentValue.AutoClosingTags + ).ConfigureAwait(false); - if (applicableProviders.Count == 0) - { - // There's currently a bug in the LSP platform where other language clients OnAutoInsert trigger characters influence every language clients trigger characters. - // To combat this we need to preemptively return so we don't try having our providers handle characters that they can't. - return null; - } - - var uri = request.TextDocument.Uri; - var position = request.Position; - - using var formattingContext = FormattingContext.Create(uri, documentContext.Snapshot, codeDocument, request.Options, _workspaceFactory); - foreach (var provider in applicableProviders) - { - if (provider.TryResolveInsertion(position, formattingContext, out var textEdit, out var format)) + if (insertTextEdit is not null) + { + return new VSInternalDocumentOnAutoInsertResponseItem() { - return new VSInternalDocumentOnAutoInsertResponseItem() - { - TextEdit = textEdit, - TextEditFormat = format, - }; - } + TextEdit = insertTextEdit.Value.TextEdit, + TextEditFormat = insertTextEdit.Value.InsertTextFormat, + }; } // No provider could handle the text edit. diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs index 2c51e812621..bf137b5ae95 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs @@ -4,12 +4,9 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.PooledObjects; using Microsoft.CodeAnalysis.Razor.ProjectSystem; -using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.VisualStudio.LanguageServer.Protocol; -using StreamJsonRpc; namespace Microsoft.CodeAnalysis.Razor.AutoInsert; @@ -18,7 +15,7 @@ internal class AutoInsertService(IEnumerable onAutoInsert private readonly IEnumerable _onAutoInsertProviders = onAutoInsertProviders; // This gets called just once - public IEnumerable TriggerCharacters => _onAutoInsertProviders.Select((provider) => provider.TriggerCharacter) + public IEnumerable TriggerCharacters => _onAutoInsertProviders.Select((provider) => provider.TriggerCharacter); public async ValueTask TryResolveInsertionAsync( IDocumentSnapshot documentSnapshot, From fe520b884cb974551b23c36d489ac522d9d567ec Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Sat, 20 Jul 2024 21:45:34 -0700 Subject: [PATCH 03/64] Adding Remote and OOB AutoInsertService classes and OnAutoInsertProviders --- eng/targets/Services.props | 1 + .../AutoInsert/OnAutoInsertEndpoint.cs | 3 +- .../AutoClosingTagOnAutoInsertProvider.cs | 2 +- .../AutoInsert/AutoInsertService.cs | 8 ++- .../CloseTextTagOnAutoInsertProvider.cs | 2 +- .../AutoInsert/IAutoInsertService.cs | 4 +- .../AutoInsert/RemoteInsertTextEdit.cs | 21 +++++++ .../Remote/IRemoteAutoInsertService.cs | 25 ++++++++ .../AutoInsert/OOPAutoInsertService.cs | 16 +++++ .../AutoInsert/RemoteAutoInsertService.cs | 58 +++++++++++++++++++ .../RemoteAutoInsertServiceFactory.cs | 25 ++++++++ .../AutoInsert/RemoteOnAutoInsertProviders.cs | 21 +++++++ 12 files changed, 181 insertions(+), 5 deletions(-) create mode 100644 src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs create mode 100644 src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteAutoInsertService.cs create mode 100644 src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/OOPAutoInsertService.cs create mode 100644 src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs create mode 100644 src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertServiceFactory.cs create mode 100644 src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteOnAutoInsertProviders.cs diff --git a/eng/targets/Services.props b/eng/targets/Services.props index 6d9d80ecd5e..b2476eab94a 100644 --- a/eng/targets/Services.props +++ b/eng/targets/Services.props @@ -28,5 +28,6 @@ + diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs index e4a3f7da33f..402085811d6 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs @@ -89,7 +89,8 @@ public void ApplyCapabilities(VSInternalServerCapabilities serverCapabilities, V documentContext.Snapshot, request.Position, character, - _optionsMonitor.CurrentValue.AutoClosingTags + _optionsMonitor.CurrentValue.AutoClosingTags, + cancellationToken ).ConfigureAwait(false); if (insertTextEdit is not null) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs index 26c7f45629a..dbec5f8b399 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs @@ -16,7 +16,7 @@ namespace Microsoft.CodeAnalysis.Razor.AutoInsert; -internal sealed class AutoClosingTagOnAutoInsertProvider : IOnAutoInsertProvider +internal class AutoClosingTagOnAutoInsertProvider : IOnAutoInsertProvider { // From http://dev.w3.org/html5/spec/Overview.html#elements-0 private static readonly ImmutableHashSet s_voidElements = ImmutableHashSet.Create(StringComparer.OrdinalIgnoreCase, diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs index bf137b5ae95..a77686c8f36 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.PooledObjects; using Microsoft.CodeAnalysis.Razor.ProjectSystem; @@ -21,7 +22,8 @@ internal class AutoInsertService(IEnumerable onAutoInsert IDocumentSnapshot documentSnapshot, Position position, string character, - bool autoCloseTags) + bool autoCloseTags, + CancellationToken cancellationToken) { using var applicableProviders = new PooledArrayBuilder(); foreach (var provider in _onAutoInsertProviders) @@ -39,6 +41,8 @@ internal class AutoInsertService(IEnumerable onAutoInsert return null; } + cancellationToken.ThrowIfCancellationRequested(); + foreach (var provider in applicableProviders) { var insertTextEdit = await provider.TryResolveInsertionAsync( @@ -51,6 +55,8 @@ internal class AutoInsertService(IEnumerable onAutoInsert { return insertTextEdit; } + + cancellationToken.ThrowIfCancellationRequested(); } // No provider could handle the text edit. diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs index b83000f217e..2acadf0faca 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs @@ -14,7 +14,7 @@ namespace Microsoft.CodeAnalysis.Razor.AutoInsert; -internal sealed class CloseTextTagOnAutoInsertProvider(ILoggerFactory loggerFactory) : IOnAutoInsertProvider +internal class CloseTextTagOnAutoInsertProvider(ILoggerFactory loggerFactory) : IOnAutoInsertProvider { private readonly ILogger _logger = loggerFactory.GetOrCreateLogger(); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs index cfd1d2d3e85..fe8153211d8 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -16,6 +17,7 @@ internal interface IAutoInsertService IDocumentSnapshot documentSnapshot, Position position, string character, - bool autoCloseTags + bool autoCloseTags, + CancellationToken cancellationToken ); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs new file mode 100644 index 00000000000..0bf0f8f95a8 --- /dev/null +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs @@ -0,0 +1,21 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Runtime.Serialization; +using Microsoft.CodeAnalysis.Razor.AutoInsert; +using Microsoft.VisualStudio.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.Razor.Protocol.AutoInsert; + +// TODO: check annotations +[DataContract] +internal readonly record struct RemoteInsertTextEdit( + [property: DataMember(Name = "textEdit")] + TextEdit TextEdit, + [property: DataMember(Name = "insertTextFormat")] + InsertTextFormat InsertTextFormat + ) +{ + public static RemoteInsertTextEdit FromLspInsertTextEdit(InsertTextEdit edit) + => new (edit.TextEdit, edit.InsertTextFormat); +} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteAutoInsertService.cs new file mode 100644 index 00000000000..c18824a9d99 --- /dev/null +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteAutoInsertService.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ExternalAccess.Razor; +using Microsoft.CodeAnalysis.Razor.Protocol.AutoInsert; +using Microsoft.VisualStudio.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.Razor.Remote; + +internal interface IRemoteAutoInsertService : IDisposable +{ + IEnumerable TriggerCharacters { get; } + + ValueTask TryResolveInsertionAsync( + RazorPinnedSolutionInfoWrapper solutionInfo, + DocumentId documentId, + Position position, + string character, + bool autoCloseTags, + CancellationToken cancellationToken); +} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/OOPAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/OOPAutoInsertService.cs new file mode 100644 index 00000000000..45d65c58bb3 --- /dev/null +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/OOPAutoInsertService.cs @@ -0,0 +1,16 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Composition; +using Microsoft.CodeAnalysis.Razor.AutoInsert; + +namespace Microsoft.CodeAnalysis.Remote.Razor.AutoInsert; + +[Export(typeof(OOPAutoInsertService)), Shared] +[method: ImportingConstructor] +internal sealed class OOPAutoInsertService( + [ImportMany]IEnumerable providers + ) : AutoInsertService(providers) +{ +} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs new file mode 100644 index 00000000000..ed972b0cea1 --- /dev/null +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs @@ -0,0 +1,58 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ExternalAccess.Razor; +using Microsoft.CodeAnalysis.Razor.AutoInsert; +using Microsoft.CodeAnalysis.Razor.Protocol.AutoInsert; +using Microsoft.CodeAnalysis.Razor.Remote; +using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; +using Microsoft.VisualStudio.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.Remote.Razor; +internal class RemoteAutoInsertService( + IRazorServiceBroker serviceBroker, + DocumentSnapshotFactory documentSnapshotFactory, + IAutoInsertService autoInsertService) + : RazorDocumentServiceBase(serviceBroker, documentSnapshotFactory), IRemoteAutoInsertService +{ + private readonly IAutoInsertService _autoInsertService = autoInsertService; + + public IEnumerable TriggerCharacters => _autoInsertService.TriggerCharacters; + + public ValueTask TryResolveInsertionAsync( + RazorPinnedSolutionInfoWrapper solutionInfo, + DocumentId documentId, + Position position, + string character, + bool autoCloseTags, + CancellationToken cancellationToken) + => RunServiceAsync( + solutionInfo, + documentId, + context => TryResolveInsertionAsync( + context, + position, + character, + autoCloseTags, + cancellationToken), + cancellationToken); + + private async ValueTask TryResolveInsertionAsync( + RemoteDocumentContext remoteDocumentContext, + Position position, + string character, + bool autoCloseTags, + CancellationToken cancellationToken) + { + var insertTextEdit = await _autoInsertService.TryResolveInsertionAsync( + remoteDocumentContext.Snapshot, + position, + character, + autoCloseTags, + cancellationToken); + return insertTextEdit.HasValue ? RemoteInsertTextEdit.FromLspInsertTextEdit(insertTextEdit.Value) : null; + } +} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertServiceFactory.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertServiceFactory.cs new file mode 100644 index 00000000000..8e4d335d9ca --- /dev/null +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertServiceFactory.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Razor.AutoInsert; +using Microsoft.CodeAnalysis.Razor.Remote; +using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; +using Microsoft.VisualStudio.Composition; + +namespace Microsoft.CodeAnalysis.Remote.Razor; + +internal sealed class RemoteAutoInsertServiceFactory : RazorServiceFactoryBase +{ + // WARNING: We must always have a parameterless constructor in order to be properly handled by ServiceHub. + public RemoteAutoInsertServiceFactory() + : base(RazorServices.Descriptors) + { + } + + protected override IRemoteAutoInsertService CreateService(IRazorServiceBroker serviceBroker, ExportProvider exportProvider) + { + var infoService = exportProvider.GetExportedValue(); + var documentSnapshotFactory = exportProvider.GetExportedValue(); + return new RemoteAutoInsertService(serviceBroker, documentSnapshotFactory, infoService); + } +} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteOnAutoInsertProviders.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteOnAutoInsertProviders.cs new file mode 100644 index 00000000000..85f62ad7f41 --- /dev/null +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteOnAutoInsertProviders.cs @@ -0,0 +1,21 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Composition; +using System.Text; +using Microsoft.CodeAnalysis.Razor.AutoInsert; +using Microsoft.CodeAnalysis.Razor.Logging; + +namespace Microsoft.CodeAnalysis.Remote.Razor.AutoInsert; + +[Shared] +[Export(typeof(IOnAutoInsertProvider))] +internal sealed class RemoteAutoClosingTagOnAutoInsertProvider(ILoggerFactory loggerFactory) + : AutoClosingTagOnAutoInsertProvider(loggerFactory); + +[Shared] +[Export(typeof(IOnAutoInsertProvider))] +internal sealed class RemoteCloseTextTagOnAutoInsertProvider(ILoggerFactory loggerFactory) + : CloseTextTagOnAutoInsertProvider(loggerFactory); From c5506e689d9f065f2dbb8e707d0a041fbae7093c Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Thu, 25 Jul 2024 20:31:50 -0700 Subject: [PATCH 04/64] Add common code for capabilities --- .../AutoInsert/OnAutoInsertEndpoint.cs | 18 ++-------- .../VSInternalServerCapabilitiesExtensions.cs | 33 +++++++++++++++++++ 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs index 402085811d6..cc1043bd752 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs @@ -34,9 +34,6 @@ internal class OnAutoInsertEndpoint( ILoggerFactory loggerFactory) : AbstractRazorDelegatingEndpoint(languageServerFeatureOptions, documentMappingService, clientConnection, loggerFactory.GetOrCreateLogger()), ICapabilitiesProvider { - private static readonly HashSet s_htmlAllowedTriggerCharacters = new(StringComparer.Ordinal) { "=", }; - private static readonly HashSet s_cSharpAllowedTriggerCharacters = new(StringComparer.Ordinal) { "'", "/", "\n" }; - private readonly LanguageServerFeatureOptions _languageServerFeatureOptions = languageServerFeatureOptions; private readonly RazorLSPOptionsMonitor _optionsMonitor = optionsMonitor; private readonly IAdhocWorkspaceFactory _workspaceFactory = workspaceFactory; @@ -55,16 +52,7 @@ internal class OnAutoInsertEndpoint( public void ApplyCapabilities(VSInternalServerCapabilities serverCapabilities, VSInternalClientCapabilities clientCapabilities) { var triggerCharacters = _autoInsertService.TriggerCharacters; - - if (_languageServerFeatureOptions.SingleServerSupport) - { - triggerCharacters = triggerCharacters.Concat(s_htmlAllowedTriggerCharacters).Concat(s_cSharpAllowedTriggerCharacters); - } - - serverCapabilities.OnAutoInsertProvider = new VSInternalDocumentOnAutoInsertOptions() - { - TriggerCharacters = triggerCharacters.Distinct().ToArray() - }; + serverCapabilities.EnableOnAutoInsert(_languageServerFeatureOptions.SingleServerSupport, triggerCharacters); } protected override async Task TryHandleAsync(VSInternalDocumentOnAutoInsertParams request, RazorRequestContext requestContext, DocumentPositionInfo positionInfo, CancellationToken cancellationToken) @@ -116,7 +104,7 @@ public void ApplyCapabilities(VSInternalServerCapabilities serverCapabilities, V if (positionInfo.LanguageKind == RazorLanguageKind.Html) { - if (!s_htmlAllowedTriggerCharacters.Contains(request.Character)) + if (!VSInternalServerCapabilitiesExtensions.HtmlAllowedAutoInsertTriggerCharacters.Contains(request.Character)) { Logger.LogInformation($"Inapplicable HTML trigger char {request.Character}."); return SpecializedTasks.Null(); @@ -132,7 +120,7 @@ public void ApplyCapabilities(VSInternalServerCapabilities serverCapabilities, V } else if (positionInfo.LanguageKind == RazorLanguageKind.CSharp) { - if (!s_cSharpAllowedTriggerCharacters.Contains(request.Character)) + if (!VSInternalServerCapabilitiesExtensions.CSharpAllowedAutoInsertTriggerCharacters.Contains(request.Character)) { Logger.LogInformation($"Inapplicable C# trigger char {request.Character}."); return SpecializedTasks.Null(); diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Hosting/VSInternalServerCapabilitiesExtensions.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Hosting/VSInternalServerCapabilitiesExtensions.cs index cb9f96cecb3..dc0c8959fea 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Hosting/VSInternalServerCapabilitiesExtensions.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Hosting/VSInternalServerCapabilitiesExtensions.cs @@ -1,8 +1,12 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. +using System.Collections.Generic; +using System; using Microsoft.CodeAnalysis.Razor.SemanticTokens; using Microsoft.VisualStudio.LanguageServer.Protocol; +using Microsoft.CodeAnalysis.Razor.Workspaces; +using System.Linq; namespace Microsoft.AspNetCore.Razor.LanguageServer.Hosting; @@ -65,4 +69,33 @@ public static void EnableMapCodeProvider(this VSInternalServerCapabilities serve { serverCapabilities.MapCodeProvider = true; } + + public static HashSet HtmlAllowedAutoInsertTriggerCharacters { get; } = new(StringComparer.Ordinal) { "=", }; + public static HashSet CSharpAllowedAutoInsertTriggerCharacters { get; } = new(StringComparer.Ordinal) { "'", "/", "\n" }; + + public static void EnableOnAutoInsert( + this VSInternalServerCapabilities serverCapabilities, + bool singleServerSupport, + IEnumerable triggerCharacters) + { + serverCapabilities.OnAutoInsertProvider = new VSInternalDocumentOnAutoInsertOptions() + .EnableOnAutoInsert(singleServerSupport, triggerCharacters); + } + + public static VSInternalDocumentOnAutoInsertOptions EnableOnAutoInsert( + this VSInternalDocumentOnAutoInsertOptions options, + bool singleServerSupport, + IEnumerable triggerCharacters) + { + if (singleServerSupport) + { + triggerCharacters = triggerCharacters + .Concat(HtmlAllowedAutoInsertTriggerCharacters) + .Concat(CSharpAllowedAutoInsertTriggerCharacters); + } + + options.TriggerCharacters = triggerCharacters.Distinct().ToArray(); + + return options; + } } From 3f74c033b77f398e9572dcab1031725df2965cd3 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Thu, 25 Jul 2024 20:32:56 -0700 Subject: [PATCH 05/64] Add cohost OnAutoInsert endpoint --- .../RazorLanguageServer.cs | 16 +- .../AutoInsert/RemoteInsertTextEdit.cs | 12 ++ .../AutoInsert/RemoteOnAutoInsertProviders.cs | 5 +- .../Cohost/CohostOnAutoInsertEndpoint.cs | 143 ++++++++++++++++++ 4 files changed, 168 insertions(+), 8 deletions(-) create mode 100644 src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs index d542644a9c8..56990d0af9c 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs @@ -24,6 +24,8 @@ using Microsoft.AspNetCore.Razor.LanguageServer.SignatureHelp; using Microsoft.AspNetCore.Razor.LanguageServer.WrapWithTag; using Microsoft.AspNetCore.Razor.Telemetry; +using Microsoft.CodeAnalysis.Razor; +using Microsoft.CodeAnalysis.Razor.AutoInsert; using Microsoft.CodeAnalysis.Razor.FoldingRanges; using Microsoft.CodeAnalysis.Razor.GoToDefinition; using Microsoft.CodeAnalysis.Razor.Logging; @@ -136,12 +138,14 @@ protected override ILspServices ConstructLspServices() services.AddHoverServices(); services.AddTextDocumentServices(featureOptions); - // Auto insert - services.AddSingleton(); - services.AddSingleton(); - if (!featureOptions.UseRazorCohostServer) { + // Auto insert + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(); + // Folding Range Providers services.AddSingleton(); services.AddSingleton(); @@ -150,6 +154,7 @@ protected override ILspServices ConstructLspServices() services.AddSingleton(); services.AddSingleton(); + } // Other @@ -178,6 +183,8 @@ static void AddHandlers(IServiceCollection services, LanguageServerFeatureOption services.AddTransient(sp => sp.GetRequiredService()); services.AddHandlerWithCapabilities(); + services.AddHandlerWithCapabilities(); + services.AddHandlerWithCapabilities(); services.AddHandlerWithCapabilities(); if (!featureOptions.UseRazorCohostServer) @@ -188,6 +195,7 @@ static void AddHandlers(IServiceCollection services, LanguageServerFeatureOption services.AddSingleton(); services.AddHandlerWithCapabilities(); + services.AddHandlerWithCapabilities(); services.AddHandlerWithCapabilities(); services.AddHandlerWithCapabilities(); services.AddHandlerWithCapabilities(); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs index 0bf0f8f95a8..c27975dd0ae 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs @@ -18,4 +18,16 @@ InsertTextFormat InsertTextFormat { public static RemoteInsertTextEdit FromLspInsertTextEdit(InsertTextEdit edit) => new (edit.TextEdit, edit.InsertTextFormat); + + public static VSInternalDocumentOnAutoInsertResponseItem ToLspInsertTextEdit(RemoteInsertTextEdit edit) + => new() + { + TextEdit = edit.TextEdit, + TextEditFormat = edit.InsertTextFormat, + }; + + public override string ToString() + { + return $"({TextEdit.Range.Start.Line}, {TextEdit.Range.Start.Character})-({TextEdit.Range.End.Line}, {TextEdit.Range.End.Character}), '{TextEdit.NewText}', {InsertTextFormat}"; + } } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteOnAutoInsertProviders.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteOnAutoInsertProviders.cs index 85f62ad7f41..832b10aa418 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteOnAutoInsertProviders.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteOnAutoInsertProviders.cs @@ -1,14 +1,11 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; using System.Composition; -using System.Text; using Microsoft.CodeAnalysis.Razor.AutoInsert; using Microsoft.CodeAnalysis.Razor.Logging; -namespace Microsoft.CodeAnalysis.Remote.Razor.AutoInsert; +namespace Microsoft.CodBeAnalysis.Remote.Razor.AutoInsert; [Shared] [Export(typeof(IOnAutoInsertProvider))] diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs new file mode 100644 index 00000000000..95ffb1dfd2b --- /dev/null +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs @@ -0,0 +1,143 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor; +using Microsoft.AspNetCore.Razor.LanguageServer.Hosting; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; +using Microsoft.CodeAnalysis.Razor.DocumentMapping; +using Microsoft.CodeAnalysis.Razor.Logging; +using Microsoft.CodeAnalysis.Razor.Protocol.AutoInsert; +using Microsoft.CodeAnalysis.Razor.Remote; +using Microsoft.VisualStudio.LanguageServer.ContainedLanguage; +using Microsoft.VisualStudio.LanguageServer.Protocol; +using Microsoft.VisualStudio.Razor.LanguageClient.Cohost; +using Microsoft.VisualStudio.Razor.LanguageClient.Extensions; + +using RazorLSPConstants = Microsoft.VisualStudio.Razor.LanguageClient.RazorLSPConstants; + +namespace Microsoft.VisualStudio.LanguageServices.Razor.LanguageClient.Cohost; + +#pragma warning disable RS0030 // Do not use banned APIs +[Shared] +[CohostEndpoint(VSInternalMethods.OnAutoInsertName)] +[Export(typeof(IDynamicRegistrationProvider))] +[ExportCohostStatelessLspService(typeof(CohostOnAutoInsertEndpoint))] +[method: ImportingConstructor] +#pragma warning restore RS0030 // Do not use banned APIs +internal class CohostOnAutoInsertEndpoint( + IRemoteServiceProvider remoteServiceProvider, + IHtmlDocumentSynchronizer htmlDocumentSynchronizer, + LSPRequestInvoker requestInvoker, + IRazorDocumentMappingService razorDocumentMappingService, + ILoggerFactory loggerFactory) + : AbstractRazorCohostDocumentRequestHandler, IDynamicRegistrationProvider +{ + private readonly IRemoteServiceProvider _remoteServiceProvider = remoteServiceProvider; + private readonly IHtmlDocumentSynchronizer _htmlDocumentSynchronizer = htmlDocumentSynchronizer; + private readonly LSPRequestInvoker _requestInvoker = requestInvoker; + private readonly ILogger _logger = loggerFactory.GetOrCreateLogger(); + private readonly IRazorDocumentMappingService _razorDocumentMappingService = razorDocumentMappingService; + + protected override bool MutatesSolutionState => false; + + protected override bool RequiresLSPSolution => true; + + public Registration? GetRegistration(VSInternalClientCapabilities clientCapabilities, DocumentFilter[] filter, RazorCohostRequestContext requestContext) + { + if (clientCapabilities.SupportsVisualStudioExtensions) + { + // TODO: Add overload that doesn't use solution + //_remoteServiceProvider.TryInvokeAsync() + var providerTriggerChars = new string[] { ">" }; + var singleServerSupport = true; // TODO: always true for now? Long-term plan? + return new Registration + { + Method = VSInternalMethods.OnAutoInsertName, + RegisterOptions = new VSInternalDocumentOnAutoInsertOptions() + .EnableOnAutoInsert(singleServerSupport, providerTriggerChars) + }; + } + + return null; + } + + protected override RazorTextDocumentIdentifier? GetRazorTextDocumentIdentifier(VSInternalDocumentOnAutoInsertParams request) + => request.TextDocument.ToRazorTextDocumentIdentifier(); + + protected override async Task HandleRequestAsync(VSInternalDocumentOnAutoInsertParams request, RazorCohostRequestContext context, CancellationToken cancellationToken) + { + var razorDocument = context.TextDocument.AssumeNotNull(); + + _logger.LogDebug($"Resolving auto-insertion for {razorDocument.FilePath}"); + + _logger.LogDebug($"Calling OOP to resolve insertion at {request.Position} invoked by typing '{request.Character}'"); + var data = await _remoteServiceProvider.TryInvokeAsync( + razorDocument.Project.Solution, + (service, solutionInfo, cancellationToken) + => service.TryResolveInsertionAsync( + solutionInfo, + razorDocument.Id, + request.Position, + request.Character, + autoCloseTags: true, // TODO: get value from client options + cancellationToken), + cancellationToken).ConfigureAwait(false); + + if (data is { } remoteInsertTextEdit) + { + _logger.LogDebug($"Got insert text edit from OOP {remoteInsertTextEdit}"); + return RemoteInsertTextEdit.ToLspInsertTextEdit(remoteInsertTextEdit); + } + + // If we are here, Razor didn't return anything, so try HTML + + return await TryResolveHtmlInsertionAsync(razorDocument, request, cancellationToken) + .ConfigureAwait(false); + } + + private async Task TryResolveHtmlInsertionAsync( + TextDocument razorDocument, + VSInternalDocumentOnAutoInsertParams request, + CancellationToken cancellationToken) + { + // We support auto-insert in HTML only on "=" + if (request.Character != "=") + { + return null; + } + + var htmlDocument = await _htmlDocumentSynchronizer.TryGetSynchronizedHtmlDocumentAsync(razorDocument, cancellationToken).ConfigureAwait(false); + if (htmlDocument is null) + { + return null; + } + + var autoInsertParams = new VSInternalDocumentOnAutoInsertParams + { + TextDocument = new TextDocumentIdentifier { Uri = htmlDocument.Uri }, + Character = request.Character, + Position = request.Position + }; + + _logger.LogDebug($"Resolving auto-insertion edit for {htmlDocument.Uri}"); + + var result = await _requestInvoker.ReinvokeRequestOnServerAsync( + htmlDocument.Buffer, + VSInternalMethods.OnAutoInsertName, + RazorLSPConstants.HtmlLanguageServerName, + autoInsertParams, + cancellationToken).ConfigureAwait(false); + + if (result?.Response is null) + { + _logger.LogDebug($"Didn't get insert edit back from Html."); + return null; + } + + return result.Response; + } +} From 85cf1f1e141c1632c6a20788d7997cf31bcb4319 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Sat, 27 Jul 2024 01:55:10 -0700 Subject: [PATCH 06/64] Parameter rename --- .../AutoInsert/AutoClosingTagOnAutoInsertProvider.cs | 4 ++-- .../AutoInsert/CloseTextTagOnAutoInsertProvider.cs | 4 ++-- .../AutoInsert/IOnAutoInsertProvider.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs index dbec5f8b399..ca02e9f58d4 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs @@ -54,9 +54,9 @@ public AutoClosingTagOnAutoInsertProvider(ILoggerFactory loggerFactory) public string TriggerCharacter => ">"; - public async ValueTask TryResolveInsertionAsync(Position position, IDocumentSnapshot documentSnapshot, bool autoClosingTagsOption) + public async ValueTask TryResolveInsertionAsync(Position position, IDocumentSnapshot documentSnapshot, bool enableAutoClosingTags) { - if (!(autoClosingTagsOption + if (!(enableAutoClosingTags && documentSnapshot.TryGetText(out var sourceText) && position.TryGetAbsoluteIndex(sourceText, _logger, out var afterCloseAngleIndex) && await TryResolveAutoClosingBehaviorAsync(documentSnapshot, afterCloseAngleIndex) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs index 2acadf0faca..dfe0d6df8be 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs @@ -20,9 +20,9 @@ internal class CloseTextTagOnAutoInsertProvider(ILoggerFactory loggerFactory) : public string TriggerCharacter => ">"; - public async ValueTask TryResolveInsertionAsync(Position position, IDocumentSnapshot documentSnapshot, bool autoClosingTags) + public async ValueTask TryResolveInsertionAsync(Position position, IDocumentSnapshot documentSnapshot, bool enableAutoClosingTags) { - if (!(autoClosingTags + if (!(enableAutoClosingTags && await IsAtTextTagAsync(documentSnapshot, position, _logger).ConfigureAwait(false))) { return default; diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs index fc3ca957a6e..a0e0027ba56 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs @@ -11,5 +11,5 @@ internal interface IOnAutoInsertProvider { string TriggerCharacter { get; } - public ValueTask TryResolveInsertionAsync(Position position, IDocumentSnapshot documentSnapshot, bool autoClosingTagsOption); + public ValueTask TryResolveInsertionAsync(Position position, IDocumentSnapshot documentSnapshot, bool enableAutoClosingTags); } From be0e85f0155b8746a7781389f90ce75666db5c3f Mon Sep 17 00:00:00 2001 From: Alex Gavrilov Date: Sat, 27 Jul 2024 01:52:50 -0700 Subject: [PATCH 07/64] Update src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs Co-authored-by: David Wengier --- .../AutoInsert/AutoClosingTagOnAutoInsertProvider.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs index ca02e9f58d4..480e9a1f3bf 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs @@ -264,7 +264,5 @@ private enum AutoClosingBehavior SelfClosing, } - private record struct TagNameWithClosingBehavior(string TagName, AutoClosingBehavior AutoClosingBehavior) - { - } + private record struct TagNameWithClosingBehavior(string TagName, AutoClosingBehavior AutoClosingBehavior); } From 3ee74796372012acfc5710d42c110f6d4d0851da Mon Sep 17 00:00:00 2001 From: Alex Gavrilov Date: Sat, 27 Jul 2024 01:57:08 -0700 Subject: [PATCH 08/64] Update src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs Co-authored-by: David Wengier --- .../AutoInsert/IAutoInsertService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs index fe8153211d8..e9dd88c58ac 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs @@ -11,7 +11,7 @@ namespace Microsoft.CodeAnalysis.Razor.AutoInsert; internal interface IAutoInsertService { - public IEnumerable TriggerCharacters { get; } + IEnumerable TriggerCharacters { get; } ValueTask TryResolveInsertionAsync( IDocumentSnapshot documentSnapshot, From 5cb62c31e545b0ca1b5e6be3948f3529db907511 Mon Sep 17 00:00:00 2001 From: Alex Gavrilov Date: Sat, 27 Jul 2024 01:57:28 -0700 Subject: [PATCH 09/64] Update src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/InsertTextEdit.cs Co-authored-by: David Wengier --- .../AutoInsert/InsertTextEdit.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/InsertTextEdit.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/InsertTextEdit.cs index e86a2a26a24..5ee88aebfbf 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/InsertTextEdit.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/InsertTextEdit.cs @@ -5,6 +5,4 @@ namespace Microsoft.CodeAnalysis.Razor.AutoInsert; -internal record struct InsertTextEdit(TextEdit TextEdit, InsertTextFormat InsertTextFormat) -{ -} +internal record struct InsertTextEdit(TextEdit TextEdit, InsertTextFormat InsertTextFormat); From 38c9436eef00a4f94b622b952d08d9435be532c2 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Sat, 27 Jul 2024 02:45:47 -0700 Subject: [PATCH 10/64] Fixup after rebase --- .../Remote/RazorServices.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/RazorServices.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/RazorServices.cs index b99ef863b7e..558b737d4c4 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/RazorServices.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/RazorServices.cs @@ -21,6 +21,7 @@ internal static class RazorServices (typeof(IRemoteUriPresentationService), null), (typeof(IRemoteFoldingRangeService), null), (typeof(IRemoteDocumentHighlightService), null), + (typeof(IRemoteAutoInsertService), null), ]; // Internal for testing From 4a6d7f6533c6e6e43288ec052451772f100d071f Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Sat, 27 Jul 2024 04:40:57 -0700 Subject: [PATCH 11/64] More post-build cleanup --- .../AutoClosingTagOnAutoInsertProvider.cs | 34 +++---------------- .../CloseTextTagOnAutoInsertProvider.cs | 18 +++------- 2 files changed, 9 insertions(+), 43 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs index 480e9a1f3bf..dd42596fdcb 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs @@ -7,9 +7,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Syntax; -using Microsoft.CodeAnalysis.Razor.Logging; using Microsoft.CodeAnalysis.Razor.ProjectSystem; -using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.VisualStudio.LanguageServer.Protocol; using RazorSyntaxNode = Microsoft.AspNetCore.Razor.Language.Syntax.SyntaxNode; @@ -38,19 +36,8 @@ internal class AutoClosingTagOnAutoInsertProvider : IOnAutoInsertProvider "track", "wbr" ); - private static readonly ImmutableHashSet s_voidElementsCaseSensitive = s_voidElements.WithComparer(StringComparer.Ordinal); - - private readonly ILogger _logger; - - public AutoClosingTagOnAutoInsertProvider(ILoggerFactory loggerFactory) - { - if (loggerFactory is null) - { - throw new ArgumentNullException(nameof(loggerFactory)); - } - _logger = loggerFactory.GetOrCreateLogger(); - } + private static readonly ImmutableHashSet s_voidElementsCaseSensitive = s_voidElements.WithComparer(StringComparer.Ordinal); public string TriggerCharacter => ">"; @@ -58,7 +45,7 @@ public AutoClosingTagOnAutoInsertProvider(ILoggerFactory loggerFactory) { if (!(enableAutoClosingTags && documentSnapshot.TryGetText(out var sourceText) - && position.TryGetAbsoluteIndex(sourceText, _logger, out var afterCloseAngleIndex) + && sourceText.TryGetAbsoluteIndex(position, out var afterCloseAngleIndex) && await TryResolveAutoClosingBehaviorAsync(documentSnapshot, afterCloseAngleIndex) .ConfigureAwait(false) is { } tagNameWithClosingBehavior)) { @@ -68,11 +55,7 @@ public AutoClosingTagOnAutoInsertProvider(ILoggerFactory loggerFactory) if (tagNameWithClosingBehavior.AutoClosingBehavior == AutoClosingBehavior.EndTag) { var formatForEndTag = InsertTextFormat.Snippet; - var editForEndTag = new TextEdit() - { - NewText = $"$0", - Range = new Range { Start = position, End = position }, - }; + var editForEndTag = VsLspFactory.CreateTextEdit(position, $"$0"); return new InsertTextEdit(editForEndTag, formatForEndTag); } @@ -83,16 +66,7 @@ public AutoClosingTagOnAutoInsertProvider(ILoggerFactory loggerFactory) // Need to replace the `>` with ' />$0' or '/>$0' depending on if there's prefixed whitespace. var insertionText = char.IsWhiteSpace(sourceText[afterCloseAngleIndex - 2]) ? "/" : " /"; - var insertionPosition = new Position(position.Line, position.Character - 1); - var edit = new TextEdit() - { - NewText = insertionText, - Range = new Range - { - Start = insertionPosition, - End = insertionPosition - } - }; + var edit = VsLspFactory.CreateTextEdit(position.Line, position.Character - 1, insertionText); return new InsertTextEdit(edit, format); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs index dfe0d6df8be..3e30d84fd1d 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs @@ -7,45 +7,37 @@ using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Legacy; using Microsoft.AspNetCore.Razor.Language.Syntax; -using Microsoft.CodeAnalysis.Razor.Logging; using Microsoft.CodeAnalysis.Razor.ProjectSystem; -using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.VisualStudio.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.Razor.AutoInsert; -internal class CloseTextTagOnAutoInsertProvider(ILoggerFactory loggerFactory) : IOnAutoInsertProvider +internal class CloseTextTagOnAutoInsertProvider : IOnAutoInsertProvider { - private readonly ILogger _logger = loggerFactory.GetOrCreateLogger(); - public string TriggerCharacter => ">"; public async ValueTask TryResolveInsertionAsync(Position position, IDocumentSnapshot documentSnapshot, bool enableAutoClosingTags) { if (!(enableAutoClosingTags - && await IsAtTextTagAsync(documentSnapshot, position, _logger).ConfigureAwait(false))) + && await IsAtTextTagAsync(documentSnapshot, position).ConfigureAwait(false))) { return default; } // This is a text tag. var format = InsertTextFormat.Snippet; - var edit = new TextEdit() - { - NewText = $"$0", - Range = new Range { Start = position, End = position }, - }; + var edit = VsLspFactory.CreateTextEdit(position, $"$0"); return new InsertTextEdit(edit, format); } - private static async ValueTask IsAtTextTagAsync(IDocumentSnapshot documentSnapshot, Position position, ILogger logger) + private static async ValueTask IsAtTextTagAsync(IDocumentSnapshot documentSnapshot, Position position) { var codeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); var syntaxTree = codeDocument.GetSyntaxTree(); if (!(documentSnapshot.TryGetText(out var sourceText) - && position.TryGetAbsoluteIndex(sourceText, logger, out var absoluteIndex))) + && sourceText.TryGetAbsoluteIndex(position, out var absoluteIndex))) { return false; } From 09452b5975b2807b6fcdf0ddafd9874ae798ddc0 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Sat, 27 Jul 2024 05:33:17 -0700 Subject: [PATCH 12/64] Move common HTML and C# auto-insert trigger chars per CR suggestion --- .../AutoInsert/OnAutoInsertEndpoint.cs | 10 +++++++++- .../VSInternalServerCapabilitiesExtensions.cs | 14 +------------- .../AutoInsert/AutoInsertService.cs | 4 ++++ .../Cohost/CohostOnAutoInsertEndpoint.cs | 10 ++++++++-- 4 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs index cc1043bd752..ea36d9d2da3 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs @@ -52,7 +52,15 @@ internal class OnAutoInsertEndpoint( public void ApplyCapabilities(VSInternalServerCapabilities serverCapabilities, VSInternalClientCapabilities clientCapabilities) { var triggerCharacters = _autoInsertService.TriggerCharacters; - serverCapabilities.EnableOnAutoInsert(_languageServerFeatureOptions.SingleServerSupport, triggerCharacters); + + if (_languageServerFeatureOptions.SingleServerSupport) + { + triggerCharacters = triggerCharacters + .Concat(AutoInsertService.HtmlAllowedAutoInsertTriggerCharacters) + .Concat(AutoInsertService.CSharpAllowedAutoInsertTriggerCharacters); + } + + serverCapabilities.EnableOnAutoInsert(triggerCharacters); } protected override async Task TryHandleAsync(VSInternalDocumentOnAutoInsertParams request, RazorRequestContext requestContext, DocumentPositionInfo positionInfo, CancellationToken cancellationToken) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Hosting/VSInternalServerCapabilitiesExtensions.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Hosting/VSInternalServerCapabilitiesExtensions.cs index dc0c8959fea..b8faa23a93e 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Hosting/VSInternalServerCapabilitiesExtensions.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Hosting/VSInternalServerCapabilitiesExtensions.cs @@ -70,30 +70,18 @@ public static void EnableMapCodeProvider(this VSInternalServerCapabilities serve serverCapabilities.MapCodeProvider = true; } - public static HashSet HtmlAllowedAutoInsertTriggerCharacters { get; } = new(StringComparer.Ordinal) { "=", }; - public static HashSet CSharpAllowedAutoInsertTriggerCharacters { get; } = new(StringComparer.Ordinal) { "'", "/", "\n" }; - public static void EnableOnAutoInsert( this VSInternalServerCapabilities serverCapabilities, - bool singleServerSupport, IEnumerable triggerCharacters) { serverCapabilities.OnAutoInsertProvider = new VSInternalDocumentOnAutoInsertOptions() - .EnableOnAutoInsert(singleServerSupport, triggerCharacters); + .EnableOnAutoInsert(triggerCharacters); } public static VSInternalDocumentOnAutoInsertOptions EnableOnAutoInsert( this VSInternalDocumentOnAutoInsertOptions options, - bool singleServerSupport, IEnumerable triggerCharacters) { - if (singleServerSupport) - { - triggerCharacters = triggerCharacters - .Concat(HtmlAllowedAutoInsertTriggerCharacters) - .Concat(CSharpAllowedAutoInsertTriggerCharacters); - } - options.TriggerCharacters = triggerCharacters.Distinct().ToArray(); return options; diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs index a77686c8f36..f5f3698247a 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -15,6 +16,9 @@ internal class AutoInsertService(IEnumerable onAutoInsert { private readonly IEnumerable _onAutoInsertProviders = onAutoInsertProviders; + public static HashSet HtmlAllowedAutoInsertTriggerCharacters { get; } = new(StringComparer.Ordinal) { "=", }; + public static HashSet CSharpAllowedAutoInsertTriggerCharacters { get; } = new(StringComparer.Ordinal) { "'", "/", "\n" }; + // This gets called just once public IEnumerable TriggerCharacters => _onAutoInsertProviders.Select((provider) => provider.TriggerCharacter); diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs index 95ffb1dfd2b..66330d9f647 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs @@ -2,12 +2,14 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System.Composition; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor; using Microsoft.AspNetCore.Razor.LanguageServer.Hosting; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; +using Microsoft.CodeAnalysis.Razor.AutoInsert; using Microsoft.CodeAnalysis.Razor.DocumentMapping; using Microsoft.CodeAnalysis.Razor.Logging; using Microsoft.CodeAnalysis.Razor.Protocol.AutoInsert; @@ -53,12 +55,16 @@ internal class CohostOnAutoInsertEndpoint( // TODO: Add overload that doesn't use solution //_remoteServiceProvider.TryInvokeAsync() var providerTriggerChars = new string[] { ">" }; - var singleServerSupport = true; // TODO: always true for now? Long-term plan? + + var triggerCharacters = providerTriggerChars + .Concat(AutoInsertService.HtmlAllowedAutoInsertTriggerCharacters) + .Concat(AutoInsertService.CSharpAllowedAutoInsertTriggerCharacters); + return new Registration { Method = VSInternalMethods.OnAutoInsertName, RegisterOptions = new VSInternalDocumentOnAutoInsertOptions() - .EnableOnAutoInsert(singleServerSupport, providerTriggerChars) + .EnableOnAutoInsert(triggerCharacters) }; } From 447dd542a5bf8c94f8c7d647bdea35ae094fdb0f Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Mon, 29 Jul 2024 05:53:12 -0700 Subject: [PATCH 13/64] More cleanup after rebase --- .../AutoInsert/OnAutoInsertEndpoint.cs | 4 +-- .../AutoInsert/RemoteAutoInsertService.cs | 16 +++++++----- .../RemoteAutoInsertServiceFactory.cs | 25 ------------------- .../AutoInsert/RemoteOnAutoInsertProviders.cs | 9 +++---- .../Cohost/CohostOnAutoInsertEndpoint.cs | 6 ++--- 5 files changed, 19 insertions(+), 41 deletions(-) delete mode 100644 src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertServiceFactory.cs diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs index ea36d9d2da3..418a5b6478f 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs @@ -112,7 +112,7 @@ public void ApplyCapabilities(VSInternalServerCapabilities serverCapabilities, V if (positionInfo.LanguageKind == RazorLanguageKind.Html) { - if (!VSInternalServerCapabilitiesExtensions.HtmlAllowedAutoInsertTriggerCharacters.Contains(request.Character)) + if (!AutoInsertService.HtmlAllowedAutoInsertTriggerCharacters.Contains(request.Character)) { Logger.LogInformation($"Inapplicable HTML trigger char {request.Character}."); return SpecializedTasks.Null(); @@ -128,7 +128,7 @@ public void ApplyCapabilities(VSInternalServerCapabilities serverCapabilities, V } else if (positionInfo.LanguageKind == RazorLanguageKind.CSharp) { - if (!VSInternalServerCapabilitiesExtensions.CSharpAllowedAutoInsertTriggerCharacters.Contains(request.Character)) + if (!AutoInsertService.CSharpAllowedAutoInsertTriggerCharacters.Contains(request.Character)) { Logger.LogInformation($"Inapplicable C# trigger char {request.Character}."); return SpecializedTasks.Null(); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs index ed972b0cea1..557ffa1f7af 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs @@ -12,13 +12,17 @@ using Microsoft.VisualStudio.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.Remote.Razor; -internal class RemoteAutoInsertService( - IRazorServiceBroker serviceBroker, - DocumentSnapshotFactory documentSnapshotFactory, - IAutoInsertService autoInsertService) - : RazorDocumentServiceBase(serviceBroker, documentSnapshotFactory), IRemoteAutoInsertService +internal class RemoteAutoInsertService(in ServiceArgs args) + : RazorDocumentServiceBase(in args), IRemoteAutoInsertService { - private readonly IAutoInsertService _autoInsertService = autoInsertService; + internal sealed class Factory : FactoryBase + { + protected override IRemoteAutoInsertService CreateService(in ServiceArgs args) + => new RemoteAutoInsertService(in args); + } + + private readonly IAutoInsertService _autoInsertService + = args.ExportProvider.GetExportedValue(); public IEnumerable TriggerCharacters => _autoInsertService.TriggerCharacters; diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertServiceFactory.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertServiceFactory.cs deleted file mode 100644 index 8e4d335d9ca..00000000000 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertServiceFactory.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root for license information. - -using Microsoft.CodeAnalysis.Razor.AutoInsert; -using Microsoft.CodeAnalysis.Razor.Remote; -using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; -using Microsoft.VisualStudio.Composition; - -namespace Microsoft.CodeAnalysis.Remote.Razor; - -internal sealed class RemoteAutoInsertServiceFactory : RazorServiceFactoryBase -{ - // WARNING: We must always have a parameterless constructor in order to be properly handled by ServiceHub. - public RemoteAutoInsertServiceFactory() - : base(RazorServices.Descriptors) - { - } - - protected override IRemoteAutoInsertService CreateService(IRazorServiceBroker serviceBroker, ExportProvider exportProvider) - { - var infoService = exportProvider.GetExportedValue(); - var documentSnapshotFactory = exportProvider.GetExportedValue(); - return new RemoteAutoInsertService(serviceBroker, documentSnapshotFactory, infoService); - } -} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteOnAutoInsertProviders.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteOnAutoInsertProviders.cs index 832b10aa418..e619a1aa100 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteOnAutoInsertProviders.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteOnAutoInsertProviders.cs @@ -3,16 +3,15 @@ using System.Composition; using Microsoft.CodeAnalysis.Razor.AutoInsert; -using Microsoft.CodeAnalysis.Razor.Logging; namespace Microsoft.CodBeAnalysis.Remote.Razor.AutoInsert; [Shared] [Export(typeof(IOnAutoInsertProvider))] -internal sealed class RemoteAutoClosingTagOnAutoInsertProvider(ILoggerFactory loggerFactory) - : AutoClosingTagOnAutoInsertProvider(loggerFactory); +internal sealed class RemoteAutoClosingTagOnAutoInsertProvider + : AutoClosingTagOnAutoInsertProvider; [Shared] [Export(typeof(IOnAutoInsertProvider))] -internal sealed class RemoteCloseTextTagOnAutoInsertProvider(ILoggerFactory loggerFactory) - : CloseTextTagOnAutoInsertProvider(loggerFactory); +internal sealed class RemoteCloseTextTagOnAutoInsertProvider + : CloseTextTagOnAutoInsertProvider; diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs index 66330d9f647..86c0598a03b 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs @@ -31,14 +31,14 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.LanguageClient.Cohost; [method: ImportingConstructor] #pragma warning restore RS0030 // Do not use banned APIs internal class CohostOnAutoInsertEndpoint( - IRemoteServiceProvider remoteServiceProvider, + IRemoteServiceInvoker remoteServiceInvoker, IHtmlDocumentSynchronizer htmlDocumentSynchronizer, LSPRequestInvoker requestInvoker, IRazorDocumentMappingService razorDocumentMappingService, ILoggerFactory loggerFactory) : AbstractRazorCohostDocumentRequestHandler, IDynamicRegistrationProvider { - private readonly IRemoteServiceProvider _remoteServiceProvider = remoteServiceProvider; + private readonly IRemoteServiceInvoker _remoteServiceInvoker = remoteServiceInvoker; private readonly IHtmlDocumentSynchronizer _htmlDocumentSynchronizer = htmlDocumentSynchronizer; private readonly LSPRequestInvoker _requestInvoker = requestInvoker; private readonly ILogger _logger = loggerFactory.GetOrCreateLogger(); @@ -81,7 +81,7 @@ internal class CohostOnAutoInsertEndpoint( _logger.LogDebug($"Resolving auto-insertion for {razorDocument.FilePath}"); _logger.LogDebug($"Calling OOP to resolve insertion at {request.Position} invoked by typing '{request.Character}'"); - var data = await _remoteServiceProvider.TryInvokeAsync( + var data = await _remoteServiceInvoker.TryInvokeAsync( razorDocument.Project.Solution, (service, solutionInfo, cancellationToken) => service.TryResolveInsertionAsync( From 70430a476b4e2120e92c65cb01a3c0c3a648839f Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Mon, 29 Jul 2024 06:29:57 -0700 Subject: [PATCH 14/64] Add IOnAutoInsertTiggerCharacterProvider per CR suggestion --- .../AutoInsert/IOnAutoInsertProvider.cs | 4 +--- .../IOnAutoInsertTriggerCharacterProvider.cs | 9 +++++++++ .../Cohost/CohostOnAutoInsertEndpoint.cs | 10 +++++++--- ...ostOnAutoInsertTriggerCharacterProviders.cs | 18 ++++++++++++++++++ 4 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertTriggerCharacterProvider.cs create mode 100644 src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertTriggerCharacterProviders.cs diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs index a0e0027ba56..23b2fe45524 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs @@ -7,9 +7,7 @@ namespace Microsoft.CodeAnalysis.Razor.AutoInsert; -internal interface IOnAutoInsertProvider +internal interface IOnAutoInsertProvider : IOnAutoInsertTriggerCharacterProvider { - string TriggerCharacter { get; } - public ValueTask TryResolveInsertionAsync(Position position, IDocumentSnapshot documentSnapshot, bool enableAutoClosingTags); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertTriggerCharacterProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertTriggerCharacterProvider.cs new file mode 100644 index 00000000000..cfc1ffbed5e --- /dev/null +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertTriggerCharacterProvider.cs @@ -0,0 +1,9 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +namespace Microsoft.CodeAnalysis.Razor.AutoInsert; + +internal interface IOnAutoInsertTriggerCharacterProvider +{ + string TriggerCharacter { get; } +} diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs index 86c0598a03b..a1c65d1e67a 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. +using System.Collections.Generic; using System.Composition; using System.Linq; using System.Threading; @@ -32,6 +33,9 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.LanguageClient.Cohost; #pragma warning restore RS0030 // Do not use banned APIs internal class CohostOnAutoInsertEndpoint( IRemoteServiceInvoker remoteServiceInvoker, +#pragma warning disable RS0030 // Do not use banned APIs + [ImportMany] IEnumerable onAutoInsertTriggerCharacterProviders, +#pragma warning restore RS0030 // Do not use banned APIs IHtmlDocumentSynchronizer htmlDocumentSynchronizer, LSPRequestInvoker requestInvoker, IRazorDocumentMappingService razorDocumentMappingService, @@ -39,6 +43,7 @@ internal class CohostOnAutoInsertEndpoint( : AbstractRazorCohostDocumentRequestHandler, IDynamicRegistrationProvider { private readonly IRemoteServiceInvoker _remoteServiceInvoker = remoteServiceInvoker; + private readonly IEnumerable _onAutoInsertTriggerCharacterProviders = onAutoInsertTriggerCharacterProviders; private readonly IHtmlDocumentSynchronizer _htmlDocumentSynchronizer = htmlDocumentSynchronizer; private readonly LSPRequestInvoker _requestInvoker = requestInvoker; private readonly ILogger _logger = loggerFactory.GetOrCreateLogger(); @@ -52,9 +57,8 @@ internal class CohostOnAutoInsertEndpoint( { if (clientCapabilities.SupportsVisualStudioExtensions) { - // TODO: Add overload that doesn't use solution - //_remoteServiceProvider.TryInvokeAsync() - var providerTriggerChars = new string[] { ">" }; + var providerTriggerChars = _onAutoInsertTriggerCharacterProviders + .Select((provider) => provider.TriggerCharacter); var triggerCharacters = providerTriggerChars .Concat(AutoInsertService.HtmlAllowedAutoInsertTriggerCharacters) diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertTriggerCharacterProviders.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertTriggerCharacterProviders.cs new file mode 100644 index 00000000000..8742bc01a15 --- /dev/null +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertTriggerCharacterProviders.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using System.ComponentModel.Composition; +using Microsoft.CodeAnalysis.Razor.AutoInsert; + +namespace Microsoft.VisualStudio.LanguageServices.Razor.LanguageClient.Cohost; + +// These are needed to we can get auto-insert trigger character collection +// during registration of CohostOnAutoInsertProvider without using a remote service + +[Export(typeof(IOnAutoInsertTriggerCharacterProvider))] +internal sealed class CohostAutoClosingTagOnAutoInsertTriggerCharacterProvider + : AutoClosingTagOnAutoInsertProvider; + +[Export(typeof(IOnAutoInsertTriggerCharacterProvider))] +internal sealed class CohostCloseTextTagOnAutoInsertTriggerCharacterProvider + : CloseTextTagOnAutoInsertProvider; From 02db4e419492cc47e3ad9be8db61189fb322aada Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Mon, 29 Jul 2024 06:59:35 -0700 Subject: [PATCH 15/64] Removing trigger characters property from RemoteAutoInsertService per CR suggestion --- .../Remote/IRemoteAutoInsertService.cs | 2 -- .../AutoInsert/RemoteAutoInsertService.cs | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteAutoInsertService.cs index c18824a9d99..098b98f21ea 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteAutoInsertService.cs @@ -13,8 +13,6 @@ namespace Microsoft.CodeAnalysis.Razor.Remote; internal interface IRemoteAutoInsertService : IDisposable { - IEnumerable TriggerCharacters { get; } - ValueTask TryResolveInsertionAsync( RazorPinnedSolutionInfoWrapper solutionInfo, DocumentId documentId, diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs index 557ffa1f7af..f5acce3fb48 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs @@ -24,8 +24,6 @@ protected override IRemoteAutoInsertService CreateService(in ServiceArgs args) private readonly IAutoInsertService _autoInsertService = args.ExportProvider.GetExportedValue(); - public IEnumerable TriggerCharacters => _autoInsertService.TriggerCharacters; - public ValueTask TryResolveInsertionAsync( RazorPinnedSolutionInfoWrapper solutionInfo, DocumentId documentId, From b3ec7968995ee695ee1677438797ca2b2d2a6ec6 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Tue, 6 Aug 2024 20:34:02 -0700 Subject: [PATCH 16/64] Switch to using RemoteResponse --- .../Remote/IRemoteAutoInsertService.cs | 7 ++- .../AutoInsert/RemoteAutoInsertService.cs | 49 ++++++++++++++----- .../Cohost/CohostOnAutoInsertEndpoint.cs | 11 +++-- 3 files changed, 49 insertions(+), 18 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteAutoInsertService.cs index 098b98f21ea..6b91b2644d9 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteAutoInsertService.cs @@ -2,18 +2,17 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.ExternalAccess.Razor; -using Microsoft.CodeAnalysis.Razor.Protocol.AutoInsert; using Microsoft.VisualStudio.LanguageServer.Protocol; -namespace Microsoft.CodeAnalysis.Razor.Remote; +using Response = Microsoft.CodeAnalysis.Razor.Remote.RemoteResponse; internal interface IRemoteAutoInsertService : IDisposable { - ValueTask TryResolveInsertionAsync( + ValueTask TryResolveInsertionAsync( RazorPinnedSolutionInfoWrapper solutionInfo, DocumentId documentId, Position position, diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs index f5acce3fb48..f6c25ea0e76 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs @@ -1,17 +1,20 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ExternalAccess.Razor; using Microsoft.CodeAnalysis.Razor.AutoInsert; +using Microsoft.CodeAnalysis.Razor.DocumentMapping; +using Microsoft.CodeAnalysis.Razor.Protocol; using Microsoft.CodeAnalysis.Razor.Protocol.AutoInsert; -using Microsoft.CodeAnalysis.Razor.Remote; using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; using Microsoft.VisualStudio.LanguageServer.Protocol; +using Response = Microsoft.CodeAnalysis.Razor.Remote.RemoteResponse; + namespace Microsoft.CodeAnalysis.Remote.Razor; + internal class RemoteAutoInsertService(in ServiceArgs args) : RazorDocumentServiceBase(in args), IRemoteAutoInsertService { @@ -23,8 +26,10 @@ protected override IRemoteAutoInsertService CreateService(in ServiceArgs args) private readonly IAutoInsertService _autoInsertService = args.ExportProvider.GetExportedValue(); + private readonly IRazorDocumentMappingService _documentMappingService + = args.ExportProvider.GetExportedValue(); - public ValueTask TryResolveInsertionAsync( + public ValueTask TryResolveInsertionAsync( RazorPinnedSolutionInfoWrapper solutionInfo, DocumentId documentId, Position position, @@ -42,19 +47,41 @@ private readonly IAutoInsertService _autoInsertService cancellationToken), cancellationToken); - private async ValueTask TryResolveInsertionAsync( + private async ValueTask TryResolveInsertionAsync( RemoteDocumentContext remoteDocumentContext, Position position, string character, bool autoCloseTags, CancellationToken cancellationToken) { - var insertTextEdit = await _autoInsertService.TryResolveInsertionAsync( - remoteDocumentContext.Snapshot, - position, - character, - autoCloseTags, - cancellationToken); - return insertTextEdit.HasValue ? RemoteInsertTextEdit.FromLspInsertTextEdit(insertTextEdit.Value) : null; + var sourceText = await remoteDocumentContext.GetSourceTextAsync(cancellationToken).ConfigureAwait(false); + if (!sourceText.TryGetAbsoluteIndex(position, out var index)) + { + return Response.NoFurtherHandling; + } + + var codeDocument = await remoteDocumentContext.GetCodeDocumentAsync(cancellationToken).ConfigureAwait(false); + + var languageKind = _documentMappingService.GetLanguageKind(codeDocument, index, rightAssociative: true); + if (languageKind is RazorLanguageKind.Html) + { + return Response.CallHtml; + } + else if (languageKind is RazorLanguageKind.Razor) + { + var insertTextEdit = await _autoInsertService.TryResolveInsertionAsync( + remoteDocumentContext.Snapshot, + position, + character, + autoCloseTags, + cancellationToken); + return insertTextEdit.HasValue + ? Response.Results(RemoteInsertTextEdit.FromLspInsertTextEdit(insertTextEdit.Value)) + : Response.NoFurtherHandling; + } + + // TODO: handle C# case + + return Response.NoFurtherHandling; } } diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs index a1c65d1e67a..a7b0e0b6558 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs @@ -21,6 +21,7 @@ using Microsoft.VisualStudio.Razor.LanguageClient.Extensions; using RazorLSPConstants = Microsoft.VisualStudio.Razor.LanguageClient.RazorLSPConstants; +using Response = Microsoft.CodeAnalysis.Razor.Remote.RemoteResponse; namespace Microsoft.VisualStudio.LanguageServices.Razor.LanguageClient.Cohost; @@ -85,7 +86,7 @@ internal class CohostOnAutoInsertEndpoint( _logger.LogDebug($"Resolving auto-insertion for {razorDocument.FilePath}"); _logger.LogDebug($"Calling OOP to resolve insertion at {request.Position} invoked by typing '{request.Character}'"); - var data = await _remoteServiceInvoker.TryInvokeAsync( + var data = await _remoteServiceInvoker.TryInvokeAsync( razorDocument.Project.Solution, (service, solutionInfo, cancellationToken) => service.TryResolveInsertionAsync( @@ -97,14 +98,18 @@ internal class CohostOnAutoInsertEndpoint( cancellationToken), cancellationToken).ConfigureAwait(false); - if (data is { } remoteInsertTextEdit) + if (data.Result is { } remoteInsertTextEdit) { _logger.LogDebug($"Got insert text edit from OOP {remoteInsertTextEdit}"); return RemoteInsertTextEdit.ToLspInsertTextEdit(remoteInsertTextEdit); } - // If we are here, Razor didn't return anything, so try HTML + if (data.StopHandling) + { + return null; + } + // Got no data but no signal to stop handling, so try HTML return await TryResolveHtmlInsertionAsync(razorDocument, request, cancellationToken) .ConfigureAwait(false); } From 2813a97cec407345b214eda8037fd091b945dd78 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Tue, 6 Aug 2024 21:03:07 -0700 Subject: [PATCH 17/64] Fixup bad resolve after rebase, and extra whitespace in RazorLangaugeServer --- .../RazorLanguageServer.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs index 56990d0af9c..bc222037136 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs @@ -154,7 +154,6 @@ protected override ILspServices ConstructLspServices() services.AddSingleton(); services.AddSingleton(); - } // Other @@ -183,8 +182,6 @@ static void AddHandlers(IServiceCollection services, LanguageServerFeatureOption services.AddTransient(sp => sp.GetRequiredService()); services.AddHandlerWithCapabilities(); - services.AddHandlerWithCapabilities(); - services.AddHandlerWithCapabilities(); services.AddHandlerWithCapabilities(); if (!featureOptions.UseRazorCohostServer) From f19fd7f43b3634db96cbc65ad77c1dafbfe396c2 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Thu, 8 Aug 2024 04:52:34 -0700 Subject: [PATCH 18/64] Complete capabilities check in CohostingOnAutoInsertEndpoing registration --- .../LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs index a7b0e0b6558..03e7cc92fdf 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs @@ -56,7 +56,8 @@ internal class CohostOnAutoInsertEndpoint( public Registration? GetRegistration(VSInternalClientCapabilities clientCapabilities, DocumentFilter[] filter, RazorCohostRequestContext requestContext) { - if (clientCapabilities.SupportsVisualStudioExtensions) + if (clientCapabilities.SupportsVisualStudioExtensions + && (clientCapabilities.TextDocument as VSInternalTextDocumentClientCapabilities)?.OnAutoInsert?.DynamicRegistration == true) { var providerTriggerChars = _onAutoInsertTriggerCharacterProviders .Select((provider) => provider.TriggerCharacter); From 1dbc2c246df8b75b643f5157f6a8189d820000eb Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Thu, 8 Aug 2024 10:47:49 -0700 Subject: [PATCH 19/64] Change input position type to serializable :LinePosition --- .../Remote/IRemoteAutoInsertService.cs | 4 ++-- .../AutoInsert/RemoteAutoInsertService.cs | 12 +++++++----- .../Cohost/CohostOnAutoInsertEndpoint.cs | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteAutoInsertService.cs index 6b91b2644d9..9cbf726f2bf 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteAutoInsertService.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.ExternalAccess.Razor; -using Microsoft.VisualStudio.LanguageServer.Protocol; +using Microsoft.CodeAnalysis.Text; using Response = Microsoft.CodeAnalysis.Razor.Remote.RemoteResponse; @@ -15,7 +15,7 @@ internal interface IRemoteAutoInsertService : IDisposable ValueTask TryResolveInsertionAsync( RazorPinnedSolutionInfoWrapper solutionInfo, DocumentId documentId, - Position position, + LinePosition position, string character, bool autoCloseTags, CancellationToken cancellationToken); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs index f6c25ea0e76..bb38f0a3c60 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.Razor.Protocol; using Microsoft.CodeAnalysis.Razor.Protocol.AutoInsert; using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; +using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; using Response = Microsoft.CodeAnalysis.Razor.Remote.RemoteResponse; @@ -32,7 +33,7 @@ private readonly IRazorDocumentMappingService _documentMappingService public ValueTask TryResolveInsertionAsync( RazorPinnedSolutionInfoWrapper solutionInfo, DocumentId documentId, - Position position, + LinePosition linePosition, string character, bool autoCloseTags, CancellationToken cancellationToken) @@ -41,7 +42,7 @@ public ValueTask TryResolveInsertionAsync( documentId, context => TryResolveInsertionAsync( context, - position, + linePosition, character, autoCloseTags, cancellationToken), @@ -49,13 +50,13 @@ public ValueTask TryResolveInsertionAsync( private async ValueTask TryResolveInsertionAsync( RemoteDocumentContext remoteDocumentContext, - Position position, + LinePosition linePosition, string character, bool autoCloseTags, CancellationToken cancellationToken) { var sourceText = await remoteDocumentContext.GetSourceTextAsync(cancellationToken).ConfigureAwait(false); - if (!sourceText.TryGetAbsoluteIndex(position, out var index)) + if (!sourceText.TryGetAbsoluteIndex(linePosition, out var index)) { return Response.NoFurtherHandling; } @@ -69,9 +70,10 @@ private async ValueTask TryResolveInsertionAsync( } else if (languageKind is RazorLanguageKind.Razor) { + linePosition.Deconstruct(out var line, out var lineCharacter); var insertTextEdit = await _autoInsertService.TryResolveInsertionAsync( remoteDocumentContext.Snapshot, - position, + new Position(line, lineCharacter), character, autoCloseTags, cancellationToken); diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs index 03e7cc92fdf..66856a118f4 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs @@ -93,7 +93,7 @@ internal class CohostOnAutoInsertEndpoint( => service.TryResolveInsertionAsync( solutionInfo, razorDocument.Id, - request.Position, + request.Position.ToLinePosition(), request.Character, autoCloseTags: true, // TODO: get value from client options cancellationToken), From c324f349927c7088d7bcfc2a000ac9c34982dd98 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Thu, 8 Aug 2024 15:35:07 -0700 Subject: [PATCH 20/64] Fixing RemoteInsertTextEdit to use properly annotated (for serialization) types only --- .../AutoInsert/RemoteInsertTextEdit.cs | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs index c27975dd0ae..364758a6a0a 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs @@ -3,31 +3,40 @@ using System.Runtime.Serialization; using Microsoft.CodeAnalysis.Razor.AutoInsert; +using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.Razor.Protocol.AutoInsert; -// TODO: check annotations [DataContract] internal readonly record struct RemoteInsertTextEdit( - [property: DataMember(Name = "textEdit")] - TextEdit TextEdit, - [property: DataMember(Name = "insertTextFormat")] + [property: DataMember(Order = 0)] + LinePositionSpan LinePositionSpan, + [property: DataMember(Order = 1)] + string NewText, + [property: DataMember(Order = 2)] InsertTextFormat InsertTextFormat ) { public static RemoteInsertTextEdit FromLspInsertTextEdit(InsertTextEdit edit) - => new (edit.TextEdit, edit.InsertTextFormat); + => new ( + edit.TextEdit.Range.ToLinePositionSpan(), + edit.TextEdit.NewText, + edit.InsertTextFormat); public static VSInternalDocumentOnAutoInsertResponseItem ToLspInsertTextEdit(RemoteInsertTextEdit edit) => new() { - TextEdit = edit.TextEdit, + TextEdit = new() + { + Range = edit.LinePositionSpan.ToRange(), + NewText = edit.NewText + }, TextEditFormat = edit.InsertTextFormat, }; public override string ToString() { - return $"({TextEdit.Range.Start.Line}, {TextEdit.Range.Start.Character})-({TextEdit.Range.End.Line}, {TextEdit.Range.End.Character}), '{TextEdit.NewText}', {InsertTextFormat}"; + return $"({LinePositionSpan.Start.Line}, {LinePositionSpan.Start.Character})-({LinePositionSpan.End.Line}, {LinePositionSpan.End.Character}), '{NewText}', {InsertTextFormat}"; } } From 10c8c15efb99e9448cdc8ecc008eb6c3b3c0330b Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Fri, 9 Aug 2024 00:15:35 -0700 Subject: [PATCH 21/64] Support for delegating auto-insert to C# --- .../AutoInsert/RemoteInsertTextEdit.cs | 7 ++++++ .../AutoInsert/RemoteAutoInsertService.cs | 25 ++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs index 364758a6a0a..3c40a5ab0fa 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs @@ -24,6 +24,13 @@ public static RemoteInsertTextEdit FromLspInsertTextEdit(InsertTextEdit edit) edit.TextEdit.NewText, edit.InsertTextFormat); + public static RemoteInsertTextEdit FromVsPlatformAutoInsertResponse( + VSInternalDocumentOnAutoInsertResponseItem response) + => new( + response.TextEdit.Range.ToLinePositionSpan(), + response.TextEdit.NewText, + response.TextEditFormat); + public static VSInternalDocumentOnAutoInsertResponseItem ToLspInsertTextEdit(RemoteInsertTextEdit edit) => new() { diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs index bb38f0a3c60..00dfe5b6209 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs @@ -4,15 +4,18 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ExternalAccess.Razor; +using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers; using Microsoft.CodeAnalysis.Razor.AutoInsert; using Microsoft.CodeAnalysis.Razor.DocumentMapping; using Microsoft.CodeAnalysis.Razor.Protocol; using Microsoft.CodeAnalysis.Razor.Protocol.AutoInsert; +using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; using Response = Microsoft.CodeAnalysis.Razor.Remote.RemoteResponse; +using RoslynFormattingOptions = Roslyn.LanguageServer.Protocol.FormattingOptions; namespace Microsoft.CodeAnalysis.Remote.Razor; @@ -29,6 +32,8 @@ private readonly IAutoInsertService _autoInsertService = args.ExportProvider.GetExportedValue(); private readonly IRazorDocumentMappingService _documentMappingService = args.ExportProvider.GetExportedValue(); + private readonly IFilePathService _filePathService = + args.ExportProvider.GetExportedValue(); public ValueTask TryResolveInsertionAsync( RazorPinnedSolutionInfoWrapper solutionInfo, @@ -82,7 +87,25 @@ private async ValueTask TryResolveInsertionAsync( : Response.NoFurtherHandling; } - // TODO: handle C# case + // C# case + + var csharpDocument = codeDocument.GetCSharpDocument(); + if (_documentMappingService.TryMapToGeneratedDocumentPosition(csharpDocument, index, out var mappedPosition, out _)) + { + var generatedDocument = await remoteDocumentContext.GetGeneratedDocumentAsync(_filePathService, cancellationToken).ConfigureAwait(false); + // TODO: use correct options rather than default + var formattingOptions = new RoslynFormattingOptions(); + var autoInsertResponseItem = await OnAutoInsert.GetOnAutoInsertResponseAsync( + generatedDocument, + mappedPosition, + character, + formattingOptions, + cancellationToken + ); + return autoInsertResponseItem is not null + ? Response.Results(RemoteInsertTextEdit.FromVsPlatformAutoInsertResponse(autoInsertResponseItem)) + : Response.NoFurtherHandling; + } return Response.NoFurtherHandling; } From c5dd51a6930073d8f53531b70b55115e65529fb8 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Fri, 9 Aug 2024 05:12:54 -0700 Subject: [PATCH 22/64] Fixup after rebase --- .../Protocol/AutoInsert/RemoteInsertTextEdit.cs | 17 +++++++++++------ .../AutoInsert/RemoteAutoInsertService.cs | 7 ++++--- .../Cohost/CohostOnAutoInsertEndpoint.cs | 5 ++--- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs index 3c40a5ab0fa..e9ca6cca32c 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs @@ -5,6 +5,11 @@ using Microsoft.CodeAnalysis.Razor.AutoInsert; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; +using static Roslyn.LanguageServer.Protocol.RoslynLspExtensions; +using static Microsoft.VisualStudio.LanguageServer.Protocol.VsLspExtensions; + +using RoslynVSInternalDocumentOnAutoInsertResponseItem = Roslyn.LanguageServer.Protocol.VSInternalDocumentOnAutoInsertResponseItem; +using RoslynInsertTextFormat = Roslyn.LanguageServer.Protocol.InsertTextFormat; namespace Microsoft.CodeAnalysis.Razor.Protocol.AutoInsert; @@ -15,17 +20,17 @@ internal readonly record struct RemoteInsertTextEdit( [property: DataMember(Order = 1)] string NewText, [property: DataMember(Order = 2)] - InsertTextFormat InsertTextFormat + RoslynInsertTextFormat InsertTextFormat ) { public static RemoteInsertTextEdit FromLspInsertTextEdit(InsertTextEdit edit) => new ( edit.TextEdit.Range.ToLinePositionSpan(), edit.TextEdit.NewText, - edit.InsertTextFormat); + (RoslynInsertTextFormat)edit.InsertTextFormat); - public static RemoteInsertTextEdit FromVsPlatformAutoInsertResponse( - VSInternalDocumentOnAutoInsertResponseItem response) + public static RemoteInsertTextEdit FromRoslynAutoInsertResponse( + RoslynVSInternalDocumentOnAutoInsertResponseItem response) => new( response.TextEdit.Range.ToLinePositionSpan(), response.TextEdit.NewText, @@ -36,10 +41,10 @@ public static VSInternalDocumentOnAutoInsertResponseItem ToLspInsertTextEdit(Rem { TextEdit = new() { - Range = edit.LinePositionSpan.ToRange(), + Range = VsLspExtensions.ToRange(edit.LinePositionSpan), NewText = edit.NewText }, - TextEditFormat = edit.InsertTextFormat, + TextEditFormat = (InsertTextFormat)edit.InsertTextFormat, }; public override string ToString() diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs index 00dfe5b6209..f7957893713 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis.ExternalAccess.Razor; using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers; using Microsoft.CodeAnalysis.Razor.AutoInsert; @@ -30,8 +31,8 @@ protected override IRemoteAutoInsertService CreateService(in ServiceArgs args) private readonly IAutoInsertService _autoInsertService = args.ExportProvider.GetExportedValue(); - private readonly IRazorDocumentMappingService _documentMappingService - = args.ExportProvider.GetExportedValue(); + private readonly IDocumentMappingService _documentMappingService + = args.ExportProvider.GetExportedValue(); private readonly IFilePathService _filePathService = args.ExportProvider.GetExportedValue(); @@ -103,7 +104,7 @@ private async ValueTask TryResolveInsertionAsync( cancellationToken ); return autoInsertResponseItem is not null - ? Response.Results(RemoteInsertTextEdit.FromVsPlatformAutoInsertResponse(autoInsertResponseItem)) + ? Response.Results(RemoteInsertTextEdit.FromRoslynAutoInsertResponse(autoInsertResponseItem)) : Response.NoFurtherHandling; } diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs index 66856a118f4..299b856b074 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs @@ -18,7 +18,6 @@ using Microsoft.VisualStudio.LanguageServer.ContainedLanguage; using Microsoft.VisualStudio.LanguageServer.Protocol; using Microsoft.VisualStudio.Razor.LanguageClient.Cohost; -using Microsoft.VisualStudio.Razor.LanguageClient.Extensions; using RazorLSPConstants = Microsoft.VisualStudio.Razor.LanguageClient.RazorLSPConstants; using Response = Microsoft.CodeAnalysis.Razor.Remote.RemoteResponse; @@ -39,7 +38,7 @@ internal class CohostOnAutoInsertEndpoint( #pragma warning restore RS0030 // Do not use banned APIs IHtmlDocumentSynchronizer htmlDocumentSynchronizer, LSPRequestInvoker requestInvoker, - IRazorDocumentMappingService razorDocumentMappingService, + IDocumentMappingService razorDocumentMappingService, ILoggerFactory loggerFactory) : AbstractRazorCohostDocumentRequestHandler, IDynamicRegistrationProvider { @@ -48,7 +47,7 @@ internal class CohostOnAutoInsertEndpoint( private readonly IHtmlDocumentSynchronizer _htmlDocumentSynchronizer = htmlDocumentSynchronizer; private readonly LSPRequestInvoker _requestInvoker = requestInvoker; private readonly ILogger _logger = loggerFactory.GetOrCreateLogger(); - private readonly IRazorDocumentMappingService _razorDocumentMappingService = razorDocumentMappingService; + private readonly IDocumentMappingService _razorDocumentMappingService = razorDocumentMappingService; protected override bool MutatesSolutionState => false; From 2fd5c8a7d481d5c0ef8188a7f36f455bb120cad7 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Fri, 9 Aug 2024 08:42:57 -0700 Subject: [PATCH 23/64] Fixup AutoClosingTagOnAutoInsertProviderTest tests --- .../AutoClosingTagOnAutoInsertProviderTest.cs | 872 +++++++++--------- .../RazorOnAutoInsertProviderTestBase.cs | 17 +- 2 files changed, 444 insertions(+), 445 deletions(-) diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/AutoClosingTagOnAutoInsertProviderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/AutoClosingTagOnAutoInsertProviderTest.cs index 0e806675535..31dbb68ef69 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/AutoClosingTagOnAutoInsertProviderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/AutoClosingTagOnAutoInsertProviderTest.cs @@ -2,9 +2,10 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System; +using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; -using Microsoft.AspNetCore.Razor.LanguageServer.Hosting; using Microsoft.AspNetCore.Razor.Test.Common; +using Microsoft.CodeAnalysis.Razor.AutoInsert; using Xunit; using Xunit.Abstractions; using static Microsoft.AspNetCore.Razor.Language.CommonMetadata; @@ -13,8 +14,6 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.AutoInsert; public class AutoClosingTagOnAutoInsertProviderTest(ITestOutputHelper testOutput) : RazorOnAutoInsertProviderTestBase(testOutput) { - private RazorLSPOptions Options { get; set; } = RazorLSPOptions.Default; - private static TagHelperDescriptor CatchAllTagHelper { get @@ -101,9 +100,9 @@ private static TagHelperDescriptor WithoutEndTagTagHelper [Fact] [WorkItem("https://github.com/dotnet/razor-tooling/issues/6217")] - public void OnTypeCloseAngle_ConflictingAutoClosingBehaviorsChoosesMostSpecific() + public async Task OnTypeCloseAngle_ConflictingAutoClosingBehaviorsChoosesMostSpecificAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" @addTagHelper *, TestAssembly @@ -121,9 +120,9 @@ public void OnTypeCloseAngle_ConflictingAutoClosingBehaviorsChoosesMostSpecific( [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/36125")] - public void OnTypeCloseAngle_TagHelperAlreadyHasEndTag() + public async Task OnTypeCloseAngle_TagHelperAlreadyHasEndTagAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" @addTagHelper *, TestAssembly @@ -140,94 +139,94 @@ public void OnTypeCloseAngle_TagHelperAlreadyHasEndTag() [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/36125")] - public void OnTypeCloseAngle_VoidTagHelperHasEndTag_ShouldStillAutoClose() + public async Task OnTypeCloseAngle_VoidTagHelperHasEndTag_ShouldStillAutoCloseAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -$$ -", + $$ + ", expected: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly - -", + + ", fileKind: FileKinds.Legacy, tagHelpers: new[] { UnspecifiedInputTagHelper }); } [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/36125")] - public void OnTypeCloseAngle_TagAlreadyHasEndTag() + public async Task OnTypeCloseAngle_TagAlreadyHasEndTagAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -
$$
-", +
$$
+ ", expected: @" -
-"); +
+ "); } [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/36125")] - public void OnTypeCloseAngle_TagDoesNotAutoCloseOutOfScope() + public async Task OnTypeCloseAngle_TagDoesNotAutoCloseOutOfScopeAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -
- @if (true) - { -
$$
- } -", +
+ @if (true) + { +
$$
+ } + ", expected: @" -
- @if (true) - { -
- } -"); +
+ @if (true) + { +
+ } + "); } [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/36125")] - public void OnTypeCloseAngle_VoidTagHasEndTag_ShouldStillAutoClose() + public async Task OnTypeCloseAngle_VoidTagHasEndTag_ShouldStilloseAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -$$ -", + $$ + ", expected: @" - -"); + + "); } [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/36568")] - public void OnTypeCloseAngle_VoidElementMirroringTagHelper() + public async Task OnTypeCloseAngle_VoidElementMirroringTagHelperAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -$$ -", + $$ + ", expected: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -$0 -", + $0 + ", fileKind: FileKinds.Legacy, tagHelpers: new[] { UnspecifiedInputMirroringTagHelper }); } [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/36568")] - public void OnTypeCloseAngle_VoidHtmlElementCapitalized_SelfCloses() + public async Task OnTypeCloseAngle_VoidHtmlElementCapitalized_SelfClosesAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: "$$", expected: "", fileKind: FileKinds.Legacy, @@ -235,723 +234,720 @@ public void OnTypeCloseAngle_VoidHtmlElementCapitalized_SelfCloses() } [Fact] - public void OnTypeCloseAngle_NormalOrSelfClosingStructureOverridesVoidTagBehavior() + public async Task OnTypeCloseAngle_NormalOrSelfClosingStructureOverridesVoidTagBehaviorAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -$$ -", + $$ + ", expected: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -$0 -", + $0 + ", fileKind: FileKinds.Legacy, tagHelpers: new[] { NormalOrSelfclosingInputTagHelper }); } [Fact] - public void OnTypeCloseAngle_UnspeccifiedStructureInheritsVoidTagBehavior() + public async Task OnTypeCloseAngle_UnspeccifiedStructureInheritsVoidTagBehaviorAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -$$ -", + $$ + ", expected: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly - -", + + ", fileKind: FileKinds.Legacy, tagHelpers: new[] { UnspecifiedInputTagHelper }); } [Fact] - public void OnTypeCloseAngle_UnspeccifiedTagHelperTagStructure() + public async Task OnTypeCloseAngle_UnspeccifiedTagHelperTagStructureAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -$$ -", + $$ + ", expected: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -$0 -", + $0 + ", fileKind: FileKinds.Legacy, tagHelpers: new[] { UnspecifiedTagHelper }); } [Fact] - public void OnTypeCloseAngle_NormalOrSelfClosingTagHelperTagStructure() + public async Task OnTypeCloseAngle_NormalOrSelfClosingTagHelperTagStructureAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -$$ -", + $$ + ", expected: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -$0 -", + $0 + ", fileKind: FileKinds.Legacy, tagHelpers: new[] { NormalOrSelfClosingTagHelper }); } [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/33930")] - public void OnTypeCloseAngle_TagHelperInHtml_NestedStatement() + public async Task OnTypeCloseAngle_TagHelperInHtml_NestedStatementAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -@if (true) -{ -
$$
-} -", + @if (true) + { +
$$
+ } + ", expected: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -@if (true) -{ -
$0
-} -", + @if (true) + { +
$0
+ } + ", fileKind: FileKinds.Legacy, tagHelpers: new[] { NormalOrSelfClosingTagHelper }); } [Fact] [WorkItem("https://github.com/dotnet/razor-tooling/issues/5694")] - public void OnTypeCloseAngle_HtmlTagInHtml_NestedStatement_WithAttribute() + public async Task OnTypeCloseAngle_HtmlTagInHtml_NestedStatement_WithAttributeAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -@if (true) -{ - -} -", + @if (true) + { + + } + ", expected: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -@if (true) -{ - -} -", + @if (true) + { + + } + ", fileKind: FileKinds.Legacy, tagHelpers: new[] { NormalOrSelfClosingTagHelper }); } [Fact] [WorkItem("https://github.com/dotnet/razor-tooling/issues/5694")] - public void OnTypeCloseAngle_HtmlTagInHtml_NestedStatement_WithAttribute_SpaceBetweenClosingAngleAndAttributeClosingQuote() + public async Task OnTypeCloseAngle_HtmlTagInHtml_NestedStatement_WithAttribute_SpaceBetweenClosingAngleAndAttributeClosingQuoteAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -@if (true) -{ - -} -", + @if (true) + { + + } + ", expected: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -@if (true) -{ - -} -", + @if (true) + { + + } + ", fileKind: FileKinds.Legacy, tagHelpers: new[] { NormalOrSelfClosingTagHelper }); } [Fact] [WorkItem("https://github.com/dotnet/razor-tooling/issues/5694")] - public void OnTypeCloseAngle_HtmlTagInHtml_NestedStatement_WithMinimalizedAttribute() + public async Task OnTypeCloseAngle_HtmlTagInHtml_NestedStatement_WithMinimalizedAttributeAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -@if (true) -{ -
$$
-} -", + @if (true) + { +
$$
+ } + ", expected: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -@if (true) -{ -
$0
-} -", + @if (true) + { +
$0
+ } + ", fileKind: FileKinds.Legacy, tagHelpers: new[] { NormalOrSelfClosingTagHelper }); } [Fact] [WorkItem("https://github.com/dotnet/razor-tooling/issues/5694")] - public void OnTypeCloseAngle_HtmlTagInHtml_NestedStatement_WithMinimalizedAttribute_SpaceBetweenClosingAngleAndAttributeClosingQuote() + public async Task OnTypeCloseAngle_HtmlTagInHtml_NestedStatement_WithMinimalizedAttribute_SpaceBetweenClosingAngleAndAttributeClosingQuoteAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -@if (true) -{ -
$$
-} -", + @if (true) + { +
$$
+ } + ", expected: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -@if (true) -{ -
$0
-} -", + @if (true) + { +
$0
+ } + ", fileKind: FileKinds.Legacy, tagHelpers: new[] { NormalOrSelfClosingTagHelper }); } [Fact] [WorkItem("https://github.com/dotnet/razor-tooling/issues/5694")] - public void OnTypeCloseAngle_TagHelperInHtml_NestedStatement_WithAttribute() + public async Task OnTypeCloseAngle_TagHelperInHtml_NestedStatement_WithAttributeAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -@if (true) -{ -
$$
-} -", + @if (true) + { +
$$
+ } + ", expected: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -@if (true) -{ -
$0
-} -", + @if (true) + { +
$0
+ } + ", fileKind: FileKinds.Legacy, tagHelpers: new[] { NormalOrSelfClosingTagHelper }); } [Fact] [WorkItem("https://github.com/dotnet/razor-tooling/issues/5694")] - public void OnTypeCloseAngle_TagHelperInHtml_NestedStatement_WithAttribute_SpaceBetweenClosingAngleAndAttributeClosingQuote() + public async Task OnTypeCloseAngle_TagHelperInHtml_NestedStatement_WithAttribute_SpaceBetweenClosingAngleAndAttributeClosingQuoteAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -@if (true) -{ -
$$
-} -", + @if (true) + { +
$$
+ } + ", expected: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -@if (true) -{ -
$0
-} -", + @if (true) + { +
$0
+ } + ", fileKind: FileKinds.Legacy, tagHelpers: new[] { NormalOrSelfClosingTagHelper }); } [Fact] [WorkItem("https://github.com/dotnet/razor-tooling/issues/5694")] - public void OnTypeCloseAngle_TagHelperInHtml_NestedStatement_WithMinimalizedAttribute() + public async Task OnTypeCloseAngle_TagHelperInHtml_NestedStatement_WithMinimalizedAttributeAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -@if (true) -{ -
$$
-} -", + @if (true) + { +
$$
+ } + ", expected: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -@if (true) -{ -
$0
-} -", + @if (true) + { +
$0
+ } + ", fileKind: FileKinds.Legacy, tagHelpers: new[] { NormalOrSelfClosingTagHelper }); } [Fact] [WorkItem("https://github.com/dotnet/razor-tooling/issues/5694")] - public void OnTypeCloseAngle_TagHelperInHtml_NestedStatement_WithMinimalizedAttribute_SpaceBetweenClosingAngleAndAttributeClosingQuote() + public async Task OnTypeCloseAngle_TagHelperInHtml_NestedStatement_WithMinimalizedAttribute_SpaceBetweenClosingAngleAndAttributeClosingQuoteAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -@if (true) -{ -
$$
-} -", + @if (true) + { +
$$
+ } + ", expected: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -@if (true) -{ -
$0
-} -", + @if (true) + { +
$0
+ } + ", fileKind: FileKinds.Legacy, tagHelpers: new[] { NormalOrSelfClosingTagHelper }); } [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/33930")] - public void OnTypeCloseAngle_TagHelperInTagHelper_NestedStatement() + public async Task OnTypeCloseAngle_TagHelperInTagHelper_NestedStatementAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -@if (true) -{ -$$ -} -", + @if (true) + { + $$ + } + ", expected: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -@if (true) -{ - -} -", + @if (true) + { + + } + ", fileKind: FileKinds.Legacy, tagHelpers: new[] { NormalOrSelfClosingTagHelper, UnspecifiedInputTagHelper }); } [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/36906")] - public void OnTypeCloseAngle_TagHelperNextToVoidTagHelper_NestedStatement() + public async Task OnTypeCloseAngle_TagHelperNextToVoidTagHelper_NestedStatementAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -@if (true) -{ -$$ -} -", + @if (true) + { + $$ + } + ", expected: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -@if (true) -{ -$0 -} -", + @if (true) + { + $0 + } + ", fileKind: FileKinds.Legacy, tagHelpers: new[] { NormalOrSelfClosingTagHelper, UnspecifiedInputTagHelper }); } [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/36906")] - public void OnTypeCloseAngle_TagHelperNextToTagHelper_NestedStatement() + public async Task OnTypeCloseAngle_TagHelperNextToTagHelper_NestedStatementAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -@if (true) -{ -$$ -} -", + @if (true) + { + $$ + } + ", expected: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -@if (true) -{ -$0 -} -", + @if (true) + { + $0 + } + ", fileKind: FileKinds.Legacy, tagHelpers: new[] { NormalOrSelfClosingTagHelper, NormalOrSelfclosingInputTagHelper }); } [Fact] - public void OnTypeCloseAngle_NormalOrSelfClosingTagHelperTagStructure_CodeBlock() + public async Task OnTypeCloseAngle_NormalOrSelfClosingTagHelperTagStructure_CodeBlockAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -@{ - $$ -} -", + @{ + $$ + } + ", expected: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -@{ - $0 -} -", + @{ + $0 + } + ", fileKind: FileKinds.Legacy, tagHelpers: new[] { NormalOrSelfClosingTagHelper }); } [Fact] - public void OnTypeCloseAngle_WithSlash_WithoutEndTagTagHelperTagStructure() + public async Task OnTypeCloseAngle_WithSlash_WithoutEndTagTagHelperTagStructureAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -$$ -", + $$ + ", expected: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly - -", + + ", fileKind: FileKinds.Legacy, tagHelpers: new[] { WithoutEndTagTagHelper }); } [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/33930")] - public void OnTypeCloseAngle_NestedStatement() + public async Task OnTypeCloseAngle_NestedStatementAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -@if (true) -{ -
$$
-} -", + @if (true) + { +
$$
+ } + ", expected: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -@if (true) -{ -
-} -", + @if (true) + { +
+ } + ", fileKind: FileKinds.Legacy, tagHelpers: new[] { WithoutEndTagTagHelper }); } [Fact] - public void OnTypeCloseAngle_WithSpace_WithoutEndTagTagHelperTagStructure() + public async Task OnTypeCloseAngle_WithSpace_WithoutEndTagTagHelperTagStructureAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -$$ -", + $$ + ", expected: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly - -", + + ", fileKind: FileKinds.Legacy, tagHelpers: new[] { WithoutEndTagTagHelper }); } [Fact] - public void OnTypeCloseAngle_WithoutEndTagTagHelperTagStructure() + public async Task OnTypeCloseAngle_WithoutEndTagTagHelperTagStructureAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -$$ -", + $$ + ", expected: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly - -", + + ", fileKind: FileKinds.Legacy, tagHelpers: new[] { WithoutEndTagTagHelper }); } [Fact] - public void OnTypeCloseAngle_WithoutEndTagTagHelperTagStructure_CodeBlock() + public async Task OnTypeCloseAngle_WithoutEndTagTagHelperTagStructure_CodeBlockAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -@{ - $$ -} -", + @{ + $$ + } + ", expected: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -@{ - -} -", + @{ + + } + ", fileKind: FileKinds.Legacy, tagHelpers: new[] { WithoutEndTagTagHelper }); } [Fact] - public void OnTypeCloseAngle_MultipleApplicableTagHelperTagStructures() + public async Task OnTypeCloseAngle_MultipleApplicableTagHelperTagStructuresAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -$$ -", + $$ + ", expected: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -$0 -", + $0 + ", fileKind: FileKinds.Legacy, tagHelpers: new[] { UnspecifiedTagHelper, NormalOrSelfClosingTagHelper, WithoutEndTagTagHelper }); } [Fact] - public void OnTypeCloseAngle_EscapedTagTagHelperAutoCompletesWithEscape() + public async Task OnTypeCloseAngle_EscapedTagTagHelperAutoCompletesWithEscapeAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -$$ -", + $$ + ", expected: @" -@addTagHelper *, TestAssembly + @addTagHelper *, TestAssembly -$0 -", + $0 + ", fileKind: FileKinds.Legacy, tagHelpers: new[] { NormalOrSelfClosingTagHelper }); } [Fact] - public void OnTypeCloseAngle_AlwaysClosesStandardHTMLTag() + public async Task OnTypeCloseAngle_AlwaysClosesStandardHTMLTagAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -
$$
-", +
$$
+ ", expected: @" -
$0
-"); +
$0
+ "); } [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/33930")] - public void OnTypeCloseAngle_ClosesStandardHTMLTag_NestedStatement() + public async Task OnTypeCloseAngle_ClosesStandardHTMLTag_NestedStatementAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@if (true) -{ -

$$

-} -", + @if (true) + { +

$$

+ } + ", expected: @" -@if (true) -{ -

$0

-} -"); + @if (true) + { +

$0

+ } + "); } [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/36906")] - public void OnTypeCloseAngle_TagNextToTag_NestedStatement() + public async Task OnTypeCloseAngle_TagNextToTag_NestedStatementAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@if (true) -{ -

$$

-} -", + @if (true) + { +

$$

+ } + ", expected: @" -@if (true) -{ -

$0

-} -"); + @if (true) + { +

$0

+ } + "); } [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/36906")] - public void OnTypeCloseAngle_TagNextToVoidTag_NestedStatement() + public async Task OnTypeCloseAngle_TagNextToVoidTag_NestedStatementAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@if (true) -{ -

$$ -} -", + @if (true) + { +

$$ + } + ", expected: @" -@if (true) -{ -

$0

-} -"); + @if (true) + { +

$0

+ } + "); } [Fact] - public void OnTypeCloseAngle_ClosesStandardHTMLTag() + public async Task OnTypeCloseAngle_ClosesStandardHTMLTagAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -
$$ -", +
$$ + ", expected: @" -
$0
-"); +
$0
+ "); } [Fact] - public void OnTypeCloseAngle_ClosesStandardHTMLTag_CodeBlock() + public async Task OnTypeCloseAngle_ClosesStandardHTMLTag_CodeBlockAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@{ -
$$ -} -", + @{ +
$$ + } + ", expected: @" -@{ -
$0
-} -"); + @{ +
$0
+ } + "); } [Fact] - public void OnTypeCloseAngle_ClosesVoidHTMLTag() + public async Task OnTypeCloseAngle_ClosesVoidHTMLTagAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" - $$ -", + $$ + ", expected: @" - -"); + + "); } [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/33930")] - public void OnTypeCloseAngle_ClosesVoidHTMLTag_NestedStatement() + public async Task OnTypeCloseAngle_ClosesVoidHTMLTag_NestedStatementAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@if (true) -{ - $$ -} -", + @if (true) + { + $$ + } + ", expected: @" -@if (true) -{ - -} -"); + @if (true) + { + + } + "); } [Fact] - public void OnTypeCloseAngle_ClosesVoidHTMLTag_CodeBlock() + public async Task OnTypeCloseAngle_ClosesVoidHTMLTag_CodeBlockAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -@{ - $$ -} -", + @{ + $$ + } + ", expected: @" -@{ - -} -"); + @{ + + } + "); } [Fact] - public void OnTypeCloseAngle_WithSlash_ClosesVoidHTMLTag() + public async Task OnTypeCloseAngle_WithSlash_ClosesVoidHTMLTagAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" - $$ -", + $$ + ", expected: @" - -"); + + "); } [Fact] - public void OnTypeCloseAngle_WithSpace_ClosesVoidHTMLTag() + public async Task OnTypeCloseAngle_WithSpace_ClosesVoidHTMLTagAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" - $$ -", + $$ + ", expected: @" - -"); + + "); } [Fact] - public void OnTypeCloseAngle_AutoInsertDisabled_Noops() + public async Task OnTypeCloseAngle_AutoInsertDisabled_NoopsAsync() { - Options = RazorLSPOptions.Default with { AutoClosingTags = false }; - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" -
$$ -", +
$$ + ", expected: @" -
-"); +
+ ", + enableAutoClosingTags: false); } internal override IOnAutoInsertProvider CreateProvider() { - var configService = StrictMock.Of(); - var optionsMonitor = new RazorLSPOptionsMonitor(configService, Options); - - var provider = new AutoClosingTagOnAutoInsertProvider(optionsMonitor); + var provider = new AutoClosingTagOnAutoInsertProvider(); return provider; } } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/RazorOnAutoInsertProviderTestBase.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/RazorOnAutoInsertProviderTestBase.cs index 2d77fa619a3..da24040ecc7 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/RazorOnAutoInsertProviderTestBase.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/RazorOnAutoInsertProviderTestBase.cs @@ -5,9 +5,11 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.LanguageServer.Test; using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer; +using Microsoft.CodeAnalysis.Razor.AutoInsert; using Microsoft.CodeAnalysis.Razor.Formatting; using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Testing; @@ -28,7 +30,7 @@ protected RazorOnAutoInsertProviderTestBase(ITestOutputHelper testOutput) internal abstract IOnAutoInsertProvider CreateProvider(); - protected void RunAutoInsertTest(string input, string expected, int tabSize = 4, bool insertSpaces = true, string fileKind = default, IReadOnlyList tagHelpers = default) + protected async Task RunAutoInsertTestAsync(string input, string expected, int tabSize = 4, bool insertSpaces = true, bool enableAutoClosingTags = true, string fileKind = default, IReadOnlyList tagHelpers = default) { // Arrange TestFileMarkupParser.GetPosition(input, out input, out var location); @@ -46,16 +48,17 @@ protected void RunAutoInsertTest(string input, string expected, int tabSize = 4, }; var provider = CreateProvider(); - using var context = FormattingContext.Create(uri, Mock.Of(MockBehavior.Strict), codeDocument, options, TestAdhocWorkspaceFactory.Instance); + var sourceText = codeDocument.Source.Text; + var snapshot = Mock.Of(document => + document.TryGetText(out sourceText) == true && + document.GetGeneratedOutputAsync() == Task.FromResult(codeDocument), + MockBehavior.Strict); // Act - if (!provider.TryResolveInsertion(position, context, out var edit, out _)) - { - edit = null; - } + var edit = await provider.TryResolveInsertionAsync(position, snapshot, enableAutoClosingTags: enableAutoClosingTags); // Assert - var edited = edit is null ? source : ApplyEdit(source, edit); + var edited = edit is null ? source : ApplyEdit(source, edit.Value.TextEdit); var actual = edited.ToString(); Assert.Equal(expected, actual); } From 75f73a05ce0f5cccd5e613b17e836d32f8d3c513 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Fri, 9 Aug 2024 19:36:29 -0700 Subject: [PATCH 24/64] Fixinfg up CloseTextTagOnAutoInsertProviderTest tests --- .../AutoClosingTagOnAutoInsertProviderTest.cs | 5 +---- .../CloseTextTagOnAutoInsertProviderTest.cs | 22 +++++++------------ 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/AutoClosingTagOnAutoInsertProviderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/AutoClosingTagOnAutoInsertProviderTest.cs index 31dbb68ef69..404330cd8b0 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/AutoClosingTagOnAutoInsertProviderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/AutoClosingTagOnAutoInsertProviderTest.cs @@ -946,8 +946,5 @@ await RunAutoInsertTestAsync( } internal override IOnAutoInsertProvider CreateProvider() - { - var provider = new AutoClosingTagOnAutoInsertProvider(); - return provider; - } + => new AutoClosingTagOnAutoInsertProvider(); } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/CloseTextTagOnAutoInsertProviderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/CloseTextTagOnAutoInsertProviderTest.cs index 18fed07bd85..925c0b00a34 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/CloseTextTagOnAutoInsertProviderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/CloseTextTagOnAutoInsertProviderTest.cs @@ -1,8 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. -using Microsoft.AspNetCore.Razor.LanguageServer.Hosting; -using Microsoft.AspNetCore.Razor.Test.Common; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Razor.AutoInsert; using Xunit; using Xunit.Abstractions; @@ -11,9 +11,9 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.AutoInsert; public class CloseTextTagOnAutoInsertProviderTest(ITestOutputHelper testOutput) : RazorOnAutoInsertProviderTestBase(testOutput) { [Fact] - public void OnTypeCloseAngle_ClosesTextTag() + public async Task OnTypeCloseAngle_ClosesTextTagAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" @{ $$ @@ -27,9 +27,9 @@ public void OnTypeCloseAngle_ClosesTextTag() } [Fact] - public void OnTypeCloseAngle_OutsideRazorBlock_DoesNotCloseTextTag() + public async Task OnTypeCloseAngle_OutsideRazorBlock_DoesNotCloseTextTagAsync() { - RunAutoInsertTest( + await RunAutoInsertTestAsync( input: @" $$ ", @@ -38,12 +38,6 @@ public void OnTypeCloseAngle_OutsideRazorBlock_DoesNotCloseTextTag() "); } - internal override IOnAutoInsertProvider CreateProvider() - { - var configService = StrictMock.Of(); - var optionsMonitor = new RazorLSPOptionsMonitor(configService, RazorLSPOptions.Default); - - var provider = new CloseTextTagOnAutoInsertProvider(optionsMonitor); - return provider; - } + internal override IOnAutoInsertProvider CreateProvider() => + new CloseTextTagOnAutoInsertProvider(); } From 2c40c10bc60ff731a1f12ce7119a4fcdc4684d96 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Fri, 9 Aug 2024 20:31:30 -0700 Subject: [PATCH 25/64] Fixing up OnAutoInsertEndpointTest (which also tests the new AutoInsertService) --- .../OnAutoInsertEndpointTest.NetFx.cs | 71 +++++++++++++++++-- .../AutoInsert/OnAutoInsertEndpointTest.cs | 52 +++++++++++--- 2 files changed, 106 insertions(+), 17 deletions(-) diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/OnAutoInsertEndpointTest.NetFx.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/OnAutoInsertEndpointTest.NetFx.cs index 26b5acd8561..b5bb61530e2 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/OnAutoInsertEndpointTest.NetFx.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/OnAutoInsertEndpointTest.NetFx.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.LanguageServer.Formatting; using Microsoft.AspNetCore.Razor.LanguageServer.Test; +using Microsoft.CodeAnalysis.Razor.AutoInsert; using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Testing; using Microsoft.CodeAnalysis.Text; @@ -27,7 +28,15 @@ public async Task Handle_SingleProvider_InvokesProvider() var optionsMonitor = GetOptionsMonitor(); var insertProvider = new TestOnAutoInsertProvider(">", canResolve: true); var formattingService = await TestRazorFormattingService.CreateWithFullSupportAsync(LoggerFactory); - var endpoint = new OnAutoInsertEndpoint(LanguageServerFeatureOptions, DocumentMappingService, languageServer, [insertProvider], optionsMonitor, TestAdhocWorkspaceFactory.Instance, formattingService, LoggerFactory); + var endpoint = new OnAutoInsertEndpoint( + LanguageServerFeatureOptions, + DocumentMappingService, + languageServer, + new AutoInsertService([insertProvider]), + optionsMonitor, + TestAdhocWorkspaceFactory.Instance, + formattingService, + LoggerFactory); var @params = new VSInternalDocumentOnAutoInsertParams() { TextDocument = new TextDocumentIdentifier { Uri = uri, }, @@ -69,7 +78,15 @@ public async Task Handle_MultipleProviderSameTrigger_UsesSuccessful() ResolvedTextEdit = VsLspFactory.CreateTextEdit(position: (0, 0), string.Empty) }; var formattingService = await TestRazorFormattingService.CreateWithFullSupportAsync(LoggerFactory); - var endpoint = new OnAutoInsertEndpoint(LanguageServerFeatureOptions, DocumentMappingService, languageServer, [insertProvider1, insertProvider2], optionsMonitor, TestAdhocWorkspaceFactory.Instance, formattingService, LoggerFactory); + var endpoint = new OnAutoInsertEndpoint( + LanguageServerFeatureOptions, + DocumentMappingService, + languageServer, + new AutoInsertService([insertProvider1, insertProvider2]), + optionsMonitor, + TestAdhocWorkspaceFactory.Instance, + formattingService, + LoggerFactory); var @params = new VSInternalDocumentOnAutoInsertParams() { TextDocument = new TextDocumentIdentifier { Uri = uri, }, @@ -114,7 +131,15 @@ public async Task Handle_MultipleProviderSameTrigger_UsesFirstSuccessful() ResolvedTextEdit = VsLspFactory.CreateTextEdit(position: (0, 0), string.Empty) }; var formattingService = await TestRazorFormattingService.CreateWithFullSupportAsync(LoggerFactory); - var endpoint = new OnAutoInsertEndpoint(LanguageServerFeatureOptions, DocumentMappingService, languageServer, [insertProvider1, insertProvider2], optionsMonitor, TestAdhocWorkspaceFactory.Instance, formattingService, LoggerFactory); + var endpoint = new OnAutoInsertEndpoint( + LanguageServerFeatureOptions, + DocumentMappingService, + languageServer, + new AutoInsertService([insertProvider1, insertProvider2]), + optionsMonitor, + TestAdhocWorkspaceFactory.Instance, + formattingService, + LoggerFactory); var @params = new VSInternalDocumentOnAutoInsertParams() { TextDocument = new TextDocumentIdentifier { Uri = uri, }, @@ -151,7 +176,15 @@ public async Task Handle_NoApplicableProvider_CallsProviderAndReturnsNull() var optionsMonitor = GetOptionsMonitor(); var insertProvider = new TestOnAutoInsertProvider(">", canResolve: false); var formattingService = await TestRazorFormattingService.CreateWithFullSupportAsync(LoggerFactory); - var endpoint = new OnAutoInsertEndpoint(LanguageServerFeatureOptions, DocumentMappingService, languageServer, [insertProvider], optionsMonitor, TestAdhocWorkspaceFactory.Instance, formattingService, LoggerFactory); + var endpoint = new OnAutoInsertEndpoint( + LanguageServerFeatureOptions, + DocumentMappingService, + languageServer, + new AutoInsertService([insertProvider]), + optionsMonitor, + TestAdhocWorkspaceFactory.Instance, + formattingService, + LoggerFactory); var @params = new VSInternalDocumentOnAutoInsertParams() { TextDocument = new TextDocumentIdentifier { Uri = uri, }, @@ -186,7 +219,15 @@ public async Task Handle_OnTypeFormattingOff_Html_CallsLanguageServer() var optionsMonitor = GetOptionsMonitor(formatOnType: false); var insertProvider = new TestOnAutoInsertProvider("<", canResolve: false); var formattingService = await TestRazorFormattingService.CreateWithFullSupportAsync(LoggerFactory); - var endpoint = new OnAutoInsertEndpoint(LanguageServerFeatureOptions, DocumentMappingService, languageServer, [insertProvider], optionsMonitor, TestAdhocWorkspaceFactory.Instance, formattingService, LoggerFactory); + var endpoint = new OnAutoInsertEndpoint( + LanguageServerFeatureOptions, + DocumentMappingService, + languageServer, + new AutoInsertService([insertProvider]), + optionsMonitor, + TestAdhocWorkspaceFactory.Instance, + formattingService, + LoggerFactory); var @params = new VSInternalDocumentOnAutoInsertParams() { TextDocument = new TextDocumentIdentifier { Uri = uri, }, @@ -219,7 +260,15 @@ public async Task Handle_AutoInsertAttributeQuotesOff_Html_DoesNotCallLanguageSe var optionsMonitor = GetOptionsMonitor(autoInsertAttributeQuotes: false); var insertProvider = new TestOnAutoInsertProvider("<", canResolve: false); var formattingService = await TestRazorFormattingService.CreateWithFullSupportAsync(LoggerFactory); - var endpoint = new OnAutoInsertEndpoint(LanguageServerFeatureOptions, DocumentMappingService, languageServer, [insertProvider], optionsMonitor, TestAdhocWorkspaceFactory.Instance, formattingService, LoggerFactory); + var endpoint = new OnAutoInsertEndpoint( + LanguageServerFeatureOptions, + DocumentMappingService, + languageServer, + new AutoInsertService([insertProvider]), + optionsMonitor, + TestAdhocWorkspaceFactory.Instance, + formattingService, + LoggerFactory); var @params = new VSInternalDocumentOnAutoInsertParams() { TextDocument = new TextDocumentIdentifier { Uri = uri, }, @@ -357,7 +406,15 @@ private async Task VerifyCSharpOnAutoInsertAsync(string input, string expected, var optionsMonitor = GetOptionsMonitor(); var insertProvider = new TestOnAutoInsertProvider("!!!", canResolve: false); var formattingService = await TestRazorFormattingService.CreateWithFullSupportAsync(LoggerFactory); - var endpoint = new OnAutoInsertEndpoint(LanguageServerFeatureOptions, DocumentMappingService, languageServer, [insertProvider], optionsMonitor, TestAdhocWorkspaceFactory.Instance, formattingService, LoggerFactory); + var endpoint = new OnAutoInsertEndpoint( + LanguageServerFeatureOptions, + DocumentMappingService, + languageServer, + new AutoInsertService([insertProvider]), + optionsMonitor, + TestAdhocWorkspaceFactory.Instance, + formattingService, + LoggerFactory); var text = codeDocument.Source.Text; var @params = new VSInternalDocumentOnAutoInsertParams() diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/OnAutoInsertEndpointTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/OnAutoInsertEndpointTest.cs index d5c57f9abff..ea6db93a4f4 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/OnAutoInsertEndpointTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/OnAutoInsertEndpointTest.cs @@ -2,11 +2,12 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System; -using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.LanguageServer.Test; +using Microsoft.CodeAnalysis.Razor.AutoInsert; using Microsoft.CodeAnalysis.Razor.Formatting; +using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.VisualStudio.LanguageServer.Protocol; using Xunit; using Xunit.Abstractions; @@ -27,7 +28,16 @@ public async Task Handle_MultipleProviderUnmatchingTrigger_ReturnsNull() var optionsMonitor = GetOptionsMonitor(); var insertProvider1 = new TestOnAutoInsertProvider(">", canResolve: true); var insertProvider2 = new TestOnAutoInsertProvider("<", canResolve: true); - var endpoint = new OnAutoInsertEndpoint(LanguageServerFeatureOptions, DocumentMappingService, languageServer, [insertProvider1, insertProvider2], optionsMonitor, TestAdhocWorkspaceFactory.Instance, null!, LoggerFactory); + var autoInsertService = new AutoInsertService([insertProvider1, insertProvider2]); + var endpoint = new OnAutoInsertEndpoint( + LanguageServerFeatureOptions, + DocumentMappingService, + languageServer, + autoInsertService, + optionsMonitor, + TestAdhocWorkspaceFactory.Instance, + null!, + LoggerFactory); var @params = new VSInternalDocumentOnAutoInsertParams() { TextDocument = new TextDocumentIdentifier { Uri = uri, }, @@ -61,7 +71,15 @@ public async Task Handle_DocumentNotFound_ReturnsNull() var optionsMonitor = GetOptionsMonitor(); var insertProvider = new TestOnAutoInsertProvider(">", canResolve: true); - var endpoint = new OnAutoInsertEndpoint(LanguageServerFeatureOptions, DocumentMappingService, languageServer, [insertProvider], optionsMonitor, TestAdhocWorkspaceFactory.Instance, null!, LoggerFactory); + var endpoint = new OnAutoInsertEndpoint( + LanguageServerFeatureOptions, + DocumentMappingService, + languageServer, + new AutoInsertService([insertProvider]), + optionsMonitor, + TestAdhocWorkspaceFactory.Instance, + null!, + LoggerFactory); var uri = new Uri("file://path/test.razor"); var @params = new VSInternalDocumentOnAutoInsertParams() { @@ -97,7 +115,15 @@ public async Task Handle_UnsupportedCodeDocument_ReturnsNull() var documentContext = CreateDocumentContext(uri, codeDocument); var optionsMonitor = GetOptionsMonitor(); var insertProvider = new TestOnAutoInsertProvider(">", canResolve: true); - var endpoint = new OnAutoInsertEndpoint(LanguageServerFeatureOptions, DocumentMappingService, languageServer, [insertProvider], optionsMonitor, TestAdhocWorkspaceFactory.Instance, null!, LoggerFactory); + var endpoint = new OnAutoInsertEndpoint( + LanguageServerFeatureOptions, + DocumentMappingService, + languageServer, + new AutoInsertService([insertProvider]), + optionsMonitor, + TestAdhocWorkspaceFactory.Instance, + razorFormattingService: null!, + LoggerFactory); var @params = new VSInternalDocumentOnAutoInsertParams() { TextDocument = new TextDocumentIdentifier { Uri = uri, }, @@ -131,7 +157,14 @@ public async Task Handle_OnTypeFormattingOff_CSharp_ReturnsNull() var documentContext = CreateDocumentContext(uri, codeDocument); var optionsMonitor = GetOptionsMonitor(formatOnType: false); var insertProvider = new TestOnAutoInsertProvider(">", canResolve: false); - var endpoint = new OnAutoInsertEndpoint(LanguageServerFeatureOptions, DocumentMappingService, languageServer, [insertProvider], optionsMonitor, TestAdhocWorkspaceFactory.Instance, null!, LoggerFactory); + var endpoint = new OnAutoInsertEndpoint( + LanguageServerFeatureOptions, + DocumentMappingService, + languageServer, + new AutoInsertService([insertProvider]), + optionsMonitor, TestAdhocWorkspaceFactory.Instance, + razorFormattingService: null!, + LoggerFactory); var @params = new VSInternalDocumentOnAutoInsertParams() { TextDocument = new TextDocumentIdentifier { Uri = uri, }, @@ -161,13 +194,12 @@ private class TestOnAutoInsertProvider(string triggerCharacter, bool canResolve) public string TriggerCharacter { get; } = triggerCharacter; - // Disabling because [NotNullWhen] is available in two Assemblies and causes warnings - public bool TryResolveInsertion(Position position, FormattingContext context, [NotNullWhen(true)] out TextEdit? edit, out InsertTextFormat format) + public ValueTask TryResolveInsertionAsync(Position position, IDocumentSnapshot snapshot, bool enableAutoClosingTags) { Called = true; - edit = ResolvedTextEdit!; - format = default; - return canResolve; + return canResolve + ? new ValueTask(new InsertTextEdit(ResolvedTextEdit!, default)) + : new ValueTask((InsertTextEdit?)default); } } From 770808f8e5b7c00723562e1b61d8268708c0fd0d Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Fri, 9 Aug 2024 21:09:26 -0700 Subject: [PATCH 26/64] Fixing duplicate OnAutoInsert handler registration (bad merge after rebate) --- .../RazorLanguageServer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs index bc222037136..d1948c5c559 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs @@ -182,7 +182,6 @@ static void AddHandlers(IServiceCollection services, LanguageServerFeatureOption services.AddTransient(sp => sp.GetRequiredService()); services.AddHandlerWithCapabilities(); - services.AddHandlerWithCapabilities(); if (!featureOptions.UseRazorCohostServer) { From bf7c45efe38d8fba4b6c20e66adbd6c2bffbc60b Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Sat, 10 Aug 2024 11:35:15 -0700 Subject: [PATCH 27/64] Fixes to MEF composition issue and capabilities check - IDocumentMappingService was not needed (and not available via MEF), so removed that - TextDocument does not implmement VSInternalTextDocumentClientCapabilities --- .../LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs index 299b856b074..e7d55304a5f 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs @@ -38,7 +38,6 @@ internal class CohostOnAutoInsertEndpoint( #pragma warning restore RS0030 // Do not use banned APIs IHtmlDocumentSynchronizer htmlDocumentSynchronizer, LSPRequestInvoker requestInvoker, - IDocumentMappingService razorDocumentMappingService, ILoggerFactory loggerFactory) : AbstractRazorCohostDocumentRequestHandler, IDynamicRegistrationProvider { @@ -47,7 +46,6 @@ internal class CohostOnAutoInsertEndpoint( private readonly IHtmlDocumentSynchronizer _htmlDocumentSynchronizer = htmlDocumentSynchronizer; private readonly LSPRequestInvoker _requestInvoker = requestInvoker; private readonly ILogger _logger = loggerFactory.GetOrCreateLogger(); - private readonly IDocumentMappingService _razorDocumentMappingService = razorDocumentMappingService; protected override bool MutatesSolutionState => false; @@ -55,8 +53,7 @@ internal class CohostOnAutoInsertEndpoint( public Registration? GetRegistration(VSInternalClientCapabilities clientCapabilities, DocumentFilter[] filter, RazorCohostRequestContext requestContext) { - if (clientCapabilities.SupportsVisualStudioExtensions - && (clientCapabilities.TextDocument as VSInternalTextDocumentClientCapabilities)?.OnAutoInsert?.DynamicRegistration == true) + if (clientCapabilities.SupportsVisualStudioExtensions) { var providerTriggerChars = _onAutoInsertTriggerCharacterProviders .Select((provider) => provider.TriggerCharacter); From ced0724c862f07e877a0c316cc1466cdad8fd817 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Mon, 12 Aug 2024 11:02:03 -0700 Subject: [PATCH 28/64] Fixing incorrect export type --- .../AutoInsert/OOPAutoInsertService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/OOPAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/OOPAutoInsertService.cs index 45d65c58bb3..821513d7e0d 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/OOPAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/OOPAutoInsertService.cs @@ -7,7 +7,7 @@ namespace Microsoft.CodeAnalysis.Remote.Razor.AutoInsert; -[Export(typeof(OOPAutoInsertService)), Shared] +[Export(typeof(IAutoInsertService)), Shared] [method: ImportingConstructor] internal sealed class OOPAutoInsertService( [ImportMany]IEnumerable providers From 52c7acb7a0b735795b9f6953b1a313a3ec46243e Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Tue, 13 Aug 2024 09:18:44 -0700 Subject: [PATCH 29/64] Minor cleanup per CR suggestions --- .../Hosting/VSInternalServerCapabilitiesExtensions.cs | 4 +--- .../AutoInsert/RemoteAutoInsertService.cs | 10 +++++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Hosting/VSInternalServerCapabilitiesExtensions.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Hosting/VSInternalServerCapabilitiesExtensions.cs index b8faa23a93e..4370ecf4298 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Hosting/VSInternalServerCapabilitiesExtensions.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Hosting/VSInternalServerCapabilitiesExtensions.cs @@ -2,11 +2,9 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System.Collections.Generic; -using System; +using System.Linq; using Microsoft.CodeAnalysis.Razor.SemanticTokens; using Microsoft.VisualStudio.LanguageServer.Protocol; -using Microsoft.CodeAnalysis.Razor.Workspaces; -using System.Linq; namespace Microsoft.AspNetCore.Razor.LanguageServer.Hosting; diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs index f7957893713..e1902b91aab 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs @@ -43,7 +43,7 @@ public ValueTask TryResolveInsertionAsync( string character, bool autoCloseTags, CancellationToken cancellationToken) - => RunServiceAsync( + => RunServiceAsync( solutionInfo, documentId, context => TryResolveInsertionAsync( @@ -76,15 +76,15 @@ private async ValueTask TryResolveInsertionAsync( } else if (languageKind is RazorLanguageKind.Razor) { - linePosition.Deconstruct(out var line, out var lineCharacter); var insertTextEdit = await _autoInsertService.TryResolveInsertionAsync( remoteDocumentContext.Snapshot, - new Position(line, lineCharacter), + linePosition.ToPosition(), character, autoCloseTags, cancellationToken); - return insertTextEdit.HasValue - ? Response.Results(RemoteInsertTextEdit.FromLspInsertTextEdit(insertTextEdit.Value)) + + return insertTextEdit is { } edit + ? Response.Results(RemoteInsertTextEdit.FromLspInsertTextEdit(edit)) : Response.NoFurtherHandling; } From 68080997777eb41ea61a16589c9f05ee5871277f Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Tue, 13 Aug 2024 10:20:39 -0700 Subject: [PATCH 30/64] Switching parameters to RazorCodeDocument and removing async in a lot of places per CR suggestion --- .../AutoInsert/OnAutoInsertEndpoint.cs | 9 +- .../AutoClosingTagOnAutoInsertProvider.cs | 12 +- .../AutoInsert/AutoInsertService.cs | 21 +-- .../CloseTextTagOnAutoInsertProvider.cs | 12 +- .../AutoInsert/IAutoInsertService.cs | 11 +- .../AutoInsert/IOnAutoInsertProvider.cs | 5 +- .../AutoInsert/RemoteAutoInsertService.cs | 7 +- .../AutoClosingTagOnAutoInsertProviderTest.cs | 176 +++++++++--------- .../CloseTextTagOnAutoInsertProviderTest.cs | 9 +- .../AutoInsert/OnAutoInsertEndpointTest.cs | 6 +- .../RazorOnAutoInsertProviderTestBase.cs | 16 +- 11 files changed, 124 insertions(+), 160 deletions(-) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs index 418a5b6478f..e97a0ccf654 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs @@ -81,13 +81,12 @@ public void ApplyCapabilities(VSInternalServerCapabilities serverCapabilities, V var character = request.Character; - var insertTextEdit = await _autoInsertService.TryResolveInsertionAsync( - documentContext.Snapshot, + var insertTextEdit = _autoInsertService.TryResolveInsertion( + codeDocument, request.Position, character, - _optionsMonitor.CurrentValue.AutoClosingTags, - cancellationToken - ).ConfigureAwait(false); + _optionsMonitor.CurrentValue.AutoClosingTags + ); if (insertTextEdit is not null) { diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs index dd42596fdcb..3eb133606d4 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs @@ -4,10 +4,8 @@ using System; using System.Collections.Immutable; using System.Diagnostics; -using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Syntax; -using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.VisualStudio.LanguageServer.Protocol; using RazorSyntaxNode = Microsoft.AspNetCore.Razor.Language.Syntax.SyntaxNode; @@ -41,13 +39,12 @@ internal class AutoClosingTagOnAutoInsertProvider : IOnAutoInsertProvider public string TriggerCharacter => ">"; - public async ValueTask TryResolveInsertionAsync(Position position, IDocumentSnapshot documentSnapshot, bool enableAutoClosingTags) + public InsertTextEdit? TryResolveInsertion(Position position, RazorCodeDocument codeDocument, bool enableAutoClosingTags) { if (!(enableAutoClosingTags - && documentSnapshot.TryGetText(out var sourceText) + && codeDocument.Source.Text is { } sourceText && sourceText.TryGetAbsoluteIndex(position, out var afterCloseAngleIndex) - && await TryResolveAutoClosingBehaviorAsync(documentSnapshot, afterCloseAngleIndex) - .ConfigureAwait(false) is { } tagNameWithClosingBehavior)) + && TryResolveAutoClosingBehavior(codeDocument, afterCloseAngleIndex) is { } tagNameWithClosingBehavior)) { return default; } @@ -71,9 +68,8 @@ internal class AutoClosingTagOnAutoInsertProvider : IOnAutoInsertProvider return new InsertTextEdit(edit, format); } - private static async ValueTask TryResolveAutoClosingBehaviorAsync(IDocumentSnapshot documentSnapshot, int afterCloseAngleIndex) + private static TagNameWithClosingBehavior? TryResolveAutoClosingBehavior(RazorCodeDocument codeDocument, int afterCloseAngleIndex) { - var codeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); var syntaxTree = codeDocument.GetSyntaxTree(); var closeAngle = syntaxTree.Root.FindToken(afterCloseAngleIndex - 1); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs index f5f3698247a..2377b77d58b 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs @@ -4,10 +4,8 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; -using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.PooledObjects; -using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.VisualStudio.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.Razor.AutoInsert; @@ -22,12 +20,11 @@ internal class AutoInsertService(IEnumerable onAutoInsert // This gets called just once public IEnumerable TriggerCharacters => _onAutoInsertProviders.Select((provider) => provider.TriggerCharacter); - public async ValueTask TryResolveInsertionAsync( - IDocumentSnapshot documentSnapshot, + public InsertTextEdit? TryResolveInsertion( + RazorCodeDocument codeDocument, Position position, string character, - bool autoCloseTags, - CancellationToken cancellationToken) + bool autoCloseTags) { using var applicableProviders = new PooledArrayBuilder(); foreach (var provider in _onAutoInsertProviders) @@ -45,22 +42,18 @@ internal class AutoInsertService(IEnumerable onAutoInsert return null; } - cancellationToken.ThrowIfCancellationRequested(); - foreach (var provider in applicableProviders) { - var insertTextEdit = await provider.TryResolveInsertionAsync( + var insertTextEdit = provider.TryResolveInsertion( position, - documentSnapshot, + codeDocument, autoCloseTags - ).ConfigureAwait(false); + ); if (insertTextEdit is not null) { return insertTextEdit; } - - cancellationToken.ThrowIfCancellationRequested(); } // No provider could handle the text edit. diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs index 3e30d84fd1d..9b5aaaeef0f 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs @@ -3,11 +3,9 @@ using System; using System.Diagnostics; -using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Legacy; using Microsoft.AspNetCore.Razor.Language.Syntax; -using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.VisualStudio.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.Razor.AutoInsert; @@ -16,10 +14,9 @@ internal class CloseTextTagOnAutoInsertProvider : IOnAutoInsertProvider { public string TriggerCharacter => ">"; - public async ValueTask TryResolveInsertionAsync(Position position, IDocumentSnapshot documentSnapshot, bool enableAutoClosingTags) + public InsertTextEdit? TryResolveInsertion(Position position, RazorCodeDocument codeDocument, bool enableAutoClosingTags) { - if (!(enableAutoClosingTags - && await IsAtTextTagAsync(documentSnapshot, position).ConfigureAwait(false))) + if (!(enableAutoClosingTags && IsAtTextTag(codeDocument, position))) { return default; } @@ -31,12 +28,11 @@ internal class CloseTextTagOnAutoInsertProvider : IOnAutoInsertProvider return new InsertTextEdit(edit, format); } - private static async ValueTask IsAtTextTagAsync(IDocumentSnapshot documentSnapshot, Position position) + private static bool IsAtTextTag(RazorCodeDocument codeDocument, Position position) { - var codeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); var syntaxTree = codeDocument.GetSyntaxTree(); - if (!(documentSnapshot.TryGetText(out var sourceText) + if (!(codeDocument.Source.Text is { } sourceText && sourceText.TryGetAbsoluteIndex(position, out var absoluteIndex))) { return false; diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs index e9dd88c58ac..6fa486f1442 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs @@ -2,9 +2,7 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Razor.ProjectSystem; +using Microsoft.AspNetCore.Razor.Language; using Microsoft.VisualStudio.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.Razor.AutoInsert; @@ -13,11 +11,10 @@ internal interface IAutoInsertService { IEnumerable TriggerCharacters { get; } - ValueTask TryResolveInsertionAsync( - IDocumentSnapshot documentSnapshot, + InsertTextEdit? TryResolveInsertion( + RazorCodeDocument codeDocument, Position position, string character, - bool autoCloseTags, - CancellationToken cancellationToken + bool autoCloseTags ); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs index 23b2fe45524..d0035c4af19 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs @@ -1,13 +1,12 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Razor.ProjectSystem; +using Microsoft.AspNetCore.Razor.Language; using Microsoft.VisualStudio.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.Razor.AutoInsert; internal interface IOnAutoInsertProvider : IOnAutoInsertTriggerCharacterProvider { - public ValueTask TryResolveInsertionAsync(Position position, IDocumentSnapshot documentSnapshot, bool enableAutoClosingTags); + public InsertTextEdit? TryResolveInsertion(Position position, RazorCodeDocument codeDocument, bool enableAutoClosingTags); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs index e1902b91aab..9f5e7237fb5 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs @@ -76,12 +76,11 @@ private async ValueTask TryResolveInsertionAsync( } else if (languageKind is RazorLanguageKind.Razor) { - var insertTextEdit = await _autoInsertService.TryResolveInsertionAsync( - remoteDocumentContext.Snapshot, + var insertTextEdit = _autoInsertService.TryResolveInsertion( + codeDocument, linePosition.ToPosition(), character, - autoCloseTags, - cancellationToken); + autoCloseTags); return insertTextEdit is { } edit ? Response.Results(RemoteInsertTextEdit.FromLspInsertTextEdit(edit)) diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/AutoClosingTagOnAutoInsertProviderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/AutoClosingTagOnAutoInsertProviderTest.cs index 404330cd8b0..3a7edbd5def 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/AutoClosingTagOnAutoInsertProviderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/AutoClosingTagOnAutoInsertProviderTest.cs @@ -100,9 +100,9 @@ private static TagHelperDescriptor WithoutEndTagTagHelper [Fact] [WorkItem("https://github.com/dotnet/razor-tooling/issues/6217")] - public async Task OnTypeCloseAngle_ConflictingAutoClosingBehaviorsChoosesMostSpecificAsync() + public void OnTypeCloseAngle_ConflictingAutoClosingBehaviorsChoosesMostSpecific() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @addTagHelper *, TestAssembly @@ -120,9 +120,9 @@ await RunAutoInsertTestAsync( [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/36125")] - public async Task OnTypeCloseAngle_TagHelperAlreadyHasEndTagAsync() + public void OnTypeCloseAngle_TagHelperAlreadyHasEndTag() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @addTagHelper *, TestAssembly @@ -139,9 +139,9 @@ await RunAutoInsertTestAsync( [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/36125")] - public async Task OnTypeCloseAngle_VoidTagHelperHasEndTag_ShouldStillAutoCloseAsync() + public void OnTypeCloseAngle_VoidTagHelperHasEndTag_ShouldStillAutoClose() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @addTagHelper *, TestAssembly @@ -158,9 +158,9 @@ await RunAutoInsertTestAsync( [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/36125")] - public async Task OnTypeCloseAngle_TagAlreadyHasEndTagAsync() + public void OnTypeCloseAngle_TagAlreadyHasEndTag() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @"
$$
", @@ -171,9 +171,9 @@ await RunAutoInsertTestAsync( [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/36125")] - public async Task OnTypeCloseAngle_TagDoesNotAutoCloseOutOfScopeAsync() + public void OnTypeCloseAngle_TagDoesNotAutoCloseOutOfScope() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @"
@if (true) @@ -192,9 +192,9 @@ await RunAutoInsertTestAsync( [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/36125")] - public async Task OnTypeCloseAngle_VoidTagHasEndTag_ShouldStilloseAsync() + public void OnTypeCloseAngle_VoidTagHasEndTag_ShouldStillose() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" $$ ", @@ -205,9 +205,9 @@ await RunAutoInsertTestAsync( [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/36568")] - public async Task OnTypeCloseAngle_VoidElementMirroringTagHelperAsync() + public void OnTypeCloseAngle_VoidElementMirroringTagHelper() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @addTagHelper *, TestAssembly @@ -224,9 +224,9 @@ await RunAutoInsertTestAsync( [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/36568")] - public async Task OnTypeCloseAngle_VoidHtmlElementCapitalized_SelfClosesAsync() + public void OnTypeCloseAngle_VoidHtmlElementCapitalized_SelfCloses() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: "$$", expected: "", fileKind: FileKinds.Legacy, @@ -234,9 +234,9 @@ await RunAutoInsertTestAsync( } [Fact] - public async Task OnTypeCloseAngle_NormalOrSelfClosingStructureOverridesVoidTagBehaviorAsync() + public void OnTypeCloseAngle_NormalOrSelfClosingStructureOverridesVoidTagBehavior() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @addTagHelper *, TestAssembly @@ -252,9 +252,9 @@ await RunAutoInsertTestAsync( } [Fact] - public async Task OnTypeCloseAngle_UnspeccifiedStructureInheritsVoidTagBehaviorAsync() + public void OnTypeCloseAngle_UnspeccifiedStructureInheritsVoidTagBehavior() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @addTagHelper *, TestAssembly @@ -270,9 +270,9 @@ await RunAutoInsertTestAsync( } [Fact] - public async Task OnTypeCloseAngle_UnspeccifiedTagHelperTagStructureAsync() + public void OnTypeCloseAngle_UnspeccifiedTagHelperTagStructure() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @addTagHelper *, TestAssembly @@ -288,9 +288,9 @@ await RunAutoInsertTestAsync( } [Fact] - public async Task OnTypeCloseAngle_NormalOrSelfClosingTagHelperTagStructureAsync() + public void OnTypeCloseAngle_NormalOrSelfClosingTagHelperTagStructure() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @addTagHelper *, TestAssembly @@ -307,9 +307,9 @@ await RunAutoInsertTestAsync( [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/33930")] - public async Task OnTypeCloseAngle_TagHelperInHtml_NestedStatementAsync() + public void OnTypeCloseAngle_TagHelperInHtml_NestedStatement() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @addTagHelper *, TestAssembly @@ -332,9 +332,9 @@ await RunAutoInsertTestAsync( [Fact] [WorkItem("https://github.com/dotnet/razor-tooling/issues/5694")] - public async Task OnTypeCloseAngle_HtmlTagInHtml_NestedStatement_WithAttributeAsync() + public void OnTypeCloseAngle_HtmlTagInHtml_NestedStatement_WithAttribute() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @addTagHelper *, TestAssembly @@ -357,9 +357,9 @@ await RunAutoInsertTestAsync( [Fact] [WorkItem("https://github.com/dotnet/razor-tooling/issues/5694")] - public async Task OnTypeCloseAngle_HtmlTagInHtml_NestedStatement_WithAttribute_SpaceBetweenClosingAngleAndAttributeClosingQuoteAsync() + public void OnTypeCloseAngle_HtmlTagInHtml_NestedStatement_WithAttribute_SpaceBetweenClosingAngleAndAttributeClosingQuote() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @addTagHelper *, TestAssembly @@ -382,9 +382,9 @@ await RunAutoInsertTestAsync( [Fact] [WorkItem("https://github.com/dotnet/razor-tooling/issues/5694")] - public async Task OnTypeCloseAngle_HtmlTagInHtml_NestedStatement_WithMinimalizedAttributeAsync() + public void OnTypeCloseAngle_HtmlTagInHtml_NestedStatement_WithMinimalizedAttribute() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @addTagHelper *, TestAssembly @@ -407,9 +407,9 @@ await RunAutoInsertTestAsync( [Fact] [WorkItem("https://github.com/dotnet/razor-tooling/issues/5694")] - public async Task OnTypeCloseAngle_HtmlTagInHtml_NestedStatement_WithMinimalizedAttribute_SpaceBetweenClosingAngleAndAttributeClosingQuoteAsync() + public void OnTypeCloseAngle_HtmlTagInHtml_NestedStatement_WithMinimalizedAttribute_SpaceBetweenClosingAngleAndAttributeClosingQuote() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @addTagHelper *, TestAssembly @@ -432,9 +432,9 @@ await RunAutoInsertTestAsync( [Fact] [WorkItem("https://github.com/dotnet/razor-tooling/issues/5694")] - public async Task OnTypeCloseAngle_TagHelperInHtml_NestedStatement_WithAttributeAsync() + public void OnTypeCloseAngle_TagHelperInHtml_NestedStatement_WithAttribute() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @addTagHelper *, TestAssembly @@ -457,9 +457,9 @@ await RunAutoInsertTestAsync( [Fact] [WorkItem("https://github.com/dotnet/razor-tooling/issues/5694")] - public async Task OnTypeCloseAngle_TagHelperInHtml_NestedStatement_WithAttribute_SpaceBetweenClosingAngleAndAttributeClosingQuoteAsync() + public void OnTypeCloseAngle_TagHelperInHtml_NestedStatement_WithAttribute_SpaceBetweenClosingAngleAndAttributeClosingQuote() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @addTagHelper *, TestAssembly @@ -482,9 +482,9 @@ await RunAutoInsertTestAsync( [Fact] [WorkItem("https://github.com/dotnet/razor-tooling/issues/5694")] - public async Task OnTypeCloseAngle_TagHelperInHtml_NestedStatement_WithMinimalizedAttributeAsync() + public void OnTypeCloseAngle_TagHelperInHtml_NestedStatement_WithMinimalizedAttribute() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @addTagHelper *, TestAssembly @@ -507,9 +507,9 @@ await RunAutoInsertTestAsync( [Fact] [WorkItem("https://github.com/dotnet/razor-tooling/issues/5694")] - public async Task OnTypeCloseAngle_TagHelperInHtml_NestedStatement_WithMinimalizedAttribute_SpaceBetweenClosingAngleAndAttributeClosingQuoteAsync() + public void OnTypeCloseAngle_TagHelperInHtml_NestedStatement_WithMinimalizedAttribute_SpaceBetweenClosingAngleAndAttributeClosingQuote() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @addTagHelper *, TestAssembly @@ -532,9 +532,9 @@ await RunAutoInsertTestAsync( [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/33930")] - public async Task OnTypeCloseAngle_TagHelperInTagHelper_NestedStatementAsync() + public void OnTypeCloseAngle_TagHelperInTagHelper_NestedStatement() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @addTagHelper *, TestAssembly @@ -557,9 +557,9 @@ await RunAutoInsertTestAsync( [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/36906")] - public async Task OnTypeCloseAngle_TagHelperNextToVoidTagHelper_NestedStatementAsync() + public void OnTypeCloseAngle_TagHelperNextToVoidTagHelper_NestedStatement() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @addTagHelper *, TestAssembly @@ -582,9 +582,9 @@ await RunAutoInsertTestAsync( [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/36906")] - public async Task OnTypeCloseAngle_TagHelperNextToTagHelper_NestedStatementAsync() + public void OnTypeCloseAngle_TagHelperNextToTagHelper_NestedStatement() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @addTagHelper *, TestAssembly @@ -606,9 +606,9 @@ await RunAutoInsertTestAsync( } [Fact] - public async Task OnTypeCloseAngle_NormalOrSelfClosingTagHelperTagStructure_CodeBlockAsync() + public void OnTypeCloseAngle_NormalOrSelfClosingTagHelperTagStructure_CodeBlock() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @addTagHelper *, TestAssembly @@ -628,9 +628,9 @@ await RunAutoInsertTestAsync( } [Fact] - public async Task OnTypeCloseAngle_WithSlash_WithoutEndTagTagHelperTagStructureAsync() + public void OnTypeCloseAngle_WithSlash_WithoutEndTagTagHelperTagStructure() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @addTagHelper *, TestAssembly @@ -647,9 +647,9 @@ await RunAutoInsertTestAsync( [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/33930")] - public async Task OnTypeCloseAngle_NestedStatementAsync() + public void OnTypeCloseAngle_NestedStatement() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @addTagHelper *, TestAssembly @@ -671,9 +671,9 @@ await RunAutoInsertTestAsync( } [Fact] - public async Task OnTypeCloseAngle_WithSpace_WithoutEndTagTagHelperTagStructureAsync() + public void OnTypeCloseAngle_WithSpace_WithoutEndTagTagHelperTagStructure() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @addTagHelper *, TestAssembly @@ -689,9 +689,9 @@ await RunAutoInsertTestAsync( } [Fact] - public async Task OnTypeCloseAngle_WithoutEndTagTagHelperTagStructureAsync() + public void OnTypeCloseAngle_WithoutEndTagTagHelperTagStructure() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @addTagHelper *, TestAssembly @@ -707,9 +707,9 @@ await RunAutoInsertTestAsync( } [Fact] - public async Task OnTypeCloseAngle_WithoutEndTagTagHelperTagStructure_CodeBlockAsync() + public void OnTypeCloseAngle_WithoutEndTagTagHelperTagStructure_CodeBlock() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @addTagHelper *, TestAssembly @@ -729,9 +729,9 @@ await RunAutoInsertTestAsync( } [Fact] - public async Task OnTypeCloseAngle_MultipleApplicableTagHelperTagStructuresAsync() + public void OnTypeCloseAngle_MultipleApplicableTagHelperTagStructures() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @addTagHelper *, TestAssembly @@ -747,9 +747,9 @@ await RunAutoInsertTestAsync( } [Fact] - public async Task OnTypeCloseAngle_EscapedTagTagHelperAutoCompletesWithEscapeAsync() + public void OnTypeCloseAngle_EscapedTagTagHelperAutoCompletesWithEscape() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @addTagHelper *, TestAssembly @@ -765,9 +765,9 @@ await RunAutoInsertTestAsync( } [Fact] - public async Task OnTypeCloseAngle_AlwaysClosesStandardHTMLTagAsync() + public void OnTypeCloseAngle_AlwaysClosesStandardHTMLTag() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @"
$$
", @@ -778,9 +778,9 @@ await RunAutoInsertTestAsync( [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/33930")] - public async Task OnTypeCloseAngle_ClosesStandardHTMLTag_NestedStatementAsync() + public void OnTypeCloseAngle_ClosesStandardHTMLTag_NestedStatement() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @if (true) { @@ -797,9 +797,9 @@ await RunAutoInsertTestAsync( [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/36906")] - public async Task OnTypeCloseAngle_TagNextToTag_NestedStatementAsync() + public void OnTypeCloseAngle_TagNextToTag_NestedStatement() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @if (true) { @@ -816,9 +816,9 @@ await RunAutoInsertTestAsync( [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/36906")] - public async Task OnTypeCloseAngle_TagNextToVoidTag_NestedStatementAsync() + public void OnTypeCloseAngle_TagNextToVoidTag_NestedStatement() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @if (true) { @@ -834,9 +834,9 @@ await RunAutoInsertTestAsync( } [Fact] - public async Task OnTypeCloseAngle_ClosesStandardHTMLTagAsync() + public void OnTypeCloseAngle_ClosesStandardHTMLTag() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @"
$$ ", @@ -846,9 +846,9 @@ await RunAutoInsertTestAsync( } [Fact] - public async Task OnTypeCloseAngle_ClosesStandardHTMLTag_CodeBlockAsync() + public void OnTypeCloseAngle_ClosesStandardHTMLTag_CodeBlock() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @{
$$ @@ -862,9 +862,9 @@ await RunAutoInsertTestAsync( } [Fact] - public async Task OnTypeCloseAngle_ClosesVoidHTMLTagAsync() + public void OnTypeCloseAngle_ClosesVoidHTMLTag() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" $$ ", @@ -875,9 +875,9 @@ await RunAutoInsertTestAsync( [Fact] [WorkItem("https://github.com/dotnet/aspnetcore/issues/33930")] - public async Task OnTypeCloseAngle_ClosesVoidHTMLTag_NestedStatementAsync() + public void OnTypeCloseAngle_ClosesVoidHTMLTag_NestedStatement() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @if (true) { @@ -893,9 +893,9 @@ await RunAutoInsertTestAsync( } [Fact] - public async Task OnTypeCloseAngle_ClosesVoidHTMLTag_CodeBlockAsync() + public void OnTypeCloseAngle_ClosesVoidHTMLTag_CodeBlock() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @{ $$ @@ -909,9 +909,9 @@ await RunAutoInsertTestAsync( } [Fact] - public async Task OnTypeCloseAngle_WithSlash_ClosesVoidHTMLTagAsync() + public void OnTypeCloseAngle_WithSlash_ClosesVoidHTMLTag() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" $$ ", @@ -921,9 +921,9 @@ await RunAutoInsertTestAsync( } [Fact] - public async Task OnTypeCloseAngle_WithSpace_ClosesVoidHTMLTagAsync() + public void OnTypeCloseAngle_WithSpace_ClosesVoidHTMLTag() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" $$ ", @@ -933,9 +933,9 @@ await RunAutoInsertTestAsync( } [Fact] - public async Task OnTypeCloseAngle_AutoInsertDisabled_NoopsAsync() + public void OnTypeCloseAngle_AutoInsertDisabled_Noops() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @"
$$ ", diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/CloseTextTagOnAutoInsertProviderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/CloseTextTagOnAutoInsertProviderTest.cs index 925c0b00a34..093f320b2bb 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/CloseTextTagOnAutoInsertProviderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/CloseTextTagOnAutoInsertProviderTest.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Razor.AutoInsert; using Xunit; using Xunit.Abstractions; @@ -11,9 +10,9 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.AutoInsert; public class CloseTextTagOnAutoInsertProviderTest(ITestOutputHelper testOutput) : RazorOnAutoInsertProviderTestBase(testOutput) { [Fact] - public async Task OnTypeCloseAngle_ClosesTextTagAsync() + public void OnTypeCloseAngle_ClosesTextTag() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" @{ $$ @@ -27,9 +26,9 @@ await RunAutoInsertTestAsync( } [Fact] - public async Task OnTypeCloseAngle_OutsideRazorBlock_DoesNotCloseTextTagAsync() + public void OnTypeCloseAngle_OutsideRazorBlock_DoesNotCloseTextTag() { - await RunAutoInsertTestAsync( + RunAutoInsertTest( input: @" $$ ", diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/OnAutoInsertEndpointTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/OnAutoInsertEndpointTest.cs index ea6db93a4f4..0ccaf380d2b 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/OnAutoInsertEndpointTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/OnAutoInsertEndpointTest.cs @@ -194,12 +194,10 @@ private class TestOnAutoInsertProvider(string triggerCharacter, bool canResolve) public string TriggerCharacter { get; } = triggerCharacter; - public ValueTask TryResolveInsertionAsync(Position position, IDocumentSnapshot snapshot, bool enableAutoClosingTags) + public InsertTextEdit? TryResolveInsertion(Position position, RazorCodeDocument codeDocument, bool enableAutoClosingTags) { Called = true; - return canResolve - ? new ValueTask(new InsertTextEdit(ResolvedTextEdit!, default)) - : new ValueTask((InsertTextEdit?)default); + return canResolve ? new(ResolvedTextEdit!, default) : default; } } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/RazorOnAutoInsertProviderTestBase.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/RazorOnAutoInsertProviderTestBase.cs index da24040ecc7..9bf904e4521 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/RazorOnAutoInsertProviderTestBase.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/RazorOnAutoInsertProviderTestBase.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; -using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.LanguageServer.Test; using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer; @@ -15,7 +14,6 @@ using Microsoft.CodeAnalysis.Testing; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; -using Moq; using Xunit; using Xunit.Abstractions; @@ -30,7 +28,7 @@ protected RazorOnAutoInsertProviderTestBase(ITestOutputHelper testOutput) internal abstract IOnAutoInsertProvider CreateProvider(); - protected async Task RunAutoInsertTestAsync(string input, string expected, int tabSize = 4, bool insertSpaces = true, bool enableAutoClosingTags = true, string fileKind = default, IReadOnlyList tagHelpers = default) + protected void RunAutoInsertTest(string input, string expected, int tabSize = 4, bool insertSpaces = true, bool enableAutoClosingTags = true, string fileKind = default, IReadOnlyList tagHelpers = default) { // Arrange TestFileMarkupParser.GetPosition(input, out input, out var location); @@ -41,21 +39,11 @@ protected async Task RunAutoInsertTestAsync(string input, string expected, int t var path = "file:///path/to/document.razor"; var uri = new Uri(path); var codeDocument = CreateCodeDocument(source, uri.AbsolutePath, tagHelpers, fileKind: fileKind); - var options = new FormattingOptions() - { - TabSize = tabSize, - InsertSpaces = insertSpaces, - }; var provider = CreateProvider(); - var sourceText = codeDocument.Source.Text; - var snapshot = Mock.Of(document => - document.TryGetText(out sourceText) == true && - document.GetGeneratedOutputAsync() == Task.FromResult(codeDocument), - MockBehavior.Strict); // Act - var edit = await provider.TryResolveInsertionAsync(position, snapshot, enableAutoClosingTags: enableAutoClosingTags); + var edit = provider.TryResolveInsertion(position, codeDocument, enableAutoClosingTags: enableAutoClosingTags); // Assert var edited = edit is null ? source : ApplyEdit(source, edit.Value.TextEdit); From d72bbb084b8a7c8ce822a6c990325973e6850225 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Tue, 13 Aug 2024 20:42:19 -0700 Subject: [PATCH 31/64] Fixing build - removing unneeded using --- .../LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs index e7d55304a5f..3e0f54fe183 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs @@ -11,7 +11,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; using Microsoft.CodeAnalysis.Razor.AutoInsert; -using Microsoft.CodeAnalysis.Razor.DocumentMapping; using Microsoft.CodeAnalysis.Razor.Logging; using Microsoft.CodeAnalysis.Razor.Protocol.AutoInsert; using Microsoft.CodeAnalysis.Razor.Remote; From 41703022dca68cc819e0f91c9af9f0100c36c3f7 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Tue, 13 Aug 2024 22:18:56 -0700 Subject: [PATCH 32/64] Fix RemoteAutoInsertService logic to follow existing code (always prefer our own AutoInsertService first) --- .../AutoInsert/RemoteAutoInsertService.cs | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs index 9f5e7237fb5..b7baa230e27 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs @@ -69,22 +69,29 @@ private async ValueTask TryResolveInsertionAsync( var codeDocument = await remoteDocumentContext.GetCodeDocumentAsync(cancellationToken).ConfigureAwait(false); + // Always try our own service first, regardless of language + // E.g. if ">" is typed for html tag, it's actually our auto-insert provider + // that adds closing tag instead of HTML even though we are in HTML + var insertTextEdit = _autoInsertService.TryResolveInsertion( + codeDocument, + linePosition.ToPosition(), + character, + autoCloseTags); + + if (insertTextEdit is { } edit) + { + return Response.Results(RemoteInsertTextEdit.FromLspInsertTextEdit(edit)); + } + var languageKind = _documentMappingService.GetLanguageKind(codeDocument, index, rightAssociative: true); - if (languageKind is RazorLanguageKind.Html) + if (languageKind is RazorLanguageKind.Razor) { - return Response.CallHtml; + // If we are in Razor and got no edit from our own service, there is nothing else to do + return Response.NoFurtherHandling; } - else if (languageKind is RazorLanguageKind.Razor) + else if (languageKind is RazorLanguageKind.Html) { - var insertTextEdit = _autoInsertService.TryResolveInsertion( - codeDocument, - linePosition.ToPosition(), - character, - autoCloseTags); - - return insertTextEdit is { } edit - ? Response.Results(RemoteInsertTextEdit.FromLspInsertTextEdit(edit)) - : Response.NoFurtherHandling; + return Response.CallHtml; } // C# case From 5c36ad16c3d6117e5004a482f756ac91a3f14a51 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Wed, 14 Aug 2024 10:26:06 -0700 Subject: [PATCH 33/64] Check allowed trigger characters before delegating to other languages/servers --- .../AutoInsert/RemoteAutoInsertService.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs index b7baa230e27..7334a451ab9 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs @@ -91,11 +91,18 @@ private async ValueTask TryResolveInsertionAsync( } else if (languageKind is RazorLanguageKind.Html) { - return Response.CallHtml; + return AutoInsertService.HtmlAllowedAutoInsertTriggerCharacters.Contains(character) + ? Response.CallHtml + : Response.NoFurtherHandling; } // C# case + if (!AutoInsertService.CSharpAllowedAutoInsertTriggerCharacters.Contains(character)) + { + return Response.NoFurtherHandling; + } + var csharpDocument = codeDocument.GetCSharpDocument(); if (_documentMappingService.TryMapToGeneratedDocumentPosition(csharpDocument, index, out var mappedPosition, out _)) { From e695e4aeea02a53fc1ac6dd1f3b7c2cd41fd54fe Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Wed, 14 Aug 2024 10:52:05 -0700 Subject: [PATCH 34/64] Plumbing through actual option values we need and using them --- .../Remote/IRemoteAutoInsertService.cs | 3 ++ .../AutoInsert/RemoteAutoInsertService.cs | 34 +++++++++++++++++-- .../Cohost/CohostOnAutoInsertEndpoint.cs | 15 ++++++-- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteAutoInsertService.cs index 9cbf726f2bf..ae9d53a4649 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteAutoInsertService.cs @@ -18,5 +18,8 @@ ValueTask TryResolveInsertionAsync( LinePosition position, string character, bool autoCloseTags, + bool formatOnType, + bool indentWithTabs, + int indentSize, CancellationToken cancellationToken); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs index 7334a451ab9..34c804b8b74 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs @@ -14,7 +14,7 @@ using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; - +using Microsoft.VisualStudio.Text.Editor; using Response = Microsoft.CodeAnalysis.Razor.Remote.RemoteResponse; using RoslynFormattingOptions = Roslyn.LanguageServer.Protocol.FormattingOptions; @@ -42,6 +42,9 @@ public ValueTask TryResolveInsertionAsync( LinePosition linePosition, string character, bool autoCloseTags, + bool formatOnType, + bool indentWithTabs, + int indentSize, CancellationToken cancellationToken) => RunServiceAsync( solutionInfo, @@ -51,6 +54,9 @@ public ValueTask TryResolveInsertionAsync( linePosition, character, autoCloseTags, + formatOnType, + indentWithTabs, + indentSize, cancellationToken), cancellationToken); @@ -59,6 +65,9 @@ private async ValueTask TryResolveInsertionAsync( LinePosition linePosition, string character, bool autoCloseTags, + bool formatOnType, + bool indentWithTabs, + int indentSize, CancellationToken cancellationToken) { var sourceText = await remoteDocumentContext.GetSourceTextAsync(cancellationToken).ConfigureAwait(false); @@ -103,12 +112,31 @@ private async ValueTask TryResolveInsertionAsync( return Response.NoFurtherHandling; } + // Special case for C# where we use AutoInsert for two purposes: + // 1. For XML documentation comments (filling out the template when typing "///") + // 2. For "on type formatting" style behavior, like adjusting indentation when pressing Enter inside empty braces + // + // If users have turned off on-type formatting, they don't want the behavior of number 2, but its impossible to separate + // that out from number 1. Typing "///" could just as easily adjust indentation on some unrelated code higher up in the + // file, which is exactly the behavior users complain about. + // + // Therefore we are just going to no-op if the user has turned off on type formatting. Maybe one day we can make this + // smarter, but at least the user can always turn the setting back on, type their "///", and turn it back off, without + // having to restart VS. Not the worst compromise (hopefully!) + if (!formatOnType) + { + return Response.NoFurtherHandling; + } + var csharpDocument = codeDocument.GetCSharpDocument(); if (_documentMappingService.TryMapToGeneratedDocumentPosition(csharpDocument, index, out var mappedPosition, out _)) { var generatedDocument = await remoteDocumentContext.GetGeneratedDocumentAsync(_filePathService, cancellationToken).ConfigureAwait(false); - // TODO: use correct options rather than default - var formattingOptions = new RoslynFormattingOptions(); + var formattingOptions = new RoslynFormattingOptions() + { + InsertSpaces = !indentWithTabs, + TabSize = indentSize + }; var autoInsertResponseItem = await OnAutoInsert.GetOnAutoInsertResponseAsync( generatedDocument, mappedPosition, diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs index 3e0f54fe183..0a027a61f90 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs @@ -17,7 +17,7 @@ using Microsoft.VisualStudio.LanguageServer.ContainedLanguage; using Microsoft.VisualStudio.LanguageServer.Protocol; using Microsoft.VisualStudio.Razor.LanguageClient.Cohost; - +using Microsoft.VisualStudio.Razor.Settings; using RazorLSPConstants = Microsoft.VisualStudio.Razor.LanguageClient.RazorLSPConstants; using Response = Microsoft.CodeAnalysis.Razor.Remote.RemoteResponse; @@ -32,6 +32,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.LanguageClient.Cohost; #pragma warning restore RS0030 // Do not use banned APIs internal class CohostOnAutoInsertEndpoint( IRemoteServiceInvoker remoteServiceInvoker, + IClientSettingsManager clientSettingsManager, #pragma warning disable RS0030 // Do not use banned APIs [ImportMany] IEnumerable onAutoInsertTriggerCharacterProviders, #pragma warning restore RS0030 // Do not use banned APIs @@ -41,6 +42,7 @@ internal class CohostOnAutoInsertEndpoint( : AbstractRazorCohostDocumentRequestHandler, IDynamicRegistrationProvider { private readonly IRemoteServiceInvoker _remoteServiceInvoker = remoteServiceInvoker; + private readonly IClientSettingsManager _clientSettingsManager = clientSettingsManager; private readonly IEnumerable _onAutoInsertTriggerCharacterProviders = onAutoInsertTriggerCharacterProviders; private readonly IHtmlDocumentSynchronizer _htmlDocumentSynchronizer = htmlDocumentSynchronizer; private readonly LSPRequestInvoker _requestInvoker = requestInvoker; @@ -81,6 +83,12 @@ internal class CohostOnAutoInsertEndpoint( _logger.LogDebug($"Resolving auto-insertion for {razorDocument.FilePath}"); + var clientSettings = _clientSettingsManager.GetClientSettings(); + var enableAutoClosingTags = clientSettings.AdvancedSettings.AutoClosingTags; + var formatOnType = clientSettings.AdvancedSettings.FormatOnType; + var indentWithTabs = clientSettings.ClientSpaceSettings.IndentWithTabs; + var indentSize = clientSettings.ClientSpaceSettings.IndentSize; + _logger.LogDebug($"Calling OOP to resolve insertion at {request.Position} invoked by typing '{request.Character}'"); var data = await _remoteServiceInvoker.TryInvokeAsync( razorDocument.Project.Solution, @@ -90,7 +98,10 @@ internal class CohostOnAutoInsertEndpoint( razorDocument.Id, request.Position.ToLinePosition(), request.Character, - autoCloseTags: true, // TODO: get value from client options + autoCloseTags: enableAutoClosingTags, + formatOnType: formatOnType, + indentWithTabs: indentWithTabs, + indentSize: indentSize, cancellationToken), cancellationToken).ConfigureAwait(false); From ca86e8f19a0b2c427957b82c7089a94b9edfa8a1 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Tue, 20 Aug 2024 22:24:17 -0700 Subject: [PATCH 35/64] Fixup After Rebase --- .../AutoInsert/RemoteAutoInsertService.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs index 34c804b8b74..00d1a0b5d5e 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs @@ -14,7 +14,6 @@ using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; -using Microsoft.VisualStudio.Text.Editor; using Response = Microsoft.CodeAnalysis.Razor.Remote.RemoteResponse; using RoslynFormattingOptions = Roslyn.LanguageServer.Protocol.FormattingOptions; @@ -131,7 +130,7 @@ private async ValueTask TryResolveInsertionAsync( var csharpDocument = codeDocument.GetCSharpDocument(); if (_documentMappingService.TryMapToGeneratedDocumentPosition(csharpDocument, index, out var mappedPosition, out _)) { - var generatedDocument = await remoteDocumentContext.GetGeneratedDocumentAsync(_filePathService, cancellationToken).ConfigureAwait(false); + var generatedDocument = await remoteDocumentContext.Snapshot.GetGeneratedDocumentAsync().ConfigureAwait(false); var formattingOptions = new RoslynFormattingOptions() { InsertSpaces = !indentWithTabs, From 0970a46949217b5dc1458f8358b4ba1b07f6ff2d Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Wed, 21 Aug 2024 08:46:56 -0700 Subject: [PATCH 36/64] Consuming RazorFormattingService in remote OnAutoInsert service --- .../RoslynLspExtensions_Position.cs | 9 ++ .../Extensions/RoslynLspExtensions_Range.cs | 9 ++ .../RoslynLspExtensions_TextEdit.cs | 18 +++ .../Formatting/CSharpFormattingPass.cs | 2 +- .../FormattingContentValidationPass.cs | 2 +- .../FormattingDiagnosticValidationPass.cs | 2 +- .../AutoInsert/RemoteInsertTextEdit.cs | 7 -- .../AutoInsert/RemoteAutoInsertService.cs | 113 ++++++++++++++---- .../RemoteCSharpOnTypeFormattingPass.cs | 3 + .../Formatting/RemoteFormattingPasses.cs | 30 +++++ .../Formatting/RemoteRazorFormattingPass.cs | 5 +- .../RemoteRazorFormattingService.cs | 18 +++ .../RemoteAdhocWorkspaceFactory.cs | 5 +- 13 files changed, 191 insertions(+), 32 deletions(-) create mode 100644 src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RoslynLspExtensions_TextEdit.cs create mode 100644 src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteFormattingPasses.cs create mode 100644 src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteRazorFormattingService.cs diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RoslynLspExtensions_Position.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RoslynLspExtensions_Position.cs index e4cd3c1ac93..a7cc192b8c9 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RoslynLspExtensions_Position.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RoslynLspExtensions_Position.cs @@ -3,6 +3,8 @@ using Microsoft.CodeAnalysis.Text; +using VsLspPosition = Microsoft.VisualStudio.LanguageServer.Protocol.Position; + namespace Roslyn.LanguageServer.Protocol; internal static partial class RoslynLspExtensions @@ -12,4 +14,11 @@ public static LinePosition ToLinePosition(this Position position) public static string ToDisplayString(this Position position) => $"({position.Line}, {position.Character})"; + + public static VsLspPosition ToVsLspPosition(this Position position) + => new () + { + Character = position.Character, + Line = position.Line + }; } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RoslynLspExtensions_Range.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RoslynLspExtensions_Range.cs index af7e54346c8..6554a4612c9 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RoslynLspExtensions_Range.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RoslynLspExtensions_Range.cs @@ -3,6 +3,8 @@ using Microsoft.CodeAnalysis.Text; +using VsLspRange = Microsoft.VisualStudio.LanguageServer.Protocol.Range; + namespace Roslyn.LanguageServer.Protocol; internal static partial class RoslynLspExtensions @@ -12,4 +14,11 @@ public static LinePositionSpan ToLinePositionSpan(this Range range) public static string ToDisplayString(this Range range) => $"{range.Start.ToDisplayString()}-{range.End.ToDisplayString()}"; + + public static VsLspRange ToVsLspRange(this Range range) + => new() + { + Start = range.Start.ToVsLspPosition(), + End = range.End.ToVsLspPosition() + }; } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RoslynLspExtensions_TextEdit.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RoslynLspExtensions_TextEdit.cs new file mode 100644 index 00000000000..fcb26a6a87a --- /dev/null +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RoslynLspExtensions_TextEdit.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using VsLspTextEdit = Microsoft.VisualStudio.LanguageServer.Protocol.TextEdit; + +namespace Roslyn.LanguageServer.Protocol; + +internal partial class RoslynLspExtensions +{ + public static VsLspTextEdit ToVsLspTextEdit(this TextEdit textEdit) + { + return new VsLspTextEdit() + { + Range = textEdit.Range.ToVsLspRange(), + NewText = textEdit.NewText + }; + } +} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/CSharpFormattingPass.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/CSharpFormattingPass.cs index e061b305aaf..722b986ffee 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/CSharpFormattingPass.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/CSharpFormattingPass.cs @@ -17,7 +17,7 @@ namespace Microsoft.CodeAnalysis.Razor.Formatting; -internal sealed class CSharpFormattingPass( +internal class CSharpFormattingPass( IDocumentMappingService documentMappingService, ILoggerFactory loggerFactory) : CSharpFormattingPassBase(documentMappingService) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/FormattingContentValidationPass.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/FormattingContentValidationPass.cs index 7973e9d0df3..0b80da73d28 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/FormattingContentValidationPass.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/FormattingContentValidationPass.cs @@ -13,7 +13,7 @@ namespace Microsoft.CodeAnalysis.Razor.Formatting; -internal sealed class FormattingContentValidationPass( +internal class FormattingContentValidationPass( IDocumentMappingService documentMappingService, ILoggerFactory loggerFactory) : FormattingPassBase(documentMappingService) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/FormattingDiagnosticValidationPass.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/FormattingDiagnosticValidationPass.cs index 64bf765dd73..8515f5ff13b 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/FormattingDiagnosticValidationPass.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/FormattingDiagnosticValidationPass.cs @@ -15,7 +15,7 @@ namespace Microsoft.CodeAnalysis.Razor.Formatting; -internal sealed class FormattingDiagnosticValidationPass( +internal class FormattingDiagnosticValidationPass( IDocumentMappingService documentMappingService, ILoggerFactory loggerFactory) : FormattingPassBase(documentMappingService) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs index e9ca6cca32c..3a6be44cbe3 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs @@ -29,13 +29,6 @@ public static RemoteInsertTextEdit FromLspInsertTextEdit(InsertTextEdit edit) edit.TextEdit.NewText, (RoslynInsertTextFormat)edit.InsertTextFormat); - public static RemoteInsertTextEdit FromRoslynAutoInsertResponse( - RoslynVSInternalDocumentOnAutoInsertResponseItem response) - => new( - response.TextEdit.Range.ToLinePositionSpan(), - response.TextEdit.NewText, - response.TextEditFormat); - public static VSInternalDocumentOnAutoInsertResponseItem ToLspInsertTextEdit(RemoteInsertTextEdit edit) => new() { diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs index 00d1a0b5d5e..feeadb6551d 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs @@ -8,14 +8,19 @@ using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers; using Microsoft.CodeAnalysis.Razor.AutoInsert; using Microsoft.CodeAnalysis.Razor.DocumentMapping; +using Microsoft.CodeAnalysis.Razor.Formatting; +using Microsoft.CodeAnalysis.Razor.Logging; using Microsoft.CodeAnalysis.Razor.Protocol; using Microsoft.CodeAnalysis.Razor.Protocol.AutoInsert; using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; +using Roslyn.LanguageServer.Protocol; using Response = Microsoft.CodeAnalysis.Razor.Remote.RemoteResponse; using RoslynFormattingOptions = Roslyn.LanguageServer.Protocol.FormattingOptions; +using RoslynInsertTextFormat = Roslyn.LanguageServer.Protocol.InsertTextFormat; +using VsLspFormattingOptions = Microsoft.VisualStudio.LanguageServer.Protocol.FormattingOptions; namespace Microsoft.CodeAnalysis.Remote.Razor; @@ -34,6 +39,11 @@ private readonly IDocumentMappingService _documentMappingService = args.ExportProvider.GetExportedValue(); private readonly IFilePathService _filePathService = args.ExportProvider.GetExportedValue(); + private readonly IRazorFormattingService _razorFormattingService = + args.ExportProvider.GetExportedValue(); + private readonly ILogger _logger = + args.ExportProvider.GetExportedValue() + .GetOrCreateLogger(nameof(RemoteAutoInsertService)); public ValueTask TryResolveInsertionAsync( RazorPinnedSolutionInfoWrapper solutionInfo, @@ -82,7 +92,7 @@ private async ValueTask TryResolveInsertionAsync( // that adds closing tag instead of HTML even though we are in HTML var insertTextEdit = _autoInsertService.TryResolveInsertion( codeDocument, - linePosition.ToPosition(), + VsLspExtensions.ToPosition(linePosition), character, autoCloseTags); @@ -104,8 +114,34 @@ private async ValueTask TryResolveInsertionAsync( : Response.NoFurtherHandling; } - // C# case + // C# case (we hope) + if (languageKind is not RazorLanguageKind.CSharp) + { + _logger.LogError($"Unsupported language {languageKind}"); + return Response.NoFurtherHandling; + } + + return await TryResolveInsertionAsyncInCSharpAsync( + remoteDocumentContext, + codeDocument, + index, + character, + formatOnType, + indentWithTabs, + indentSize, + cancellationToken); + } + private async ValueTask TryResolveInsertionAsyncInCSharpAsync( + RemoteDocumentContext remoteDocumentContext, + RazorCodeDocument codeDocument, + int index, + string character, + bool formatOnType, + bool indentWithTabs, + int indentSize, + CancellationToken cancellationToken) + { if (!AutoInsertService.CSharpAllowedAutoInsertTriggerCharacters.Contains(character)) { return Response.NoFurtherHandling; @@ -128,26 +164,63 @@ private async ValueTask TryResolveInsertionAsync( } var csharpDocument = codeDocument.GetCSharpDocument(); - if (_documentMappingService.TryMapToGeneratedDocumentPosition(csharpDocument, index, out var mappedPosition, out _)) + if (!_documentMappingService.TryMapToGeneratedDocumentPosition(csharpDocument, index, out var mappedPosition, out _)) { - var generatedDocument = await remoteDocumentContext.Snapshot.GetGeneratedDocumentAsync().ConfigureAwait(false); - var formattingOptions = new RoslynFormattingOptions() - { - InsertSpaces = !indentWithTabs, - TabSize = indentSize - }; - var autoInsertResponseItem = await OnAutoInsert.GetOnAutoInsertResponseAsync( - generatedDocument, - mappedPosition, - character, - formattingOptions, - cancellationToken - ); - return autoInsertResponseItem is not null - ? Response.Results(RemoteInsertTextEdit.FromRoslynAutoInsertResponse(autoInsertResponseItem)) - : Response.NoFurtherHandling; + return Response.NoFurtherHandling; + } + + var generatedDocument = await remoteDocumentContext.Snapshot.GetGeneratedDocumentAsync().ConfigureAwait(false); + var formattingOptions = new RoslynFormattingOptions() + { + InsertSpaces = !indentWithTabs, + TabSize = indentSize + }; + var autoInsertResponseItem = await OnAutoInsert.GetOnAutoInsertResponseAsync( + generatedDocument, + mappedPosition, + character, + formattingOptions, + cancellationToken + ); + + if (autoInsertResponseItem is null) + { + return Response.NoFurtherHandling; + } + + var razorFormattingOptions = new VsLspFormattingOptions() + { + InsertSpaces = !indentWithTabs, + TabSize = indentSize + }; + + var mappedEdits = autoInsertResponseItem.TextEditFormat == RoslynInsertTextFormat.Snippet + ? await _razorFormattingService.FormatSnippetAsync( + remoteDocumentContext, + RazorLanguageKind.CSharp, + [autoInsertResponseItem.TextEdit.ToVsLspTextEdit()], + razorFormattingOptions, + cancellationToken) + .ConfigureAwait(false) + : await _razorFormattingService.FormatOnTypeAsync( + remoteDocumentContext, + RazorLanguageKind.CSharp, + [autoInsertResponseItem.TextEdit.ToVsLspTextEdit()], + razorFormattingOptions, + hostDocumentIndex: 0, + triggerCharacter: '\0', + cancellationToken) + .ConfigureAwait(false); + + if (mappedEdits is not [{ } edit]) + { + return Response.NoFurtherHandling; } - return Response.NoFurtherHandling; + return Response.Results( + new RemoteInsertTextEdit( + edit.Range.ToLinePositionSpan(), + edit.NewText, + autoInsertResponseItem.TextEditFormat)); } } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteCSharpOnTypeFormattingPass.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteCSharpOnTypeFormattingPass.cs index 6d4e33d0af8..6e1bd45cdfd 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteCSharpOnTypeFormattingPass.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteCSharpOnTypeFormattingPass.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. +using System.Composition; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; @@ -13,6 +14,8 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting; +[Export(typeof(IFormattingPass)), Shared] +[method: ImportingConstructor] internal sealed class RemoteCSharpOnTypeFormattingPass( IDocumentMappingService documentMappingService, ILoggerFactory loggerFactory) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteFormattingPasses.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteFormattingPasses.cs new file mode 100644 index 00000000000..2ca3bda8a99 --- /dev/null +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteFormattingPasses.cs @@ -0,0 +1,30 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Composition; +using Microsoft.CodeAnalysis.Razor.DocumentMapping; +using Microsoft.CodeAnalysis.Razor.Formatting; +using Microsoft.CodeAnalysis.Razor.Logging; + +namespace Microsoft.CodeAnalysis.Remote.Razor.Formatting; + +[Export(typeof(IFormattingPass)), Shared] +[method: ImportingConstructor] +internal sealed class RemoteCSharpFormattingPass( + IDocumentMappingService documentMappingService, + ILoggerFactory loggerFactory) + : CSharpFormattingPass(documentMappingService, loggerFactory); + +[Export(typeof(IFormattingPass)), Shared] +[method: ImportingConstructor] +internal sealed class RemoteFormattingContentValidationPass( + IDocumentMappingService documentMappingService, + ILoggerFactory loggerFactory) + : FormattingContentValidationPass(documentMappingService, loggerFactory); + +[Export(typeof(IFormattingPass)), Shared] +[method: ImportingConstructor] +internal sealed class RemoteFormattingDiagnosticValidationPass( + IDocumentMappingService documentMappingService, + ILoggerFactory loggerFactory) + : FormattingDiagnosticValidationPass(documentMappingService, loggerFactory); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteRazorFormattingPass.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteRazorFormattingPass.cs index 500d610a99e..0347e5591c9 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteRazorFormattingPass.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteRazorFormattingPass.cs @@ -1,15 +1,18 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. +using System.Composition; using Microsoft.CodeAnalysis.Razor.DocumentMapping; using Microsoft.CodeAnalysis.Razor.Formatting; namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting; +[Export(typeof(IFormattingPass)), Shared] +[method: ImportingConstructor] internal sealed class RemoteRazorFormattingPass( IDocumentMappingService documentMappingService) : RazorFormattingPassBase(documentMappingService) { // TODO: properly plumb this through - protected override bool CodeBlockBraceOnNextLine => true; + protected override bool CodeBlockBraceOnNextLine => false; } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteRazorFormattingService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteRazorFormattingService.cs new file mode 100644 index 00000000000..6ebcccf42d7 --- /dev/null +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteRazorFormattingService.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Composition; +using Microsoft.CodeAnalysis.Razor.Formatting; +using Microsoft.CodeAnalysis.Razor.Workspaces; + +namespace Microsoft.CodeAnalysis.Remote.Razor.Formatting; + +[Export(typeof(IRazorFormattingService)), Shared] +[method: ImportingConstructor] +internal class RemoteRazorFormattingService( + [ImportMany] IEnumerable formattingPasses, + IAdhocWorkspaceFactory adhocWorkspaceFactory) + : RazorFormattingService(formattingPasses, adhocWorkspaceFactory) +{ +} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RemoteAdhocWorkspaceFactory.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RemoteAdhocWorkspaceFactory.cs index 232561f871f..01d7e5814cb 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RemoteAdhocWorkspaceFactory.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RemoteAdhocWorkspaceFactory.cs @@ -2,16 +2,19 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System.Collections.Immutable; +using System.Composition; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Razor.Workspaces; namespace Microsoft.CodeAnalysis.Remote.Razor; -internal sealed class RemoteAdhocWorkspaceFactory(HostServices hostServices) : IAdhocWorkspaceFactory +[Export(typeof(IAdhocWorkspaceFactory)), Shared] +internal sealed class RemoteAdhocWorkspaceFactory() : IAdhocWorkspaceFactory { public AdhocWorkspace Create(params IWorkspaceService[] workspaceServices) { workspaceServices ??= []; + var hostServices = RemoteWorkspaceAccessor.GetWorkspace().Services.HostServices; var services = AdhocServices.Create( workspaceServices: workspaceServices.ToImmutableArray(), From 8db53139a824ed302645b06feb142b0747cce2af Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Wed, 21 Aug 2024 18:10:14 -0700 Subject: [PATCH 37/64] Fixing exception in RemoteProjectSnapshot.Configuration Moving GetFormatterCodeDocumentAsync() into IDocumentSnapshot (and implementations of that) to allow eaiser differentiation of behavior in remote (cohosting) case where we don't need to check the flag on Project.Configuration. Also AddUsingStatementsIfNeeded *always* gets called, even in cases when they are not actually needed, so we can't Debug.Fail there. --- .../ProjectSystem/DocumentSnapshot.cs | 25 ++++++++++++++++++ .../ProjectSystem/IDocumentSnapshot.cs | 2 ++ .../IDocumentSnapshotExtensions.cs | 26 ------------------- .../ProjectSystem/ImportDocumentSnapshot.cs | 3 +++ .../RemoteCSharpOnTypeFormattingPass.cs | 3 ++- .../ProjectSystem/RemoteDocumentSnapshot.cs | 3 +++ 6 files changed, 35 insertions(+), 27 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs index 00d3e9fa182..8b8b1f86dac 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs @@ -73,4 +73,29 @@ public async Task GetCSharpSyntaxTreeAsync(CancellationToken cancell var csharpText = codeDocument.GetCSharpSourceText(); return CSharpSyntaxTree.ParseText(csharpText, cancellationToken: cancellationToken); } + + public Task GetFormatterCodeDocumentAsync() + { + var forceRuntimeCodeGeneration = Project.Configuration.LanguageServerFlags?.ForceRuntimeCodeGeneration ?? false; + if (!forceRuntimeCodeGeneration) + { + return GetGeneratedOutputAsync(); + } + + // if forceRuntimeCodeGeneration is on, GetGeneratedOutputAsync will get runtime code. As of now + // the formatting service doesn't expect the form of code generated to be what the compiler does with + // runtime. For now force usage of design time and avoid the cache. There may be a slight perf hit + // but either the user is typing (which will invalidate the cache) or the user is manually attempting to + // format. We expect formatting to invalidate the cache if it changes things and consider this an + // acceptable overhead for now. + return GetDesignTimeGeneratedOutputAsync(); + } + + private async Task GetDesignTimeGeneratedOutputAsync() + { + var tagHelpers = await Project.GetTagHelpersAsync(CancellationToken.None).ConfigureAwait(false); + var projectEngine = Project.GetProjectEngine(); + var imports = await DocumentState.GetImportsAsync(this, projectEngine).ConfigureAwait(false); + return await DocumentState.GenerateCodeDocumentAsync(this, Project.GetProjectEngine(), imports, tagHelpers, false).ConfigureAwait(false); + } } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshot.cs index 3e226a3eda9..c8b780b1536 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshot.cs @@ -33,5 +33,7 @@ internal interface IDocumentSnapshot bool TryGetTextVersion(out VersionStamp result); bool TryGetGeneratedOutput([NotNullWhen(true)] out RazorCodeDocument? result); + Task GetFormatterCodeDocumentAsync(); + IDocumentSnapshot WithText(SourceText text); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshotExtensions.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshotExtensions.cs index ef8b26ca9e9..6c3e9ec8764 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshotExtensions.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshotExtensions.cs @@ -58,30 +58,4 @@ public static bool IsPathCandidateForComponent(this IDocumentSnapshot documentSn var fileName = Path.GetFileNameWithoutExtension(documentSnapshot.FilePath); return fileName.AsSpan().Equals(path.Span, FilePathComparison.Instance); } - - public static Task GetFormatterCodeDocumentAsync(this IDocumentSnapshot documentSnapshot) - { - var forceRuntimeCodeGeneration = documentSnapshot.Project.Configuration.LanguageServerFlags?.ForceRuntimeCodeGeneration ?? false; - if (!forceRuntimeCodeGeneration) - { - return documentSnapshot.GetGeneratedOutputAsync(); - } - - // if forceRuntimeCodeGeneration is on, GetGeneratedOutputAsync will get runtime code. As of now - // the formatting service doesn't expect the form of code generated to be what the compiler does with - // runtime. For now force usage of design time and avoid the cache. There may be a slight perf hit - // but either the user is typing (which will invalidate the cache) or the user is manually attempting to - // format. We expect formatting to invalidate the cache if it changes things and consider this an - // acceptable overhead for now. - return GetDesignTimeDocumentAsync(documentSnapshot); - } - - private static async Task GetDesignTimeDocumentAsync(IDocumentSnapshot documentSnapshot) - { - var project = documentSnapshot.Project; - var tagHelpers = await project.GetTagHelpersAsync(CancellationToken.None).ConfigureAwait(false); - var projectEngine = project.GetProjectEngine(); - var imports = await DocumentState.GetImportsAsync(documentSnapshot, projectEngine).ConfigureAwait(false); - return await DocumentState.GenerateCodeDocumentAsync(documentSnapshot, project.GetProjectEngine(), imports, tagHelpers, false).ConfigureAwait(false); - } } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ImportDocumentSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ImportDocumentSnapshot.cs index e77a615ed5c..557695063ae 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ImportDocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ImportDocumentSnapshot.cs @@ -81,4 +81,7 @@ public IDocumentSnapshot WithText(SourceText text) public Task GetCSharpSyntaxTreeAsync(CancellationToken cancellationToken) => throw new NotSupportedException(); + + public Task GetFormatterCodeDocumentAsync() + => throw new NotSupportedException(); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteCSharpOnTypeFormattingPass.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteCSharpOnTypeFormattingPass.cs index 6e1bd45cdfd..08f299f0a67 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteCSharpOnTypeFormattingPass.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteCSharpOnTypeFormattingPass.cs @@ -23,7 +23,8 @@ internal sealed class RemoteCSharpOnTypeFormattingPass( { protected override Task AddUsingStatementEditsIfNecessaryAsync(CodeAnalysis.Razor.Formatting.FormattingContext context, RazorCodeDocument codeDocument, SourceText csharpText, TextEdit[] textEdits, SourceText originalTextWithChanges, TextEdit[] finalEdits, CancellationToken cancellationToken) { - Debug.Fail("Implement this when code actions are migrated to cohosting"); + // Implement this when code actions are migrated to cohosting, + // probably will be able to move it back into base class and make that non-abstract. return Task.FromResult(finalEdits); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs index e4e413bc5a6..e401ed4830a 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs @@ -126,4 +126,7 @@ public async Task GetCSharpSyntaxTreeAsync(CancellationToken cancell var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); return tree.AssumeNotNull(); } + + public Task GetFormatterCodeDocumentAsync() + => GetGeneratedOutputAsync(); } From 2357c4ae6f2ec91075c411ff317cc95798dee9ef Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Wed, 21 Aug 2024 19:12:28 -0700 Subject: [PATCH 38/64] Switch to PreferHtmlInAttributeValuesDocumentPositionInfoStrategy as the original code does That allows the code insert double-quotes by delegating to HTML language server after attribute name and equals. --- .../AutoInsert/RemoteAutoInsertService.cs | 40 ++++++++----------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs index feeadb6551d..ffffc756d5d 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs @@ -3,7 +3,6 @@ using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis.ExternalAccess.Razor; using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers; using Microsoft.CodeAnalysis.Razor.AutoInsert; @@ -101,7 +100,9 @@ private async ValueTask TryResolveInsertionAsync( return Response.Results(RemoteInsertTextEdit.FromLspInsertTextEdit(edit)); } - var languageKind = _documentMappingService.GetLanguageKind(codeDocument, index, rightAssociative: true); + var positionInfo = PreferHtmlInAttributeValuesDocumentPositionInfoStrategy.Instance.GetPositionInfo(_documentMappingService, codeDocument, index); + var languageKind = positionInfo.LanguageKind; + if (languageKind is RazorLanguageKind.Razor) { // If we are in Razor and got no edit from our own service, there is nothing else to do @@ -113,29 +114,26 @@ private async ValueTask TryResolveInsertionAsync( ? Response.CallHtml : Response.NoFurtherHandling; } - - // C# case (we hope) - if (languageKind is not RazorLanguageKind.CSharp) + else if (languageKind is RazorLanguageKind.CSharp) { - _logger.LogError($"Unsupported language {languageKind}"); - return Response.NoFurtherHandling; + var mappedPosition = positionInfo.Position.ToLinePosition(); + return await TryResolveInsertionAsyncInCSharpAsync( + remoteDocumentContext, + mappedPosition, + character, + formatOnType, + indentWithTabs, + indentSize, + cancellationToken); } - return await TryResolveInsertionAsyncInCSharpAsync( - remoteDocumentContext, - codeDocument, - index, - character, - formatOnType, - indentWithTabs, - indentSize, - cancellationToken); + _logger.LogError($"Unsupported language {languageKind}"); + return Response.NoFurtherHandling; } private async ValueTask TryResolveInsertionAsyncInCSharpAsync( RemoteDocumentContext remoteDocumentContext, - RazorCodeDocument codeDocument, - int index, + LinePosition mappedPosition, string character, bool formatOnType, bool indentWithTabs, @@ -163,12 +161,6 @@ private async ValueTask TryResolveInsertionAsyncInCSharpAsync( return Response.NoFurtherHandling; } - var csharpDocument = codeDocument.GetCSharpDocument(); - if (!_documentMappingService.TryMapToGeneratedDocumentPosition(csharpDocument, index, out var mappedPosition, out _)) - { - return Response.NoFurtherHandling; - } - var generatedDocument = await remoteDocumentContext.Snapshot.GetGeneratedDocumentAsync().ConfigureAwait(false); var formattingOptions = new RoslynFormattingOptions() { From 536fe775a72105fd1d573546605005901521536a Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Wed, 21 Aug 2024 19:43:44 -0700 Subject: [PATCH 39/64] Cleanup usings --- .../Protocol/AutoInsert/RemoteInsertTextEdit.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs index 3a6be44cbe3..82dc4251c36 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs @@ -5,10 +5,8 @@ using Microsoft.CodeAnalysis.Razor.AutoInsert; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; -using static Roslyn.LanguageServer.Protocol.RoslynLspExtensions; using static Microsoft.VisualStudio.LanguageServer.Protocol.VsLspExtensions; - -using RoslynVSInternalDocumentOnAutoInsertResponseItem = Roslyn.LanguageServer.Protocol.VSInternalDocumentOnAutoInsertResponseItem; +using static Roslyn.LanguageServer.Protocol.RoslynLspExtensions; using RoslynInsertTextFormat = Roslyn.LanguageServer.Protocol.InsertTextFormat; namespace Microsoft.CodeAnalysis.Razor.Protocol.AutoInsert; From 5e47c94e37e6543b8a1e3b5323aaec4292ba5971 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Wed, 21 Aug 2024 21:26:58 -0700 Subject: [PATCH 40/64] More usings cleanup --- .../Formatting/RemoteCSharpOnTypeFormattingPass.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteCSharpOnTypeFormattingPass.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteCSharpOnTypeFormattingPass.cs index 08f299f0a67..638aa40b213 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteCSharpOnTypeFormattingPass.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteCSharpOnTypeFormattingPass.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System.Composition; -using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; From e2d4b658d546761eb6fe4e941e2974b13210e4c5 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Thu, 22 Aug 2024 04:24:12 -0700 Subject: [PATCH 41/64] PR feedback - minor cleanup and removal of InsertTextEdit type in favor of VSInternalDocumentOnAutoInsertResponseItem --- .../AutoInsert/OnAutoInsertEndpoint.cs | 10 ++++++---- .../AutoClosingTagOnAutoInsertProvider.cs | 14 +++++++++++--- .../AutoInsert/AutoInsertService.cs | 11 +++++++---- .../AutoInsert/CloseTextTagOnAutoInsertProvider.cs | 8 ++++++-- .../AutoInsert/IAutoInsertService.cs | 6 +++--- .../AutoInsert/IOnAutoInsertProvider.cs | 2 +- .../Protocol/AutoInsert/RemoteInsertTextEdit.cs | 5 ++--- .../AutoInsert/OnAutoInsertEndpointTest.cs | 6 ++---- .../RazorOnAutoInsertProviderTestBase.cs | 5 +---- 9 files changed, 39 insertions(+), 28 deletions(-) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs index e97a0ccf654..27908304bc3 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System; +using System.Collections.Frozen; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -57,7 +58,8 @@ public void ApplyCapabilities(VSInternalServerCapabilities serverCapabilities, V { triggerCharacters = triggerCharacters .Concat(AutoInsertService.HtmlAllowedAutoInsertTriggerCharacters) - .Concat(AutoInsertService.CSharpAllowedAutoInsertTriggerCharacters); + .Concat(AutoInsertService.CSharpAllowedAutoInsertTriggerCharacters) + .ToFrozenSet(StringComparer.Ordinal); } serverCapabilities.EnableOnAutoInsert(triggerCharacters); @@ -88,12 +90,12 @@ public void ApplyCapabilities(VSInternalServerCapabilities serverCapabilities, V _optionsMonitor.CurrentValue.AutoClosingTags ); - if (insertTextEdit is not null) + if (insertTextEdit is { } edit) { return new VSInternalDocumentOnAutoInsertResponseItem() { - TextEdit = insertTextEdit.Value.TextEdit, - TextEditFormat = insertTextEdit.Value.InsertTextFormat, + TextEdit = edit.TextEdit, + TextEditFormat = edit.TextEditFormat, }; } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs index 3eb133606d4..e35459a80a5 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs @@ -39,7 +39,7 @@ internal class AutoClosingTagOnAutoInsertProvider : IOnAutoInsertProvider public string TriggerCharacter => ">"; - public InsertTextEdit? TryResolveInsertion(Position position, RazorCodeDocument codeDocument, bool enableAutoClosingTags) + public VSInternalDocumentOnAutoInsertResponseItem? TryResolveInsertion(Position position, RazorCodeDocument codeDocument, bool enableAutoClosingTags) { if (!(enableAutoClosingTags && codeDocument.Source.Text is { } sourceText @@ -54,7 +54,11 @@ internal class AutoClosingTagOnAutoInsertProvider : IOnAutoInsertProvider var formatForEndTag = InsertTextFormat.Snippet; var editForEndTag = VsLspFactory.CreateTextEdit(position, $"$0"); - return new InsertTextEdit(editForEndTag, formatForEndTag); + return new() + { + TextEdit = editForEndTag, + TextEditFormat = formatForEndTag + }; } Debug.Assert(tagNameWithClosingBehavior.AutoClosingBehavior == AutoClosingBehavior.SelfClosing); @@ -65,7 +69,11 @@ internal class AutoClosingTagOnAutoInsertProvider : IOnAutoInsertProvider var insertionText = char.IsWhiteSpace(sourceText[afterCloseAngleIndex - 2]) ? "/" : " /"; var edit = VsLspFactory.CreateTextEdit(position.Line, position.Character - 1, insertionText); - return new InsertTextEdit(edit, format); + return new() + { + TextEdit = edit, + TextEditFormat = format + }; } private static TagNameWithClosingBehavior? TryResolveAutoClosingBehavior(RazorCodeDocument codeDocument, int afterCloseAngleIndex) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs index 2377b77d58b..10fabe3c762 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System; +using System.Collections.Frozen; using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Razor.Language; @@ -14,13 +15,15 @@ internal class AutoInsertService(IEnumerable onAutoInsert { private readonly IEnumerable _onAutoInsertProviders = onAutoInsertProviders; - public static HashSet HtmlAllowedAutoInsertTriggerCharacters { get; } = new(StringComparer.Ordinal) { "=", }; - public static HashSet CSharpAllowedAutoInsertTriggerCharacters { get; } = new(StringComparer.Ordinal) { "'", "/", "\n" }; + public static FrozenSet HtmlAllowedAutoInsertTriggerCharacters { get; } + = new string[] { "=" }.ToFrozenSet(StringComparer.Ordinal); + public static FrozenSet CSharpAllowedAutoInsertTriggerCharacters { get; } + = new string[] { "'", "/", "\n" }.ToFrozenSet(StringComparer.Ordinal); // This gets called just once - public IEnumerable TriggerCharacters => _onAutoInsertProviders.Select((provider) => provider.TriggerCharacter); + public FrozenSet TriggerCharacters => _onAutoInsertProviders.Select((provider) => provider.TriggerCharacter).ToFrozenSet(StringComparer.Ordinal); - public InsertTextEdit? TryResolveInsertion( + public VSInternalDocumentOnAutoInsertResponseItem? TryResolveInsertion( RazorCodeDocument codeDocument, Position position, string character, diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs index 9b5aaaeef0f..fc20d774235 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs @@ -14,7 +14,7 @@ internal class CloseTextTagOnAutoInsertProvider : IOnAutoInsertProvider { public string TriggerCharacter => ">"; - public InsertTextEdit? TryResolveInsertion(Position position, RazorCodeDocument codeDocument, bool enableAutoClosingTags) + public VSInternalDocumentOnAutoInsertResponseItem? TryResolveInsertion(Position position, RazorCodeDocument codeDocument, bool enableAutoClosingTags) { if (!(enableAutoClosingTags && IsAtTextTag(codeDocument, position))) { @@ -25,7 +25,11 @@ internal class CloseTextTagOnAutoInsertProvider : IOnAutoInsertProvider var format = InsertTextFormat.Snippet; var edit = VsLspFactory.CreateTextEdit(position, $"$0"); - return new InsertTextEdit(edit, format); + return new() + { + TextEdit = edit, + TextEditFormat = format + }; } private static bool IsAtTextTag(RazorCodeDocument codeDocument, Position position) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs index 6fa486f1442..c5e4323cce2 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs @@ -1,7 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. -using System.Collections.Generic; +using System.Collections.Frozen; using Microsoft.AspNetCore.Razor.Language; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -9,9 +9,9 @@ namespace Microsoft.CodeAnalysis.Razor.AutoInsert; internal interface IAutoInsertService { - IEnumerable TriggerCharacters { get; } + FrozenSet TriggerCharacters { get; } - InsertTextEdit? TryResolveInsertion( + VSInternalDocumentOnAutoInsertResponseItem? TryResolveInsertion( RazorCodeDocument codeDocument, Position position, string character, diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs index d0035c4af19..48333a2708b 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs @@ -8,5 +8,5 @@ namespace Microsoft.CodeAnalysis.Razor.AutoInsert; internal interface IOnAutoInsertProvider : IOnAutoInsertTriggerCharacterProvider { - public InsertTextEdit? TryResolveInsertion(Position position, RazorCodeDocument codeDocument, bool enableAutoClosingTags); + public VSInternalDocumentOnAutoInsertResponseItem? TryResolveInsertion(Position position, RazorCodeDocument codeDocument, bool enableAutoClosingTags); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs index 82dc4251c36..85b1184fa8f 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System.Runtime.Serialization; -using Microsoft.CodeAnalysis.Razor.AutoInsert; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; using static Microsoft.VisualStudio.LanguageServer.Protocol.VsLspExtensions; @@ -21,11 +20,11 @@ internal readonly record struct RemoteInsertTextEdit( RoslynInsertTextFormat InsertTextFormat ) { - public static RemoteInsertTextEdit FromLspInsertTextEdit(InsertTextEdit edit) + public static RemoteInsertTextEdit FromLspInsertTextEdit(VSInternalDocumentOnAutoInsertResponseItem edit) => new ( edit.TextEdit.Range.ToLinePositionSpan(), edit.TextEdit.NewText, - (RoslynInsertTextFormat)edit.InsertTextFormat); + (RoslynInsertTextFormat)edit.TextEditFormat); public static VSInternalDocumentOnAutoInsertResponseItem ToLspInsertTextEdit(RemoteInsertTextEdit edit) => new() diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/OnAutoInsertEndpointTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/OnAutoInsertEndpointTest.cs index 0ccaf380d2b..4cfbc846952 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/OnAutoInsertEndpointTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/OnAutoInsertEndpointTest.cs @@ -6,8 +6,6 @@ using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.LanguageServer.Test; using Microsoft.CodeAnalysis.Razor.AutoInsert; -using Microsoft.CodeAnalysis.Razor.Formatting; -using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.VisualStudio.LanguageServer.Protocol; using Xunit; using Xunit.Abstractions; @@ -194,10 +192,10 @@ private class TestOnAutoInsertProvider(string triggerCharacter, bool canResolve) public string TriggerCharacter { get; } = triggerCharacter; - public InsertTextEdit? TryResolveInsertion(Position position, RazorCodeDocument codeDocument, bool enableAutoClosingTags) + public VSInternalDocumentOnAutoInsertResponseItem? TryResolveInsertion(Position position, RazorCodeDocument codeDocument, bool enableAutoClosingTags) { Called = true; - return canResolve ? new(ResolvedTextEdit!, default) : default; + return canResolve ? new() { TextEdit = ResolvedTextEdit!, TextEditFormat = default } : default; } } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/RazorOnAutoInsertProviderTestBase.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/RazorOnAutoInsertProviderTestBase.cs index 9bf904e4521..5c7ca878e5b 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/RazorOnAutoInsertProviderTestBase.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/RazorOnAutoInsertProviderTestBase.cs @@ -6,11 +6,8 @@ using System; using System.Collections.Generic; using Microsoft.AspNetCore.Razor.Language; -using Microsoft.AspNetCore.Razor.LanguageServer.Test; using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer; using Microsoft.CodeAnalysis.Razor.AutoInsert; -using Microsoft.CodeAnalysis.Razor.Formatting; -using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Testing; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -46,7 +43,7 @@ protected void RunAutoInsertTest(string input, string expected, int tabSize = 4, var edit = provider.TryResolveInsertion(position, codeDocument, enableAutoClosingTags: enableAutoClosingTags); // Assert - var edited = edit is null ? source : ApplyEdit(source, edit.Value.TextEdit); + var edited = edit is null ? source : ApplyEdit(source, edit.TextEdit); var actual = edited.ToString(); Assert.Equal(expected, actual); } From 614af2e8eb5cd9dd02b3f6d0321dc519d7f6e832 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Thu, 22 Aug 2024 05:18:24 -0700 Subject: [PATCH 42/64] PR feedback - removing MEF usage from RemoteAdhocWorkspaceFactory, minor cleanup --- .../AutoInsert/RemoteAutoInsertService.cs | 10 +++++----- .../Formatting/RemoteRazorFormattingService.cs | 6 ++---- .../RemoteAdhocWorkspaceFactory.cs | 4 +--- .../Cohost/CohostOnAutoInsertEndpoint.cs | 18 +++++++++++++----- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs index ffffc756d5d..3832df70f2f 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs @@ -140,11 +140,6 @@ private async ValueTask TryResolveInsertionAsyncInCSharpAsync( int indentSize, CancellationToken cancellationToken) { - if (!AutoInsertService.CSharpAllowedAutoInsertTriggerCharacters.Contains(character)) - { - return Response.NoFurtherHandling; - } - // Special case for C# where we use AutoInsert for two purposes: // 1. For XML documentation comments (filling out the template when typing "///") // 2. For "on type formatting" style behavior, like adjusting indentation when pressing Enter inside empty braces @@ -161,6 +156,11 @@ private async ValueTask TryResolveInsertionAsyncInCSharpAsync( return Response.NoFurtherHandling; } + if (!AutoInsertService.CSharpAllowedAutoInsertTriggerCharacters.Contains(character)) + { + return Response.NoFurtherHandling; + } + var generatedDocument = await remoteDocumentContext.Snapshot.GetGeneratedDocumentAsync().ConfigureAwait(false); var formattingOptions = new RoslynFormattingOptions() { diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteRazorFormattingService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteRazorFormattingService.cs index 6ebcccf42d7..94f62b403e7 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteRazorFormattingService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteRazorFormattingService.cs @@ -4,15 +4,13 @@ using System.Collections.Generic; using System.Composition; using Microsoft.CodeAnalysis.Razor.Formatting; -using Microsoft.CodeAnalysis.Razor.Workspaces; namespace Microsoft.CodeAnalysis.Remote.Razor.Formatting; [Export(typeof(IRazorFormattingService)), Shared] [method: ImportingConstructor] internal class RemoteRazorFormattingService( - [ImportMany] IEnumerable formattingPasses, - IAdhocWorkspaceFactory adhocWorkspaceFactory) - : RazorFormattingService(formattingPasses, adhocWorkspaceFactory) + [ImportMany] IEnumerable formattingPasses) + : RazorFormattingService(formattingPasses, new RemoteAdhocWorkspaceFactory()) { } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RemoteAdhocWorkspaceFactory.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RemoteAdhocWorkspaceFactory.cs index 01d7e5814cb..5c4f1d548fe 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RemoteAdhocWorkspaceFactory.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RemoteAdhocWorkspaceFactory.cs @@ -2,13 +2,11 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System.Collections.Immutable; -using System.Composition; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Razor.Workspaces; namespace Microsoft.CodeAnalysis.Remote.Razor; -[Export(typeof(IAdhocWorkspaceFactory)), Shared] internal sealed class RemoteAdhocWorkspaceFactory() : IAdhocWorkspaceFactory { public AdhocWorkspace Create(params IWorkspaceService[] workspaceServices) @@ -18,7 +16,7 @@ public AdhocWorkspace Create(params IWorkspaceService[] workspaceServices) var services = AdhocServices.Create( workspaceServices: workspaceServices.ToImmutableArray(), - languageServices: ImmutableArray.Empty, + languageServices: [], fallbackServices: hostServices); return new AdhocWorkspace(services); diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs index 0a027a61f90..c335dc709fc 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs @@ -87,7 +87,7 @@ internal class CohostOnAutoInsertEndpoint( var enableAutoClosingTags = clientSettings.AdvancedSettings.AutoClosingTags; var formatOnType = clientSettings.AdvancedSettings.FormatOnType; var indentWithTabs = clientSettings.ClientSpaceSettings.IndentWithTabs; - var indentSize = clientSettings.ClientSpaceSettings.IndentSize; + var indentSize = clientSettings.ClientSpaceSettings.IndentSize; _logger.LogDebug($"Calling OOP to resolve insertion at {request.Position} invoked by typing '{request.Character}'"); var data = await _remoteServiceInvoker.TryInvokeAsync( @@ -116,19 +116,27 @@ internal class CohostOnAutoInsertEndpoint( return null; } - // Got no data but no signal to stop handling, so try HTML - return await TryResolveHtmlInsertionAsync(razorDocument, request, cancellationToken) + // Got no data but no signal to stop handling + + return await TryResolveHtmlInsertionAsync( + razorDocument, + request, + clientSettings.AdvancedSettings.AutoInsertAttributeQuotes, + cancellationToken) .ConfigureAwait(false); } private async Task TryResolveHtmlInsertionAsync( TextDocument razorDocument, VSInternalDocumentOnAutoInsertParams request, + bool autoInsertAttributeQuotes, CancellationToken cancellationToken) { - // We support auto-insert in HTML only on "=" - if (request.Character != "=") + if (!autoInsertAttributeQuotes && request.Character == "=") { + // Use Razor setting for auto insert attribute quotes. HTML Server doesn't have a way to pass that + // information along so instead we just don't delegate the request. + _logger.LogTrace($"Not delegating to HTML completion because AutoInsertAttributeQuotes is disabled"); return null; } From 0f1d30d529f9e22a4cf51bdcec3a136a33c429f0 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Thu, 22 Aug 2024 05:25:02 -0700 Subject: [PATCH 43/64] PR feedback --- .../LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs index c335dc709fc..1a6c1e28e5e 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs @@ -146,12 +146,7 @@ internal class CohostOnAutoInsertEndpoint( return null; } - var autoInsertParams = new VSInternalDocumentOnAutoInsertParams - { - TextDocument = new TextDocumentIdentifier { Uri = htmlDocument.Uri }, - Character = request.Character, - Position = request.Position - }; + request.TextDocument = request.TextDocument.WithUri(htmlDocument.Uri); _logger.LogDebug($"Resolving auto-insertion edit for {htmlDocument.Uri}"); @@ -159,7 +154,7 @@ internal class CohostOnAutoInsertEndpoint( htmlDocument.Buffer, VSInternalMethods.OnAutoInsertName, RazorLSPConstants.HtmlLanguageServerName, - autoInsertParams, + request, cancellationToken).ConfigureAwait(false); if (result?.Response is null) From 01d3d80e8945b6ed281a2e1969ef49325ec9880c Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Thu, 22 Aug 2024 15:28:17 -0700 Subject: [PATCH 44/64] Type and method renames, minor cleanup per PR feedback --- .../AutoInsert/OnAutoInsertEndpoint.cs | 3 +-- .../AutoInsert/InsertTextEdit.cs | 8 -------- ...emoteInsertTextEdit.cs => RemoteAutoInsertTextEdit.cs} | 6 +++--- .../Remote/IRemoteAutoInsertService.cs | 6 +++--- .../AutoInsert/RemoteAutoInsertService.cs | 8 ++++---- .../LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs | 6 +++--- 6 files changed, 14 insertions(+), 23 deletions(-) delete mode 100644 src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/InsertTextEdit.cs rename src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/{RemoteInsertTextEdit.cs => RemoteAutoInsertTextEdit.cs} (87%) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs index 27908304bc3..a113db597d8 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs @@ -87,8 +87,7 @@ public void ApplyCapabilities(VSInternalServerCapabilities serverCapabilities, V codeDocument, request.Position, character, - _optionsMonitor.CurrentValue.AutoClosingTags - ); + _optionsMonitor.CurrentValue.AutoClosingTags); if (insertTextEdit is { } edit) { diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/InsertTextEdit.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/InsertTextEdit.cs deleted file mode 100644 index 5ee88aebfbf..00000000000 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/InsertTextEdit.cs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root for license information. - -using Microsoft.VisualStudio.LanguageServer.Protocol; - -namespace Microsoft.CodeAnalysis.Razor.AutoInsert; - -internal record struct InsertTextEdit(TextEdit TextEdit, InsertTextFormat InsertTextFormat); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteAutoInsertTextEdit.cs similarity index 87% rename from src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs rename to src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteAutoInsertTextEdit.cs index 85b1184fa8f..00ab068e965 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteInsertTextEdit.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteAutoInsertTextEdit.cs @@ -11,7 +11,7 @@ namespace Microsoft.CodeAnalysis.Razor.Protocol.AutoInsert; [DataContract] -internal readonly record struct RemoteInsertTextEdit( +internal readonly record struct RemoteAutoInsertTextEdit( [property: DataMember(Order = 0)] LinePositionSpan LinePositionSpan, [property: DataMember(Order = 1)] @@ -20,13 +20,13 @@ internal readonly record struct RemoteInsertTextEdit( RoslynInsertTextFormat InsertTextFormat ) { - public static RemoteInsertTextEdit FromLspInsertTextEdit(VSInternalDocumentOnAutoInsertResponseItem edit) + public static RemoteAutoInsertTextEdit FromLspInsertTextEdit(VSInternalDocumentOnAutoInsertResponseItem edit) => new ( edit.TextEdit.Range.ToLinePositionSpan(), edit.TextEdit.NewText, (RoslynInsertTextFormat)edit.TextEditFormat); - public static VSInternalDocumentOnAutoInsertResponseItem ToLspInsertTextEdit(RemoteInsertTextEdit edit) + public static VSInternalDocumentOnAutoInsertResponseItem ToLspInsertTextEdit(RemoteAutoInsertTextEdit edit) => new() { TextEdit = new() diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteAutoInsertService.cs index ae9d53a4649..b8d0c3cb591 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteAutoInsertService.cs @@ -8,11 +8,11 @@ using Microsoft.CodeAnalysis.ExternalAccess.Razor; using Microsoft.CodeAnalysis.Text; -using Response = Microsoft.CodeAnalysis.Razor.Remote.RemoteResponse; +using Response = Microsoft.CodeAnalysis.Razor.Remote.RemoteResponse; -internal interface IRemoteAutoInsertService : IDisposable +internal interface IRemoteAutoInsertService { - ValueTask TryResolveInsertionAsync( + ValueTask GetAutoInsertTextEditAsync( RazorPinnedSolutionInfoWrapper solutionInfo, DocumentId documentId, LinePosition position, diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs index 3832df70f2f..304c6f2dc72 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs @@ -16,7 +16,7 @@ using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; using Roslyn.LanguageServer.Protocol; -using Response = Microsoft.CodeAnalysis.Razor.Remote.RemoteResponse; +using Response = Microsoft.CodeAnalysis.Razor.Remote.RemoteResponse; using RoslynFormattingOptions = Roslyn.LanguageServer.Protocol.FormattingOptions; using RoslynInsertTextFormat = Roslyn.LanguageServer.Protocol.InsertTextFormat; using VsLspFormattingOptions = Microsoft.VisualStudio.LanguageServer.Protocol.FormattingOptions; @@ -44,7 +44,7 @@ private readonly IDocumentMappingService _documentMappingService args.ExportProvider.GetExportedValue() .GetOrCreateLogger(nameof(RemoteAutoInsertService)); - public ValueTask TryResolveInsertionAsync( + public ValueTask GetAutoInsertTextEditAsync( RazorPinnedSolutionInfoWrapper solutionInfo, DocumentId documentId, LinePosition linePosition, @@ -97,7 +97,7 @@ private async ValueTask TryResolveInsertionAsync( if (insertTextEdit is { } edit) { - return Response.Results(RemoteInsertTextEdit.FromLspInsertTextEdit(edit)); + return Response.Results(RemoteAutoInsertTextEdit.FromLspInsertTextEdit(edit)); } var positionInfo = PreferHtmlInAttributeValuesDocumentPositionInfoStrategy.Instance.GetPositionInfo(_documentMappingService, codeDocument, index); @@ -210,7 +210,7 @@ private async ValueTask TryResolveInsertionAsyncInCSharpAsync( } return Response.Results( - new RemoteInsertTextEdit( + new RemoteAutoInsertTextEdit( edit.Range.ToLinePositionSpan(), edit.NewText, autoInsertResponseItem.TextEditFormat)); diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs index 1a6c1e28e5e..7a494652b74 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs @@ -19,7 +19,7 @@ using Microsoft.VisualStudio.Razor.LanguageClient.Cohost; using Microsoft.VisualStudio.Razor.Settings; using RazorLSPConstants = Microsoft.VisualStudio.Razor.LanguageClient.RazorLSPConstants; -using Response = Microsoft.CodeAnalysis.Razor.Remote.RemoteResponse; +using Response = Microsoft.CodeAnalysis.Razor.Remote.RemoteResponse; namespace Microsoft.VisualStudio.LanguageServices.Razor.LanguageClient.Cohost; @@ -93,7 +93,7 @@ internal class CohostOnAutoInsertEndpoint( var data = await _remoteServiceInvoker.TryInvokeAsync( razorDocument.Project.Solution, (service, solutionInfo, cancellationToken) - => service.TryResolveInsertionAsync( + => service.GetAutoInsertTextEditAsync( solutionInfo, razorDocument.Id, request.Position.ToLinePosition(), @@ -108,7 +108,7 @@ internal class CohostOnAutoInsertEndpoint( if (data.Result is { } remoteInsertTextEdit) { _logger.LogDebug($"Got insert text edit from OOP {remoteInsertTextEdit}"); - return RemoteInsertTextEdit.ToLspInsertTextEdit(remoteInsertTextEdit); + return RemoteAutoInsertTextEdit.ToLspInsertTextEdit(remoteInsertTextEdit); } if (data.StopHandling) From 7d61fbffbe39182b23218e43098a9835fd39b712 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Thu, 22 Aug 2024 16:43:52 -0700 Subject: [PATCH 45/64] Renaming interface methods, removing unnecessary usage of FrozenSet --- .../AutoInsert/OnAutoInsertEndpoint.cs | 9 ++--- .../AutoClosingTagOnAutoInsertProvider.cs | 14 +++++--- .../AutoInsert/AutoInsertService.cs | 33 ++++++++++++++----- .../CloseTextTagOnAutoInsertProvider.cs | 9 +++-- .../AutoInsert/IAutoInsertService.cs | 4 +-- .../AutoInsert/IOnAutoInsertProvider.cs | 2 +- .../Remote/IRemoteAutoInsertService.cs | 1 - .../AutoInsert/OnAutoInsertEndpointTest.cs | 10 ++++-- .../RazorOnAutoInsertProviderTestBase.cs | 2 +- 9 files changed, 57 insertions(+), 27 deletions(-) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs index a113db597d8..9b57d860fa1 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Frozen; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -56,10 +57,10 @@ public void ApplyCapabilities(VSInternalServerCapabilities serverCapabilities, V if (_languageServerFeatureOptions.SingleServerSupport) { - triggerCharacters = triggerCharacters - .Concat(AutoInsertService.HtmlAllowedAutoInsertTriggerCharacters) - .Concat(AutoInsertService.CSharpAllowedAutoInsertTriggerCharacters) - .ToFrozenSet(StringComparer.Ordinal); + triggerCharacters = [ + .. triggerCharacters, + .. AutoInsertService.HtmlAllowedAutoInsertTriggerCharacters, + .. AutoInsertService.CSharpAllowedAutoInsertTriggerCharacters]; } serverCapabilities.EnableOnAutoInsert(triggerCharacters); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs index e35459a80a5..5f2e1e10cf1 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs @@ -39,14 +39,16 @@ internal class AutoClosingTagOnAutoInsertProvider : IOnAutoInsertProvider public string TriggerCharacter => ">"; - public VSInternalDocumentOnAutoInsertResponseItem? TryResolveInsertion(Position position, RazorCodeDocument codeDocument, bool enableAutoClosingTags) + public bool TryResolveInsertion(Position position, RazorCodeDocument codeDocument, bool enableAutoClosingTags, out VSInternalDocumentOnAutoInsertResponseItem? autoInsertEdit) { + autoInsertEdit = null; + if (!(enableAutoClosingTags && codeDocument.Source.Text is { } sourceText && sourceText.TryGetAbsoluteIndex(position, out var afterCloseAngleIndex) && TryResolveAutoClosingBehavior(codeDocument, afterCloseAngleIndex) is { } tagNameWithClosingBehavior)) { - return default; + return false; } if (tagNameWithClosingBehavior.AutoClosingBehavior == AutoClosingBehavior.EndTag) @@ -54,11 +56,13 @@ internal class AutoClosingTagOnAutoInsertProvider : IOnAutoInsertProvider var formatForEndTag = InsertTextFormat.Snippet; var editForEndTag = VsLspFactory.CreateTextEdit(position, $"$0"); - return new() + autoInsertEdit = new() { TextEdit = editForEndTag, TextEditFormat = formatForEndTag }; + + return true; } Debug.Assert(tagNameWithClosingBehavior.AutoClosingBehavior == AutoClosingBehavior.SelfClosing); @@ -69,11 +73,13 @@ internal class AutoClosingTagOnAutoInsertProvider : IOnAutoInsertProvider var insertionText = char.IsWhiteSpace(sourceText[afterCloseAngleIndex - 2]) ? "/" : " /"; var edit = VsLspFactory.CreateTextEdit(position.Line, position.Character - 1, insertionText); - return new() + autoInsertEdit = new() { TextEdit = edit, TextEditFormat = format }; + + return true; } private static TagNameWithClosingBehavior? TryResolveAutoClosingBehavior(RazorCodeDocument codeDocument, int afterCloseAngleIndex) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs index 10fabe3c762..bf41608bcd6 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Frozen; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.PooledObjects; @@ -20,8 +21,24 @@ internal class AutoInsertService(IEnumerable onAutoInsert public static FrozenSet CSharpAllowedAutoInsertTriggerCharacters { get; } = new string[] { "'", "/", "\n" }.ToFrozenSet(StringComparer.Ordinal); - // This gets called just once - public FrozenSet TriggerCharacters => _onAutoInsertProviders.Select((provider) => provider.TriggerCharacter).ToFrozenSet(StringComparer.Ordinal); + private readonly ImmutableArray _triggerCharacters = CalculateTriggerCharacters(onAutoInsertProviders); + + private static ImmutableArray CalculateTriggerCharacters(IEnumerable onAutoInsertProviders) + { + var builder = ImmutableArray.CreateBuilder(onAutoInsertProviders.Count()); + foreach (var provider in onAutoInsertProviders) + { + var triggerCharacter = provider.TriggerCharacter; + if (!builder.Contains(triggerCharacter)) + { + builder.Add(triggerCharacter); + } + } + + return builder.ToImmutable(); + } + + public ImmutableArray TriggerCharacters => _triggerCharacters; public VSInternalDocumentOnAutoInsertResponseItem? TryResolveInsertion( RazorCodeDocument codeDocument, @@ -47,13 +64,11 @@ internal class AutoInsertService(IEnumerable onAutoInsert foreach (var provider in applicableProviders) { - var insertTextEdit = provider.TryResolveInsertion( - position, - codeDocument, - autoCloseTags - ); - - if (insertTextEdit is not null) + if (provider.TryResolveInsertion( + position, + codeDocument, + autoCloseTags, + out var insertTextEdit)) { return insertTextEdit; } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs index fc20d774235..485a30add00 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs @@ -14,22 +14,25 @@ internal class CloseTextTagOnAutoInsertProvider : IOnAutoInsertProvider { public string TriggerCharacter => ">"; - public VSInternalDocumentOnAutoInsertResponseItem? TryResolveInsertion(Position position, RazorCodeDocument codeDocument, bool enableAutoClosingTags) + public bool TryResolveInsertion(Position position, RazorCodeDocument codeDocument, bool enableAutoClosingTags, out VSInternalDocumentOnAutoInsertResponseItem? autoInsertEdit) { if (!(enableAutoClosingTags && IsAtTextTag(codeDocument, position))) { - return default; + autoInsertEdit = null; + return false; } // This is a text tag. var format = InsertTextFormat.Snippet; var edit = VsLspFactory.CreateTextEdit(position, $"$0"); - return new() + autoInsertEdit = new() { TextEdit = edit, TextEditFormat = format }; + + return true; } private static bool IsAtTextTag(RazorCodeDocument codeDocument, Position position) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs index c5e4323cce2..abb32159648 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs @@ -1,7 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. -using System.Collections.Frozen; +using System.Collections.Immutable; using Microsoft.AspNetCore.Razor.Language; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -9,7 +9,7 @@ namespace Microsoft.CodeAnalysis.Razor.AutoInsert; internal interface IAutoInsertService { - FrozenSet TriggerCharacters { get; } + ImmutableArray TriggerCharacters { get; } VSInternalDocumentOnAutoInsertResponseItem? TryResolveInsertion( RazorCodeDocument codeDocument, diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs index 48333a2708b..5eb0cd0e86e 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs @@ -8,5 +8,5 @@ namespace Microsoft.CodeAnalysis.Razor.AutoInsert; internal interface IOnAutoInsertProvider : IOnAutoInsertTriggerCharacterProvider { - public VSInternalDocumentOnAutoInsertResponseItem? TryResolveInsertion(Position position, RazorCodeDocument codeDocument, bool enableAutoClosingTags); + public bool TryResolveInsertion(Position position, RazorCodeDocument codeDocument, bool enableAutoClosingTags, out VSInternalDocumentOnAutoInsertResponseItem? autoInsertEdit); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteAutoInsertService.cs index b8d0c3cb591..3872965c0f7 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteAutoInsertService.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. -using System; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/OnAutoInsertEndpointTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/OnAutoInsertEndpointTest.cs index 4cfbc846952..19456219ac8 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/OnAutoInsertEndpointTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/OnAutoInsertEndpointTest.cs @@ -192,10 +192,16 @@ private class TestOnAutoInsertProvider(string triggerCharacter, bool canResolve) public string TriggerCharacter { get; } = triggerCharacter; - public VSInternalDocumentOnAutoInsertResponseItem? TryResolveInsertion(Position position, RazorCodeDocument codeDocument, bool enableAutoClosingTags) + public bool TryResolveInsertion( + Position position, + RazorCodeDocument codeDocument, + bool enableAutoClosingTags, + out VSInternalDocumentOnAutoInsertResponseItem? autoInsertEdit) { Called = true; - return canResolve ? new() { TextEdit = ResolvedTextEdit!, TextEditFormat = default } : default; + autoInsertEdit = canResolve ? new() { TextEdit = ResolvedTextEdit!, TextEditFormat = InsertTextFormat.Plaintext } : null; + + return canResolve; } } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/RazorOnAutoInsertProviderTestBase.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/RazorOnAutoInsertProviderTestBase.cs index 5c7ca878e5b..c5e809d705a 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/RazorOnAutoInsertProviderTestBase.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/RazorOnAutoInsertProviderTestBase.cs @@ -40,7 +40,7 @@ protected void RunAutoInsertTest(string input, string expected, int tabSize = 4, var provider = CreateProvider(); // Act - var edit = provider.TryResolveInsertion(position, codeDocument, enableAutoClosingTags: enableAutoClosingTags); + provider.TryResolveInsertion(position, codeDocument, enableAutoClosingTags: enableAutoClosingTags, out var edit); // Assert var edited = edit is null ? source : ApplyEdit(source, edit.TextEdit); From a7df9efc744c924c10c8cae224eeb8906249babd Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Thu, 22 Aug 2024 20:30:25 -0700 Subject: [PATCH 46/64] Use ImmutableArray to store providers --- .../AutoInsert/AutoInsertService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs index bf41608bcd6..ef0e7d3b7d7 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs @@ -14,7 +14,7 @@ namespace Microsoft.CodeAnalysis.Razor.AutoInsert; internal class AutoInsertService(IEnumerable onAutoInsertProviders) : IAutoInsertService { - private readonly IEnumerable _onAutoInsertProviders = onAutoInsertProviders; + private readonly ImmutableArray _onAutoInsertProviders = onAutoInsertProviders.ToImmutableArray(); public static FrozenSet HtmlAllowedAutoInsertTriggerCharacters { get; } = new string[] { "=" }.ToFrozenSet(StringComparer.Ordinal); @@ -46,7 +46,7 @@ private static ImmutableArray CalculateTriggerCharacters(IEnumerable(); + using var applicableProviders = new PooledArrayBuilder(capacity: _onAutoInsertProviders.Length); foreach (var provider in _onAutoInsertProviders) { if (provider.TriggerCharacter == character) From 34beda33be20e32a5cbde2c029ac631ec31365c5 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Thu, 22 Aug 2024 21:04:00 -0700 Subject: [PATCH 47/64] Switching to bool and out on IAutoInsertService and implementation --- .../AutoInsert/OnAutoInsertEndpoint.cs | 19 ++++++----- .../AutoClosingTagOnAutoInsertProvider.cs | 8 ++++- .../AutoInsert/AutoInsertService.cs | 17 ++++++---- .../CloseTextTagOnAutoInsertProvider.cs | 8 ++++- .../AutoInsert/IAutoInsertService.cs | 8 +++-- .../AutoInsert/IOnAutoInsertProvider.cs | 8 ++++- .../AutoInsert/RemoteAutoInsertService.cs | 32 +++++++------------ .../AutoInsert/OnAutoInsertEndpointTest.cs | 2 ++ 8 files changed, 60 insertions(+), 42 deletions(-) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs index 9b57d860fa1..372d74e461c 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs @@ -84,18 +84,17 @@ public void ApplyCapabilities(VSInternalServerCapabilities serverCapabilities, V var character = request.Character; - var insertTextEdit = _autoInsertService.TryResolveInsertion( - codeDocument, - request.Position, - character, - _optionsMonitor.CurrentValue.AutoClosingTags); - - if (insertTextEdit is { } edit) - { + if(_autoInsertService.TryResolveInsertion( + codeDocument, + request.Position, + character, + _optionsMonitor.CurrentValue.AutoClosingTags, + out var insertTextEdit)) + { return new VSInternalDocumentOnAutoInsertResponseItem() { - TextEdit = edit.TextEdit, - TextEditFormat = edit.TextEditFormat, + TextEdit = insertTextEdit.TextEdit, + TextEditFormat = insertTextEdit.TextEditFormat, }; } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs index 5f2e1e10cf1..04a90006975 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Immutable; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Syntax; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -39,7 +40,12 @@ internal class AutoClosingTagOnAutoInsertProvider : IOnAutoInsertProvider public string TriggerCharacter => ">"; - public bool TryResolveInsertion(Position position, RazorCodeDocument codeDocument, bool enableAutoClosingTags, out VSInternalDocumentOnAutoInsertResponseItem? autoInsertEdit) + public bool TryResolveInsertion( + Position position, + RazorCodeDocument codeDocument, + bool enableAutoClosingTags, + [NotNullWhen(true)] + out VSInternalDocumentOnAutoInsertResponseItem? autoInsertEdit) { autoInsertEdit = null; diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs index ef0e7d3b7d7..27483c3a0b1 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs @@ -5,6 +5,7 @@ using System.Collections.Frozen; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.PooledObjects; @@ -40,11 +41,13 @@ private static ImmutableArray CalculateTriggerCharacters(IEnumerable TriggerCharacters => _triggerCharacters; - public VSInternalDocumentOnAutoInsertResponseItem? TryResolveInsertion( + public bool TryResolveInsertion( RazorCodeDocument codeDocument, Position position, string character, - bool autoCloseTags) + bool autoCloseTags, + [NotNullWhen(true)] + out VSInternalDocumentOnAutoInsertResponseItem? insertTextEdit) { using var applicableProviders = new PooledArrayBuilder(capacity: _onAutoInsertProviders.Length); foreach (var provider in _onAutoInsertProviders) @@ -59,7 +62,8 @@ private static ImmutableArray CalculateTriggerCharacters(IEnumerable CalculateTriggerCharacters(IEnumerable ">"; - public bool TryResolveInsertion(Position position, RazorCodeDocument codeDocument, bool enableAutoClosingTags, out VSInternalDocumentOnAutoInsertResponseItem? autoInsertEdit) + public bool TryResolveInsertion( + Position position, + RazorCodeDocument codeDocument, + bool enableAutoClosingTags, + [NotNullWhen(true)] + out VSInternalDocumentOnAutoInsertResponseItem? autoInsertEdit) { if (!(enableAutoClosingTags && IsAtTextTag(codeDocument, position))) { diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs index abb32159648..32f48a14bed 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using Microsoft.AspNetCore.Razor.Language; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -11,10 +12,11 @@ internal interface IAutoInsertService { ImmutableArray TriggerCharacters { get; } - VSInternalDocumentOnAutoInsertResponseItem? TryResolveInsertion( + bool TryResolveInsertion( RazorCodeDocument codeDocument, Position position, string character, - bool autoCloseTags - ); + bool autoCloseTags, + [NotNullWhen(true)] + out VSInternalDocumentOnAutoInsertResponseItem? insertTextEdit); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs index 5eb0cd0e86e..0f4a4046855 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. +using System.Diagnostics.CodeAnalysis; using Microsoft.AspNetCore.Razor.Language; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -8,5 +9,10 @@ namespace Microsoft.CodeAnalysis.Razor.AutoInsert; internal interface IOnAutoInsertProvider : IOnAutoInsertTriggerCharacterProvider { - public bool TryResolveInsertion(Position position, RazorCodeDocument codeDocument, bool enableAutoClosingTags, out VSInternalDocumentOnAutoInsertResponseItem? autoInsertEdit); + public bool TryResolveInsertion( + Position position, + RazorCodeDocument codeDocument, + bool enableAutoClosingTags, + [NotNullWhen(true)] + out VSInternalDocumentOnAutoInsertResponseItem? autoInsertEdit); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs index 304c6f2dc72..9c3483cad75 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs @@ -32,17 +32,10 @@ protected override IRemoteAutoInsertService CreateService(in ServiceArgs args) => new RemoteAutoInsertService(in args); } - private readonly IAutoInsertService _autoInsertService - = args.ExportProvider.GetExportedValue(); - private readonly IDocumentMappingService _documentMappingService - = args.ExportProvider.GetExportedValue(); - private readonly IFilePathService _filePathService = - args.ExportProvider.GetExportedValue(); - private readonly IRazorFormattingService _razorFormattingService = - args.ExportProvider.GetExportedValue(); - private readonly ILogger _logger = - args.ExportProvider.GetExportedValue() - .GetOrCreateLogger(nameof(RemoteAutoInsertService)); + private readonly IAutoInsertService _autoInsertService = args.ExportProvider.GetExportedValue(); + private readonly IDocumentMappingService _documentMappingService = args.ExportProvider.GetExportedValue(); + private readonly IFilePathService _filePathService = args.ExportProvider.GetExportedValue(); + private readonly IRazorFormattingService _razorFormattingService = args.ExportProvider.GetExportedValue(); public ValueTask GetAutoInsertTextEditAsync( RazorPinnedSolutionInfoWrapper solutionInfo, @@ -89,15 +82,14 @@ private async ValueTask TryResolveInsertionAsync( // Always try our own service first, regardless of language // E.g. if ">" is typed for html tag, it's actually our auto-insert provider // that adds closing tag instead of HTML even though we are in HTML - var insertTextEdit = _autoInsertService.TryResolveInsertion( - codeDocument, - VsLspExtensions.ToPosition(linePosition), - character, - autoCloseTags); - - if (insertTextEdit is { } edit) + if (_autoInsertService.TryResolveInsertion( + codeDocument, + VsLspExtensions.ToPosition(linePosition), + character, + autoCloseTags, + out var insertTextEdit)) { - return Response.Results(RemoteAutoInsertTextEdit.FromLspInsertTextEdit(edit)); + return Response.Results(RemoteAutoInsertTextEdit.FromLspInsertTextEdit(insertTextEdit)); } var positionInfo = PreferHtmlInAttributeValuesDocumentPositionInfoStrategy.Instance.GetPositionInfo(_documentMappingService, codeDocument, index); @@ -127,7 +119,7 @@ private async ValueTask TryResolveInsertionAsync( cancellationToken); } - _logger.LogError($"Unsupported language {languageKind}"); + Logger.LogError($"Unsupported language {languageKind} in {nameof(RemoteAutoInsertService)}"); return Response.NoFurtherHandling; } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/OnAutoInsertEndpointTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/OnAutoInsertEndpointTest.cs index 19456219ac8..dbfaabf5d55 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/OnAutoInsertEndpointTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/OnAutoInsertEndpointTest.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.LanguageServer.Test; @@ -196,6 +197,7 @@ public bool TryResolveInsertion( Position position, RazorCodeDocument codeDocument, bool enableAutoClosingTags, + [NotNullWhen(true)] out VSInternalDocumentOnAutoInsertResponseItem? autoInsertEdit) { Called = true; From a4beaef0614a93f72e007f3904d8b1403cad0735 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Thu, 22 Aug 2024 21:19:19 -0700 Subject: [PATCH 48/64] Cleaned up document position info calculation per PR feedback --- .../AutoInsert/RemoteAutoInsertService.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs index 9c3483cad75..1361664902b 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs @@ -33,10 +33,11 @@ protected override IRemoteAutoInsertService CreateService(in ServiceArgs args) } private readonly IAutoInsertService _autoInsertService = args.ExportProvider.GetExportedValue(); - private readonly IDocumentMappingService _documentMappingService = args.ExportProvider.GetExportedValue(); private readonly IFilePathService _filePathService = args.ExportProvider.GetExportedValue(); private readonly IRazorFormattingService _razorFormattingService = args.ExportProvider.GetExportedValue(); + protected override IDocumentPositionInfoStrategy DocumentPositionInfoStrategy => PreferHtmlInAttributeValuesDocumentPositionInfoStrategy.Instance; + public ValueTask GetAutoInsertTextEditAsync( RazorPinnedSolutionInfoWrapper solutionInfo, DocumentId documentId, @@ -92,7 +93,7 @@ private async ValueTask TryResolveInsertionAsync( return Response.Results(RemoteAutoInsertTextEdit.FromLspInsertTextEdit(insertTextEdit)); } - var positionInfo = PreferHtmlInAttributeValuesDocumentPositionInfoStrategy.Instance.GetPositionInfo(_documentMappingService, codeDocument, index); + var positionInfo = GetPositionInfo(codeDocument, index); var languageKind = positionInfo.LanguageKind; if (languageKind is RazorLanguageKind.Razor) From 46133646722faa3b2c81bc1601d962f11388dd18 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Thu, 22 Aug 2024 21:24:05 -0700 Subject: [PATCH 49/64] Minor cleanup in RemoteAutoInsertService --- .../AutoInsert/RemoteAutoInsertService.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs index 1361664902b..d4c5cc1a115 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs @@ -23,7 +23,7 @@ namespace Microsoft.CodeAnalysis.Remote.Razor; -internal class RemoteAutoInsertService(in ServiceArgs args) +internal sealed class RemoteAutoInsertService(in ServiceArgs args) : RazorDocumentServiceBase(in args), IRemoteAutoInsertService { internal sealed class Factory : FactoryBase @@ -160,6 +160,7 @@ private async ValueTask TryResolveInsertionAsyncInCSharpAsync( InsertSpaces = !indentWithTabs, TabSize = indentSize }; + var autoInsertResponseItem = await OnAutoInsert.GetOnAutoInsertResponseAsync( generatedDocument, mappedPosition, From bf389fb4e5f38894dfca5116192feb24a69c3e04 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Thu, 22 Aug 2024 21:58:51 -0700 Subject: [PATCH 50/64] Cleanup trigger character calculation in CohostOnAutoInsertEndpoint --- .../Cohost/CohostOnAutoInsertEndpoint.cs | 26 ++++++++++++------- ...stOnAutoInsertTriggerCharacterProviders.cs | 6 ++--- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs index 7a494652b74..45d5e98993c 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System.Collections.Generic; +using System.Collections.Immutable; using System.Composition; using System.Linq; using System.Threading; @@ -48,6 +49,20 @@ internal class CohostOnAutoInsertEndpoint( private readonly LSPRequestInvoker _requestInvoker = requestInvoker; private readonly ILogger _logger = loggerFactory.GetOrCreateLogger(); + private readonly ImmutableArray _triggerCharacters = CalculateTriggerChars(onAutoInsertTriggerCharacterProviders); + + private static ImmutableArray CalculateTriggerChars(IEnumerable onAutoInsertTriggerCharacterProviders) + { + var providerTriggerCharacters = onAutoInsertTriggerCharacterProviders.Select((provider) => provider.TriggerCharacter).Distinct(); + + ImmutableArray _triggerCharacters = [ + .. providerTriggerCharacters, + .. AutoInsertService.HtmlAllowedAutoInsertTriggerCharacters, + .. AutoInsertService.CSharpAllowedAutoInsertTriggerCharacters ]; + + return _triggerCharacters; + } + protected override bool MutatesSolutionState => false; protected override bool RequiresLSPSolution => true; @@ -56,18 +71,11 @@ internal class CohostOnAutoInsertEndpoint( { if (clientCapabilities.SupportsVisualStudioExtensions) { - var providerTriggerChars = _onAutoInsertTriggerCharacterProviders - .Select((provider) => provider.TriggerCharacter); - - var triggerCharacters = providerTriggerChars - .Concat(AutoInsertService.HtmlAllowedAutoInsertTriggerCharacters) - .Concat(AutoInsertService.CSharpAllowedAutoInsertTriggerCharacters); - return new Registration { Method = VSInternalMethods.OnAutoInsertName, RegisterOptions = new VSInternalDocumentOnAutoInsertOptions() - .EnableOnAutoInsert(triggerCharacters) + .EnableOnAutoInsert(_triggerCharacters) }; } @@ -87,7 +95,7 @@ internal class CohostOnAutoInsertEndpoint( var enableAutoClosingTags = clientSettings.AdvancedSettings.AutoClosingTags; var formatOnType = clientSettings.AdvancedSettings.FormatOnType; var indentWithTabs = clientSettings.ClientSpaceSettings.IndentWithTabs; - var indentSize = clientSettings.ClientSpaceSettings.IndentSize; + var indentSize = clientSettings.ClientSpaceSettings.IndentSize; _logger.LogDebug($"Calling OOP to resolve insertion at {request.Position} invoked by typing '{request.Character}'"); var data = await _remoteServiceInvoker.TryInvokeAsync( diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertTriggerCharacterProviders.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertTriggerCharacterProviders.cs index 8742bc01a15..84a7207e1cd 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertTriggerCharacterProviders.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertTriggerCharacterProviders.cs @@ -10,9 +10,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.LanguageClient.Cohost; // during registration of CohostOnAutoInsertProvider without using a remote service [Export(typeof(IOnAutoInsertTriggerCharacterProvider))] -internal sealed class CohostAutoClosingTagOnAutoInsertTriggerCharacterProvider - : AutoClosingTagOnAutoInsertProvider; +internal sealed class CohostAutoClosingTagOnAutoInsertTriggerCharacterProvider : AutoClosingTagOnAutoInsertProvider; [Export(typeof(IOnAutoInsertTriggerCharacterProvider))] -internal sealed class CohostCloseTextTagOnAutoInsertTriggerCharacterProvider - : CloseTextTagOnAutoInsertProvider; +internal sealed class CohostCloseTextTagOnAutoInsertTriggerCharacterProvider : CloseTextTagOnAutoInsertProvider; From c9e513091e8c91576462f49bd6c8fea8a011adcc Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Fri, 23 Aug 2024 06:54:37 -0700 Subject: [PATCH 51/64] Formatting changes, VsLspFactory usage --- .../AutoInsert/RemoteAutoInsertTextEdit.cs | 16 ++++------------ .../AutoInsert/OOPAutoInsertService.cs | 6 +----- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteAutoInsertTextEdit.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteAutoInsertTextEdit.cs index 00ab068e965..0d64ec20a57 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteAutoInsertTextEdit.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteAutoInsertTextEdit.cs @@ -12,13 +12,9 @@ namespace Microsoft.CodeAnalysis.Razor.Protocol.AutoInsert; [DataContract] internal readonly record struct RemoteAutoInsertTextEdit( - [property: DataMember(Order = 0)] - LinePositionSpan LinePositionSpan, - [property: DataMember(Order = 1)] - string NewText, - [property: DataMember(Order = 2)] - RoslynInsertTextFormat InsertTextFormat - ) + [property: DataMember(Order = 0)] LinePositionSpan LinePositionSpan, + [property: DataMember(Order = 1)] string NewText, + [property: DataMember(Order = 2)] RoslynInsertTextFormat InsertTextFormat) { public static RemoteAutoInsertTextEdit FromLspInsertTextEdit(VSInternalDocumentOnAutoInsertResponseItem edit) => new ( @@ -29,11 +25,7 @@ public static RemoteAutoInsertTextEdit FromLspInsertTextEdit(VSInternalDocumentO public static VSInternalDocumentOnAutoInsertResponseItem ToLspInsertTextEdit(RemoteAutoInsertTextEdit edit) => new() { - TextEdit = new() - { - Range = VsLspExtensions.ToRange(edit.LinePositionSpan), - NewText = edit.NewText - }, + TextEdit = VsLspFactory.CreateTextEdit(edit.LinePositionSpan, edit.NewText), TextEditFormat = (InsertTextFormat)edit.InsertTextFormat, }; diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/OOPAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/OOPAutoInsertService.cs index 821513d7e0d..c08b3107144 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/OOPAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/OOPAutoInsertService.cs @@ -9,8 +9,4 @@ namespace Microsoft.CodeAnalysis.Remote.Razor.AutoInsert; [Export(typeof(IAutoInsertService)), Shared] [method: ImportingConstructor] -internal sealed class OOPAutoInsertService( - [ImportMany]IEnumerable providers - ) : AutoInsertService(providers) -{ -} +internal sealed class OOPAutoInsertService([ImportMany] IEnumerable providers) : AutoInsertService(providers) From 9d339ef017edec6468f031095ea093ec0e7ac52b Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Fri, 23 Aug 2024 07:11:54 -0700 Subject: [PATCH 52/64] Revert to MEF DI in RemoteAdhocWorkspaceFactory/RemoteRazorFormattingService --- .../AutoInsert/OOPAutoInsertService.cs | 2 ++ .../Formatting/RemoteRazorFormattingService.cs | 6 ++++-- .../RemoteAdhocWorkspaceFactory.cs | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/OOPAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/OOPAutoInsertService.cs index c08b3107144..6e19d616fcf 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/OOPAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/OOPAutoInsertService.cs @@ -10,3 +10,5 @@ namespace Microsoft.CodeAnalysis.Remote.Razor.AutoInsert; [Export(typeof(IAutoInsertService)), Shared] [method: ImportingConstructor] internal sealed class OOPAutoInsertService([ImportMany] IEnumerable providers) : AutoInsertService(providers) +{ +} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteRazorFormattingService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteRazorFormattingService.cs index 94f62b403e7..6ebcccf42d7 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteRazorFormattingService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteRazorFormattingService.cs @@ -4,13 +4,15 @@ using System.Collections.Generic; using System.Composition; using Microsoft.CodeAnalysis.Razor.Formatting; +using Microsoft.CodeAnalysis.Razor.Workspaces; namespace Microsoft.CodeAnalysis.Remote.Razor.Formatting; [Export(typeof(IRazorFormattingService)), Shared] [method: ImportingConstructor] internal class RemoteRazorFormattingService( - [ImportMany] IEnumerable formattingPasses) - : RazorFormattingService(formattingPasses, new RemoteAdhocWorkspaceFactory()) + [ImportMany] IEnumerable formattingPasses, + IAdhocWorkspaceFactory adhocWorkspaceFactory) + : RazorFormattingService(formattingPasses, adhocWorkspaceFactory) { } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RemoteAdhocWorkspaceFactory.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RemoteAdhocWorkspaceFactory.cs index 5c4f1d548fe..9abf7da6394 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RemoteAdhocWorkspaceFactory.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RemoteAdhocWorkspaceFactory.cs @@ -2,11 +2,13 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System.Collections.Immutable; +using System.Composition; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Razor.Workspaces; namespace Microsoft.CodeAnalysis.Remote.Razor; +[Export(typeof(IAdhocWorkspaceFactory)), Shared] internal sealed class RemoteAdhocWorkspaceFactory() : IAdhocWorkspaceFactory { public AdhocWorkspace Create(params IWorkspaceService[] workspaceServices) From a629cf36b9bb02ce1ff234cbc0d1f2835d0d4373 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Fri, 23 Aug 2024 07:36:47 -0700 Subject: [PATCH 53/64] Removing Rolsyn to VS LSP extension methods --- .../Extensions/RoslynLspExtensions_Position.cs | 9 --------- .../Extensions/RoslynLspExtensions_Range.cs | 9 --------- .../Extensions/RoslynLspExtensions_TextEdit.cs | 18 ------------------ .../AutoInsert/RemoteAutoInsertService.cs | 7 +++++-- 4 files changed, 5 insertions(+), 38 deletions(-) delete mode 100644 src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RoslynLspExtensions_TextEdit.cs diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RoslynLspExtensions_Position.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RoslynLspExtensions_Position.cs index a7cc192b8c9..e4cd3c1ac93 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RoslynLspExtensions_Position.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RoslynLspExtensions_Position.cs @@ -3,8 +3,6 @@ using Microsoft.CodeAnalysis.Text; -using VsLspPosition = Microsoft.VisualStudio.LanguageServer.Protocol.Position; - namespace Roslyn.LanguageServer.Protocol; internal static partial class RoslynLspExtensions @@ -14,11 +12,4 @@ public static LinePosition ToLinePosition(this Position position) public static string ToDisplayString(this Position position) => $"({position.Line}, {position.Character})"; - - public static VsLspPosition ToVsLspPosition(this Position position) - => new () - { - Character = position.Character, - Line = position.Line - }; } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RoslynLspExtensions_Range.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RoslynLspExtensions_Range.cs index 6554a4612c9..af7e54346c8 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RoslynLspExtensions_Range.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RoslynLspExtensions_Range.cs @@ -3,8 +3,6 @@ using Microsoft.CodeAnalysis.Text; -using VsLspRange = Microsoft.VisualStudio.LanguageServer.Protocol.Range; - namespace Roslyn.LanguageServer.Protocol; internal static partial class RoslynLspExtensions @@ -14,11 +12,4 @@ public static LinePositionSpan ToLinePositionSpan(this Range range) public static string ToDisplayString(this Range range) => $"{range.Start.ToDisplayString()}-{range.End.ToDisplayString()}"; - - public static VsLspRange ToVsLspRange(this Range range) - => new() - { - Start = range.Start.ToVsLspPosition(), - End = range.End.ToVsLspPosition() - }; } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RoslynLspExtensions_TextEdit.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RoslynLspExtensions_TextEdit.cs deleted file mode 100644 index fcb26a6a87a..00000000000 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RoslynLspExtensions_TextEdit.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root for license information. - -using VsLspTextEdit = Microsoft.VisualStudio.LanguageServer.Protocol.TextEdit; - -namespace Roslyn.LanguageServer.Protocol; - -internal partial class RoslynLspExtensions -{ - public static VsLspTextEdit ToVsLspTextEdit(this TextEdit textEdit) - { - return new VsLspTextEdit() - { - Range = textEdit.Range.ToVsLspRange(), - NewText = textEdit.NewText - }; - } -} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs index d4c5cc1a115..f1fb4e4fc17 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs @@ -180,18 +180,21 @@ private async ValueTask TryResolveInsertionAsyncInCSharpAsync( TabSize = indentSize }; + var vsLspTextEdit = VsLspFactory.CreateTextEdit( + autoInsertResponseItem.TextEdit.Range.ToLinePositionSpan(), + autoInsertResponseItem.TextEdit.NewText); var mappedEdits = autoInsertResponseItem.TextEditFormat == RoslynInsertTextFormat.Snippet ? await _razorFormattingService.FormatSnippetAsync( remoteDocumentContext, RazorLanguageKind.CSharp, - [autoInsertResponseItem.TextEdit.ToVsLspTextEdit()], + [vsLspTextEdit], razorFormattingOptions, cancellationToken) .ConfigureAwait(false) : await _razorFormattingService.FormatOnTypeAsync( remoteDocumentContext, RazorLanguageKind.CSharp, - [autoInsertResponseItem.TextEdit.ToVsLspTextEdit()], + [vsLspTextEdit], razorFormattingOptions, hostDocumentIndex: 0, triggerCharacter: '\0', From 46c9356636f34a27ea14aae6a0c6148123671d11 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Fri, 23 Aug 2024 09:11:34 -0700 Subject: [PATCH 54/64] Switch to raw strings in tests and minor whitespace cleanup --- .../AutoInsert/OnAutoInsertEndpoint.cs | 2 +- .../AutoClosingTagOnAutoInsertProviderTest.cs | 1001 ++++++++--------- .../CloseTextTagOnAutoInsertProviderTest.cs | 32 +- .../RazorOnAutoInsertProviderTestBase.cs | 3 +- 4 files changed, 519 insertions(+), 519 deletions(-) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs index 372d74e461c..93dd2e890e8 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AutoInsert/OnAutoInsertEndpoint.cs @@ -84,7 +84,7 @@ public void ApplyCapabilities(VSInternalServerCapabilities serverCapabilities, V var character = request.Character; - if(_autoInsertService.TryResolveInsertion( + if (_autoInsertService.TryResolveInsertion( codeDocument, request.Position, character, diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/AutoClosingTagOnAutoInsertProviderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/AutoClosingTagOnAutoInsertProviderTest.cs index 3a7edbd5def..a985b0d3fbb 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/AutoClosingTagOnAutoInsertProviderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/AutoClosingTagOnAutoInsertProviderTest.cs @@ -103,19 +103,18 @@ private static TagHelperDescriptor WithoutEndTagTagHelper public void OnTypeCloseAngle_ConflictingAutoClosingBehaviorsChoosesMostSpecific() { RunAutoInsertTest( -input: @" -@addTagHelper *, TestAssembly + input: """ + @addTagHelper *, TestAssembly -$$ -", -expected: @" -@addTagHelper *, TestAssembly - - -", -fileKind: FileKinds.Legacy, -tagHelpers: new[] { WithoutEndTagTagHelper, CatchAllTagHelper }); + $$ + """, + expected: """ + @addTagHelper *, TestAssembly + + """, + fileKind: FileKinds.Legacy, + tagHelpers: new[] { WithoutEndTagTagHelper, CatchAllTagHelper }); } [Fact] @@ -123,18 +122,18 @@ public void OnTypeCloseAngle_ConflictingAutoClosingBehaviorsChoosesMostSpecific( public void OnTypeCloseAngle_TagHelperAlreadyHasEndTag() { RunAutoInsertTest( -input: @" -@addTagHelper *, TestAssembly + input: """ + @addTagHelper *, TestAssembly -$$ -", -expected: @" -@addTagHelper *, TestAssembly + $$ + """, + expected: """ + @addTagHelper *, TestAssembly - -", -fileKind: FileKinds.Legacy, -tagHelpers: new[] { NormalOrSelfClosingTagHelper }); + + """, + fileKind: FileKinds.Legacy, + tagHelpers: new[] { NormalOrSelfClosingTagHelper }); } [Fact] @@ -142,18 +141,18 @@ public void OnTypeCloseAngle_TagHelperAlreadyHasEndTag() public void OnTypeCloseAngle_VoidTagHelperHasEndTag_ShouldStillAutoClose() { RunAutoInsertTest( -input: @" - @addTagHelper *, TestAssembly + input: """ + @addTagHelper *, TestAssembly - $$ - ", -expected: @" - @addTagHelper *, TestAssembly + $$ + """, + expected: """ + @addTagHelper *, TestAssembly - - ", -fileKind: FileKinds.Legacy, -tagHelpers: new[] { UnspecifiedInputTagHelper }); + + """, + fileKind: FileKinds.Legacy, + tagHelpers: new[] { UnspecifiedInputTagHelper }); } [Fact] @@ -161,12 +160,12 @@ public void OnTypeCloseAngle_VoidTagHelperHasEndTag_ShouldStillAutoClose() public void OnTypeCloseAngle_TagAlreadyHasEndTag() { RunAutoInsertTest( -input: @" -
$$
- ", -expected: @" -
- "); + input: """ +
$$
+ """, + expected: """ +
+ """); } [Fact] @@ -174,20 +173,20 @@ public void OnTypeCloseAngle_TagAlreadyHasEndTag() public void OnTypeCloseAngle_TagDoesNotAutoCloseOutOfScope() { RunAutoInsertTest( -input: @" -
- @if (true) - { -
$$
- } - ", -expected: @" -
- @if (true) - { -
- } - "); + input: """ +
+ @if (true) + { +
$$
+ } + """, + expected: """ +
+ @if (true) + { +
+ } + """); } [Fact] @@ -195,12 +194,12 @@ public void OnTypeCloseAngle_TagDoesNotAutoCloseOutOfScope() public void OnTypeCloseAngle_VoidTagHasEndTag_ShouldStillose() { RunAutoInsertTest( -input: @" - $$ - ", -expected: @" - - "); + input: """ + $$ + """, + expected: """ + + """); } [Fact] @@ -208,18 +207,18 @@ public void OnTypeCloseAngle_VoidTagHasEndTag_ShouldStillose() public void OnTypeCloseAngle_VoidElementMirroringTagHelper() { RunAutoInsertTest( -input: @" - @addTagHelper *, TestAssembly + input: """ + @addTagHelper *, TestAssembly - $$ - ", -expected: @" - @addTagHelper *, TestAssembly + $$ + """, + expected: """ + @addTagHelper *, TestAssembly - $0 - ", -fileKind: FileKinds.Legacy, -tagHelpers: new[] { UnspecifiedInputMirroringTagHelper }); + $0 + """, + fileKind: FileKinds.Legacy, + tagHelpers: new[] { UnspecifiedInputMirroringTagHelper }); } [Fact] @@ -227,82 +226,82 @@ public void OnTypeCloseAngle_VoidElementMirroringTagHelper() public void OnTypeCloseAngle_VoidHtmlElementCapitalized_SelfCloses() { RunAutoInsertTest( -input: "$$", -expected: "", -fileKind: FileKinds.Legacy, -tagHelpers: Array.Empty()); + input: "$$", + expected: "", + fileKind: FileKinds.Legacy, + tagHelpers: Array.Empty()); } [Fact] public void OnTypeCloseAngle_NormalOrSelfClosingStructureOverridesVoidTagBehavior() { RunAutoInsertTest( -input: @" - @addTagHelper *, TestAssembly + input: """ + @addTagHelper *, TestAssembly - $$ - ", -expected: @" - @addTagHelper *, TestAssembly + $$ + """, + expected: """ + @addTagHelper *, TestAssembly - $0 - ", -fileKind: FileKinds.Legacy, -tagHelpers: new[] { NormalOrSelfclosingInputTagHelper }); + $0 + """, + fileKind: FileKinds.Legacy, + tagHelpers: new[] { NormalOrSelfclosingInputTagHelper }); } [Fact] public void OnTypeCloseAngle_UnspeccifiedStructureInheritsVoidTagBehavior() { RunAutoInsertTest( -input: @" - @addTagHelper *, TestAssembly + input: """ + @addTagHelper *, TestAssembly - $$ - ", -expected: @" - @addTagHelper *, TestAssembly + $$ + """, + expected: """ + @addTagHelper *, TestAssembly - - ", -fileKind: FileKinds.Legacy, -tagHelpers: new[] { UnspecifiedInputTagHelper }); + + """, + fileKind: FileKinds.Legacy, + tagHelpers: new[] { UnspecifiedInputTagHelper }); } [Fact] public void OnTypeCloseAngle_UnspeccifiedTagHelperTagStructure() { RunAutoInsertTest( -input: @" - @addTagHelper *, TestAssembly + input: """ + @addTagHelper *, TestAssembly - $$ - ", -expected: @" - @addTagHelper *, TestAssembly + $$ + """, + expected: """ + @addTagHelper *, TestAssembly - $0 - ", -fileKind: FileKinds.Legacy, -tagHelpers: new[] { UnspecifiedTagHelper }); + $0 + """, + fileKind: FileKinds.Legacy, + tagHelpers: new[] { UnspecifiedTagHelper }); } [Fact] public void OnTypeCloseAngle_NormalOrSelfClosingTagHelperTagStructure() { RunAutoInsertTest( -input: @" - @addTagHelper *, TestAssembly + input: """ + @addTagHelper *, TestAssembly - $$ - ", -expected: @" - @addTagHelper *, TestAssembly + $$ + """, + expected: """ + @addTagHelper *, TestAssembly - $0 - ", -fileKind: FileKinds.Legacy, -tagHelpers: new[] { NormalOrSelfClosingTagHelper }); + $0 + """, + fileKind: FileKinds.Legacy, + tagHelpers: new[] { NormalOrSelfClosingTagHelper }); } [Fact] @@ -310,24 +309,24 @@ public void OnTypeCloseAngle_NormalOrSelfClosingTagHelperTagStructure() public void OnTypeCloseAngle_TagHelperInHtml_NestedStatement() { RunAutoInsertTest( -input: @" - @addTagHelper *, TestAssembly + input: """ + @addTagHelper *, TestAssembly - @if (true) - { -
$$
- } - ", -expected: @" - @addTagHelper *, TestAssembly + @if (true) + { +
$$
+ } + """, + expected: """ + @addTagHelper *, TestAssembly - @if (true) - { -
$0
- } - ", -fileKind: FileKinds.Legacy, -tagHelpers: new[] { NormalOrSelfClosingTagHelper }); + @if (true) + { +
$0
+ } + """, + fileKind: FileKinds.Legacy, + tagHelpers: new[] { NormalOrSelfClosingTagHelper }); } [Fact] @@ -335,24 +334,24 @@ public void OnTypeCloseAngle_TagHelperInHtml_NestedStatement() public void OnTypeCloseAngle_HtmlTagInHtml_NestedStatement_WithAttribute() { RunAutoInsertTest( -input: @" - @addTagHelper *, TestAssembly + input: """ + @addTagHelper *, TestAssembly - @if (true) - { - - } - ", -expected: @" - @addTagHelper *, TestAssembly + @if (true) + { + + } + """, + expected: """ + @addTagHelper *, TestAssembly - @if (true) - { - - } - ", -fileKind: FileKinds.Legacy, -tagHelpers: new[] { NormalOrSelfClosingTagHelper }); + @if (true) + { + + } + """, + fileKind: FileKinds.Legacy, + tagHelpers: new[] { NormalOrSelfClosingTagHelper }); } [Fact] @@ -360,24 +359,24 @@ public void OnTypeCloseAngle_HtmlTagInHtml_NestedStatement_WithAttribute() public void OnTypeCloseAngle_HtmlTagInHtml_NestedStatement_WithAttribute_SpaceBetweenClosingAngleAndAttributeClosingQuote() { RunAutoInsertTest( -input: @" - @addTagHelper *, TestAssembly + input: """ + @addTagHelper *, TestAssembly - @if (true) - { - - } - ", -expected: @" - @addTagHelper *, TestAssembly + @if (true) + { + + } + """, + expected: """ + @addTagHelper *, TestAssembly - @if (true) - { - - } - ", -fileKind: FileKinds.Legacy, -tagHelpers: new[] { NormalOrSelfClosingTagHelper }); + @if (true) + { + + } + """, + fileKind: FileKinds.Legacy, + tagHelpers: new[] { NormalOrSelfClosingTagHelper }); } [Fact] @@ -385,24 +384,24 @@ public void OnTypeCloseAngle_HtmlTagInHtml_NestedStatement_WithAttribute_SpaceBe public void OnTypeCloseAngle_HtmlTagInHtml_NestedStatement_WithMinimalizedAttribute() { RunAutoInsertTest( -input: @" - @addTagHelper *, TestAssembly + input: """ + @addTagHelper *, TestAssembly - @if (true) - { -
$$
- } - ", -expected: @" - @addTagHelper *, TestAssembly + @if (true) + { +
$$
+ } + """, + expected: """ + @addTagHelper *, TestAssembly - @if (true) - { -
$0
- } - ", -fileKind: FileKinds.Legacy, -tagHelpers: new[] { NormalOrSelfClosingTagHelper }); + @if (true) + { +
$0
+ } + """, + fileKind: FileKinds.Legacy, + tagHelpers: new[] { NormalOrSelfClosingTagHelper }); } [Fact] @@ -410,24 +409,24 @@ public void OnTypeCloseAngle_HtmlTagInHtml_NestedStatement_WithMinimalizedAttrib public void OnTypeCloseAngle_HtmlTagInHtml_NestedStatement_WithMinimalizedAttribute_SpaceBetweenClosingAngleAndAttributeClosingQuote() { RunAutoInsertTest( -input: @" - @addTagHelper *, TestAssembly + input: """ + @addTagHelper *, TestAssembly - @if (true) - { -
$$
- } - ", -expected: @" - @addTagHelper *, TestAssembly + @if (true) + { +
$$
+ } + """, + expected: """ + @addTagHelper *, TestAssembly - @if (true) - { -
$0
- } - ", -fileKind: FileKinds.Legacy, -tagHelpers: new[] { NormalOrSelfClosingTagHelper }); + @if (true) + { +
$0
+ } + """, + fileKind: FileKinds.Legacy, + tagHelpers: new[] { NormalOrSelfClosingTagHelper }); } [Fact] @@ -435,24 +434,24 @@ public void OnTypeCloseAngle_HtmlTagInHtml_NestedStatement_WithMinimalizedAttrib public void OnTypeCloseAngle_TagHelperInHtml_NestedStatement_WithAttribute() { RunAutoInsertTest( -input: @" - @addTagHelper *, TestAssembly + input: """ + @addTagHelper *, TestAssembly - @if (true) - { -
$$
- } - ", -expected: @" - @addTagHelper *, TestAssembly + @if (true) + { +
$$
+ } + """, + expected: """ + @addTagHelper *, TestAssembly - @if (true) - { -
$0
- } - ", -fileKind: FileKinds.Legacy, -tagHelpers: new[] { NormalOrSelfClosingTagHelper }); + @if (true) + { +
$0
+ } + """, + fileKind: FileKinds.Legacy, + tagHelpers: new[] { NormalOrSelfClosingTagHelper }); } [Fact] @@ -460,24 +459,24 @@ public void OnTypeCloseAngle_TagHelperInHtml_NestedStatement_WithAttribute() public void OnTypeCloseAngle_TagHelperInHtml_NestedStatement_WithAttribute_SpaceBetweenClosingAngleAndAttributeClosingQuote() { RunAutoInsertTest( -input: @" - @addTagHelper *, TestAssembly + input: """ + @addTagHelper *, TestAssembly - @if (true) - { -
$$
- } - ", -expected: @" - @addTagHelper *, TestAssembly + @if (true) + { +
$$
+ } + """, + expected: """ + @addTagHelper *, TestAssembly - @if (true) - { -
$0
- } - ", -fileKind: FileKinds.Legacy, -tagHelpers: new[] { NormalOrSelfClosingTagHelper }); + @if (true) + { +
$0
+ } + """, + fileKind: FileKinds.Legacy, + tagHelpers: new[] { NormalOrSelfClosingTagHelper }); } [Fact] @@ -485,24 +484,24 @@ public void OnTypeCloseAngle_TagHelperInHtml_NestedStatement_WithAttribute_Space public void OnTypeCloseAngle_TagHelperInHtml_NestedStatement_WithMinimalizedAttribute() { RunAutoInsertTest( -input: @" - @addTagHelper *, TestAssembly + input: """ + @addTagHelper *, TestAssembly - @if (true) - { -
$$
- } - ", -expected: @" - @addTagHelper *, TestAssembly + @if (true) + { +
$$
+ } + """, + expected: """ + @addTagHelper *, TestAssembly - @if (true) - { -
$0
- } - ", -fileKind: FileKinds.Legacy, -tagHelpers: new[] { NormalOrSelfClosingTagHelper }); + @if (true) + { +
$0
+ } + """, + fileKind: FileKinds.Legacy, + tagHelpers: new[] { NormalOrSelfClosingTagHelper }); } [Fact] @@ -510,24 +509,24 @@ public void OnTypeCloseAngle_TagHelperInHtml_NestedStatement_WithMinimalizedAttr public void OnTypeCloseAngle_TagHelperInHtml_NestedStatement_WithMinimalizedAttribute_SpaceBetweenClosingAngleAndAttributeClosingQuote() { RunAutoInsertTest( -input: @" - @addTagHelper *, TestAssembly + input: """ + @addTagHelper *, TestAssembly - @if (true) - { -
$$
- } - ", -expected: @" - @addTagHelper *, TestAssembly + @if (true) + { +
$$
+ } + """, + expected: """ + @addTagHelper *, TestAssembly - @if (true) - { -
$0
- } - ", -fileKind: FileKinds.Legacy, -tagHelpers: new[] { NormalOrSelfClosingTagHelper }); + @if (true) + { +
$0
+ } + """, + fileKind: FileKinds.Legacy, + tagHelpers: new[] { NormalOrSelfClosingTagHelper }); } [Fact] @@ -535,24 +534,24 @@ public void OnTypeCloseAngle_TagHelperInHtml_NestedStatement_WithMinimalizedAttr public void OnTypeCloseAngle_TagHelperInTagHelper_NestedStatement() { RunAutoInsertTest( -input: @" - @addTagHelper *, TestAssembly + input: """ + @addTagHelper *, TestAssembly - @if (true) - { - $$ - } - ", -expected: @" - @addTagHelper *, TestAssembly + @if (true) + { + $$ + } + """, + expected: """ + @addTagHelper *, TestAssembly - @if (true) - { - - } - ", -fileKind: FileKinds.Legacy, -tagHelpers: new[] { NormalOrSelfClosingTagHelper, UnspecifiedInputTagHelper }); + @if (true) + { + + } + """, + fileKind: FileKinds.Legacy, + tagHelpers: new[] { NormalOrSelfClosingTagHelper, UnspecifiedInputTagHelper }); } [Fact] @@ -560,24 +559,24 @@ public void OnTypeCloseAngle_TagHelperInTagHelper_NestedStatement() public void OnTypeCloseAngle_TagHelperNextToVoidTagHelper_NestedStatement() { RunAutoInsertTest( -input: @" - @addTagHelper *, TestAssembly + input: """ + @addTagHelper *, TestAssembly - @if (true) - { - $$ - } - ", -expected: @" - @addTagHelper *, TestAssembly + @if (true) + { + $$ + } + """, + expected: """ + @addTagHelper *, TestAssembly - @if (true) - { - $0 - } - ", -fileKind: FileKinds.Legacy, -tagHelpers: new[] { NormalOrSelfClosingTagHelper, UnspecifiedInputTagHelper }); + @if (true) + { + $0 + } + """, + fileKind: FileKinds.Legacy, + tagHelpers: new[] { NormalOrSelfClosingTagHelper, UnspecifiedInputTagHelper }); } [Fact] @@ -585,64 +584,64 @@ public void OnTypeCloseAngle_TagHelperNextToVoidTagHelper_NestedStatement() public void OnTypeCloseAngle_TagHelperNextToTagHelper_NestedStatement() { RunAutoInsertTest( -input: @" - @addTagHelper *, TestAssembly + input: """ + @addTagHelper *, TestAssembly - @if (true) - { - $$ - } - ", -expected: @" - @addTagHelper *, TestAssembly + @if (true) + { + $$ + } + """, + expected: """ + @addTagHelper *, TestAssembly - @if (true) - { - $0 - } - ", -fileKind: FileKinds.Legacy, -tagHelpers: new[] { NormalOrSelfClosingTagHelper, NormalOrSelfclosingInputTagHelper }); + @if (true) + { + $0 + } + """, + fileKind: FileKinds.Legacy, + tagHelpers: new[] { NormalOrSelfClosingTagHelper, NormalOrSelfclosingInputTagHelper }); } [Fact] public void OnTypeCloseAngle_NormalOrSelfClosingTagHelperTagStructure_CodeBlock() { RunAutoInsertTest( -input: @" - @addTagHelper *, TestAssembly + input: """ + @addTagHelper *, TestAssembly - @{ - $$ - } - ", -expected: @" - @addTagHelper *, TestAssembly + @{ + $$ + } + """, + expected: """ + @addTagHelper *, TestAssembly - @{ - $0 - } - ", -fileKind: FileKinds.Legacy, -tagHelpers: new[] { NormalOrSelfClosingTagHelper }); + @{ + $0 + } + """, + fileKind: FileKinds.Legacy, + tagHelpers: new[] { NormalOrSelfClosingTagHelper }); } [Fact] public void OnTypeCloseAngle_WithSlash_WithoutEndTagTagHelperTagStructure() { RunAutoInsertTest( -input: @" - @addTagHelper *, TestAssembly + input: """ + @addTagHelper *, TestAssembly - $$ - ", -expected: @" - @addTagHelper *, TestAssembly + $$ + """, + expected: """ + @addTagHelper *, TestAssembly - - ", -fileKind: FileKinds.Legacy, -tagHelpers: new[] { WithoutEndTagTagHelper }); + + """, + fileKind: FileKinds.Legacy, + tagHelpers: new[] { WithoutEndTagTagHelper }); } [Fact] @@ -650,130 +649,130 @@ public void OnTypeCloseAngle_WithSlash_WithoutEndTagTagHelperTagStructure() public void OnTypeCloseAngle_NestedStatement() { RunAutoInsertTest( -input: @" - @addTagHelper *, TestAssembly + input: """ + @addTagHelper *, TestAssembly - @if (true) - { -
$$
- } - ", -expected: @" - @addTagHelper *, TestAssembly + @if (true) + { +
$$
+ } + """, + expected: """ + @addTagHelper *, TestAssembly - @if (true) - { -
- } - ", -fileKind: FileKinds.Legacy, -tagHelpers: new[] { WithoutEndTagTagHelper }); + @if (true) + { +
+ } + """, + fileKind: FileKinds.Legacy, + tagHelpers: new[] { WithoutEndTagTagHelper }); } [Fact] public void OnTypeCloseAngle_WithSpace_WithoutEndTagTagHelperTagStructure() { RunAutoInsertTest( -input: @" - @addTagHelper *, TestAssembly + input: """ + @addTagHelper *, TestAssembly - $$ - ", -expected: @" - @addTagHelper *, TestAssembly + $$ + """, + expected: """ + @addTagHelper *, TestAssembly - - ", -fileKind: FileKinds.Legacy, -tagHelpers: new[] { WithoutEndTagTagHelper }); + + """, + fileKind: FileKinds.Legacy, + tagHelpers: new[] { WithoutEndTagTagHelper }); } [Fact] public void OnTypeCloseAngle_WithoutEndTagTagHelperTagStructure() { RunAutoInsertTest( -input: @" - @addTagHelper *, TestAssembly + input: """ + @addTagHelper *, TestAssembly - $$ - ", -expected: @" - @addTagHelper *, TestAssembly + $$ + """, + expected: """ + @addTagHelper *, TestAssembly - - ", -fileKind: FileKinds.Legacy, -tagHelpers: new[] { WithoutEndTagTagHelper }); + + """, + fileKind: FileKinds.Legacy, + tagHelpers: new[] { WithoutEndTagTagHelper }); } [Fact] public void OnTypeCloseAngle_WithoutEndTagTagHelperTagStructure_CodeBlock() { RunAutoInsertTest( -input: @" - @addTagHelper *, TestAssembly + input: """ + @addTagHelper *, TestAssembly - @{ - $$ - } - ", -expected: @" - @addTagHelper *, TestAssembly + @{ + $$ + } + """, + expected: """ + @addTagHelper *, TestAssembly - @{ - - } - ", -fileKind: FileKinds.Legacy, -tagHelpers: new[] { WithoutEndTagTagHelper }); + @{ + + } + """, + fileKind: FileKinds.Legacy, + tagHelpers: new[] { WithoutEndTagTagHelper }); } [Fact] public void OnTypeCloseAngle_MultipleApplicableTagHelperTagStructures() { RunAutoInsertTest( -input: @" - @addTagHelper *, TestAssembly + input: """ + @addTagHelper *, TestAssembly - $$ - ", -expected: @" - @addTagHelper *, TestAssembly + $$ + """, + expected: """ + @addTagHelper *, TestAssembly - $0 - ", -fileKind: FileKinds.Legacy, -tagHelpers: new[] { UnspecifiedTagHelper, NormalOrSelfClosingTagHelper, WithoutEndTagTagHelper }); + $0 + """, + fileKind: FileKinds.Legacy, + tagHelpers: new[] { UnspecifiedTagHelper, NormalOrSelfClosingTagHelper, WithoutEndTagTagHelper }); } [Fact] public void OnTypeCloseAngle_EscapedTagTagHelperAutoCompletesWithEscape() { RunAutoInsertTest( -input: @" - @addTagHelper *, TestAssembly + input: """ + @addTagHelper *, TestAssembly - $$ - ", -expected: @" - @addTagHelper *, TestAssembly + $$ + """, + expected: """ + @addTagHelper *, TestAssembly - $0 - ", -fileKind: FileKinds.Legacy, -tagHelpers: new[] { NormalOrSelfClosingTagHelper }); + $0 + """, + fileKind: FileKinds.Legacy, + tagHelpers: new[] { NormalOrSelfClosingTagHelper }); } [Fact] public void OnTypeCloseAngle_AlwaysClosesStandardHTMLTag() { RunAutoInsertTest( -input: @" -
$$
- ", -expected: @" -
$0
- "); + input: """ +
$$
+ """, + expected: """ +
$0
+ """); } [Fact] @@ -781,18 +780,18 @@ public void OnTypeCloseAngle_AlwaysClosesStandardHTMLTag() public void OnTypeCloseAngle_ClosesStandardHTMLTag_NestedStatement() { RunAutoInsertTest( -input: @" - @if (true) - { -

$$

- } - ", -expected: @" - @if (true) - { -

$0

- } - "); + input: """ + @if (true) + { +

$$

+ } + """, + expected: """ + @if (true) + { +

$0

+ } + """); } [Fact] @@ -800,18 +799,18 @@ public void OnTypeCloseAngle_ClosesStandardHTMLTag_NestedStatement() public void OnTypeCloseAngle_TagNextToTag_NestedStatement() { RunAutoInsertTest( -input: @" - @if (true) - { -

$$

- } - ", -expected: @" - @if (true) - { -

$0

- } - "); + input: """ + @if (true) + { +

$$

+ } + """, + expected: """ + @if (true) + { +

$0

+ } + """); } [Fact] @@ -819,58 +818,58 @@ public void OnTypeCloseAngle_TagNextToTag_NestedStatement() public void OnTypeCloseAngle_TagNextToVoidTag_NestedStatement() { RunAutoInsertTest( -input: @" - @if (true) - { -

$$ - } - ", -expected: @" - @if (true) - { -

$0

- } - "); + input: """ + @if (true) + { +

$$ + } + """, + expected: """ + @if (true) + { +

$0

+ } + """); } [Fact] public void OnTypeCloseAngle_ClosesStandardHTMLTag() { RunAutoInsertTest( -input: @" -
$$ - ", -expected: @" -
$0
- "); + input: """ +
$$ + """, + expected: """ +
$0
+ """); } [Fact] public void OnTypeCloseAngle_ClosesStandardHTMLTag_CodeBlock() { RunAutoInsertTest( -input: @" - @{ -
$$ - } - ", -expected: @" - @{ -
$0
- } - "); + input: """ + @{ +
$$ + } + """, + expected: """ + @{ +
$0
+ } + """); } [Fact] public void OnTypeCloseAngle_ClosesVoidHTMLTag() { RunAutoInsertTest( -input: @" - $$ - ", -expected: @" - - "); + input: """ + $$ + """, + expected: """ + + """); } [Fact] @@ -878,70 +877,70 @@ public void OnTypeCloseAngle_ClosesVoidHTMLTag() public void OnTypeCloseAngle_ClosesVoidHTMLTag_NestedStatement() { RunAutoInsertTest( -input: @" - @if (true) - { - $$ - } - ", -expected: @" - @if (true) - { - - } - "); + input: """ + @if (true) + { + $$ + } + """, + expected: """ + @if (true) + { + + } + """); } [Fact] public void OnTypeCloseAngle_ClosesVoidHTMLTag_CodeBlock() { RunAutoInsertTest( -input: @" - @{ - $$ - } - ", -expected: @" - @{ - - } - "); + input: """ + @{ + $$ + } + """, + expected: """ + @{ + + } + """); } [Fact] public void OnTypeCloseAngle_WithSlash_ClosesVoidHTMLTag() { RunAutoInsertTest( -input: @" - $$ - ", -expected: @" - - "); + input: """ + $$ + """, + expected: """ + + """); } [Fact] public void OnTypeCloseAngle_WithSpace_ClosesVoidHTMLTag() { RunAutoInsertTest( -input: @" - $$ - ", -expected: @" - - "); + input: """ + $$ + """, + expected: """ + + """); } [Fact] public void OnTypeCloseAngle_AutoInsertDisabled_Noops() { RunAutoInsertTest( -input: @" -
$$ - ", -expected: @" -
- ", + input: """ +
$$ + """, + expected: """ +
+ """, enableAutoClosingTags: false); } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/CloseTextTagOnAutoInsertProviderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/CloseTextTagOnAutoInsertProviderTest.cs index 093f320b2bb..74b17456c4c 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/CloseTextTagOnAutoInsertProviderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/CloseTextTagOnAutoInsertProviderTest.cs @@ -13,28 +13,28 @@ public class CloseTextTagOnAutoInsertProviderTest(ITestOutputHelper testOutput) public void OnTypeCloseAngle_ClosesTextTag() { RunAutoInsertTest( -input: @" -@{ - $$ -} -", -expected: @" -@{ - $0 -} -"); + input: """ + @{ + $$ + } + """, + expected: """ + @{ + $0 + } + """); } [Fact] public void OnTypeCloseAngle_OutsideRazorBlock_DoesNotCloseTextTag() { RunAutoInsertTest( -input: @" - $$ -", -expected: @" - -"); + input: """ + $$ + """, + expected: """ + + """); } internal override IOnAutoInsertProvider CreateProvider() => diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/RazorOnAutoInsertProviderTestBase.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/RazorOnAutoInsertProviderTestBase.cs index c5e809d705a..7d4648f72d8 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/RazorOnAutoInsertProviderTestBase.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/RazorOnAutoInsertProviderTestBase.cs @@ -40,9 +40,10 @@ protected void RunAutoInsertTest(string input, string expected, int tabSize = 4, var provider = CreateProvider(); // Act - provider.TryResolveInsertion(position, codeDocument, enableAutoClosingTags: enableAutoClosingTags, out var edit); + var result = provider.TryResolveInsertion(position, codeDocument, enableAutoClosingTags: enableAutoClosingTags, out var edit); // Assert + Assert.True((result && edit is not null) || (!result && edit is null)); // check return value consistency var edited = edit is null ? source : ApplyEdit(source, edit.TextEdit); var actual = edited.ToString(); Assert.Equal(expected, actual); From 0eab01030e7180fb54da9d35b9df85d05abde95e Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Fri, 23 Aug 2024 17:43:24 -0700 Subject: [PATCH 55/64] Rename per PR feedback suggestion to better indicate the purpose --- ...verCapabilitiesExtensions.cs => LspInitializationHelpers.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Hosting/{VSInternalServerCapabilitiesExtensions.cs => LspInitializationHelpers.cs} (97%) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Hosting/VSInternalServerCapabilitiesExtensions.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Hosting/LspInitializationHelpers.cs similarity index 97% rename from src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Hosting/VSInternalServerCapabilitiesExtensions.cs rename to src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Hosting/LspInitializationHelpers.cs index 4370ecf4298..ac141c81680 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Hosting/VSInternalServerCapabilitiesExtensions.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Hosting/LspInitializationHelpers.cs @@ -8,7 +8,7 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Hosting; -internal static class VSInternalServerCapabilitiesExtensions +internal static class LspInitializationHelpers { public static void EnableInlayHints(this VSInternalServerCapabilities serverCapabilities) { From 7bb3d324715e37d2067c1a2e830a4d3dad5e9d01 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Tue, 27 Aug 2024 10:09:24 -0700 Subject: [PATCH 56/64] Cleanup formatting code document acquisition per PR feedback Creating IFormattingCodeDocumentProvider service with seprate LSP and Remote implementations to provide code document appropriate for formatting. --- .../InlineCompletionEndPoint.cs | 10 +++- .../RazorLanguageServer.cs | 3 ++ .../FormattingCodeDocumentProvider.cs | 17 +++++++ .../Formatting/FormattingContext.cs | 46 +++++++++++++++++-- .../IFormattingCodeDocumentProvider.cs | 13 ++++++ .../Formatting/RazorFormattingService.cs | 24 ++++++++-- .../ProjectSystem/DocumentSnapshot.cs | 18 +------- .../ProjectSystem/IDocumentSnapshot.cs | 3 +- .../ProjectSystem/ImportDocumentSnapshot.cs | 3 ++ .../RemoteFormattingCodeDocumentProvider.cs | 19 ++++++++ .../RemoteRazorFormattingService.cs | 6 ++- .../ProjectSystem/RemoteDocumentSnapshot.cs | 2 +- .../FormattingContentValidationPassTest.cs | 8 +++- .../FormattingDiagnosticValidationPassTest.cs | 8 +++- .../Formatting_NetFx/FormattingTestBase.cs | 3 ++ .../TestRazorFormattingService.cs | 5 +- 16 files changed, 156 insertions(+), 32 deletions(-) create mode 100644 src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/FormattingCodeDocumentProvider.cs create mode 100644 src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/IFormattingCodeDocumentProvider.cs create mode 100644 src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteFormattingCodeDocumentProvider.cs diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/InlineCompletionEndPoint.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/InlineCompletionEndPoint.cs index 0993fac51f9..76d61ba0eb8 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/InlineCompletionEndPoint.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/InlineCompletionEndPoint.cs @@ -28,6 +28,7 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.InlineCompletion; internal sealed class InlineCompletionEndpoint( IDocumentMappingService documentMappingService, IClientConnection clientConnection, + IFormattingCodeDocumentProvider formattingCodeDocumentProvider, IAdhocWorkspaceFactory adhocWorkspaceFactory, ILoggerFactory loggerFactory) : IRazorRequestHandler, ICapabilitiesProvider @@ -39,6 +40,7 @@ internal sealed class InlineCompletionEndpoint( private readonly IDocumentMappingService _documentMappingService = documentMappingService ?? throw new ArgumentNullException(nameof(documentMappingService)); private readonly IClientConnection _clientConnection = clientConnection ?? throw new ArgumentNullException(nameof(clientConnection)); + private readonly IFormattingCodeDocumentProvider _formattingCodeDocumentProvider = formattingCodeDocumentProvider; private readonly IAdhocWorkspaceFactory _adhocWorkspaceFactory = adhocWorkspaceFactory ?? throw new ArgumentNullException(nameof(adhocWorkspaceFactory)); private readonly ILogger _logger = loggerFactory.GetOrCreateLogger(); @@ -123,7 +125,13 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(VSInternalInlineCompleti continue; } - using var formattingContext = FormattingContext.Create(request.TextDocument.Uri, documentContext.Snapshot, codeDocument, request.Options, _adhocWorkspaceFactory); + using var formattingContext = FormattingContext.Create( + request.TextDocument.Uri, + documentContext.Snapshot, + codeDocument, + request.Options, + _formattingCodeDocumentProvider, + _adhocWorkspaceFactory); if (!TryGetSnippetWithAdjustedIndentation(formattingContext, item.Text, hostDocumentIndex, out var newSnippetText)) { continue; diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs index d1948c5c559..9ff2229db9f 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs @@ -27,6 +27,7 @@ using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor.AutoInsert; using Microsoft.CodeAnalysis.Razor.FoldingRanges; +using Microsoft.CodeAnalysis.Razor.Formatting; using Microsoft.CodeAnalysis.Razor.GoToDefinition; using Microsoft.CodeAnalysis.Razor.Logging; using Microsoft.CodeAnalysis.Razor.Protocol.DocumentSymbols; @@ -121,6 +122,8 @@ protected override ILspServices ConstructLspServices() services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + var featureOptions = _featureOptions ?? new DefaultLanguageServerFeatureOptions(); services.AddSingleton(featureOptions); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/FormattingCodeDocumentProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/FormattingCodeDocumentProvider.cs new file mode 100644 index 00000000000..a5d62efd251 --- /dev/null +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/FormattingCodeDocumentProvider.cs @@ -0,0 +1,17 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.CodeAnalysis.Razor.ProjectSystem; + +namespace Microsoft.CodeAnalysis.Razor.Formatting; + +internal sealed class FormattingCodeDocumentProvider : IFormattingCodeDocumentProvider +{ + public Task GetCodeDocumentAsync(IDocumentSnapshot snapshot) + { + var useDesignTimeGeneratedOutput = snapshot.Project.Configuration.LanguageServerFlags?.ForceRuntimeCodeGeneration ?? false; + return snapshot.GetGeneratedOutputAsync(useDesignTimeGeneratedOutput); + } +} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/FormattingContext.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/FormattingContext.cs index e5329eccbc7..df67f5d1972 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/FormattingContext.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/FormattingContext.cs @@ -24,13 +24,24 @@ internal sealed class FormattingContext : IDisposable private Document? _csharpWorkspaceDocument; private AdhocWorkspace? _csharpWorkspace; + private readonly IFormattingCodeDocumentProvider _codeDocumentProvider; private IReadOnlyList? _formattingSpans; private IReadOnlyDictionary? _indentations; - private FormattingContext(IAdhocWorkspaceFactory workspaceFactory, Uri uri, IDocumentSnapshot originalSnapshot, RazorCodeDocument codeDocument, FormattingOptions options, - bool isFormatOnType, bool automaticallyAddUsings, int hostDocumentIndex, char triggerCharacter) + private FormattingContext( + IFormattingCodeDocumentProvider codeDocumentProvider, + IAdhocWorkspaceFactory workspaceFactory, + Uri uri, + IDocumentSnapshot originalSnapshot, + RazorCodeDocument codeDocument, + FormattingOptions options, + bool isFormatOnType, + bool automaticallyAddUsings, + int hostDocumentIndex, + char triggerCharacter) { + _codeDocumentProvider = codeDocumentProvider; _workspaceFactory = workspaceFactory; Uri = uri; OriginalSnapshot = originalSnapshot; @@ -268,11 +279,12 @@ public async Task WithTextAsync(SourceText changedText) var changedSnapshot = OriginalSnapshot.WithText(changedText); - var codeDocument = await changedSnapshot.GetFormatterCodeDocumentAsync().ConfigureAwait(false); + var codeDocument = await _codeDocumentProvider.GetCodeDocumentAsync(changedSnapshot).ConfigureAwait(false); DEBUG_ValidateComponents(CodeDocument, codeDocument); var newContext = new FormattingContext( + _codeDocumentProvider, _workspaceFactory, Uri, OriginalSnapshot, @@ -309,12 +321,23 @@ public static FormattingContext CreateForOnTypeFormatting( IDocumentSnapshot originalSnapshot, RazorCodeDocument codeDocument, FormattingOptions options, + IFormattingCodeDocumentProvider codeDocumentProvider, IAdhocWorkspaceFactory workspaceFactory, bool automaticallyAddUsings, int hostDocumentIndex, char triggerCharacter) { - return CreateCore(uri, originalSnapshot, codeDocument, options, workspaceFactory, isFormatOnType: true, automaticallyAddUsings, hostDocumentIndex, triggerCharacter); + return CreateCore( + uri, + originalSnapshot, + codeDocument, + options, + codeDocumentProvider, + workspaceFactory, + isFormatOnType: true, + automaticallyAddUsings, + hostDocumentIndex, + triggerCharacter); } public static FormattingContext Create( @@ -322,9 +345,20 @@ public static FormattingContext Create( IDocumentSnapshot originalSnapshot, RazorCodeDocument codeDocument, FormattingOptions options, + IFormattingCodeDocumentProvider codeDocumentProvider, IAdhocWorkspaceFactory workspaceFactory) { - return CreateCore(uri, originalSnapshot, codeDocument, options, workspaceFactory, isFormatOnType: false, automaticallyAddUsings: false, hostDocumentIndex: 0, triggerCharacter: '\0'); + return CreateCore( + uri, + originalSnapshot, + codeDocument, + options, + codeDocumentProvider, + workspaceFactory, + isFormatOnType: false, + automaticallyAddUsings: false, + hostDocumentIndex: 0, + triggerCharacter: '\0'); } private static FormattingContext CreateCore( @@ -332,6 +366,7 @@ private static FormattingContext CreateCore( IDocumentSnapshot originalSnapshot, RazorCodeDocument codeDocument, FormattingOptions options, + IFormattingCodeDocumentProvider codeDocumentProvider, IAdhocWorkspaceFactory workspaceFactory, bool isFormatOnType, bool automaticallyAddUsings, @@ -367,6 +402,7 @@ private static FormattingContext CreateCore( Debug.Assert(isFormatOnType || (hostDocumentIndex == 0 && triggerCharacter == '\0' && automaticallyAddUsings == false)); var result = new FormattingContext( + codeDocumentProvider, workspaceFactory, uri, originalSnapshot, diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/IFormattingCodeDocumentProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/IFormattingCodeDocumentProvider.cs new file mode 100644 index 00000000000..0d1c25da983 --- /dev/null +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/IFormattingCodeDocumentProvider.cs @@ -0,0 +1,13 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.CodeAnalysis.Razor.ProjectSystem; + +namespace Microsoft.CodeAnalysis.Razor.Formatting; + +internal interface IFormattingCodeDocumentProvider +{ + Task GetCodeDocumentAsync(IDocumentSnapshot snapshot); +} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/RazorFormattingService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/RazorFormattingService.cs index 79d03f61986..15ef27643c9 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/RazorFormattingService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/RazorFormattingService.cs @@ -21,10 +21,12 @@ namespace Microsoft.CodeAnalysis.Razor.Formatting; internal class RazorFormattingService : IRazorFormattingService { private readonly List _formattingPasses; + private readonly IFormattingCodeDocumentProvider _codeDocumentProvider; private readonly IAdhocWorkspaceFactory _workspaceFactory; public RazorFormattingService( IEnumerable formattingPasses, + IFormattingCodeDocumentProvider codeDocumentProvider, IAdhocWorkspaceFactory workspaceFactory) { if (formattingPasses is null) @@ -33,6 +35,7 @@ public RazorFormattingService( } _formattingPasses = formattingPasses.OrderBy(f => f.Order).ToList(); + _codeDocumentProvider = codeDocumentProvider ?? throw new ArgumentNullException(nameof(codeDocumentProvider)); _workspaceFactory = workspaceFactory ?? throw new ArgumentNullException(nameof(workspaceFactory)); } @@ -42,7 +45,7 @@ public async Task FormatAsync( FormattingOptions options, CancellationToken cancellationToken) { - var codeDocument = await documentContext.Snapshot.GetFormatterCodeDocumentAsync().ConfigureAwait(false); + var codeDocument = await _codeDocumentProvider.GetCodeDocumentAsync(documentContext.Snapshot).ConfigureAwait(false); // Range formatting happens on every paste, and if there are Razor diagnostics in the file // that can make some very bad results. eg, given: @@ -70,7 +73,13 @@ public async Task FormatAsync( var uri = documentContext.Uri; var documentSnapshot = documentContext.Snapshot; var hostDocumentVersion = documentContext.Snapshot.Version; - using var context = FormattingContext.Create(uri, documentSnapshot, codeDocument, options, _workspaceFactory); + using var context = FormattingContext.Create( + uri, + documentSnapshot, + codeDocument, + options, + _codeDocumentProvider, + _workspaceFactory); var originalText = context.SourceText; var result = new FormattingResult([]); @@ -156,7 +165,16 @@ private async Task ApplyFormattedEditsAsync( var documentSnapshot = documentContext.Snapshot; var uri = documentContext.Uri; var codeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); - using var context = FormattingContext.CreateForOnTypeFormatting(uri, documentSnapshot, codeDocument, options, _workspaceFactory, automaticallyAddUsings: automaticallyAddUsings, hostDocumentIndex, triggerCharacter); + using var context = FormattingContext.CreateForOnTypeFormatting( + uri, + documentSnapshot, + codeDocument, + options, + _codeDocumentProvider, + _workspaceFactory, + automaticallyAddUsings: automaticallyAddUsings, + hostDocumentIndex, + triggerCharacter); var result = new FormattingResult(formattedEdits, kind); foreach (var pass in _formattingPasses) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs index 8b8b1f86dac..01ecdee6466 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs @@ -74,22 +74,8 @@ public async Task GetCSharpSyntaxTreeAsync(CancellationToken cancell return CSharpSyntaxTree.ParseText(csharpText, cancellationToken: cancellationToken); } - public Task GetFormatterCodeDocumentAsync() - { - var forceRuntimeCodeGeneration = Project.Configuration.LanguageServerFlags?.ForceRuntimeCodeGeneration ?? false; - if (!forceRuntimeCodeGeneration) - { - return GetGeneratedOutputAsync(); - } - - // if forceRuntimeCodeGeneration is on, GetGeneratedOutputAsync will get runtime code. As of now - // the formatting service doesn't expect the form of code generated to be what the compiler does with - // runtime. For now force usage of design time and avoid the cache. There may be a slight perf hit - // but either the user is typing (which will invalidate the cache) or the user is manually attempting to - // format. We expect formatting to invalidate the cache if it changes things and consider this an - // acceptable overhead for now. - return GetDesignTimeGeneratedOutputAsync(); - } + public Task GetGeneratedOutputAsync(bool useDesignTimeGeneratedOutput) + => useDesignTimeGeneratedOutput ? GetDesignTimeGeneratedOutputAsync() : GetGeneratedOutputAsync(); private async Task GetDesignTimeGeneratedOutputAsync() { diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshot.cs index c8b780b1536..a6995a4d668 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshot.cs @@ -22,6 +22,7 @@ internal interface IDocumentSnapshot Task GetTextAsync(); Task GetTextVersionAsync(); Task GetGeneratedOutputAsync(); + Task GetGeneratedOutputAsync(bool useDesignTimeGeneratedOutput); /// /// Gets the Roslyn syntax tree for the generated C# for this Razor document @@ -33,7 +34,5 @@ internal interface IDocumentSnapshot bool TryGetTextVersion(out VersionStamp result); bool TryGetGeneratedOutput([NotNullWhen(true)] out RazorCodeDocument? result); - Task GetFormatterCodeDocumentAsync(); - IDocumentSnapshot WithText(SourceText text); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ImportDocumentSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ImportDocumentSnapshot.cs index 557695063ae..56855f32152 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ImportDocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ImportDocumentSnapshot.cs @@ -50,6 +50,9 @@ public async Task GetTextAsync() return _sourceText; } + public Task GetGeneratedOutputAsync(bool _) + => GetGeneratedOutputAsync(); + public Task GetTextVersionAsync() { return Task.FromResult(_version); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteFormattingCodeDocumentProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteFormattingCodeDocumentProvider.cs new file mode 100644 index 00000000000..92a0ccd1ae6 --- /dev/null +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteFormattingCodeDocumentProvider.cs @@ -0,0 +1,19 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Composition; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.CodeAnalysis.Razor.Formatting; +using Microsoft.CodeAnalysis.Razor.ProjectSystem; + +namespace Microsoft.CodeAnalysis.Remote.Razor.Formatting; + +[Export(typeof(IFormattingCodeDocumentProvider)), Shared] +internal sealed class RemoteFormattingCodeDocumentProvider : IFormattingCodeDocumentProvider +{ + public Task GetCodeDocumentAsync(IDocumentSnapshot snapshot) + { + return snapshot.GetGeneratedOutputAsync(); + } +} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteRazorFormattingService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteRazorFormattingService.cs index 6ebcccf42d7..bff121bd4c0 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteRazorFormattingService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteRazorFormattingService.cs @@ -12,7 +12,11 @@ namespace Microsoft.CodeAnalysis.Remote.Razor.Formatting; [method: ImportingConstructor] internal class RemoteRazorFormattingService( [ImportMany] IEnumerable formattingPasses, + IFormattingCodeDocumentProvider codeDocumentProvider, IAdhocWorkspaceFactory adhocWorkspaceFactory) - : RazorFormattingService(formattingPasses, adhocWorkspaceFactory) + : RazorFormattingService( + formattingPasses, + codeDocumentProvider, + adhocWorkspaceFactory) { } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs index e401ed4830a..228fba60c30 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs @@ -127,6 +127,6 @@ public async Task GetCSharpSyntaxTreeAsync(CancellationToken cancell return tree.AssumeNotNull(); } - public Task GetFormatterCodeDocumentAsync() + public Task GetGeneratedOutputAsync(bool _) => GetGeneratedOutputAsync(); } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingContentValidationPassTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingContentValidationPassTest.cs index ba5973a36ef..ab22a21c6b2 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingContentValidationPassTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingContentValidationPassTest.cs @@ -132,7 +132,13 @@ private static FormattingContext CreateFormattingContext(SourceText source, int InsertSpaces = insertSpaces, }; - var context = FormattingContext.Create(uri, documentSnapshot, codeDocument, options, TestAdhocWorkspaceFactory.Instance); + var context = FormattingContext.Create( + uri, + documentSnapshot, + codeDocument, + options, + new FormattingCodeDocumentProvider(), + TestAdhocWorkspaceFactory.Instance); return context; } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingDiagnosticValidationPassTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingDiagnosticValidationPassTest.cs index 60adc04a1fc..133d8180a7c 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingDiagnosticValidationPassTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingDiagnosticValidationPassTest.cs @@ -129,7 +129,13 @@ private static FormattingContext CreateFormattingContext(SourceText source, int InsertSpaces = insertSpaces, }; - var context = FormattingContext.Create(uri, documentSnapshot, codeDocument, options, TestAdhocWorkspaceFactory.Instance); + var context = FormattingContext.Create( + uri, + documentSnapshot, + codeDocument, + options, + new FormattingCodeDocumentProvider(), + TestAdhocWorkspaceFactory.Instance); return context; } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingTestBase.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingTestBase.cs index 62221cac30b..f9004cd9cbe 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingTestBase.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingTestBase.cs @@ -289,6 +289,9 @@ internal static IDocumentSnapshot CreateDocumentSnapshot(string path, ImmutableA documentSnapshot .Setup(d => d.GetGeneratedOutputAsync()) .ReturnsAsync(codeDocument); + documentSnapshot + .Setup(d => d.GetGeneratedOutputAsync(It.IsAny())) + .ReturnsAsync(codeDocument); documentSnapshot .Setup(d => d.FilePath) .Returns(path); diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/TestRazorFormattingService.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/TestRazorFormattingService.cs index 151cfc51df9..a35b39a7f36 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/TestRazorFormattingService.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/TestRazorFormattingService.cs @@ -57,6 +57,9 @@ public static async Task CreateWithFullSupportAsync( new FormattingContentValidationPass(mappingService, loggerFactory), }; - return new RazorFormattingService(passes, TestAdhocWorkspaceFactory.Instance); + return new RazorFormattingService( + passes, + new FormattingCodeDocumentProvider(), + TestAdhocWorkspaceFactory.Instance); } } From e53bf51e113ebcee4365dbeb37da2ad65c752fdd Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Tue, 27 Aug 2024 10:17:03 -0700 Subject: [PATCH 57/64] Removing redundant assert as compiler is already doing the check --- .../AutoInsert/RazorOnAutoInsertProviderTestBase.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/RazorOnAutoInsertProviderTestBase.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/RazorOnAutoInsertProviderTestBase.cs index 7d4648f72d8..f29711822f9 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/RazorOnAutoInsertProviderTestBase.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/RazorOnAutoInsertProviderTestBase.cs @@ -43,7 +43,6 @@ protected void RunAutoInsertTest(string input, string expected, int tabSize = 4, var result = provider.TryResolveInsertion(position, codeDocument, enableAutoClosingTags: enableAutoClosingTags, out var edit); // Assert - Assert.True((result && edit is not null) || (!result && edit is null)); // check return value consistency var edited = edit is null ? source : ApplyEdit(source, edit.TextEdit); var actual = edited.ToString(); Assert.Equal(expected, actual); From 348f76fd6ea4d18025638e826e2ecca1a01f8c85 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Tue, 27 Aug 2024 10:20:16 -0700 Subject: [PATCH 58/64] Unnecessary assignment cleanup --- .../AutoInsert/RazorOnAutoInsertProviderTestBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/RazorOnAutoInsertProviderTestBase.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/RazorOnAutoInsertProviderTestBase.cs index f29711822f9..c5e809d705a 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/RazorOnAutoInsertProviderTestBase.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/RazorOnAutoInsertProviderTestBase.cs @@ -40,7 +40,7 @@ protected void RunAutoInsertTest(string input, string expected, int tabSize = 4, var provider = CreateProvider(); // Act - var result = provider.TryResolveInsertion(position, codeDocument, enableAutoClosingTags: enableAutoClosingTags, out var edit); + provider.TryResolveInsertion(position, codeDocument, enableAutoClosingTags: enableAutoClosingTags, out var edit); // Assert var edited = edit is null ? source : ApplyEdit(source, edit.TextEdit); From c984b469f123bd9a0b4f5eb0fbb4421d586cc4b8 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Tue, 27 Aug 2024 18:04:45 -0700 Subject: [PATCH 59/64] Misc PR feedback cleanup --- .../AutoClosingTagOnAutoInsertProvider.cs | 5 ++--- .../AutoInsert/AutoInsertService.cs | 16 +++++----------- .../CloseTextTagOnAutoInsertProvider.cs | 3 +-- .../ProjectSystem/ImportDocumentSnapshot.cs | 3 --- .../AutoInsert/RemoteAutoInsertTextEdit.cs | 8 ++++---- .../AutoClosingTagOnAutoInsertProviderTest.cs | 1 - 6 files changed, 12 insertions(+), 24 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs index 04a90006975..e5e9febc604 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoClosingTagOnAutoInsertProvider.cs @@ -44,8 +44,7 @@ public bool TryResolveInsertion( Position position, RazorCodeDocument codeDocument, bool enableAutoClosingTags, - [NotNullWhen(true)] - out VSInternalDocumentOnAutoInsertResponseItem? autoInsertEdit) + [NotNullWhen(true)] out VSInternalDocumentOnAutoInsertResponseItem? autoInsertEdit) { autoInsertEdit = null; @@ -254,5 +253,5 @@ private enum AutoClosingBehavior SelfClosing, } - private record struct TagNameWithClosingBehavior(string TagName, AutoClosingBehavior AutoClosingBehavior); + private readonly record struct TagNameWithClosingBehavior(string TagName, AutoClosingBehavior AutoClosingBehavior); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs index 27483c3a0b1..c96ab07b21f 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; -using System.Linq; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.PooledObjects; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -26,17 +25,17 @@ internal class AutoInsertService(IEnumerable onAutoInsert private static ImmutableArray CalculateTriggerCharacters(IEnumerable onAutoInsertProviders) { - var builder = ImmutableArray.CreateBuilder(onAutoInsertProviders.Count()); + using var builder = new PooledArrayBuilder(); foreach (var provider in onAutoInsertProviders) { var triggerCharacter = provider.TriggerCharacter; - if (!builder.Contains(triggerCharacter)) + if (!builder.Any(character => character == triggerCharacter)) { builder.Add(triggerCharacter); } } - return builder.ToImmutable(); + return builder.DrainToImmutable(); } public ImmutableArray TriggerCharacters => _triggerCharacters; @@ -46,8 +45,7 @@ public bool TryResolveInsertion( Position position, string character, bool autoCloseTags, - [NotNullWhen(true)] - out VSInternalDocumentOnAutoInsertResponseItem? insertTextEdit) + [NotNullWhen(true)] out VSInternalDocumentOnAutoInsertResponseItem? insertTextEdit) { using var applicableProviders = new PooledArrayBuilder(capacity: _onAutoInsertProviders.Length); foreach (var provider in _onAutoInsertProviders) @@ -68,11 +66,7 @@ public bool TryResolveInsertion( foreach (var provider in applicableProviders) { - if (provider.TryResolveInsertion( - position, - codeDocument, - autoCloseTags, - out insertTextEdit)) + if (provider.TryResolveInsertion(position, codeDocument, autoCloseTags, out insertTextEdit)) { return true; } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs index 4dbe37c61c9..b2a791b6881 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/CloseTextTagOnAutoInsertProvider.cs @@ -19,8 +19,7 @@ public bool TryResolveInsertion( Position position, RazorCodeDocument codeDocument, bool enableAutoClosingTags, - [NotNullWhen(true)] - out VSInternalDocumentOnAutoInsertResponseItem? autoInsertEdit) + [NotNullWhen(true)] out VSInternalDocumentOnAutoInsertResponseItem? autoInsertEdit) { if (!(enableAutoClosingTags && IsAtTextTag(codeDocument, position))) { diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ImportDocumentSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ImportDocumentSnapshot.cs index 56855f32152..0b235a2ce36 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ImportDocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ImportDocumentSnapshot.cs @@ -84,7 +84,4 @@ public IDocumentSnapshot WithText(SourceText text) public Task GetCSharpSyntaxTreeAsync(CancellationToken cancellationToken) => throw new NotSupportedException(); - - public Task GetFormatterCodeDocumentAsync() - => throw new NotSupportedException(); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteAutoInsertTextEdit.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteAutoInsertTextEdit.cs index 0d64ec20a57..faaf69dc274 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteAutoInsertTextEdit.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AutoInsert/RemoteAutoInsertTextEdit.cs @@ -12,9 +12,9 @@ namespace Microsoft.CodeAnalysis.Razor.Protocol.AutoInsert; [DataContract] internal readonly record struct RemoteAutoInsertTextEdit( - [property: DataMember(Order = 0)] LinePositionSpan LinePositionSpan, - [property: DataMember(Order = 1)] string NewText, - [property: DataMember(Order = 2)] RoslynInsertTextFormat InsertTextFormat) + [property: DataMember(Order = 0)] LinePositionSpan LinePositionSpan, + [property: DataMember(Order = 1)] string NewText, + [property: DataMember(Order = 2)] RoslynInsertTextFormat InsertTextFormat) { public static RemoteAutoInsertTextEdit FromLspInsertTextEdit(VSInternalDocumentOnAutoInsertResponseItem edit) => new ( @@ -31,6 +31,6 @@ public static VSInternalDocumentOnAutoInsertResponseItem ToLspInsertTextEdit(Rem public override string ToString() { - return $"({LinePositionSpan.Start.Line}, {LinePositionSpan.Start.Character})-({LinePositionSpan.End.Line}, {LinePositionSpan.End.Character}), '{NewText}', {InsertTextFormat}"; + return $"({LinePositionSpan}), '{NewText}', {InsertTextFormat}"; } } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/AutoClosingTagOnAutoInsertProviderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/AutoClosingTagOnAutoInsertProviderTest.cs index a985b0d3fbb..f65fa825fc4 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/AutoClosingTagOnAutoInsertProviderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/AutoClosingTagOnAutoInsertProviderTest.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System; -using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Test.Common; using Microsoft.CodeAnalysis.Razor.AutoInsert; From 3f9fe844482d0859e95aaf21c54c98e1dd35e117 Mon Sep 17 00:00:00 2001 From: Alex Gavrilov Date: Wed, 28 Aug 2024 11:14:48 -0700 Subject: [PATCH 60/64] Made GetGeneratedOutputAsync an extension method per PR feedback --- .../Mapping/RazorLanguageQueryEndpoint.cs | 1 + .../ProjectSystem/DocumentSnapshot.cs | 4 ++-- .../ProjectSystem/IDocumentSnapshot.cs | 3 +-- .../ProjectSystem/IDocumentSnapshotExtensions.cs | 6 ++++++ .../ProjectSystem/ImportDocumentSnapshot.cs | 5 +---- .../DocumentMapping/RemoteDocumentMappingService.cs | 1 + .../ProjectSystem/RemoteDocumentSnapshot.cs | 5 +---- .../ProjectSystem/TestDocumentSnapshot.cs | 2 +- .../Cohost/RazorComponentDefinitionServiceTest.cs | 1 + 9 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Mapping/RazorLanguageQueryEndpoint.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Mapping/RazorLanguageQueryEndpoint.cs index 7c7bc03cc47..2ef39d5ab1f 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Mapping/RazorLanguageQueryEndpoint.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Mapping/RazorLanguageQueryEndpoint.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Razor.LanguageServer.EndpointContracts; using Microsoft.CodeAnalysis.Razor.DocumentMapping; using Microsoft.CodeAnalysis.Razor.Logging; +using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Razor.Protocol; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs index 01ecdee6466..6fa61c15986 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs @@ -74,8 +74,8 @@ public async Task GetCSharpSyntaxTreeAsync(CancellationToken cancell return CSharpSyntaxTree.ParseText(csharpText, cancellationToken: cancellationToken); } - public Task GetGeneratedOutputAsync(bool useDesignTimeGeneratedOutput) - => useDesignTimeGeneratedOutput ? GetDesignTimeGeneratedOutputAsync() : GetGeneratedOutputAsync(); + public virtual Task GetGeneratedOutputAsync(bool forceDesignTimeGeneratedOutput) + => forceDesignTimeGeneratedOutput ? GetDesignTimeGeneratedOutputAsync() : GetGeneratedOutputAsync(); private async Task GetDesignTimeGeneratedOutputAsync() { diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshot.cs index a6995a4d668..c3e7f78f698 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshot.cs @@ -21,8 +21,7 @@ internal interface IDocumentSnapshot Task GetTextAsync(); Task GetTextVersionAsync(); - Task GetGeneratedOutputAsync(); - Task GetGeneratedOutputAsync(bool useDesignTimeGeneratedOutput); + Task GetGeneratedOutputAsync(bool forceDesignTimeGeneratedOutput); /// /// Gets the Roslyn syntax tree for the generated C# for this Razor document diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshotExtensions.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshotExtensions.cs index 6c3e9ec8764..6b6ec6a9360 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshotExtensions.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshotExtensions.cs @@ -58,4 +58,10 @@ public static bool IsPathCandidateForComponent(this IDocumentSnapshot documentSn var fileName = Path.GetFileNameWithoutExtension(documentSnapshot.FilePath); return fileName.AsSpan().Equals(path.Span, FilePathComparison.Instance); } + + public static Task GetGeneratedOutputAsync(this IDocumentSnapshot documentSnapshot) + { + return documentSnapshot.GetGeneratedOutputAsync(forceDesignTimeGeneratedOutput: false); + } + } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ImportDocumentSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ImportDocumentSnapshot.cs index 0b235a2ce36..213a2f2352c 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ImportDocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ImportDocumentSnapshot.cs @@ -35,9 +35,6 @@ public ImportDocumentSnapshot(IProjectSnapshot project, RazorProjectItem item) public int Version => 1; - public Task GetGeneratedOutputAsync() - => throw new NotSupportedException(); - public async Task GetTextAsync() { using (var stream = _importItem.Read()) @@ -51,7 +48,7 @@ public async Task GetTextAsync() } public Task GetGeneratedOutputAsync(bool _) - => GetGeneratedOutputAsync(); + => throw new NotSupportedException(); public Task GetTextVersionAsync() { diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentMapping/RemoteDocumentMappingService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentMapping/RemoteDocumentMappingService.cs index 50fbc2fec25..0d3a170efac 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentMapping/RemoteDocumentMappingService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentMapping/RemoteDocumentMappingService.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis.Razor.DocumentMapping; using Microsoft.CodeAnalysis.Razor.Logging; +using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Text; diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs index 228fba60c30..bf65778cd50 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs @@ -46,7 +46,7 @@ internal class RemoteDocumentSnapshot(TextDocument textDocument, RemoteProjectSn public bool TryGetTextVersion(out VersionStamp result) => _textDocument.TryGetTextVersion(out result); - public async Task GetGeneratedOutputAsync() + public async Task GetGeneratedOutputAsync(bool _) { // TODO: We don't need to worry about locking if we get called from the didOpen/didChange LSP requests, as CLaSP // takes care of that for us, and blocks requests until those are complete. If that doesn't end up happening, @@ -126,7 +126,4 @@ public async Task GetCSharpSyntaxTreeAsync(CancellationToken cancell var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); return tree.AssumeNotNull(); } - - public Task GetGeneratedOutputAsync(bool _) - => GetGeneratedOutputAsync(); } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/ProjectSystem/TestDocumentSnapshot.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/ProjectSystem/TestDocumentSnapshot.cs index 65c0670b26c..645bec36bae 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/ProjectSystem/TestDocumentSnapshot.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/ProjectSystem/TestDocumentSnapshot.cs @@ -79,7 +79,7 @@ public TestDocumentSnapshot(ProjectSnapshot projectSnapshot, DocumentState docum public HostDocument HostDocument => State.HostDocument; - public override Task GetGeneratedOutputAsync() + public override Task GetGeneratedOutputAsync(bool _) { if (_codeDocument is null) { diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/RazorComponentDefinitionServiceTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/RazorComponentDefinitionServiceTest.cs index 22c43a1a755..041b95c6eee 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/RazorComponentDefinitionServiceTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/RazorComponentDefinitionServiceTest.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Razor.Test.Common; using Microsoft.CodeAnalysis.Razor.DocumentMapping; using Microsoft.CodeAnalysis.Razor.GoToDefinition; +using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; From 69105c1fc56a672dccfe5dcdc85bfa5f26f05fd1 Mon Sep 17 00:00:00 2001 From: Alex Gavrilov Date: Wed, 28 Aug 2024 11:22:03 -0700 Subject: [PATCH 61/64] Remaining MEF parameter attrivute formatting changes --- .../AutoInsert/IAutoInsertService.cs | 3 +-- .../AutoInsert/IOnAutoInsertProvider.cs | 3 +-- .../AutoInsert/OnAutoInsertEndpointTest.cs | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs index 32f48a14bed..8bad0493d0f 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IAutoInsertService.cs @@ -17,6 +17,5 @@ bool TryResolveInsertion( Position position, string character, bool autoCloseTags, - [NotNullWhen(true)] - out VSInternalDocumentOnAutoInsertResponseItem? insertTextEdit); + [NotNullWhen(true)] out VSInternalDocumentOnAutoInsertResponseItem? insertTextEdit); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs index 0f4a4046855..ba6458712a0 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/IOnAutoInsertProvider.cs @@ -13,6 +13,5 @@ public bool TryResolveInsertion( Position position, RazorCodeDocument codeDocument, bool enableAutoClosingTags, - [NotNullWhen(true)] - out VSInternalDocumentOnAutoInsertResponseItem? autoInsertEdit); + [NotNullWhen(true)] out VSInternalDocumentOnAutoInsertResponseItem? autoInsertEdit); } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/OnAutoInsertEndpointTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/OnAutoInsertEndpointTest.cs index dbfaabf5d55..6811b702468 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/OnAutoInsertEndpointTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/AutoInsert/OnAutoInsertEndpointTest.cs @@ -197,8 +197,7 @@ public bool TryResolveInsertion( Position position, RazorCodeDocument codeDocument, bool enableAutoClosingTags, - [NotNullWhen(true)] - out VSInternalDocumentOnAutoInsertResponseItem? autoInsertEdit) + [NotNullWhen(true)] out VSInternalDocumentOnAutoInsertResponseItem? autoInsertEdit) { Called = true; autoInsertEdit = canResolve ? new() { TextEdit = ResolvedTextEdit!, TextEditFormat = InsertTextFormat.Plaintext } : null; From 137d9ad1c2bce5b7c6dc9d398dd97ec38ef9e4d1 Mon Sep 17 00:00:00 2001 From: Alex Gavrilov Date: Wed, 28 Aug 2024 12:58:51 -0700 Subject: [PATCH 62/64] Last of the PR feedback. --- .../LspFormattingCodeDocumentProvider.cs} | 5 +- .../RazorLanguageServer.cs | 4 +- .../AutoInsert/AutoInsertService.cs | 3 +- .../Formatting/FormattingContext.cs | 4 +- .../ProjectSystem/DocumentSnapshot.cs | 2 +- .../AutoInsert/RemoteAutoInsertService.cs | 47 +++++++++---------- .../Cohost/CohostOnAutoInsertEndpoint.cs | 8 ++-- .../AutoClosingTagOnAutoInsertProviderTest.cs | 2 +- .../FormattingContentValidationPassTest.cs | 2 +- .../FormattingDiagnosticValidationPassTest.cs | 2 +- .../TestRazorFormattingService.cs | 2 +- 11 files changed, 40 insertions(+), 41 deletions(-) rename src/Razor/src/{Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/FormattingCodeDocumentProvider.cs => Microsoft.AspNetCore.Razor.LanguageServer/Formatting/LspFormattingCodeDocumentProvider.cs} (74%) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/FormattingCodeDocumentProvider.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/LspFormattingCodeDocumentProvider.cs similarity index 74% rename from src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/FormattingCodeDocumentProvider.cs rename to src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/LspFormattingCodeDocumentProvider.cs index a5d62efd251..eaf9bfa091b 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/FormattingCodeDocumentProvider.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/LspFormattingCodeDocumentProvider.cs @@ -3,11 +3,12 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; +using Microsoft.CodeAnalysis.Razor.Formatting; using Microsoft.CodeAnalysis.Razor.ProjectSystem; -namespace Microsoft.CodeAnalysis.Razor.Formatting; +namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting; -internal sealed class FormattingCodeDocumentProvider : IFormattingCodeDocumentProvider +internal sealed class LspFormattingCodeDocumentProvider : IFormattingCodeDocumentProvider { public Task GetCodeDocumentAsync(IDocumentSnapshot snapshot) { diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs index 9ff2229db9f..d3162a0e5b1 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs @@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Razor.LanguageServer.Extensions; using Microsoft.AspNetCore.Razor.LanguageServer.FindAllReferences; using Microsoft.AspNetCore.Razor.LanguageServer.Folding; +using Microsoft.AspNetCore.Razor.LanguageServer.Formatting; using Microsoft.AspNetCore.Razor.LanguageServer.Hosting; using Microsoft.AspNetCore.Razor.LanguageServer.Implementation; using Microsoft.AspNetCore.Razor.LanguageServer.InlayHints; @@ -24,7 +25,6 @@ using Microsoft.AspNetCore.Razor.LanguageServer.SignatureHelp; using Microsoft.AspNetCore.Razor.LanguageServer.WrapWithTag; using Microsoft.AspNetCore.Razor.Telemetry; -using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor.AutoInsert; using Microsoft.CodeAnalysis.Razor.FoldingRanges; using Microsoft.CodeAnalysis.Razor.Formatting; @@ -122,7 +122,7 @@ protected override ILspServices ConstructLspServices() services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); var featureOptions = _featureOptions ?? new DefaultLanguageServerFeatureOptions(); services.AddSingleton(featureOptions); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs index c96ab07b21f..d6b6e3e3a47 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs @@ -26,10 +26,11 @@ internal class AutoInsertService(IEnumerable onAutoInsert private static ImmutableArray CalculateTriggerCharacters(IEnumerable onAutoInsertProviders) { using var builder = new PooledArrayBuilder(); + using var _ = StringHashSetPool.Ordinal.GetPooledObject(out var set); foreach (var provider in onAutoInsertProviders) { var triggerCharacter = provider.TriggerCharacter; - if (!builder.Any(character => character == triggerCharacter)) + if (set.Add(triggerCharacter)) { builder.Add(triggerCharacter); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/FormattingContext.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/FormattingContext.cs index df67f5d1972..bd0fd0db7b7 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/FormattingContext.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/FormattingContext.cs @@ -21,10 +21,10 @@ namespace Microsoft.CodeAnalysis.Razor.Formatting; internal sealed class FormattingContext : IDisposable { private readonly IAdhocWorkspaceFactory _workspaceFactory; - private Document? _csharpWorkspaceDocument; + private readonly IFormattingCodeDocumentProvider _codeDocumentProvider; + private Document? _csharpWorkspaceDocument; private AdhocWorkspace? _csharpWorkspace; - private readonly IFormattingCodeDocumentProvider _codeDocumentProvider; private IReadOnlyList? _formattingSpans; private IReadOnlyDictionary? _indentations; diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs index 6fa61c15986..573b4fe0f27 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs @@ -82,6 +82,6 @@ private async Task GetDesignTimeGeneratedOutputAsync() var tagHelpers = await Project.GetTagHelpersAsync(CancellationToken.None).ConfigureAwait(false); var projectEngine = Project.GetProjectEngine(); var imports = await DocumentState.GetImportsAsync(this, projectEngine).ConfigureAwait(false); - return await DocumentState.GenerateCodeDocumentAsync(this, Project.GetProjectEngine(), imports, tagHelpers, false).ConfigureAwait(false); + return await DocumentState.GenerateCodeDocumentAsync(this, projectEngine, imports, tagHelpers, forceRuntimeCodeGeneration: false).ConfigureAwait(false); } } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs index f1fb4e4fc17..4c009c6b7ad 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs @@ -96,35 +96,32 @@ private async ValueTask TryResolveInsertionAsync( var positionInfo = GetPositionInfo(codeDocument, index); var languageKind = positionInfo.LanguageKind; - if (languageKind is RazorLanguageKind.Razor) + switch (languageKind) { - // If we are in Razor and got no edit from our own service, there is nothing else to do - return Response.NoFurtherHandling; - } - else if (languageKind is RazorLanguageKind.Html) - { - return AutoInsertService.HtmlAllowedAutoInsertTriggerCharacters.Contains(character) - ? Response.CallHtml - : Response.NoFurtherHandling; + case RazorLanguageKind.Razor: + // If we are in Razor and got no edit from our own service, there is nothing else to do + return Response.NoFurtherHandling; + case RazorLanguageKind.Html: + return AutoInsertService.HtmlAllowedAutoInsertTriggerCharacters.Contains(character) + ? Response.CallHtml + : Response.NoFurtherHandling; + case RazorLanguageKind.CSharp: + var mappedPosition = positionInfo.Position.ToLinePosition(); + return await TryResolveInsertionInCSharpAsync( + remoteDocumentContext, + mappedPosition, + character, + formatOnType, + indentWithTabs, + indentSize, + cancellationToken); + default: + Logger.LogError($"Unsupported language {languageKind} in {nameof(RemoteAutoInsertService)}"); + return Response.NoFurtherHandling; } - else if (languageKind is RazorLanguageKind.CSharp) - { - var mappedPosition = positionInfo.Position.ToLinePosition(); - return await TryResolveInsertionAsyncInCSharpAsync( - remoteDocumentContext, - mappedPosition, - character, - formatOnType, - indentWithTabs, - indentSize, - cancellationToken); - } - - Logger.LogError($"Unsupported language {languageKind} in {nameof(RemoteAutoInsertService)}"); - return Response.NoFurtherHandling; } - private async ValueTask TryResolveInsertionAsyncInCSharpAsync( + private async ValueTask TryResolveInsertionInCSharpAsync( RemoteDocumentContext remoteDocumentContext, LinePosition mappedPosition, string character, diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs index 45d5e98993c..1b90bd110ac 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostOnAutoInsertEndpoint.cs @@ -106,10 +106,10 @@ private static ImmutableArray CalculateTriggerChars(IEnumerable CreateWithFullSupportAsync( return new RazorFormattingService( passes, - new FormattingCodeDocumentProvider(), + new LspFormattingCodeDocumentProvider(), TestAdhocWorkspaceFactory.Instance); } } From 7715a4c8e0909f16bbf5d08fa8892555d43f445a Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Thu, 29 Aug 2024 09:49:01 -0700 Subject: [PATCH 63/64] Fixing most unit test failures. Extension methods can't be used for Mock setups, so since I made GetGeneratedOutputAsync() with no parameter an extension method, I had to switch unit tests to use GeGeneratedOutputAsync(It.Any()) --- .../CSharp/DefaultCSharpCodeActionProviderTest.cs | 2 +- .../TypeAccessibilityCodeActionProviderTest.cs | 2 +- .../Html/DefaultHtmlCodeActionProviderTest.cs | 2 +- ...ComponentAccessibilityCodeActionProviderTest.cs | 2 +- .../ExtractToCodeBehindCodeActionProviderTest.cs | 2 +- .../TextDocumentUriPresentationEndpointTests.cs | 14 +++++++------- .../FormattingContentValidationPassTest.cs | 2 +- .../Formatting_NetFx/FormattingTestBase.cs | 3 --- .../Hover/HoverServiceTest.cs | 2 +- .../Semantic/SemanticTokensTest.cs | 2 +- 10 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CSharp/DefaultCSharpCodeActionProviderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CSharp/DefaultCSharpCodeActionProviderTest.cs index bd93514e88f..2f668586916 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CSharp/DefaultCSharpCodeActionProviderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CSharp/DefaultCSharpCodeActionProviderTest.cs @@ -333,7 +333,7 @@ private static RazorCodeActionContext CreateRazorCodeActionContext( codeDocument.SetCSharpDocument(csharpDocumentWithDiagnostic); var documentSnapshot = Mock.Of(document => - document.GetGeneratedOutputAsync() == Task.FromResult(codeDocument) && + document.GetGeneratedOutputAsync(It.IsAny()) == Task.FromResult(codeDocument) && document.GetTextAsync() == Task.FromResult(codeDocument.Source.Text) && document.Project.GetTagHelpersAsync(It.IsAny()) == new ValueTask>(tagHelpers), MockBehavior.Strict); diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CSharp/TypeAccessibilityCodeActionProviderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CSharp/TypeAccessibilityCodeActionProviderTest.cs index 610e0462269..86d1b8239e5 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CSharp/TypeAccessibilityCodeActionProviderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CSharp/TypeAccessibilityCodeActionProviderTest.cs @@ -464,7 +464,7 @@ private static RazorCodeActionContext CreateRazorCodeActionContext( codeDocument.SetCSharpDocument(csharpDocumentWithDiagnostic); var documentSnapshot = Mock.Of(document => - document.GetGeneratedOutputAsync() == Task.FromResult(codeDocument) && + document.GetGeneratedOutputAsync(It.IsAny()) == Task.FromResult(codeDocument) && document.GetTextAsync() == Task.FromResult(codeDocument.Source.Text) && document.Project.GetTagHelpersAsync(It.IsAny()) == new ValueTask>(tagHelpers), MockBehavior.Strict); diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Html/DefaultHtmlCodeActionProviderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Html/DefaultHtmlCodeActionProviderTest.cs index 297848bba6c..8bcc81013b1 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Html/DefaultHtmlCodeActionProviderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Html/DefaultHtmlCodeActionProviderTest.cs @@ -152,7 +152,7 @@ private static RazorCodeActionContext CreateRazorCodeActionContext( var codeDocument = projectEngine.ProcessDesignTime(sourceDocument, FileKinds.Component, importSources: default, tagHelpers); var documentSnapshot = Mock.Of(document => - document.GetGeneratedOutputAsync() == Task.FromResult(codeDocument) && + document.GetGeneratedOutputAsync(It.IsAny()) == Task.FromResult(codeDocument) && document.GetTextAsync() == Task.FromResult(codeDocument.Source.Text) && document.Project.GetTagHelpersAsync(It.IsAny()) == new ValueTask>(tagHelpers), MockBehavior.Strict); diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ComponentAccessibilityCodeActionProviderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ComponentAccessibilityCodeActionProviderTest.cs index 56874c402da..2a0e033c4b5 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ComponentAccessibilityCodeActionProviderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ComponentAccessibilityCodeActionProviderTest.cs @@ -464,7 +464,7 @@ private static RazorCodeActionContext CreateRazorCodeActionContext(VSCodeActionP codeDocument.SetCSharpDocument(csharpDocumentWithDiagnostic); var documentSnapshot = Mock.Of(document => - document.GetGeneratedOutputAsync() == Task.FromResult(codeDocument) && + document.GetGeneratedOutputAsync(It.IsAny()) == Task.FromResult(codeDocument) && document.GetTextAsync() == Task.FromResult(codeDocument.Source.Text) && document.Project.GetTagHelpersAsync(It.IsAny()) == new ValueTask>(tagHelpers), MockBehavior.Strict); diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToCodeBehindCodeActionProviderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToCodeBehindCodeActionProviderTest.cs index a0995f885a9..698d462b310 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToCodeBehindCodeActionProviderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToCodeBehindCodeActionProviderTest.cs @@ -396,7 +396,7 @@ private static RazorCodeActionContext CreateRazorCodeActionContext(VSCodeActionP codeDocument.SetSyntaxTree(syntaxTree); var documentSnapshot = Mock.Of(document => - document.GetGeneratedOutputAsync() == Task.FromResult(codeDocument) && + document.GetGeneratedOutputAsync(It.IsAny()) == Task.FromResult(codeDocument) && document.GetTextAsync() == Task.FromResult(codeDocument.Source.Text), MockBehavior.Strict); var sourceText = SourceText.From(text); diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/DocumentPresentation/TextDocumentUriPresentationEndpointTests.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/DocumentPresentation/TextDocumentUriPresentationEndpointTests.cs index 52ee2ceb54a..3b814f35d33 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/DocumentPresentation/TextDocumentUriPresentationEndpointTests.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/DocumentPresentation/TextDocumentUriPresentationEndpointTests.cs @@ -216,7 +216,7 @@ public async Task Handle_NoTypeNameIdentifier_ReturnsNull() var builder = TagHelperDescriptorBuilder.Create("MyTagHelper", "MyAssembly"); var tagHelperDescriptor = builder.Build(); - var documentSnapshot = Mock.Of(s => s.GetGeneratedOutputAsync() == Task.FromResult(componentCodeDocument), MockBehavior.Strict); + var documentSnapshot = Mock.Of(s => s.GetGeneratedOutputAsync(It.IsAny()) == Task.FromResult(componentCodeDocument), MockBehavior.Strict); var uri = new Uri("file://path/test.razor"); var documentContextFactory = CreateDocumentContextFactory(uri, codeDocument); @@ -262,7 +262,7 @@ public async Task Handle_MultipleUris_ReturnsNull() var documentMappingService = Mock.Of( s => s.GetLanguageKind(codeDocument, It.IsAny(), It.IsAny()) == RazorLanguageKind.Html, MockBehavior.Strict); - var documentSnapshot = Mock.Of(s => s.GetGeneratedOutputAsync() == Task.FromResult(codeDocument), MockBehavior.Strict); + var documentSnapshot = Mock.Of(s => s.GetGeneratedOutputAsync(false) == Task.FromResult(codeDocument), MockBehavior.Strict); var uri = new Uri("file://path/test.razor"); var documentContextFactory = CreateDocumentContextFactory(uri, codeDocument); @@ -313,7 +313,7 @@ public async Task Handle_NotComponent_ReturnsNull() var documentMappingService = Mock.Of( s => s.GetLanguageKind(codeDocument, It.IsAny(), It.IsAny()) == RazorLanguageKind.Html, MockBehavior.Strict); - var documentSnapshot = Mock.Of(s => s.GetGeneratedOutputAsync() == Task.FromResult(codeDocument), MockBehavior.Strict); + var documentSnapshot = Mock.Of(s => s.GetGeneratedOutputAsync(false) == Task.FromResult(codeDocument), MockBehavior.Strict); var droppedUri = new Uri("file:///c:/path/MyTagHelper.cshtml"); var uri = new Uri("file://path/test.razor"); @@ -422,7 +422,7 @@ public async Task Handle_CSharp_ReturnsNull() s => s.GetLanguageKind(codeDocument, It.IsAny(), It.IsAny()) == RazorLanguageKind.CSharp && s.TryMapToGeneratedDocumentRange(csharpDocument, It.IsAny(), out projectedRange) == true, MockBehavior.Strict); - var documentSnapshot = Mock.Of(s => s.GetGeneratedOutputAsync() == Task.FromResult(codeDocument), MockBehavior.Strict); + var documentSnapshot = Mock.Of(s => s.GetGeneratedOutputAsync(false) == Task.FromResult(codeDocument), MockBehavior.Strict); var documentContextFactory = CreateDocumentContextFactory(uri, codeDocument); var response = (WorkspaceEdit?)null; @@ -466,7 +466,7 @@ public async Task Handle_DocumentNotFound_ReturnsNull() var documentMappingService = Mock.Of( s => s.GetLanguageKind(codeDocument, It.IsAny(), It.IsAny()) == RazorLanguageKind.Html, MockBehavior.Strict); - var documentSnapshot = Mock.Of(s => s.GetGeneratedOutputAsync() == Task.FromResult(codeDocument), MockBehavior.Strict); + var documentSnapshot = Mock.Of(s => s.GetGeneratedOutputAsync(false) == Task.FromResult(codeDocument), MockBehavior.Strict); var documentContextFactory = CreateDocumentContextFactory(uri, codeDocument); var response = (WorkspaceEdit?)null; @@ -511,7 +511,7 @@ public async Task Handle_UnsupportedCodeDocument_ReturnsNull() var documentMappingService = Mock.Of( s => s.GetLanguageKind(codeDocument, It.IsAny(), It.IsAny()) == RazorLanguageKind.Html, MockBehavior.Strict); - var documentSnapshot = Mock.Of(s => s.GetGeneratedOutputAsync() == Task.FromResult(codeDocument), MockBehavior.Strict); + var documentSnapshot = Mock.Of(s => s.GetGeneratedOutputAsync(false) == Task.FromResult(codeDocument), MockBehavior.Strict); var documentContextFactory = CreateDocumentContextFactory(uri, codeDocument); var response = new WorkspaceEdit(); @@ -555,7 +555,7 @@ public async Task Handle_NoUris_ReturnsNull() var documentMappingService = Mock.Of( s => s.GetLanguageKind(codeDocument, It.IsAny(), It.IsAny()) == RazorLanguageKind.Html, MockBehavior.Strict); - var documentSnapshot = Mock.Of(s => s.GetGeneratedOutputAsync() == Task.FromResult(codeDocument), MockBehavior.Strict); + var documentSnapshot = Mock.Of(s => s.GetGeneratedOutputAsync(false) == Task.FromResult(codeDocument), MockBehavior.Strict); var documentContextFactory = CreateDocumentContextFactory(uri, codeDocument); var response = (WorkspaceEdit?)null; diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingContentValidationPassTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingContentValidationPassTest.cs index 1813c9810a4..f35c3dffeeb 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingContentValidationPassTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingContentValidationPassTest.cs @@ -152,7 +152,7 @@ private static (RazorCodeDocument, IDocumentSnapshot) CreateCodeDocumentAndSnaps var documentSnapshot = new Mock(MockBehavior.Strict); documentSnapshot - .Setup(d => d.GetGeneratedOutputAsync()) + .Setup(d => d.GetGeneratedOutputAsync(It.IsAny())) .ReturnsAsync(codeDocument); documentSnapshot .Setup(d => d.TargetPath) diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingTestBase.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingTestBase.cs index f9004cd9cbe..dc8c6ddd2be 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingTestBase.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingTestBase.cs @@ -286,9 +286,6 @@ @using Microsoft.AspNetCore.Components.Web internal static IDocumentSnapshot CreateDocumentSnapshot(string path, ImmutableArray tagHelpers, string? fileKind, ImmutableArray importsDocuments, ImmutableArray imports, RazorProjectEngine projectEngine, RazorCodeDocument codeDocument, bool inGlobalNamespace = false) { var documentSnapshot = new Mock(MockBehavior.Strict); - documentSnapshot - .Setup(d => d.GetGeneratedOutputAsync()) - .ReturnsAsync(codeDocument); documentSnapshot .Setup(d => d.GetGeneratedOutputAsync(It.IsAny())) .ReturnsAsync(codeDocument); diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Hover/HoverServiceTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Hover/HoverServiceTest.cs index 411d6405171..4430868d72e 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Hover/HoverServiceTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Hover/HoverServiceTest.cs @@ -923,7 +923,7 @@ public void Increment(){ var sourceText = SourceText.From(txt); var snapshot = Mock.Of(d => - d.GetGeneratedOutputAsync() == Task.FromResult(codeDocument) && + d.GetGeneratedOutputAsync(It.IsAny()) == Task.FromResult(codeDocument) && d.FilePath == path && d.FileKind == FileKinds.Component && d.GetTextAsync() == Task.FromResult(sourceText) && diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Semantic/SemanticTokensTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Semantic/SemanticTokensTest.cs index 748262ec53a..455da63ae60 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Semantic/SemanticTokensTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Semantic/SemanticTokensTest.cs @@ -937,7 +937,7 @@ private static DocumentContext CreateDocumentContext( .SetupGet(x => x.Project) .Returns(projectSnapshot.Object); documentSnapshotMock - .Setup(x => x.GetGeneratedOutputAsync()) + .Setup(x => x.GetGeneratedOutputAsync(It.IsAny())) .ReturnsAsync(document); documentSnapshotMock .Setup(x => x.GetTextAsync()) From ffaa2efd5e5cc711fc0a80814b5c6e895d33ffb9 Mon Sep 17 00:00:00 2001 From: "Alex Gavrilov (DEV PROD)" Date: Thu, 29 Aug 2024 10:42:13 -0700 Subject: [PATCH 64/64] Fixing last 4 unit test failures We still had non-parameter GetGeneratedOutputAsycnc in DocumentSnapshot (even though it wasn't in IDocumentSnapshot) which was getting called internally. That was both messy (since there is now no-parameter extension method on IDocumentSnapshot) and was causing issues in tests --- .../ProjectSystem/DocumentSnapshot.cs | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs index 573b4fe0f27..7a145bf3ee1 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs @@ -36,12 +36,6 @@ public Task GetTextAsync() public Task GetTextVersionAsync() => State.GetTextVersionAsync(); - public virtual async Task GetGeneratedOutputAsync() - { - var (output, _) = await State.GetGeneratedOutputAndVersionAsync(ProjectInternal, this).ConfigureAwait(false); - return output; - } - public bool TryGetText([NotNullWhen(true)] out SourceText? result) => State.TryGetText(out result); @@ -69,13 +63,21 @@ public IDocumentSnapshot WithText(SourceText text) public async Task GetCSharpSyntaxTreeAsync(CancellationToken cancellationToken) { - var codeDocument = await GetGeneratedOutputAsync().ConfigureAwait(false); + var codeDocument = await GetGeneratedOutputAsync(forceDesignTimeGeneratedOutput: false).ConfigureAwait(false); var csharpText = codeDocument.GetCSharpSourceText(); return CSharpSyntaxTree.ParseText(csharpText, cancellationToken: cancellationToken); } - public virtual Task GetGeneratedOutputAsync(bool forceDesignTimeGeneratedOutput) - => forceDesignTimeGeneratedOutput ? GetDesignTimeGeneratedOutputAsync() : GetGeneratedOutputAsync(); + public virtual async Task GetGeneratedOutputAsync(bool forceDesignTimeGeneratedOutput) + { + if (forceDesignTimeGeneratedOutput) + { + return await GetDesignTimeGeneratedOutputAsync().ConfigureAwait(false); + } + + var (output, _) = await State.GetGeneratedOutputAndVersionAsync(ProjectInternal, this).ConfigureAwait(false); + return output; + } private async Task GetDesignTimeGeneratedOutputAsync() {