Skip to content

Commit

Permalink
Add option for choosing stdio as the LSP communication channel (#76437)
Browse files Browse the repository at this point in the history
Resolves #76436
  • Loading branch information
dibarbet authored Jan 10, 2025
2 parents 7d49593 + 891660d commit 23545d2
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ public static Task<ExportProvider> CreateExportProviderAsync(
RazorSourceGenerator: null,
RazorDesignTimePath: null,
ExtensionLogDirectory: string.Empty,
ServerPipeName: null);
ServerPipeName: null,
UseStdIo: false);
var extensionManager = ExtensionAssemblyManager.Create(serverConfiguration, loggerFactory);
assemblyLoader = new CustomExportAssemblyLoader(extensionManager, loggerFactory);
return ExportProviderBuilder.CreateExportProviderAsync(extensionManager, assemblyLoader, devKitDependencyPath, cacheDirectory, loggerFactory);
Expand Down
61 changes: 46 additions & 15 deletions src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,18 @@

static async Task RunAsync(ServerConfiguration serverConfiguration, CancellationToken cancellationToken)
{
// Before we initialize the LSP server we can't send LSP log messages.
if (serverConfiguration.UseStdIo)
{
if (serverConfiguration.ServerPipeName is not null)
{
throw new InvalidOperationException("Server cannot be started with both --stdio and --pipe options.");
}

// Redirect Console.Out to try prevent the standard output stream from being corrupted.
// This should be done before the logger is created as it can write to the standard output.
Console.SetOut(new StreamWriter(Console.OpenStandardError()));
}

// Create a console logger as a fallback to use before the LSP server starts.
using var loggerFactory = LoggerFactory.Create(builder =>
{
Expand Down Expand Up @@ -125,23 +136,32 @@ static async Task RunAsync(ServerConfiguration serverConfiguration, Cancellation

var languageServerLogger = loggerFactory.CreateLogger(nameof(LanguageServerHost));

var (clientPipeName, serverPipeName) = serverConfiguration.ServerPipeName is null
? CreateNewPipeNames()
: (serverConfiguration.ServerPipeName, serverConfiguration.ServerPipeName);
LanguageServerHost? server = null;
if (serverConfiguration.UseStdIo)
{
server = new LanguageServerHost(Console.OpenStandardInput(), Console.OpenStandardOutput(), exportProvider, languageServerLogger, typeRefResolver);
}
else
{
var (clientPipeName, serverPipeName) = serverConfiguration.ServerPipeName is null
? CreateNewPipeNames()
: (serverConfiguration.ServerPipeName, serverConfiguration.ServerPipeName);

var pipeServer = new NamedPipeServerStream(serverPipeName,
PipeDirection.InOut,
maxNumberOfServerInstances: 1,
PipeTransmissionMode.Byte,
PipeOptions.CurrentUserOnly | PipeOptions.Asynchronous);
var pipeServer = new NamedPipeServerStream(serverPipeName,
PipeDirection.InOut,
maxNumberOfServerInstances: 1,
PipeTransmissionMode.Byte,
PipeOptions.CurrentUserOnly | PipeOptions.Asynchronous);

// Send the named pipe connection info to the client
Console.WriteLine(JsonSerializer.Serialize(new NamedPipeInformation(clientPipeName)));
// Send the named pipe connection info to the client
Console.WriteLine(JsonSerializer.Serialize(new NamedPipeInformation(clientPipeName)));

// Wait for connection from client
await pipeServer.WaitForConnectionAsync(cancellationToken);
// Wait for connection from client
await pipeServer.WaitForConnectionAsync(cancellationToken);

server = new LanguageServerHost(pipeServer, pipeServer, exportProvider, languageServerLogger, typeRefResolver);
}

var server = new LanguageServerHost(pipeServer, pipeServer, exportProvider, languageServerLogger, typeRefResolver);
server.Start();

logger.LogInformation("Language server initialized");
Expand Down Expand Up @@ -230,7 +250,15 @@ static CliRootCommand CreateCommandLineParser()
var serverPipeNameOption = new CliOption<string?>("--pipe")
{
Description = "The name of the pipe the server will connect to.",
Required = false
};

var useStdIoOption = new CliOption<bool>("--stdio")
{
Description = "Use stdio for communication with the client.",
Required = false,
DefaultValueFactory = _ => false,

};

var rootCommand = new CliRootCommand()
Expand All @@ -246,7 +274,8 @@ static CliRootCommand CreateCommandLineParser()
razorSourceGeneratorOption,
razorDesignTimePathOption,
extensionLogDirectoryOption,
serverPipeNameOption
serverPipeNameOption,
useStdIoOption
};
rootCommand.SetAction((parseResult, cancellationToken) =>
{
Expand All @@ -261,6 +290,7 @@ static CliRootCommand CreateCommandLineParser()
var razorDesignTimePath = parseResult.GetValue(razorDesignTimePathOption);
var extensionLogDirectory = parseResult.GetValue(extensionLogDirectoryOption)!;
var serverPipeName = parseResult.GetValue(serverPipeNameOption);
var useStdIo = parseResult.GetValue(useStdIoOption);

var serverConfiguration = new ServerConfiguration(
LaunchDebugger: launchDebugger,
Expand All @@ -273,6 +303,7 @@ static CliRootCommand CreateCommandLineParser()
RazorSourceGenerator: razorSourceGenerator,
RazorDesignTimePath: razorDesignTimePath,
ServerPipeName: serverPipeName,
UseStdIo: useStdIo,
ExtensionLogDirectory: extensionLogDirectory);

return RunAsync(serverConfiguration, cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ internal record class ServerConfiguration(
string? RazorSourceGenerator,
string? RazorDesignTimePath,
string? ServerPipeName,
bool UseStdIo,
string ExtensionLogDirectory);

internal class LogConfiguration
Expand Down

0 comments on commit 23545d2

Please sign in to comment.