diff --git a/Directory.Packages.props b/Directory.Packages.props index d1ad925b..0d4d315a 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -43,8 +43,10 @@ + + diff --git a/MSBuildLanguageServer/Import/MSBuildLanguageServer.cs b/MSBuildLanguageServer/Import/MSBuildLanguageServer.cs index 1a11c8d8..d9ce7cf8 100644 --- a/MSBuildLanguageServer/Import/MSBuildLanguageServer.cs +++ b/MSBuildLanguageServer/Import/MSBuildLanguageServer.cs @@ -1,7 +1,7 @@ // modified copy of -// https://github.com/dotnet/roslyn/blob/c5b98ae61ab894230786db2cde711ce4525a2597/src/Features/LanguageServer/Protocol/RoslynLanguageServer.cs +// https://github.com/dotnet/roslyn/blob/b292a51d13e020e350366ac305706367cd9db5c2/src/Features/LanguageServer/Protocol/RoslynLanguageServer.cs // changes annotated inline with // MODIFICATION -// + // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -9,21 +9,20 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CodeAnalysis.LanguageServer.Handler.ServerLifetime; using Microsoft.CommonLanguageServerProtocol.Framework; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Roslyn.LanguageServer.Protocol; using Roslyn.Utilities; using StreamJsonRpc; namespace Microsoft.CodeAnalysis.LanguageServer { - internal sealed class MSBuildLanguageServer : AbstractLanguageServer, IOnInitialized + internal sealed class MSBuildLanguageServer : SystemTextJsonLanguageServer, IOnInitialized { private readonly AbstractLspServiceProvider _lspServiceProvider; private readonly ImmutableDictionary>> _baseServices; @@ -32,19 +31,17 @@ internal sealed class MSBuildLanguageServer : AbstractLanguageServer supportedLanguages, WellKnownLspServerKinds serverKind) - : base(jsonRpc, serializer, logger) + : base(jsonRpc, serializerOptions, logger) { _lspServiceProvider = lspServiceProvider; _serverKind = serverKind; - VSCodeInternalExtensionUtilities.AddVSCodeInternalExtensionConverters(serializer); - // Create services that require base dependencies (jsonrpc) or are more complex to create to the set manually. _baseServices = GetBaseServices(jsonRpc, logger, capabilitiesProvider, hostServices, serverKind, supportedLanguages); @@ -52,6 +49,13 @@ public MSBuildLanguageServer( Initialize(); } + public static SystemTextJsonFormatter CreateJsonMessageFormatter() + { + var messageFormatter = new SystemTextJsonFormatter(); + messageFormatter.JsonSerializerOptions.AddLspSerializerOptions(); + return messageFormatter; + } + protected override ILspServices ConstructLspServices() { return _lspServiceProvider.CreateServices(_serverKind, _baseServices); @@ -89,9 +93,9 @@ private ImmutableDictionary>> Ge AddBaseService(new InitializedHandler()); AddBaseService(this); - //MODIFICATION 1 + // MODIFICATION 1 /* - AddBaseService(new LanguageInfoProvider()); + AddBaseService(new LanguageInfoProvider()); // In all VS cases, we already have a misc workspace. Specifically // Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.MiscellaneousFilesWorkspace. In @@ -102,7 +106,7 @@ private ImmutableDictionary>> Ge */ // END MODIFICATION 1 - return baseServices.ToImmutableDictionary(); + return baseServices.ToImmutableDictionary(); void AddBaseService(T instance) where T : class { @@ -122,9 +126,9 @@ public Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestCon return Task.CompletedTask; } - // MODIFICATION 3 + // MODIFICATION 2 /* - protected override string GetLanguageForRequest(string methodName, JToken? parameters) + protected override string GetLanguageForRequest(string methodName, JsonElement? parameters) { if (parameters == null) { @@ -145,12 +149,11 @@ protected override string GetLanguageForRequest(string methodName, JToken? param // { "textDocument": { "uri": "" ... } ... } // // We can easily identify the URI for the request by looking for this structure - var textDocumentToken = parameters["textDocument"] ?? parameters["_vs_textDocument"]; - if (textDocumentToken is not null) + if (parameters.Value.TryGetProperty("textDocument", out var textDocumentToken) || + parameters.Value.TryGetProperty("_vs_textDocument", out textDocumentToken)) { - var uriToken = textDocumentToken["uri"]; - Contract.ThrowIfNull(uriToken, "textDocument does not have a uri property"); - var uri = uriToken.ToObject(_jsonSerializer); + var uriToken = textDocumentToken.GetProperty("uri"); + var uri = JsonSerializer.Deserialize(uriToken, ProtocolConversions.LspJsonSerializerOptions); Contract.ThrowIfNull(uri, "Failed to deserialize uri property"); var language = lspWorkspaceManager.GetLanguageForUri(uri); Logger.LogInformation($"Using {language} from request text document"); @@ -161,10 +164,10 @@ protected override string GetLanguageForRequest(string methodName, JToken? param // { "data": { "TextDocument": { "uri": "" ... } ... } ... } // // We can deserialize the data object using our unified DocumentResolveData. - var dataToken = parameters["data"]; - if (dataToken is not null) + //var dataToken = parameters["data"]; + if (parameters.Value.TryGetProperty("data", out var dataToken)) { - var data = dataToken.ToObject(_jsonSerializer); + var data = JsonSerializer.Deserialize(dataToken, ProtocolConversions.LspJsonSerializerOptions); Contract.ThrowIfNull(data, "Failed to document resolve data object"); var language = lspWorkspaceManager.GetLanguageForUri(data.TextDocument.Uri); Logger.LogInformation($"Using {language} from data text document"); @@ -176,7 +179,8 @@ protected override string GetLanguageForRequest(string methodName, JToken? param return LanguageServerConstants.DefaultLanguageName; static bool ShouldUseDefaultLanguage(string methodName) - => methodName switch + { + return methodName switch { Methods.InitializeName => true, Methods.InitializedName => true, @@ -188,7 +192,9 @@ static bool ShouldUseDefaultLanguage(string methodName) Methods.ExitName => true, _ => false, }; + } } */ + // END MODIFICATION 2 } } \ No newline at end of file diff --git a/MSBuildLanguageServer/Import/ProtocolConversions.cs b/MSBuildLanguageServer/Import/ProtocolConversions.cs index 211dae38..993c03c7 100644 --- a/MSBuildLanguageServer/Import/ProtocolConversions.cs +++ b/MSBuildLanguageServer/Import/ProtocolConversions.cs @@ -1,5 +1,6 @@ // modified copy of -// https://raw.githubusercontent.com/dotnet/roslyn/fca6e1fcdcded85e69cc32e15acfb6820cd45597/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs// changes annotated inline with // MODIFICATION +// https://raw.githubusercontent.com/dotnet/roslyn/044acb4ec888bf080b707e3db6818107e018d80b/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs +// changes annotated inline with // MODIFICATION // with portions commented out using /* */ comments // Licensed to the .NET Foundation under one or more agreements. @@ -12,10 +13,11 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Linq; +using System.Text.Json; using System.Text.RegularExpressions; -/* using System.Threading; using System.Threading.Tasks; +/* using Microsoft.CodeAnalysis.DocumentHighlighting; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Formatting; @@ -111,7 +113,23 @@ internal static partial class ProtocolConversions { { WellKnownTags.Deprecated, ImmutableArray.Create(LSP.CompletionItemTag.Deprecated) }, }.ToImmutableDictionary(); + */ + + public static JsonSerializerOptions AddLspSerializerOptions(this JsonSerializerOptions options) + { + LSP.VSInternalExtensionUtilities.AddVSInternalExtensionConverters(options); + options.Converters.Add(new NaturalObjectConverter()); + return options; + } + /// + /// Options that know how to serialize / deserialize basic LSP types. + /// Useful when there are particular fields that are not serialized or deserialized by normal request handling (for example + /// deserializing a field that is typed as object instead of a concrete type). + /// + public static JsonSerializerOptions LspJsonSerializerOptions = new JsonSerializerOptions().AddLspSerializerOptions(); + + /* // TO-DO: More LSP.CompletionTriggerKind mappings are required to properly map to Roslyn CompletionTriggerKinds. // https://dev.azure.com/devdiv/DevDiv/_workitems/edit/1178726 public static async Task LSPToRoslynCompletionTriggerAsync( @@ -176,8 +194,9 @@ static async Task GetInsertionCharacterAsync(Document document, int positi return triggerCharacter; } } -*/ - public static string GetDocumentFilePathFromUri(Uri uri) + */ + + public static string GetDocumentFilePathFromUri(Uri uri) => uri.IsFile ? uri.LocalPath : uri.AbsoluteUri; /// @@ -207,9 +226,9 @@ public static Uri CreateAbsoluteUri(string absolutePath) internal static Uri CreateRelativePatternBaseUri(string path) { - // According to VSCode LSP RelativePattern spec, + // According to VSCode LSP RelativePattern spec, // found at https://github.com/microsoft/vscode/blob/9e1974682eb84eebb073d4ae775bad1738c281f6/src/vscode-dts/vscode.d.ts#L2226 - // the baseUri should not end in a trailing separator, nor should it + // the baseUri should not end in a trailing separator, nor should it // have any relative segmeents (., ..) if (path[^1] == System.IO.Path.DirectorySeparatorChar) { @@ -289,7 +308,8 @@ private static bool IsAscii(string filePath) return true; } -/* + + /* public static LSP.TextDocumentPositionParams PositionToTextDocumentPositionParams(int position, SourceText text, Document document) { return new LSP.TextDocumentPositionParams() @@ -535,10 +555,12 @@ static LSP.Location ConvertTextSpanWithTextToLocation(TextSpan span, SourceText return location; } } -*/ + */ + public static LSP.CodeDescription? HelpLinkToCodeDescription(Uri? uri) => (uri != null) ? new LSP.CodeDescription { Href = uri } : null; -/* + /* + public static LSP.SymbolKind NavigateToKindToSymbolKind(string kind) { if (Enum.TryParse(kind, out var symbolKind)) @@ -958,7 +980,7 @@ static string GetStyledText(TaggedText taggedText, bool isInCodeBlock) if (!string.IsNullOrEmpty(taggedText.NavigationHint) && taggedText.NavigationHint == taggedText.NavigationTarget) return $"[{text}]({taggedText.NavigationHint})"; - // Markdown ignores spaces at the start of lines outside of code blocks, + // Markdown ignores spaces at the start of lines outside of code blocks, // so we replace regular spaces with non-breaking spaces to ensure structural space is retained. // We want to use regular spaces everywhere else to allow the client to wrap long text. if (!isCode && taggedText.Tag is TextTags.Space or TextTags.ContainerStart) diff --git a/MSBuildLanguageServer/MSBuildLanguageServer.csproj b/MSBuildLanguageServer/MSBuildLanguageServer.csproj index b1c48c00..f8ca539a 100644 --- a/MSBuildLanguageServer/MSBuildLanguageServer.csproj +++ b/MSBuildLanguageServer/MSBuildLanguageServer.csproj @@ -95,6 +95,7 @@ + @@ -109,6 +110,13 @@ GenerateSource="true" ClassName="Microsoft.CodeAnalysis.CodeAnalysisResources" /> + + + + + @@ -122,7 +130,8 @@ - + + @@ -132,7 +141,6 @@ StronglyTypedClassName="$([System.String]::Copy('%(ClassName)').Substring($([MSBuild]::Add($([System.String]::Copy('%(ClassName)').LastIndexOf('.')), 1))))" StronglyTypedNamespace="$([System.String]::Copy('%(ClassName)').Substring(0, $([System.String]::Copy('%(ClassName)').LastIndexOf('.'))))" Generator="MSBuild:Compile" - Link="sdk\resource\%(ClassName).resx" StronglyTypedFileName="$(IntermediateOutputPath)\%(ClassName).Designer.cs" LogicalName="%(ClassName).resources" /> diff --git a/MSBuildLanguageServer/MSBuildLanguageServerFactory.cs b/MSBuildLanguageServer/MSBuildLanguageServerFactory.cs index 3061faad..fcc0262d 100644 --- a/MSBuildLanguageServer/MSBuildLanguageServerFactory.cs +++ b/MSBuildLanguageServer/MSBuildLanguageServerFactory.cs @@ -7,7 +7,7 @@ using System.Composition; using Microsoft.CommonLanguageServerProtocol.Framework; using Microsoft.CodeAnalysis.Host; -using Newtonsoft.Json; +using System.Text.Json; [Export(typeof(ILanguageServerFactory)), Shared] [method: ImportingConstructor] @@ -17,7 +17,7 @@ class MSBuildLanguageServerFactory (CSharpVisualBasicLspServiceProvider lspServi public AbstractLanguageServer Create ( JsonRpc jsonRpc, - JsonSerializer jsonSerializer, + JsonSerializerOptions options, ICapabilitiesProvider capabilitiesProvider, WellKnownLspServerKinds serverKind, AbstractLspLogger logger, @@ -26,7 +26,7 @@ public AbstractLanguageServer Create ( return new Microsoft.CodeAnalysis.LanguageServer.MSBuildLanguageServer ( lspServiceProvider, jsonRpc, - jsonSerializer, + options, capabilitiesProvider, logger, hostServices, diff --git a/external/roslyn b/external/roslyn index 93d19c2e..044acb4e 160000 --- a/external/roslyn +++ b/external/roslyn @@ -1 +1 @@ -Subproject commit 93d19c2e18409d849860569be30128b2077f2ce8 +Subproject commit 044acb4ec888bf080b707e3db6818107e018d80b