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