如何使用C# Language Server Client连接语言服务器(language-server.exe)并获取可用实现示例
如何用OmniSharp的C#语言服务器协议库连接外部语言服务器(language-server.exe)
我来帮你搞定这个问题——要连接外部的language-server.exe,核心是通过启动外部进程并获取它的标准输入/输出流,然后把这些流交给OmniSharp的LanguageClient来处理通信。下面是一个完整的、可直接运行的示例,基于最新版的csharp-language-server-protocol库:
第一步:安装必要的NuGet包
首先确保你的项目引用了这些包:
OmniSharp.Extensions.LanguageServer.ClientMicrosoft.Extensions.Logging.Console(可选,用于调试日志输出)
可以通过命令行快速安装:
dotnet add package OmniSharp.Extensions.LanguageServer.Client dotnet add package Microsoft.Extensions.Logging.Console
第二步:完整的客户端实现代码
using System; using System.Diagnostics; using System.IO; using System.Reactive.Disposables; using Microsoft.Extensions.Logging; using OmniSharp.Extensions.LanguageServer.Client; using OmniSharp.Extensions.LanguageServer.Protocol.Client; using OmniSharp.Extensions.LanguageServer.Protocol.Models; namespace LanguageServerClientDemo { class Program { private static readonly CompositeDisposable _disposables = new(); private static ILanguageClient _client; static void Main(string[] args) { // 初始化日志工厂(可选但推荐,方便排查通信问题) var loggerFactory = LoggerFactory.Create(builder => { builder.AddConsole().SetMinimumLevel(LogLevel.Trace); }); // 替换为你实际的language-server.exe路径 var serverPath = @"C:\Your\Path\To\language-server.exe"; // 创建并启动客户端 _client = CreateLanguageClient(loggerFactory, serverPath); _client.Start().Wait(); // 按照LSP规范完成握手:先发送initialize请求 var initResponse = _client.SendRequest<InitializeParams, InitializeResult>( "initialize", new InitializeParams { ProcessId = Process.GetCurrentProcess().Id, RootUri = new Uri("file:///C:/Your/Project/Directory"), Capabilities = new ClientCapabilities() } ).Result; Console.WriteLine($"服务器初始化成功,版本:{initResponse.ServerInfo?.Version ?? "未知"}"); // 发送initialized通知完成握手 _client.SendNotification("initialized", new InitializedParams()); // 保持程序运行,等待用户触发退出 Console.WriteLine("按任意键退出客户端..."); Console.ReadKey(); // 优雅关闭客户端和服务器进程 _client.Stop().Wait(); _disposables.Dispose(); } private static ILanguageClient CreateLanguageClient(ILoggerFactory loggerFactory, string serverExePath) { var client = LanguageClient.Create(options => { // 启动外部服务器并获取通信流 var (reader, writer, process) = SetupExternalServer(serverExePath); _disposables.Add(process); // 确保进程资源被正确释放 options .WithInput(reader) .WithOutput(writer) .WithLoggerFactory(loggerFactory) // 注册基础服务,如需自定义通信管道可在此扩展 .Services .AddLogging(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Trace)); }); _disposables.Add(client); return client; } private static (StreamReader reader, StreamWriter writer, Process process) SetupExternalServer(string serverExePath) { var startInfo = new ProcessStartInfo(serverExePath) { UseShellExecute = false, // 必须设为false才能重定向输入输出 RedirectStandardInput = true, RedirectStandardOutput = true, CreateNoWindow = true // 可选,避免弹出服务器控制台窗口 }; // 如需给服务器传递启动参数,可添加: // startInfo.Arguments = "--some-argument value"; var process = Process.Start(startInfo); if (process == null) { throw new InvalidOperationException($"无法启动语言服务器:{serverExePath}"); } // 监听服务器退出事件,方便调试 process.Exited += (_, __) => { Console.WriteLine($"语言服务器已退出,退出码:{process.ExitCode}"); }; process.EnableRaisingEvents = true; return (process.StandardOutput, process.StandardInput, process); } } }
关键细节说明
SetupExternalServer方法:这是连接外部服务器的核心,它负责启动目标exe并配置进程使用标准输入输出进行LSP通信,记得替换成你实际的服务器路径。- LSP握手流程:必须严格按照协议规范,先发送
initialize请求,收到响应后再发送initialized通知,否则服务器可能无法正常工作。 - 资源管理:使用
CompositeDisposable统一管理客户端和服务器进程的生命周期,确保程序退出时能优雅关闭连接和进程。 - 日志调试:保留日志配置可以帮你快速定位通信中的异常,比如请求未响应、协议格式错误等问题。
内容的提问来源于stack exchange,提问作者Basti




