我正在与Clangd语言服务器通信的C#应用程序上工作。 clangd作为一个单独的进程启动,由我的C#程序管理,通过I / O重定向进行通信。
我的程序正在与c交换language server protocol request-response pairs。请求通过其进程的StandardInput流发送到clangd,并使用其进程的StandardOutput流读取响应。 clangd使用其进程的StandardError流发出调试信息。 我正在使用异步方法进行读写,以保持用户界面的响应速度。
但是,在发送第三条textDocument/didOpen消息之后,我的程序在尝试读取响应时冻结。
利用我发现的StandardError流,当发出调试消息时,clangd正确处理了第三条textDocument/didOpen
消息,这意味着响应应该在StandardOutput上可用。
我将请求保存在文件中,并将其发送到在命令行上运行的clangd实例,该实例的工作原理就像一个超级按钮。 I attached that file in case you need it.
此外,在SendRequest
方法底部读取的调试消息表明文件已打开:
I[09:55:53.552] <-- textDocument/didOpen(26)
I[09:55:56.512] Updating file C:\Temp\crossrail\src\ClLogic.cpp with command [C:\Temp\crossrail\src] clang C:\Temp\crossrail\src\ClLogic.cpp -resource-dir=C:\Program Files (x86)\LLVM\bin\..\lib\clang\8.0.0
在下面,您可以看到LSP客户端代码,用于读取和写入响应。我标记了要阻止的位置。
private Process languageServer;
private StreamWriter requestWriter;
private StreamReader responseReader;
private StreamReader errorReader;
// ...
public void Connect(String workingDirectory)
{
if (this.Connected == false)
{
this.currentMessageID = LSP_FIRST_MESSAGE_ID;
this.languageServer.StartInfo.WorkingDirectory = workingDirectory;
this.languageServer.Start();
this.Connected = true;
this.requestWriter = this.languageServer.StandardInput;
this.responseReader = this.languageServer.StandardOutput;
this.errorReader = this.languageServer.StandardError;
}
}
public async Task<String> Query<T>(JsonRpcRequest<T> request)
{
await mutex.WaitAsync();
try
{
await this.SendRequest(request);
return await this.ReadResponse();
}
finally
{
mutex.Release();
}
}
private async Task SendRequest<T>(JsonRpcRequest<T> request)
{
request.ID = this.currentMessageID;
++this.currentMessageID;
String requestBody = request.ToString();
Console.WriteLine(requestBody);
await this.requestWriter.WriteAsync(requestBody.ToCharArray(), 0, requestBody.Length);
await this.requestWriter.FlushAsync();
if (request.ID == 26) // ID of the third textDocument/didOpen message
{
//await this.ReadErrors(); // the debug messages following the third textDocument/didOpen request are printed correctly
}
}
private async Task<String> ReadResponse()
{
String contentLengthHeader = await this.responseReader.ReadLineAsync(); // blocks after the third textDocument/didOpen message
int responseLength = Int32.Parse
(
contentLengthHeader.Substring(contentLengthHeader.IndexOf(LSP_HEADER_KEY_VALUE_DELIMITER) + LSP_HEADER_VALUE_OFFSET)
.Trim()
);
await this.responseReader.ReadLineAsync();
char[] buffer = new char[BUFFER_SIZE];
StringBuilder response = new StringBuilder();
int totalReadBytes = 0;
while (totalReadBytes < responseLength)
{
int readBytes = await this.responseReader.ReadAsync(buffer, 0, BUFFER_SIZE);
response.Append(buffer, 0, readBytes);
totalReadBytes += readBytes;
}
Console.WriteLine(response.ToString());
return response.ToString();
}
public async Task SendFileCloseMessage(DocumentCloseRequest request)
{
await mutex.WaitAsync();
try
{
await this.SendRequest(request);
this.responseReader.DiscardBufferedData();
}
finally
{
mutex.Release();
}
}
这是我的代码,使用LSP客户端的方法发送textDocument / didOpen消息:
private async Task InitializeLanguageServer(bool highlightFunctionDeclarations)
{
if (this.languageServer.Connected == false)
{
this.languageServer.Connect(this.workingDirectory);
await this.SendInitializationMessage();
}
await this.SendOpenFileMessage();
await this.LoadSymbolDeclarationLocations(highlightFunctionDeclarations);
await this.LoadSymbolUsages();
}
private async Task SendInitializationMessage()
{
InitializationRequest request = new InitializationRequest
(
this.workingDirectory,
System.Diagnostics.Process.GetCurrentProcess().Id,
false,
ApplicationSettings.LANGUAGE_SERVER_PROTOCOL_SUPPORTED_SYMBOLS
);
Console.WriteLine(await this.languageServer.Query(request));
}
private async Task SendOpenFileMessage()
{
DocumentOpenRequest request = new DocumentOpenRequest(this.filePath, "cpp", 1, this.SourceCode);
Console.WriteLine(await this.languageServer.Query(request));
}
在构造函数中 await
中调用 InitializeLanguageServer,但这不成问题,因为clangd足够快,可以在2.5秒内处理每个源代码文件。
languageServer
成员是使用TinyIoC检索的:
public SourceViewerVM()
{
// ...
this.languageServer = TinyIoCContainer.Current.Resolve<LanguageServerProtocolClient>();
#pragma warning disable CS4014
this.InitializeLanguageServer(highlightFunctionDeclarations);
#pragma warning restore CS4014
}
编辑:
阅读确实会阻塞,而不仅仅是在等待换行符。如果将以下代码放在通常读取StandardError的断点处,执行也会被阻塞:
if (request.ID == 26) // 26 is the ID of the third textRequest/didOpen message
{
char[] buffer = new char[BUFFER_SIZE];
int readBytes = await this.responseReader.ReadAsync(buffer, 0, BUFFER_SIZE); // blocking
Console.WriteLine(new String(buffer, 0, readBytes));
//await this.ReadErrors();
}