diff --git a/eng/Versions.props b/eng/Versions.props
index 0b69a55c261..e00b6a4aac4 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -93,7 +93,7 @@
17.0.31723.112
17.0.487
4.1.0-1.21471.13
- 17.1.2
+ 17.1.8
5.0.0-preview.4.20205.1
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/LanguageServerConstants.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/LanguageServerConstants.cs
index 1291baeccc4..5bbf232d5f9 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/LanguageServerConstants.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/LanguageServerConstants.cs
@@ -50,6 +50,8 @@ public static class LanguageServerConstants
public const string RazorServerReadyEndpoint = "razor/serverReady";
+ public const string RazorInlineCompletionEndpoint = "razor/inlineCompletion";
+
// This needs to be the same as in Web Tools, that is used by the HTML editor, because
// we actually respond to the Web Tools "Wrap With Div" command handler, which sends this message
// to all servers. We then take the message, get the HTML virtual document, and send it
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/IInlineCompletionHandler.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/IInlineCompletionHandler.cs
new file mode 100644
index 00000000000..bfa6bd39337
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/IInlineCompletionHandler.cs
@@ -0,0 +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 OmniSharp.Extensions.JsonRpc;
+
+namespace Microsoft.AspNetCore.Razor.LanguageServer;
+
+[Parallel, Method("textDocument/_vs_inlineCompletion")]
+internal interface IInlineCompletionHandler : IJsonRpcRequestHandler, IRegistrationExtension
+{
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/InlineCompletionContext.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/InlineCompletionContext.cs
new file mode 100644
index 00000000000..75f282c22b2
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/InlineCompletionContext.cs
@@ -0,0 +1,23 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the MIT license. See License.txt in the project root for license information.
+
+#nullable disable
+
+using System.Runtime.Serialization;
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNetCore.Razor.LanguageServer;
+
+///
+/// Corresponds to https://devdiv.visualstudio.com/DevDiv/_git/VSLanguageServerClient?path=/src/product/Protocol/LanguageServer.Protocol.Internal/VSInternalInlineCompletionContext.cs
+///
+internal class InlineCompletionContext
+{
+ [DataMember(Name = "_vs_triggerKind")]
+ [JsonProperty(Required = Required.Always)]
+ public InlineCompletionTriggerKind TriggerKind { get; set; } = InlineCompletionTriggerKind.Explicit;
+
+ [DataMember(Name = "_vs_selectedCompletionInfo")]
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
+ public SelectedCompletionInfo SelectedCompletionInfo { get; set; }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/InlineCompletionEndPoint.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/InlineCompletionEndPoint.cs
new file mode 100644
index 00000000000..0f432c690b8
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/InlineCompletionEndPoint.cs
@@ -0,0 +1,254 @@
+// 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.Collections.Immutable;
+using System.Composition;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.AspNetCore.Razor.LanguageServer.Common;
+using Microsoft.AspNetCore.Razor.LanguageServer.Common.Extensions;
+using Microsoft.AspNetCore.Razor.LanguageServer.Extensions;
+using Microsoft.AspNetCore.Razor.LanguageServer.Formatting;
+using Microsoft.AspNetCore.Razor.LanguageServer.ProjectSystem;
+using Microsoft.CodeAnalysis.Razor;
+using Microsoft.CodeAnalysis.Text;
+using Microsoft.Extensions.Logging;
+using OmniSharp.Extensions.LanguageServer.Protocol.Models;
+
+namespace Microsoft.AspNetCore.Razor.LanguageServer;
+
+internal class InlineCompletionEndpoint : IInlineCompletionHandler
+{
+ // Usually when we need to format code, we utilize the formatting options provided
+ // by the platform. Similar to DefaultCSharpCodeActionResolver we do not have any, so use defaults.
+ private static readonly FormattingOptions s_defaultFormattingOptions = new FormattingOptions()
+ {
+ TabSize = 4,
+ InsertSpaces = true,
+ TrimTrailingWhitespace = true,
+ InsertFinalNewline = true,
+ TrimFinalNewlines = true
+ };
+
+ private static readonly ImmutableHashSet s_cSharpKeywords = ImmutableHashSet.Create(
+ "~", "Attribute", "checked", "class", "ctor", "cw", "do", "else", "enum", "equals", "Exception", "for", "foreach", "forr",
+ "if", "indexer", "interface", "invoke", "iterator", "iterindex", "lock", "mbox", "namespace", "#if", "#region", "prop",
+ "propfull", "propg", "sim", "struct", "svm", "switch", "try", "tryf", "unchecked", "unsafe", "using", "while");
+
+ private readonly ProjectSnapshotManagerDispatcher _projectSnapshotManagerDispatcher;
+ private readonly DocumentResolver _documentResolver;
+ private readonly RazorDocumentMappingService _documentMappingService;
+ private readonly ClientNotifierServiceBase _languageServer;
+ private readonly AdhocWorkspaceFactory _adhocWorkspaceFactory;
+ private readonly ILogger _logger;
+
+ [ImportingConstructor]
+ public InlineCompletionEndpoint(
+ ProjectSnapshotManagerDispatcher projectSnapshotManagerDispatcher,
+ DocumentResolver documentResolver,
+ RazorDocumentMappingService documentMappingService,
+ ClientNotifierServiceBase languageServer,
+ AdhocWorkspaceFactory adhocWorkspaceFactory,
+ ILoggerFactory loggerFactory)
+ {
+ if (projectSnapshotManagerDispatcher is null)
+ {
+ throw new ArgumentNullException(nameof(projectSnapshotManagerDispatcher));
+ }
+
+ if (documentResolver is null)
+ {
+ throw new ArgumentNullException(nameof(documentResolver));
+ }
+
+ if (documentMappingService is null)
+ {
+ throw new ArgumentNullException(nameof(documentMappingService));
+ }
+
+ if (languageServer is null)
+ {
+ throw new ArgumentNullException(nameof(languageServer));
+ }
+
+ if (adhocWorkspaceFactory is null)
+ {
+ throw new ArgumentNullException(nameof(adhocWorkspaceFactory));
+ }
+
+ if (loggerFactory is null)
+ {
+ throw new ArgumentNullException(nameof(loggerFactory));
+ }
+
+ _projectSnapshotManagerDispatcher = projectSnapshotManagerDispatcher;
+ _documentResolver = documentResolver;
+ _documentMappingService = documentMappingService;
+ _languageServer = languageServer;
+ _adhocWorkspaceFactory = adhocWorkspaceFactory;
+ _logger = loggerFactory.CreateLogger();
+ }
+
+ public RegistrationExtensionResult GetRegistration()
+ {
+ const string AssociatedServerCapability = "_vs_inlineCompletionOptions";
+
+ var registrationOptions = new InlineCompletionOptions()
+ {
+ DocumentSelector = RazorDefaults.Selector,
+ Pattern = string.Join("|", s_cSharpKeywords)
+ };
+
+ return new RegistrationExtensionResult(AssociatedServerCapability, registrationOptions);
+ }
+
+ public async Task Handle(InlineCompletionRequest request, CancellationToken cancellationToken)
+ {
+ if (request is null)
+ {
+ throw new ArgumentNullException(nameof(request));
+ }
+
+ _logger.LogInformation($"Starting request for {request.TextDocument.Uri} at {request.Position}.");
+
+ var document = await _projectSnapshotManagerDispatcher.RunOnDispatcherThreadAsync(() =>
+ {
+ _documentResolver.TryResolveDocument(request.TextDocument.Uri.GetAbsoluteOrUNCPath(), out var documentSnapshot);
+
+ return documentSnapshot;
+ }, cancellationToken).ConfigureAwait(false);
+
+ if (document is null)
+ {
+ return null;
+ }
+
+ var codeDocument = await document.GetGeneratedOutputAsync();
+ if (codeDocument.IsUnsupported())
+ {
+ return null;
+ }
+
+ var sourceText = await document.GetTextAsync();
+ var linePosition = new LinePosition(request.Position.Line, request.Position.Character);
+ var hostDocumentIndex = sourceText.Lines.GetPosition(linePosition);
+
+ var languageKind = _documentMappingService.GetLanguageKind(codeDocument, hostDocumentIndex);
+
+ // Map to the location in the C# document.
+ if (languageKind != RazorLanguageKind.CSharp ||
+ !_documentMappingService.TryMapToProjectedDocumentPosition(codeDocument, hostDocumentIndex, out var projectedPosition, out _))
+ {
+ _logger.LogInformation($"Unsupported location for {request.TextDocument.Uri}.");
+ return null;
+ }
+
+ var razorRequest = new RazorInlineCompletionRequest
+ {
+ TextDocument = request.TextDocument,
+ Context = request.Context,
+ Position = projectedPosition,
+ Kind = languageKind,
+ };
+
+ request.Position = projectedPosition;
+ var response = await _languageServer.SendRequestAsync(LanguageServerConstants.RazorInlineCompletionEndpoint, razorRequest).ConfigureAwait(false);
+ var list = await response.Returning(cancellationToken).ConfigureAwait(false);
+ if (list == null || !list.Items.Any())
+ {
+ _logger.LogInformation($"Did not get any inline completions from delegation.");
+ return null;
+ }
+
+ var items = new List();
+ var csharpDocOptions = codeDocument.GetCSharpDocument();
+ foreach (var item in list.Items)
+ {
+ var containsSnippet = item.TextFormat == InsertTextFormat.Snippet;
+ var range = item.Range ?? new Range { Start = projectedPosition, End = projectedPosition };
+
+ if (!_documentMappingService.TryMapFromProjectedDocumentRange(codeDocument, range, out var rangeInRazorDoc))
+ {
+ _logger.LogWarning($"Could not remap projected range {range} to razor document");
+ continue;
+ }
+
+ using var formattingContext = FormattingContext.Create(request.TextDocument.Uri, document, codeDocument, s_defaultFormattingOptions, _adhocWorkspaceFactory, isFormatOnType: true, automaticallyAddUsings: false);
+ if (!TryGetSnippetWithAdjustedIndentation(formattingContext, item.Text, hostDocumentIndex, out var newSnippetText))
+ {
+ continue;
+ }
+
+ var remappedItem = new InlineCompletionItem
+ {
+ Command = item.Command,
+ Range = rangeInRazorDoc,
+ Text = newSnippetText.ToString(),
+ TextFormat = item.TextFormat,
+ };
+ items.Add(remappedItem);
+ }
+
+ if (items.Count == 0)
+ {
+ _logger.LogInformation($"Could not format / map the items from delegation.");
+ return null;
+ }
+
+ _logger.LogInformation($"Returning {items.Count} items.");
+ return new InlineCompletionList
+ {
+ Items = items.ToArray()
+ };
+ }
+
+ private static bool TryGetSnippetWithAdjustedIndentation(FormattingContext formattingContext, string snippetText, int hostDocumentIndex, [NotNullWhen(true)] out string? newSnippetText)
+ {
+ newSnippetText = null;
+ if (!formattingContext.TryGetFormattingSpan(hostDocumentIndex, out var formattingSpan))
+ {
+ return false;
+ }
+
+ // Take the amount of indentation razor and html are adding, then remove the amount of C# indentation that is 'hidden'.
+ // This should give us the desired base indentation that must be applied to each line.
+ var razorAndHtmlContributionsToIndentation = formattingSpan.RazorIndentationLevel + formattingSpan.HtmlIndentationLevel;
+ var amountToAddToCSharpIndentation = razorAndHtmlContributionsToIndentation - formattingSpan.MinCSharpIndentLevel;
+
+ var snippetSourceText = SourceText.From(snippetText);
+ List indentationChanges = new();
+ // Adjust each line, skipping the first since it must start at the snippet keyword.
+ foreach (var line in snippetSourceText.Lines.Skip(1))
+ {
+ var lineText = snippetSourceText.GetSubText(line.Span);
+ if (lineText.Length == 0)
+ {
+ // We just have an empty line, nothing to do.
+ continue;
+ }
+
+ // Get the indentation of the line in the C# document based on what options the C# document was generated with.
+ var csharpLineIndentationSize = line.GetIndentationSize(formattingContext.Options.TabSize);
+ var csharpIndentationLevel = csharpLineIndentationSize / formattingContext.Options.TabSize;
+
+ // Get the new indentation level based on the context in the razor document.
+ var newIndentationLevel = csharpIndentationLevel + amountToAddToCSharpIndentation;
+ var newIndentationString = formattingContext.GetIndentationLevelString(newIndentationLevel);
+
+ // Replace the current indentation with the new indentation.
+ var spanToReplace = new TextSpan(line.Start, line.GetFirstNonWhitespaceOffset() ?? line.Span.End);
+ var textChange = new TextChange(spanToReplace, newIndentationString);
+ indentationChanges.Add(textChange);
+ }
+
+ var newSnippetSourceText = snippetSourceText.WithChanges(indentationChanges);
+ newSnippetText = newSnippetSourceText.ToString();
+ return true;
+ }
+}
+
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/InlineCompletionItem.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/InlineCompletionItem.cs
new file mode 100644
index 00000000000..146032cba3e
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/InlineCompletionItem.cs
@@ -0,0 +1,32 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the MIT license. See License.txt in the project root for license information.
+
+#nullable disable
+
+using System.Runtime.Serialization;
+using Newtonsoft.Json;
+using OmniSharp.Extensions.LanguageServer.Protocol.Models;
+
+namespace Microsoft.AspNetCore.Razor.LanguageServer;
+
+///
+/// Corresponds to https://devdiv.visualstudio.com/DevDiv/_git/VSLanguageServerClient?path=/src/product/Protocol/LanguageServer.Protocol.Internal/VSInternalInlineCompletionItem.cs
+///
+internal class InlineCompletionItem
+{
+ [DataMember(Name = "_vs_text")]
+ [JsonProperty(Required = Required.Always)]
+ public string Text { get; set; }
+
+ [DataMember(Name = "_vs_range")]
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
+ public Range Range { get; set; }
+
+ [DataMember(Name = "_vs_command")]
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
+ public Command Command { get; set; }
+
+ [DataMember(Name = "_vs_insertTextFormat")]
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
+ public InsertTextFormat? TextFormat { get; set; } = InsertTextFormat.PlainText;
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/InlineCompletionList.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/InlineCompletionList.cs
new file mode 100644
index 00000000000..e28dd815e12
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/InlineCompletionList.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.
+
+#nullable disable
+
+using System.Runtime.Serialization;
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNetCore.Razor.LanguageServer;
+
+internal class InlineCompletionList
+{
+ ///
+ /// Gets or sets the inline completion items.
+ ///
+ [DataMember(Name = "_vs_items")]
+ [JsonProperty(Required = Required.Always)]
+ public InlineCompletionItem[] Items { get; set; }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/InlineCompletionOptions.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/InlineCompletionOptions.cs
new file mode 100644
index 00000000000..88f2d7e3f44
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/InlineCompletionOptions.cs
@@ -0,0 +1,24 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the MIT license. See License.txt in the project root for license information.
+
+#nullable disable
+
+using System.Runtime.Serialization;
+using System.Text.RegularExpressions;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+using OmniSharp.Extensions.LanguageServer.Protocol.Models;
+
+namespace Microsoft.AspNetCore.Razor.LanguageServer;
+
+internal class InlineCompletionOptions : ITextDocumentRegistrationOptions
+{
+ ///
+ /// Gets or sets a regex used by the client to determine when to ask the server for snippets.
+ ///
+ [DataMember(Name = "_vs_pattern")]
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
+ public string Pattern { get; set; }
+
+ public DocumentSelector DocumentSelector { get; set; }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/InlineCompletionRequest.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/InlineCompletionRequest.cs
new file mode 100644
index 00000000000..80b6d24555b
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/InlineCompletionRequest.cs
@@ -0,0 +1,29 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the MIT license. See License.txt in the project root for license information.
+
+#nullable disable
+
+using System.Runtime.Serialization;
+using MediatR;
+using Newtonsoft.Json;
+using OmniSharp.Extensions.LanguageServer.Protocol.Models;
+
+namespace Microsoft.AspNetCore.Razor.LanguageServer;
+
+///
+/// Corresponds to https://devdiv.visualstudio.com/DevDiv/_git/VSLanguageServerClient?path=/src/product/Protocol/LanguageServer.Protocol.Internal/VSInternalInlineCompletionRequest.cs
+///
+internal class InlineCompletionRequest : ITextDocumentIdentifierParams, IRequest, IBaseRequest
+{
+ [DataMember(Name = "_vs_textDocument")]
+ [JsonProperty(Required = Required.Always)]
+ public TextDocumentIdentifier TextDocument { get; set; }
+
+ [DataMember(Name = "_vs_position")]
+ [JsonProperty(Required = Required.Always)]
+ public Position Position { get; set; }
+
+ [DataMember(Name = "_vs_context")]
+ [JsonProperty(Required = Required.Always)]
+ public InlineCompletionContext Context { get; set; }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/InlineCompletionTriggerKind.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/InlineCompletionTriggerKind.cs
new file mode 100644
index 00000000000..f32bd4b1641
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/InlineCompletionTriggerKind.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.
+
+#nullable disable
+
+using System.Runtime.Serialization;
+
+namespace Microsoft.AspNetCore.Razor.LanguageServer;
+
+///
+/// Corresponds to https://devdiv.visualstudio.com/DevDiv/_git/VSLanguageServerClient?path=/src/product/Protocol/LanguageServer.Protocol.Internal/VSInternalInlineCompletionTriggerKind.cs
+///
+[DataContract]
+internal enum InlineCompletionTriggerKind
+{
+ Automatic = 0,
+
+ Explicit = 1,
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/RazorInlineCompletionRequest.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/RazorInlineCompletionRequest.cs
new file mode 100644
index 00000000000..cdca37f5711
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/RazorInlineCompletionRequest.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.
+
+#nullable disable
+
+using System.Runtime.Serialization;
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNetCore.Razor.LanguageServer;
+
+internal class RazorInlineCompletionRequest : InlineCompletionRequest
+{
+ [DataMember(Name = "razorLanguageKind")]
+ [JsonProperty(Required = Required.Always)]
+ public RazorLanguageKind Kind { get; set; }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/SelectedCompletionInfo.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/SelectedCompletionInfo.cs
new file mode 100644
index 00000000000..08913cf69d3
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/SelectedCompletionInfo.cs
@@ -0,0 +1,32 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the MIT license. See License.txt in the project root for license information.
+
+#nullable disable
+
+using System.Runtime.Serialization;
+using Newtonsoft.Json;
+using OmniSharp.Extensions.LanguageServer.Protocol.Models;
+
+namespace Microsoft.AspNetCore.Razor.LanguageServer;
+
+///
+/// Corresponds to https://devdiv.visualstudio.com/DevDiv/_git/VSLanguageServerClient?path=/src/product/Protocol/LanguageServer.Protocol.Internal/VSInternalSelectedCompletionInfo.cs
+///
+internal class SelectedCompletionInfo
+{
+ [DataMember(Name = "_vs_range")]
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
+ public Range Range { get; set; }
+
+ [DataMember(Name = "_vs_text")]
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
+ public string Text { get; set; }
+
+ [DataMember(Name = "_vs_completionKind")]
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
+ public CompletionItemKind CompletionKind { get; set; }
+
+ [DataMember(Name = "_vs_isSnippetText")]
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
+ public bool IsSnippetText { get; set; }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs
index 06a0817da18..85f1da51d6a 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs
@@ -135,6 +135,7 @@ public static Task CreateAsync(Stream input, Stream output,
.WithHandler()
.WithHandler()
.WithHandler()
+ .WithHandler()
.WithHandler()
.WithServices(services =>
{
diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/DefaultRazorLanguageServerCustomMessageTarget.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/DefaultRazorLanguageServerCustomMessageTarget.cs
index 79789934888..291dde802dd 100644
--- a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/DefaultRazorLanguageServerCustomMessageTarget.cs
+++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/DefaultRazorLanguageServerCustomMessageTarget.cs
@@ -9,6 +9,7 @@
using System.Net;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.AspNetCore.Razor.LanguageServer;
using Microsoft.AspNetCore.Razor.LanguageServer.Semantic;
using Microsoft.AspNetCore.Razor.LanguageServer.Semantic.Models;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
@@ -20,6 +21,7 @@
using Microsoft.VisualStudio.LanguageServerClient.Razor.WrapWithTag;
using Microsoft.VisualStudio.Threading;
using Newtonsoft.Json.Linq;
+using OmniSharp.Extensions.LanguageServer.Protocol;
using OmniSharpConfigurationParams = OmniSharp.Extensions.LanguageServer.Protocol.Models.ConfigurationParams;
using SemanticTokensRangeParams = OmniSharp.Extensions.LanguageServer.Protocol.Models.SemanticTokensRangeParams;
using Task = System.Threading.Tasks.Task;
@@ -523,5 +525,41 @@ public override async Task RazorWrapWithTagAsync(
return response;
}
+
+ public override async Task ProvideInlineCompletionAsync(RazorInlineCompletionRequest inlineCompletionParams, CancellationToken cancellationToken)
+ {
+ if (inlineCompletionParams is null)
+ {
+ throw new ArgumentNullException(nameof(inlineCompletionParams));
+ }
+
+ var hostDocumentUri = inlineCompletionParams.TextDocument.Uri.ToUri();
+ if (!_documentManager.TryGetDocument(hostDocumentUri, out var documentSnapshot))
+ {
+ return null;
+ }
+
+ if (!documentSnapshot.TryGetVirtualDocument(out var csharpDoc))
+ {
+ return null;
+ }
+
+ var csharpRequest = new InlineCompletionRequest
+ {
+ Context = inlineCompletionParams.Context,
+ Position = inlineCompletionParams.Position,
+ TextDocument = DocumentUri.From(csharpDoc.Uri),
+ };
+
+ var textBuffer = csharpDoc.Snapshot.TextBuffer;
+ var request = await _requestInvoker.ReinvokeRequestOnServerAsync(
+ textBuffer,
+ VSInternalMethods.TextDocumentInlineCompletionName,
+ RazorLSPConstants.RazorCSharpLanguageServerName,
+ csharpRequest,
+ cancellationToken).ConfigureAwait(false);
+
+ return request?.Response;
+ }
}
}
diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/HtmlCSharp/CompletionHandler.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/HtmlCSharp/CompletionHandler.cs
index 91f1229b0ef..383424ca955 100644
--- a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/HtmlCSharp/CompletionHandler.cs
+++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/HtmlCSharp/CompletionHandler.cs
@@ -32,11 +32,6 @@ internal class CompletionHandler : IRequestHandler s_keywords = new string[] {
- "for", "foreach", "while", "switch", "lock",
- "case", "if", "try", "do", "using"
- };
-
private static readonly IReadOnlyCollection s_designTimeHelpers = new string[]
{
"__builder",
@@ -49,7 +44,6 @@ internal class CompletionHandler : IRequestHandler s_keywordCompletionItems = GenerateCompletionItems(s_keywords);
private static readonly IReadOnlyCollection s_designTimeHelpersCompletionItems = GenerateCompletionItems(s_designTimeHelpers);
private readonly JoinableTaskFactory _joinableTaskFactory;
@@ -159,17 +153,6 @@ public CompletionHandler(
cancellationToken).ConfigureAwait(false);
if (projectionResult is null)
{
- if (IsRazorCompilerBugWithCSharpKeywords(request, wordExtent.Value))
- {
- var csharpPolyfilledCompletionList = new CompletionList()
- {
- Items = Array.Empty(),
- IsIncomplete = true,
- };
- csharpPolyfilledCompletionList = IncludeCSharpKeywords(csharpPolyfilledCompletionList);
- return csharpPolyfilledCompletionList;
- }
-
return null;
}
@@ -285,62 +268,6 @@ static bool TryConvertToCompletionList(SumType
}
}
- // Internal for testing
- internal static bool IsRazorCompilerBugWithCSharpKeywords(CompletionParams request, TextExtent wordExtent)
- {
- // This was originally found when users would attempt to type out `@using` in an _Imports.razor file and get 0 completion items at the `g` of `using`.
- // After lots of investigation it turns out that the Razor compiler will generate 0 C# source for an incomplete using directive. This in turn results
- // in 0 C# information at `@using|`. This is tracked here: https://github.com/dotnet/aspnetcore/issues/37568
- //
- // The entire purpose of this method is to encapsulate this compiler bug and try and make users experiences a little better in a low-risk fashion.
- return request.Context!.TriggerKind == CompletionTriggerKind.TriggerForIncompleteCompletions &&
- WordSpanMatchesCSharpPolyfills(wordExtent);
- }
-
- private static bool WordSpanMatchesCSharpPolyfills(TextExtent? wordExtent)
- {
- if (wordExtent is null || !wordExtent.Value.IsSignificant)
- {
- return false;
- }
-
- var wordSpan = wordExtent.Value.Span;
-
- foreach (var keyword in s_keywords)
- {
- if (wordSpan.Length != keyword.Length)
- {
- // Word can't match, different length
- continue;
- }
-
- var allCharactersMatch = true;
- for (var j = 0; j < keyword.Length; j++)
- {
- var wordSpanIndex = wordSpan.Start.Position + j;
- if (wordSpanIndex >= wordSpan.Snapshot.Length)
- {
- // Don't think this is technically possible but being extra cautious to stay low-risk.
- break;
- }
-
- var wordCharacter = wordSpan.Snapshot[wordSpanIndex];
- if (keyword[j] != wordCharacter)
- {
- allCharactersMatch = false;
- break;
- }
- }
-
- if (allCharactersMatch)
- {
- return true;
- }
- }
-
- return false;
- }
-
private bool TryGetWordExtent(CompletionParams request, LSPDocumentSnapshot documentSnapshot, [NotNullWhen(true)] out TextExtent? wordExtent)
{
var wordCharacterPosition = request.Position.Character;
@@ -390,7 +317,6 @@ private CompletionList PostProcessCSharpCompletionList(
if (IsSimpleImplicitExpression(request, documentSnapshot, wordExtent))
{
completionList = RemovePreselection(completionList);
- completionList = IncludeCSharpKeywords(completionList);
// -1 is to account for the transition so base indentation is "|@if" instead of "@|if"
var baseIndentation = Math.Max(GetBaseIndentation(wordExtent, formattingOptions) - 1, 0);
@@ -736,17 +662,6 @@ private static CompletionList RemovePreselection(CompletionList completionList)
return completionList;
}
- // C# keywords were previously provided by snippets, but as of now C# LSP doesn't provide snippets.
- // We're providing these for now to improve the user experience (not having to ESC out of completions to finish),
- // but once C# starts providing them their completion will be offered instead, at which point we should be able to remove this step.
- private static CompletionList IncludeCSharpKeywords(CompletionList completionList)
- {
- var newList = completionList.Items.Union(s_keywordCompletionItems, CompletionItemComparer.Instance);
- completionList.Items = newList.ToArray();
-
- return completionList;
- }
-
// The TextEdit positions returned to us from the C#/HTML language servers are positions correlating to the virtual document.
// We need to translate these positions to apply to the Razor document instead. Performance is a big concern here, so we want to
// make the logic as simple as possible, i.e. no asynchronous calls.
diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/RazorLanguageServerCustomMessageTarget.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/RazorLanguageServerCustomMessageTarget.cs
index 7e4fb7bf1ec..32a7ee15646 100644
--- a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/RazorLanguageServerCustomMessageTarget.cs
+++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/RazorLanguageServerCustomMessageTarget.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.AspNetCore.Razor.LanguageServer;
using Microsoft.AspNetCore.Razor.LanguageServer.Common;
using Microsoft.AspNetCore.Razor.LanguageServer.Semantic;
using Microsoft.AspNetCore.Razor.LanguageServer.Semantic.Models;
@@ -57,5 +58,9 @@ internal abstract class RazorLanguageServerCustomMessageTarget
// Called by Visual Studio to wrap the current selection with a tag
[JsonRpcMethod(LanguageServerConstants.RazorWrapWithTagEndpoint, UseSingleObjectParameterDeserialization = true)]
public abstract Task RazorWrapWithTagAsync(VSInternalWrapWithTagParams wrapWithParams, CancellationToken cancellationToken);
+
+ // Called by the Razor Language Server to provide inline completions from the platform.
+ [JsonRpcMethod(LanguageServerConstants.RazorInlineCompletionEndpoint, UseSingleObjectParameterDeserialization = true)]
+ public abstract Task ProvideInlineCompletionAsync(RazorInlineCompletionRequest inlineCompletionParams, CancellationToken cancellationToken);
}
}
diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServerClient.Razor.Test/HtmlCSharp/CompletionHandlerTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServerClient.Razor.Test/HtmlCSharp/CompletionHandlerTest.cs
index cf0f43969de..7f522223a06 100644
--- a/src/Razor/test/Microsoft.VisualStudio.LanguageServerClient.Razor.Test/HtmlCSharp/CompletionHandlerTest.cs
+++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServerClient.Razor.Test/HtmlCSharp/CompletionHandlerTest.cs
@@ -58,94 +58,6 @@ public CompletionHandlerTest()
private readonly string _languageClient = "languageClient";
- [Theory]
- [WorkItem("https://github.com/dotnet/razor-tooling/issues/5606")]
- [WorkItem("https://github.com/dotnet/aspnetcore/issues/37568")]
- [InlineData("if")]
- [InlineData("using")]
- [InlineData("foreach")]
- [InlineData("try")]
- public void IsRazorCompilerBugWithCSharpKeywords_ReturnsTrue(string keyword)
- {
- // Arrange & Act
- var result = RunTest_IsRazorCompilerBugWithCSharpKeywords($"@|{keyword}|");
-
- // Assert
- Assert.True(result);
- }
-
- [Fact]
- [WorkItem("https://github.com/dotnet/razor-tooling/issues/5606")]
- [WorkItem("https://github.com/dotnet/aspnetcore/issues/37568")]
- public void IsRazorCompilerBugWithCSharpKeywords_Invoked_ReturnsFalse()
- {
- // Arrange & Act
- var result = RunTest_IsRazorCompilerBugWithCSharpKeywords("@|using| SomeNamespace.Foo", CompletionTriggerKind.Invoked);
-
- // Assert
- Assert.False(result);
- }
-
- [Fact]
- [WorkItem("https://github.com/dotnet/razor-tooling/issues/5606")]
- [WorkItem("https://github.com/dotnet/aspnetcore/issues/37568")]
- public void IsRazorCompilerBugWithCSharpKeywords_MoreThanKeyword_ReturnsFalse()
- {
- // Arrange & Act
- var result = RunTest_IsRazorCompilerBugWithCSharpKeywords("@|usingMore|");
-
- // Assert
- Assert.False(result);
- }
-
- [Fact]
- [WorkItem("https://github.com/dotnet/razor-tooling/issues/5606")]
- [WorkItem("https://github.com/dotnet/aspnetcore/issues/37568")]
- public void IsRazorCompilerBugWithCSharpKeywords_Other_ReturnsFalse()
- {
- // Arrange & Act
- var result = RunTest_IsRazorCompilerBugWithCSharpKeywords("@|unrelated|");
-
- // Assert
- Assert.False(result);
- }
-
- [Fact]
- [WorkItem("https://github.com/dotnet/razor-tooling/issues/5606")]
- [WorkItem("https://github.com/dotnet/aspnetcore/issues/37568")]
- public void IsRazorCompilerBugWithCSharpKeywords_NonSignificant_ReturnsFalse()
- {
- // Arrange & Act
- var result = RunTest_IsRazorCompilerBugWithCSharpKeywords("@using | |");
-
- // Assert
- Assert.False(result);
- }
-
- [Fact]
- [WorkItem("https://github.com/dotnet/razor-tooling/issues/5606")]
- [WorkItem("https://github.com/dotnet/aspnetcore/issues/37568")]
- public void IsRazorCompilerBugWithCSharpKeywords_Empty_ReturnsFalse()
- {
- // Arrange & Act
- var result = RunTest_IsRazorCompilerBugWithCSharpKeywords("@usin||g");
-
- // Assert
- Assert.False(result);
- }
-
- [Fact]
- [WorkItem("https://github.com/dotnet/razor-tooling/issues/5606")]
- [WorkItem("https://github.com/dotnet/aspnetcore/issues/37568")]
- public void IsRazorCompilerBugWithCSharpKeywords_Subset_ReturnsFalse()
- {
- // Arrange & Act
- var result = RunTest_IsRazorCompilerBugWithCSharpKeywords("@u|sin|g");
-
- // Assert
- Assert.False(result);
- }
-
[Fact]
public async Task HandleRequestAsync_DocumentNotFound_ReturnsNull()
{
@@ -193,48 +105,6 @@ public async Task HandleRequestAsync_ProjectionNotFound_ReturnsNull()
Assert.Null(result);
}
- [Fact]
- [WorkItem("https://github.com/dotnet/razor-tooling/issues/5606")]
- [WorkItem("https://github.com/dotnet/aspnetcore/issues/37568")]
- public async Task HandleRequestAsync_ProjectionNotFound_IsCompilerBug_ReturnsPolyfills()
- {
- // Arrange
- var documentManager = new TestDocumentManager();
- var snapshot = new TestLSPDocumentSnapshot(new Uri("C:/path/file.razor"), version: 0, "@using");
- documentManager.AddDocument(Uri, snapshot);
- var wordExtent = GetWordExtent("@|using|");
- var navigator = BuildNavigatorSelector(wordExtent);
- var requestInvoker = Mock.Of(MockBehavior.Strict);
- var projectionProvider = new Mock(MockBehavior.Strict).Object;
- Mock.Get(projectionProvider).Setup(projectionProvider => projectionProvider.GetProjectionForCompletionAsync(It.IsAny(), It.IsAny(), CancellationToken.None))
- .Returns(Task.FromResult(null));
- var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker, documentManager, projectionProvider, navigator, CompletionRequestContextCache, FormattingOptionsProvider, LoggerProvider);
- var completionRequest = new CompletionParams()
- {
- TextDocument = new TextDocumentIdentifier() { Uri = Uri },
- Context = new CompletionContext() { TriggerKind = CompletionTriggerKind.TriggerForIncompleteCompletions },
- Position = new Position(0, 6)
- };
-
- // Act
- var result = await completionHandler.HandleRequestAsync(completionRequest, new ClientCapabilities(), CancellationToken.None).ConfigureAwait(false);
-
- // Assert
- var completionList = result.Value.Second;
- Assert.True(completionList.IsIncomplete);
- Assert.Collection(completionList.Items,
- item => Assert.Equal("for", item.Label),
- item => Assert.Equal("foreach", item.Label),
- item => Assert.Equal("while", item.Label),
- item => Assert.Equal("switch", item.Label),
- item => Assert.Equal("lock", item.Label),
- item => Assert.Equal("case", item.Label),
- item => Assert.Equal("if", item.Label),
- item => Assert.Equal("try", item.Label),
- item => Assert.Equal("do", item.Label),
- item => Assert.Equal("using", item.Label));
- }
-
[Fact]
public async Task HandleRequestAsync_HtmlProjection_InvokesHtmlLanguageServer()
{
@@ -615,234 +485,6 @@ public async Task HandleRequestAsync_CSharpProjection_ReturnsKeywordsFromRazor()
});
}
- [Fact]
- public async Task HandleRequestAsync_CSharpProjection_ReturnsKeywordsFromCSharp_Triggered()
- {
- // Arrange
- var called = false;
- var expectedItems = new CompletionItem[] {
- new CompletionItem() { InsertText = "DateTime", Label = "DateTime" },
- new CompletionItem() { InsertText = "FROMCSHARP", Label = "for" },
-
- };
-
- var completionRequest = new CompletionParams()
- {
- TextDocument = new TextDocumentIdentifier() { Uri = Uri },
- Context = new CompletionContext() { TriggerKind = CompletionTriggerKind.TriggerCharacter, TriggerCharacter = "@" },
- Position = new Position(0, 1)
- };
-
- var documentManager = new TestDocumentManager();
- documentManager.AddDocument(Uri, new TestLSPDocumentSnapshot(new Uri("C:/path/file.razor"), 0, CSharpVirtualDocumentSnapshot));
-
- var requestInvoker = new Mock(MockBehavior.Strict);
- requestInvoker
- .Setup(r => r.ReinvokeRequestOnServerAsync?>(TextBuffer, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))
- .Callback((textBuffer, method, clientName, completionParams, ct) =>
- {
- Assert.Equal(Methods.TextDocumentCompletionName, method);
- Assert.Equal(RazorLSPConstants.RazorCSharpLanguageServerName, clientName);
- called = true;
- })
- .Returns(Task.FromResult(new ReinvocationResponse?>(_languageClient, expectedItems)));
-
- var projectionResult = new ProjectionResult()
- {
- LanguageKind = RazorLanguageKind.CSharp,
- };
- var projectionProvider = new Mock(MockBehavior.Strict);
- projectionProvider.Setup(p => p.GetProjectionForCompletionAsync(It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.FromResult(projectionResult));
-
- var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object, TextStructureNavigatorSelectorService, CompletionRequestContextCache, FormattingOptionsProvider, LoggerProvider);
-
- // Act
- var result = await completionHandler.HandleRequestAsync(completionRequest, new ClientCapabilities(), CancellationToken.None).ConfigureAwait(false);
-
- // Assert
- Assert.True(called);
- Assert.True(result.HasValue);
- var _ = result.Value.Match>(
- array => throw new NotImplementedException(),
- list =>
- {
- Assert.Collection(list.Items,
- item => Assert.Equal("DateTime", item.InsertText),
- item =>
- {
- Assert.Equal("for", item.Label);
- Assert.Equal("FROMCSHARP", item.InsertText);
- },
- item => Assert.Equal("foreach", item.Label),
- item => Assert.Equal("while", item.Label),
- item => Assert.Equal("switch", item.Label),
- item => Assert.Equal("lock", item.Label),
- item => Assert.Equal("case", item.Label),
- item => Assert.Equal("if", item.Label),
- item => Assert.Equal("try", item.Label),
- item => Assert.Equal("do", item.Label),
- item => Assert.Equal("using", item.Label),
- item => Assert.Equal("for (...)", item.Label),
- item => Assert.Equal("foreach (...)", item.Label),
- item => Assert.Equal("if (...)", item.Label),
- item => Assert.Equal("prop", item.Label)
- );
-
- return list;
- });
- }
-
- [Fact]
- public async Task HandleRequestAsync_CSharpProjection_ReturnsKeywordsFromCSharp_Reinvoked()
- {
- // Arrange
- var called = false;
- var expectedItems = new CompletionItem[] {
- new CompletionItem() { InsertText = "DateTime", Label = "DateTime" },
- new CompletionItem() { InsertText = "FROMCSHARP", Label = "for" },
- };
-
- var completionRequest = new CompletionParams()
- {
- TextDocument = new TextDocumentIdentifier() { Uri = Uri },
- Context = new CompletionContext() { TriggerKind = CompletionTriggerKind.Invoked },
- Position = new Position(0, 1)
- };
-
- var documentSnapshot = new TestLSPDocumentSnapshot(new Uri("C:/path/file.razor"), 0, snapshotContent: "@Da", CSharpVirtualDocumentSnapshot);
- var documentManager = new TestDocumentManager();
- documentManager.AddDocument(Uri, documentSnapshot);
-
- var wordSnapshotSpan = new SnapshotSpan(documentSnapshot.Snapshot, new Span(1, 2));
- var wordRange = new TextExtent(wordSnapshotSpan, isSignificant: true);
- var navigatorSelector = BuildNavigatorSelector(wordRange);
- var requestInvoker = new Mock(MockBehavior.Strict);
- requestInvoker
- .Setup(r => r.ReinvokeRequestOnServerAsync?>(TextBuffer, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))
- .Callback((textBuffer, method, clientName, completionParams, ct) =>
- {
- Assert.Equal(Methods.TextDocumentCompletionName, method);
- Assert.Equal(RazorLSPConstants.RazorCSharpLanguageServerName, clientName);
- called = true;
- })
- .Returns(Task.FromResult(new ReinvocationResponse?>(_languageClient, expectedItems)));
-
- var projectionResult = new ProjectionResult()
- {
- LanguageKind = RazorLanguageKind.CSharp,
- };
- var projectionProvider = new Mock(MockBehavior.Strict);
- projectionProvider.Setup(p => p.GetProjectionForCompletionAsync(It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.FromResult(projectionResult));
-
- var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object, navigatorSelector, CompletionRequestContextCache, FormattingOptionsProvider, LoggerProvider);
-
- // Act
- var result = await completionHandler.HandleRequestAsync(completionRequest, new ClientCapabilities(), CancellationToken.None).ConfigureAwait(false);
-
- // Assert
- Assert.True(called);
- Assert.True(result.HasValue);
- var _ = result.Value.Match>(
- array => throw new NotImplementedException(),
- list =>
- {
- Assert.Collection(list.Items,
- item => Assert.Equal("DateTime", item.InsertText),
- item =>
- {
- Assert.Equal("for", item.Label);
- Assert.Equal("FROMCSHARP", item.InsertText);
- },
- item => Assert.Equal("foreach", item.Label),
- item => Assert.Equal("while", item.Label),
- item => Assert.Equal("switch", item.Label),
- item => Assert.Equal("lock", item.Label),
- item => Assert.Equal("case", item.Label),
- item => Assert.Equal("if", item.Label),
- item => Assert.Equal("try", item.Label),
- item => Assert.Equal("do", item.Label),
- item => Assert.Equal("using", item.Label),
- item => Assert.Equal("for (...)", item.Label),
- item => Assert.Equal("foreach (...)", item.Label),
- item => Assert.Equal("if (...)", item.Label),
- item => Assert.Equal("prop", item.Label)
- ); ;
-
- return list;
- });
- }
-
- [Fact]
- public async Task HandleRequestAsync_CSharpProjection_ReturnsKeywordsFromCSharp_Reinvoked_UsingStatement()
- {
- // Arrange
- var called = false;
-
- var completionRequest = new CompletionParams()
- {
- TextDocument = new TextDocumentIdentifier() { Uri = Uri },
- Context = new CompletionContext() { TriggerKind = CompletionTriggerKind.Invoked },
- Position = new Position(0, 3)
- };
-
- var documentSnapshot = new TestLSPDocumentSnapshot(new Uri("C:/path/file.razor"), 0, snapshotContent: "@us", CSharpVirtualDocumentSnapshot);
- var documentManager = new TestDocumentManager();
- documentManager.AddDocument(Uri, documentSnapshot);
-
- var wordSnapshotSpan = new SnapshotSpan(documentSnapshot.Snapshot, new Span(1, 2));
- var wordRange = new TextExtent(wordSnapshotSpan, isSignificant: true);
- var navigatorSelector = BuildNavigatorSelector(wordRange);
- var requestInvoker = new Mock(MockBehavior.Strict);
- requestInvoker
- .Setup(r => r.ReinvokeRequestOnServerAsync?>(TextBuffer, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))
- .Callback((textBuffer, method, clientName, completionParams, ct) =>
- {
- Assert.Equal(Methods.TextDocumentCompletionName, method);
- Assert.Equal(RazorLSPConstants.RazorCSharpLanguageServerName, clientName);
- called = true;
- })
- .Returns(Task.FromResult(new ReinvocationResponse?>(_languageClient, Array.Empty())));
-
- var projectionResult = new ProjectionResult()
- {
- LanguageKind = RazorLanguageKind.CSharp,
- };
- var projectionProvider = new Mock(MockBehavior.Strict);
- projectionProvider.Setup(p => p.GetProjectionForCompletionAsync(It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.FromResult(projectionResult));
-
- var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object, navigatorSelector, CompletionRequestContextCache, FormattingOptionsProvider, LoggerProvider);
-
- // Act
- var result = await completionHandler.HandleRequestAsync(completionRequest, new ClientCapabilities(), CancellationToken.None).ConfigureAwait(false);
-
- // Assert
- Assert.True(called);
- Assert.True(result.HasValue);
- _ = result.Value.Match>(
- array => throw new NotImplementedException(),
- list =>
- {
- Assert.Collection(list.Items,
- item => Assert.Equal("for", item.Label),
- item => Assert.Equal("foreach", item.Label),
- item => Assert.Equal("while", item.Label),
- item => Assert.Equal("switch", item.Label),
- item => Assert.Equal("lock", item.Label),
- item => Assert.Equal("case", item.Label),
- item => Assert.Equal("if", item.Label),
- item => Assert.Equal("try", item.Label),
- item => Assert.Equal("do", item.Label),
- item => Assert.Equal("using", item.Label),
- item => Assert.Equal("for (...)", item.Label),
- item => Assert.Equal("foreach (...)", item.Label),
- item => Assert.Equal("if (...)", item.Label),
- item => Assert.Equal("prop", item.Label)
- );
-
- return list;
- });
- }
-
[Fact]
public async Task HandleRequestAsync_HtmlProjection_IncompatibleTriggerCharacter_ReturnsNull()
{
@@ -1122,16 +764,6 @@ public async Task HandleRequestAsync_CSharpProjection_RemoveAllDesignTimeHelpers
{
Assert.Collection(list.Items,
item => Assert.Equal("DateTime", item.InsertText),
- item => Assert.Equal("for", item.Label),
- item => Assert.Equal("foreach", item.Label),
- item => Assert.Equal("while", item.Label),
- item => Assert.Equal("switch", item.Label),
- item => Assert.Equal("lock", item.Label),
- item => Assert.Equal("case", item.Label),
- item => Assert.Equal("if", item.Label),
- item => Assert.Equal("try", item.Label),
- item => Assert.Equal("do", item.Label),
- item => Assert.Equal("using", item.Label),
item => Assert.Equal("for (...)", item.Label),
item => Assert.Equal("foreach (...)", item.Label),
item => Assert.Equal("if (...)", item.Label),
@@ -1944,20 +1576,6 @@ private static ITextStructureNavigatorSelectorService BuildNavigatorSelector(Tex
return navigatorSelector.Object;
}
- private static bool RunTest_IsRazorCompilerBugWithCSharpKeywords(string input, CompletionTriggerKind? triggerKind = null)
- {
- var request = new CompletionParams()
- {
- Context = new CompletionContext()
- {
- TriggerKind = triggerKind ?? CompletionTriggerKind.TriggerForIncompleteCompletions,
- }
- };
- var wordExtent = GetWordExtent(input);
- var result = CompletionHandler.IsRazorCompilerBugWithCSharpKeywords(request, wordExtent);
- return result;
- }
-
private static TextExtent GetWordExtent(string input)
{
var wordStart = input.IndexOf("|");