VS Code 的 LSP - 客户端/服务器,需要一些提示

时间:2021-08-01 16:23:40

标签: c# visual-studio-code language-server-protocol

我很难理解 LSP 客户端的工作原理。我的意思是我认为我了解通信理论(JSON-RPC/LSP 协议基础知识),但我对用于 VS Code 的现有库感到困惑,我认为尝试重写它是毫无意义的,尤其是在客户端我感觉一点都不熟练

我看到的所有示例都提供了到服务器的路径,以便 LSP 客户端可以启动它

这是有道理的,但我宁愿在开发过程中避免它,我希望在调试模式下打开服务器并启动 VS Code

我尝试从基本服务器实现 (C#) 的基础开始

public class Server
{
    private JsonRpc RPC { get; set; }

    public async Task Start()
    {
        Log.Logger = new LoggerConfiguration()
                        .MinimumLevel.Debug()
                        .WriteTo.Console()
                        .CreateLogger();

        var pipeName = "LSP_Pipe";

        var writerPipe = new NamedPipeClientStream(pipeName);
        var readerPipe = new NamedPipeClientStream(pipeName);

        await writerPipe.ConnectAsync(10_000);
        await readerPipe.ConnectAsync(10_000);

        Log.Information("RPC Listening");

        RPC = new JsonRpc(writerPipe, readerPipe, this);
        RPC.StartListening();

        this.RPC.Disconnected += RPC_Disconnected;

        await Task.Delay(-1);
    }

    private void RPC_Disconnected(object sender, JsonRpcDisconnectedEventArgs e)
    {
        Log.Information("Disconnected");
    }

    [JsonRpcMethod(RPCMethods.InitializeName)]
    public object Initialize(JToken arg)
    {
        Log.Information("Initialization");

        var serializer = new JsonSerializer()
        {
            ContractResolver = new ResourceOperationKindContractResolver()
        };

        var param = arg.ToObject<InitializeParams>();
        var clientCapabilities = param?.Capabilities;

        var capabilities = new ServerCapabilities
        {
            TextDocumentSync = new TextDocumentSyncOptions(),
            CompletionProvider = new CompletionOptions(),
            SignatureHelpProvider = new SignatureHelpOptions(),
            ExecuteCommandProvider = new ExecuteCommandOptions(),
            DocumentRangeFormattingProvider = false,
        };

        capabilities.TextDocumentSync.Change = TextDocumentSyncKind.Incremental;
        capabilities.TextDocumentSync.OpenClose = true;
        capabilities.TextDocumentSync.Save = new SaveOptions { IncludeText = true };
        capabilities.CodeActionProvider = clientCapabilities?.Workspace?.ApplyEdit ?? true;
        capabilities.DefinitionProvider = true;
        capabilities.ReferencesProvider = true;
        capabilities.DocumentSymbolProvider = true;
        capabilities.WorkspaceSymbolProvider = false;
        capabilities.RenameProvider = true;
        capabilities.HoverProvider = true;
        capabilities.DocumentHighlightProvider = true;

        return new InitializeResult { Capabilities = capabilities };
    }
}

但我无法使用那些 vscode-languageclient/node 库设置客户端,甚至无法获得 Log.Information("Initialization"); 部分

我如何提供他们通信的方式——例如命名管道的名称?还是只是 HTTP 帖子?

我对js/node开发一点都不精通,很抱歉每一个愚蠢的问题

我已经看到了成熟/生产级的 C# 语言服务器实现,但我被他们的构建者淹没了,发生了太多的事情,所以我想从头开始编写服务器,但对于客户端使用现有的库

var server = await LanguageServer.From(
options =>
    options
       .WithInput(Console.OpenStandardInput())
       .WithOutput(Console.OpenStandardOutput())
       .ConfigureLogging(
            x => x
                .AddSerilog(Log.Logger)
                .AddLanguageProtocolLogging()
                .SetMinimumLevel(LogLevel.Debug)
        )
       .WithHandler<TextDocumentHandler>()
       .WithHandler<DidChangeWatchedFilesHandler>()
       .WithHandler<FoldingRangeHandler>()
       .WithHandler<MyWorkspaceSymbolsHandler>()
       .WithHandler<MyDocumentSymbolHandler>()
       .WithHandler<SemanticTokensHandler>()
       .WithServices(x => x.AddLogging(b => b.SetMinimumLevel(LogLevel.Trace)))
       .WithServices(
            services => {
                services.AddSingleton(
                    provider => {
                        var loggerFactory = provider.GetService<ILoggerFactory>();
                        var logger = loggerFactory.CreateLogger<Foo>();

                        logger.LogInformation("Configuring");

                        return new Foo(logger);
                    }
                );
                services.AddSingleton(
                    new ConfigurationItem {
                        Section = "typescript",
                    }
                ).AddSingleton(
                    new ConfigurationItem {
                        Section = "terminal",
                    }
                );
            }
        )
       .OnInitialize(
            async (server, request, token) => {
                var manager = server.WorkDoneManager.For(
                    request, new WorkDoneProgressBegin {
                        Title = "Server is starting...",
                        Percentage = 10,
                    }
                );
                workDone = manager;

                await Task.Delay(2000);

                manager.OnNext(
                    new WorkDoneProgressReport {
                        Percentage = 20,
                        Message = "loading in progress"
                    }
                );
            }
        )
       .OnInitialized(
            async (server, request, response, token) => {
                workDone.OnNext(
                    new WorkDoneProgressReport {
                        Percentage = 40,
                        Message = "loading almost done",
                    }
                );

                await Task.Delay(2000);

                workDone.OnNext(
                    new WorkDoneProgressReport {
                        Message = "loading done",
                        Percentage = 100,
                    }
                );
                workDone.OnCompleted();
            }
        )
       .OnStarted(
            async (languageServer, token) => {
                using var manager = await languageServer.WorkDoneManager.Create(new WorkDoneProgressBegin { Title = "Doing some work..." });

                manager.OnNext(new WorkDoneProgressReport { Message = "doing things..." });
                await Task.Delay(10000);
                manager.OnNext(new WorkDoneProgressReport { Message = "doing things... 1234" });
                await Task.Delay(10000);
                manager.OnNext(new WorkDoneProgressReport { Message = "doing things... 56789" });

                var logger = languageServer.Services.GetService<ILogger<Foo>>();
                var configuration = await languageServer.Configuration.GetConfiguration(
                    new ConfigurationItem {
                        Section = "typescript",
                    }, new ConfigurationItem {
                        Section = "terminal",
                    }
                );

                var baseConfig = new JObject();
                foreach (var config in languageServer.Configuration.AsEnumerable())
                {
                    baseConfig.Add(config.Key, config.Value);
                }

                logger.LogInformation("Base Config: {Config}", baseConfig);

                var scopedConfig = new JObject();
                foreach (var config in configuration.AsEnumerable())
                {
                    scopedConfig.Add(config.Key, config.Value);
                }

                logger.LogInformation("Scoped Config: {Config}", scopedConfig);
            }
        )
);

提前致谢

0 个答案:

没有答案