提交 323844fc 编写于 作者: D David Barbet

address feedback

上级 1ab4589b
......@@ -60,6 +60,10 @@ internal static class ProtocolConversions
{ WellKnownTags.NuGet, LSP.CompletionItemKind.Text }
};
/// <summary>
/// Workaround for razor file paths being provided with a preceding slash on windows.
/// Long term fix in razor here - https://github.com/dotnet/aspnetcore/issues/19948
/// </summary>
public static Uri GetUriFromFilePath(string filePath)
{
if (filePath is null)
......
// 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.
#nullable enable
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.LanguageServer;
using Microsoft.VisualStudio.LanguageServer.Client;
using Microsoft.VisualStudio.Threading;
using Nerdbank.Streams;
using Roslyn.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService
{
internal abstract class AbstractLiveShareLanguageServerClient : ILanguageClient
{
private readonly IDiagnosticService _diagnosticService;
private readonly LanguageServerProtocol _languageServerProtocol;
private readonly Workspace _workspace;
private InProcLanguageServer? _languageServer;
/// <summary>
/// Gets the name of the language client (displayed to the user).
/// </summary>
public abstract string Name { get; }
/// <summary>
/// Unused, implementing <see cref="ILanguageClient"/>
/// No additional settings are provided for this server, so we do not need any configuration section names.
/// </summary>
public IEnumerable<string>? ConfigurationSections { get; } = null;
/// <summary>
/// Gets the initialization options object the client wants to send when 'initialize' message is sent.
/// See https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#initialize
/// We do not provide any additional initialization options.
/// </summary>
public object? InitializationOptions { get; } = null;
/// <summary>
/// Unused, implementing <see cref="ILanguageClient"/>
/// Files that we care about are already provided and watched by the workspace.
/// </summary>
public IEnumerable<string>? FilesToWatch { get; } = null;
public event AsyncEventHandler<EventArgs>? StartAsync;
/// <summary>
/// Unused, implementing <see cref="ILanguageClient"/>
/// </summary>
public event AsyncEventHandler<EventArgs>? StopAsync { add { } remove { } }
public AbstractLiveShareLanguageServerClient(LanguageServerProtocol languageServerProtocol, VisualStudioWorkspace workspace, IDiagnosticService diagnosticService)
{
_languageServerProtocol = languageServerProtocol;
_workspace = workspace;
_diagnosticService = diagnosticService;
}
public Task<Connection> ActivateAsync(CancellationToken token)
{
Contract.ThrowIfFalse(_languageServer == null, "This language server has already been initialized");
var (clientStream, serverStream) = FullDuplexStream.CreatePair();
_languageServer = new InProcLanguageServer(serverStream, serverStream, _languageServerProtocol, _workspace, _diagnosticService, clientName: null);
return Task.FromResult(new Connection(clientStream, clientStream));
}
/// <summary>
/// Signals that the extension has been loaded. The server can be started immediately, or wait for user action to start.
/// To start the server, invoke the <see cref="StartAsync"/> event;
/// </summary>
public async Task OnLoadedAsync()
{
await StartAsync.InvokeAsync(this, EventArgs.Empty).ConfigureAwait(false);
}
/// <summary>
/// Signals the extension that the language server has been successfully initialized.
/// </summary>
/// <returns>A <see cref="Task"/> which completes when actions that need to be performed when the server is ready are done.</returns>
public Task OnServerInitializedAsync()
{
// We don't have any tasks that need to be triggered after the server has successfully initialized.
return Task.CompletedTask;
}
/// <summary>
/// Signals the extension that the language server failed to initialize.
/// </summary>
/// <returns>A <see cref="Task"/> which completes when additional actions that need to be performed when the server fails to initialize are done.</returns>
public Task OnServerInitializeFailedAsync(Exception e)
{
// We don't need to provide additional exception handling here, liveshare already handles failure cases for this server.
return Task.CompletedTask;
}
}
}
......@@ -229,13 +229,11 @@ protected virtual async Task PublishDiagnosticsAsync(Document document)
{
var fileUriToDiagnostics = await GetDiagnosticsAsync(document, CancellationToken.None).ConfigureAwait(false);
var publishTasks = fileUriToDiagnostics.Keys.Select(async fileUri =>
foreach (var fileUri in fileUriToDiagnostics.Keys)
{
var publishDiagnosticsParams = new PublishDiagnosticParams { Diagnostics = fileUriToDiagnostics[fileUri], Uri = fileUri };
await _jsonRpc.NotifyWithParameterObjectAsync(Methods.TextDocumentPublishDiagnosticsName, publishDiagnosticsParams).ConfigureAwait(false);
});
await Task.WhenAll(publishTasks).ConfigureAwait(false);
}
}
private async Task<Dictionary<Uri, LanguageServer.Protocol.Diagnostic[]>> GetDiagnosticsAsync(Document document, CancellationToken cancellationToken)
......@@ -249,11 +247,13 @@ protected virtual async Task PublishDiagnosticsAsync(Document document)
// https://docs.microsoft.com/en-us/aspnet/core/mvc/views/layout?view=aspnetcore-3.1#importing-shared-directives
// https://docs.microsoft.com/en-us/aspnet/core/blazor/layouts?view=aspnetcore-3.1#centralized-layout-selection
// The imported files contents are added to the content of the generated C# file, so we get diagnostics
// for both the c# contents in the original razor document and for any of the content in any of the imported files when we query diagnostics for the generated C# file.
// These diagnostics will be reported with DiagnosticDataLocation.OriginalFilePath = generated C# file, and DiagnosticDataLocation.MappedFilePath = imported razor file.
// for both the c# contents in the original razor document and for any of the content in any of the imported
// files when we query diagnostics for the generated C# file.
// These diagnostics will be reported with DiagnosticDataLocation.OriginalFilePath = generated C# file and
// DiagnosticDataLocation.MappedFilePath = imported razor file.
// This means that in general we could have diagnostics produced by one generated file that map to many different actual razor files.
// We can't filter them out as we don't know which razor file(s) the underlying generated C# document actually maps to, and which are just imported.
// So we publish them all and let them get de-duped.
// We can't filter them out as we don't know which razor file(s) the underlying generated C# document actually maps to
// and which are just imported. So we publish them all and let them get de-duped.
var fileUriToDiagnostics = diagnostics.GroupBy(diagnostic => GetDiagnosticUri(document, diagnostic)).ToDictionary(
group => group.Key,
group => group.Select(diagnostic => ConvertToLspDiagnostic(diagnostic, text)).ToArray());
......@@ -261,7 +261,9 @@ protected virtual async Task PublishDiagnosticsAsync(Document document)
static Uri GetDiagnosticUri(Document document, DiagnosticData diagnosticData)
{
var filePath = diagnosticData.DataLocation?.MappedFilePath ?? document.FilePath;
Contract.ThrowIfNull(diagnosticData.DataLocation, "Diagnostic data location should not be null here");
var filePath = diagnosticData.DataLocation.MappedFilePath ?? diagnosticData.DataLocation.OriginalFilePath;
return ProtocolConversions.GetUriFromFilePath(filePath);
}
......@@ -274,7 +276,9 @@ static LanguageServer.Protocol.Diagnostic ConvertToLspDiagnostic(DiagnosticData
Severity = ProtocolConversions.DiagnosticSeverityToLspDiagnositcSeverity(diagnosticData.Severity),
Range = GetDiagnosticRange(diagnosticData.DataLocation, text),
// Only the unnecessary diagnostic tag is currently supported via LSP.
Tags = diagnosticData.CustomTags.Contains("Unnecessary") ? new DiagnosticTag[] { DiagnosticTag.Unnecessary } : Array.Empty<DiagnosticTag>()
Tags = diagnosticData.CustomTags.Contains(WellKnownDiagnosticTags.Unnecessary)
? new DiagnosticTag[] { DiagnosticTag.Unnecessary }
: Array.Empty<DiagnosticTag>()
};
}
}
......@@ -293,21 +297,8 @@ private bool IncludeDiagnostic(DiagnosticData diagnostic)
private static LanguageServer.Protocol.Range? GetDiagnosticRange(DiagnosticDataLocation? diagnosticDataLocation, SourceText text)
{
(var startLine, var endLine) = DiagnosticData.GetLinePositions(diagnosticDataLocation, text, useMapped: true);
return new LanguageServer.Protocol.Range
{
Start = new Position
{
Line = startLine.Line,
Character = startLine.Character,
},
End = new Position
{
Line = endLine.Line,
Character = endLine.Character
}
};
var linePositionSpan = DiagnosticData.GetLinePositionSpan(diagnosticDataLocation, text, useMapped: true);
return ProtocolConversions.LinePositionToRange(linePositionSpan);
}
}
}
......@@ -5,106 +5,28 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.LanguageServer;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.VisualStudio.LanguageServer.Client;
using Microsoft.VisualStudio.Threading;
using Microsoft.VisualStudio.Utilities;
using Nerdbank.Streams;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService
{
[ContentType(ContentTypeNames.CSharpLspContentTypeName)]
[ContentType(ContentTypeNames.VBLspContentTypeName)]
[Export(typeof(ILanguageClient))]
internal class LiveShareLanguageServerClient : ILanguageClient
internal class LiveShareLanguageServerClient : AbstractLiveShareLanguageServerClient
{
private readonly IDiagnosticService _diagnosticService;
private readonly LanguageServerProtocol _languageServerProtocol;
private readonly Workspace _workspace;
/// <summary>
/// Gets the name of the language client (displayed to the user).
/// </summary>
public string Name => ServicesVSResources.Live_Share_CSharp_Visual_Basic_Language_Server_Client;
/// <summary>
/// Unused, implementing <see cref="ILanguageClient"/>
/// No additional settings are provided for this server, so we do not need any configuration section names.
/// </summary>
public IEnumerable<string>? ConfigurationSections { get; } = null;
/// <summary>
/// Gets the initialization options object the client wants to send when 'initialize' message is sent.
/// See https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#initialize
/// We do not provide any additional initialization options.
/// </summary>
public object? InitializationOptions { get; } = null;
/// <summary>
/// Unused, implementing <see cref="ILanguageClient"/>
/// Files that we care about are already provided and watched by the workspace.
/// </summary>
public IEnumerable<string>? FilesToWatch { get; } = null;
public event AsyncEventHandler<EventArgs>? StartAsync;
/// <summary>
/// Unused, implementing <see cref="ILanguageClient"/>
/// </summary>
public event AsyncEventHandler<EventArgs>? StopAsync { add { } remove { } }
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
[Obsolete(MefConstruction.ImportingConstructorMessage, true)]
public LiveShareLanguageServerClient(LanguageServerProtocol languageServerProtocol, VisualStudioWorkspace workspace, IDiagnosticService diagnosticService)
: base(languageServerProtocol, workspace, diagnosticService)
{
_languageServerProtocol = languageServerProtocol;
_workspace = workspace;
_diagnosticService = diagnosticService;
}
public Task<Connection> ActivateAsync(CancellationToken token)
{
var (clientStream, serverStream) = FullDuplexStream.CreatePair();
_ = new InProcLanguageServer(serverStream, serverStream, _languageServerProtocol, _workspace, _diagnosticService, clientName: null);
return Task.FromResult(new Connection(clientStream, clientStream));
}
/// <summary>
/// Signals that the extension has been loaded. The server can be started immediately, or wait for user action to start.
/// To start the server, invoke the <see cref="StartAsync"/> event;
/// </summary>
public async Task OnLoadedAsync()
{
await StartAsync.InvokeAsync(this, EventArgs.Empty).ConfigureAwait(false);
}
/// <summary>
/// Signals the extension that the language server has been successfully initialized.
/// </summary>
/// <returns>A <see cref="Task"/> which completes when actions that need to be performed when the server is ready are done.</returns>
public Task OnServerInitializedAsync()
{
// We don't have any tasks that need to be triggered after the server has successfully initialized.
return Task.CompletedTask;
}
/// <summary>
/// Signals the extension that the language server failed to initialize.
/// </summary>
/// <returns>A <see cref="Task"/> which completes when additional actions that need to be performed when the server fails to initialize are done.</returns>
public Task OnServerInitializeFailedAsync(Exception e)
{
// We don't need to provide additional exception handling here, liveshare already handles failure cases for this server.
return Task.CompletedTask;
}
public override string Name => ServicesVSResources.Live_Share_CSharp_Visual_Basic_Language_Server_Client;
}
}
......@@ -4,20 +4,15 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.LanguageServer;
using Microsoft.VisualStudio.LanguageServer.Client;
using Microsoft.VisualStudio.LanguageServices;
using Microsoft.VisualStudio.Threading;
using Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService;
using Microsoft.VisualStudio.Utilities;
using Nerdbank.Streams;
namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Lsp
{
......@@ -26,93 +21,24 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Lsp
/// activate this outside of a liveshare session and publish diagnostics
/// only for razor cs files.
/// TODO - This can be removed once C# is using LSP for diagnostics.
/// https://github.com/dotnet/roslyn/issues/42630
/// </summary>
[ContentType(ContentTypeNames.CSharpLspContentTypeName)]
[ContentType(ContentTypeNames.VBLspContentTypeName)]
[ClientName(ClientName)]
[Export(typeof(ILanguageClient))]
class RazorLanguageClient : ILanguageClient
internal class RazorLanguageClient : AbstractLiveShareLanguageServerClient
{
private readonly IDiagnosticService _diagnosticService;
private readonly LanguageServerProtocol _languageServerProtocol;
private readonly Workspace _workspace;
public const string ClientName = "RazorCSharp";
/// <summary>
/// Gets the name of the language client (displayed to the user).
/// </summary>
public string Name => ServicesVSResources.Razor_CSharp_Language_Server_Client;
/// <summary>
/// Unused, implementing <see cref="ILanguageClient"/>
/// No additional settings are provided for this server, so we do not need any configuration section names.
/// </summary>
public IEnumerable<string>? ConfigurationSections { get; } = null;
/// <summary>
/// Gets the initialization options object the client wants to send when 'initialize' message is sent.
/// See https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#initialize
/// We do not provide any additional initialization options.
/// </summary>
public object? InitializationOptions { get; } = null;
/// <summary>
/// Unused, implementing <see cref="ILanguageClient"/>
/// Files that we care about are already provided and watched by the workspace.
/// </summary>
public IEnumerable<string>? FilesToWatch { get; } = null;
public event AsyncEventHandler<EventArgs>? StartAsync;
/// <summary>
/// Unused, implementing <see cref="ILanguageClient"/>
/// </summary>
public event AsyncEventHandler<EventArgs>? StopAsync { add { } remove { } }
public override string Name => ServicesVSResources.Razor_CSharp_Language_Server_Client;
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public RazorLanguageClient(LanguageServerProtocol languageServerProtocol, VisualStudioWorkspace workspace, IDiagnosticService diagnosticService)
: base(languageServerProtocol, workspace, diagnosticService)
{
_languageServerProtocol = languageServerProtocol;
_workspace = workspace;
_diagnosticService = diagnosticService;
}
public Task<Connection> ActivateAsync(CancellationToken token)
{
var (clientStream, serverStream) = FullDuplexStream.CreatePair();
_ = new RazorLanguageServer(serverStream, serverStream, _languageServerProtocol, _workspace, _diagnosticService);
return Task.FromResult(new Connection(clientStream, clientStream));
}
/// <summary>
/// Signals that the extension has been loaded. The server can be started immediately, or wait for user action to start.
/// To start the server, invoke the <see cref="StartAsync"/> event;
/// </summary>
public async Task OnLoadedAsync()
{
await StartAsync.InvokeAsync(this, EventArgs.Empty).ConfigureAwait(false);
}
/// <summary>
/// Signals the extension that the language server has been successfully initialized.
/// </summary>
/// <returns>A <see cref="Task"/> which completes when actions that need to be performed when the server is ready are done.</returns>
public Task OnServerInitializedAsync()
{
// We don't have any tasks that need to be triggered after the server has successfully initialized.
return Task.CompletedTask;
}
/// <summary>
/// Signals the extension that the language server failed to initialize.
/// </summary>
/// <returns>A <see cref="Task"/> which completes when additional actions that need to be performed when the server fails to initialize are done.</returns>
public Task OnServerInitializeFailedAsync(Exception e)
{
// We don't need to provide additional exception handling here, LSP client already handles failure cases for this server.
return Task.CompletedTask;
}
}
}
// 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.
#nullable enable
using System.IO;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.LanguageServer;
using Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService;
namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Lsp
{
class RazorLanguageServer : InProcLanguageServer
{
public RazorLanguageServer(Stream inputStream, Stream outputStream, LanguageServerProtocol protocol, Workspace workspace, IDiagnosticService diagnosticService)
: base(inputStream, outputStream, protocol, workspace, diagnosticService, clientName: RazorLanguageClient.ClientName)
{
}
protected override Task PublishDiagnosticsAsync(Document document)
{
// TODO - Filter razor diagnostics.
return base.PublishDiagnosticsAsync(document);
}
}
}
......@@ -57,6 +57,7 @@ public async Task<object[]> HandleAsync(LSP.ReferenceParams request, RequestCont
await findUsagesService.FindReferencesAsync(document, position, context).ConfigureAwait(false);
if (requestContext?.ClientCapabilities?.ToObject<VSClientCapabilities>()?.HasVisualStudioLspCapability() == true)
{
return await GetReferenceGroupsAsync(request, context, cancellationToken).ConfigureAwait(false);
......
......@@ -225,7 +225,7 @@ public async Task<Diagnostic> ToDiagnosticAsync(Project project, CancellationTok
location, additionalLocations, customTags: CustomTags, properties: Properties);
}
public static (LinePosition startLine, LinePosition endLine) GetLinePositions(DiagnosticDataLocation? dataLocation, SourceText text, bool useMapped)
public static LinePositionSpan GetLinePositionSpan(DiagnosticDataLocation? dataLocation, SourceText text, bool useMapped)
{
var lines = text.Lines;
if (lines.Count == 0)
......@@ -241,7 +241,7 @@ public static (LinePosition startLine, LinePosition endLine) GetLinePositions(Di
if (dataLocationStartLine >= lines.Count)
{
var lastLine = lines.GetLinePosition(text.Length);
return (lastLine, lastLine);
return new LinePositionSpan(lastLine, lastLine);
}
AdjustBoundaries(dataLocationStartLine, dataLocationStartColumn, dataLocationEndLine, dataLocationEndColumn, lines,
......@@ -251,14 +251,14 @@ public static (LinePosition startLine, LinePosition endLine) GetLinePositions(Di
var endLinePosition = new LinePosition(endLine, endColumn);
SwapIfNeeded(ref startLinePosition, ref endLinePosition);
return (startLinePosition, endLinePosition);
return new LinePositionSpan(startLinePosition, endLinePosition);
}
public static TextSpan GetTextSpan(DiagnosticDataLocation? dataLocation, SourceText text)
{
(var startLinePosition, var endLinePosition) = GetLinePositions(dataLocation, text, useMapped: false);
var linePositionSpan = GetLinePositionSpan(dataLocation, text, useMapped: false);
var span = text.Lines.GetTextSpan(new LinePositionSpan(startLinePosition, endLinePosition));
var span = text.Lines.GetTextSpan(linePositionSpan);
return EnsureInBounds(TextSpan.FromBounds(Math.Max(span.Start, 0), Math.Max(span.End, 0)), text);
}
......
......@@ -21,7 +21,9 @@ internal class DocumentPropertiesService : IDocumentService
/// <summary>
/// The LSP client name that should get the diagnostics produced by this document; any other source
/// will not show these diagnostics. If null, the diagnostics do not have this special handling.
/// will not show these diagnostics. For example, razor uses this to exclude diagnostics from the error list
/// so that they can handle the final display.
/// If null, the diagnostics do not have this special handling.
/// </summary>
public virtual string? DiagnosticsLspClientName => null;
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册