2

I struggle with understanding how does LSP client-side works. I mean I think I understand the theory of communication (JSON-RPC/LSP Protocol basics) but I struggle with existing libraries that are used for this for VS Code and I think trying to rewrite it is kinda pointless, especially client-side where I do not feel proficient at all

All examples I see provide a path to the server, so the LSP client can start it

it makes sense, but I'd rather avoid it during development, I'd want to have the server aopen in debugging mode and just start VS Code

I tried to start with basic of basic server implementation (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 };
    }
}

but I'm unable to setup client with those vscode-languageclient/node libraries even to get Log.Information("Initialization"); part

How can I provide the way they communicate - e.g name of named pipe? or just HTTP posts?

I'm not proficent in js / node development at all, so sorry for every foolish question

I've seen mature/production grade C# Language Server implementations but I'm overwhelmed just by their builders, there's sooo much stuff happening, sop that's why I'd want to write server from scratch, but for client use existing libs

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);
            }
        )
);

Thanks in advance

avocadoLambda
  • 1,332
  • 7
  • 16
  • 33
Axelly
  • 589
  • 3
  • 7
  • 26
  • 2
    "it makes sense, but I'd rather avoid it during development, I'd want to have the server aopen in debugging mode and just start VS Code". No, you cannot. The LSP server process must be created by VSCode (LSP client) so that its stdin/stdout can be redirected. To debug your LSP server, add some delay in its initialization code, so that you can attach a debugger to it. (LSP can go through other channels like you described, but in practice you cannot easily find examples to follow.) – Lex Li Aug 01 '21 at 20:46
  • Thank you, it makes stuff a little more clear – Axelly Aug 02 '21 at 20:38

0 Answers0