forked from PowerShell/PowerShellEditorServices
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathOmnisharpLanguageServer.cs
219 lines (183 loc) · 9.24 KB
/
OmnisharpLanguageServer.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.IO;
using System.IO.Pipes;
using System.Reflection;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.PowerShell.EditorServices.TextDocument;
using OmniSharp.Extensions.LanguageServer.Server;
using OS = OmniSharp.Extensions.LanguageServer.Server;
using PowerShellEditorServices.Engine.Services.Handlers;
namespace Microsoft.PowerShell.EditorServices.Engine
{
public class OmnisharpLanguageServer : ILanguageServer
{
public class Configuration
{
public bool Stdio { get; set; }
public string NamedPipeName { get; set; }
public string OutNamedPipeName { get; set; }
public ILoggerFactory LoggerFactory { get; set; }
public LogLevel MinimumLogLevel { get; set; }
public IServiceCollection Services { get; set; }
}
// This int will be casted to a PipeOptions enum that only exists in .NET Core 2.1 and up which is why it's not available to us in .NET Standard.
private const int CurrentUserOnly = 0x20000000;
// In .NET Framework, NamedPipeServerStream has a constructor that takes in a PipeSecurity object. We will use reflection to call the constructor,
// since .NET Framework doesn't have the `CurrentUserOnly` PipeOption.
// doc: https://docs.microsoft.com/en-us/dotnet/api/system.io.pipes.namedpipeserverstream.-ctor?view=netframework-4.7.2#System_IO_Pipes_NamedPipeServerStream__ctor_System_String_System_IO_Pipes_PipeDirection_System_Int32_System_IO_Pipes_PipeTransmissionMode_System_IO_Pipes_PipeOptions_System_Int32_System_Int32_System_IO_Pipes_PipeSecurity_
private static readonly ConstructorInfo s_netFrameworkPipeServerConstructor =
typeof(NamedPipeServerStream).GetConstructor(new [] { typeof(string), typeof(PipeDirection), typeof(int), typeof(PipeTransmissionMode), typeof(PipeOptions), typeof(int), typeof(int), typeof(PipeSecurity) });
private OS.ILanguageServer _languageServer;
private TaskCompletionSource<bool> _serverStart;
private readonly Configuration _configuration;
public OmnisharpLanguageServer(
Configuration configuration)
{
_configuration = configuration;
_serverStart = new TaskCompletionSource<bool>();
}
public async Task StartAsync()
{
_languageServer = await OS.LanguageServer.From(options => {
ILogger logger = options.LoggerFactory.CreateLogger("OptionsStartup");
if (_configuration.Stdio)
{
options.WithInput(System.Console.OpenStandardInput());
options.WithOutput(System.Console.OpenStandardOutput());
}
else
{
NamedPipeServerStream namedPipe = CreateNamedPipe(
_configuration.NamedPipeName,
_configuration.OutNamedPipeName,
out NamedPipeServerStream outNamedPipe);
logger.LogInformation("Waiting for connection");
namedPipe.WaitForConnection();
if (outNamedPipe != null)
{
outNamedPipe.WaitForConnection();
}
logger.LogInformation("Connected");
options.Input = namedPipe;
options.Output = outNamedPipe ?? namedPipe;
}
options.LoggerFactory = _configuration.LoggerFactory;
options.MinimumLogLevel = _configuration.MinimumLogLevel;
options.Services = _configuration.Services;
logger.LogInformation("Adding handlers");
options
.WithHandler<WorkspaceSymbolsHandler>()
.WithHandler<TextDocumentHandler>()
.WithHandler<GetVersionHandler>()
.WithHandler<ConfigurationHandler>()
.WithHandler<FoldingRangeHandler>()
.WithHandler<DocumentFormattingHandler>()
.WithHandler<DocumentRangeFormattingHandler>()
.WithHandler<ReferencesHandler>()
.WithHandler<DocumentSymbolHandler>()
.WithHandler<DocumentHighlightHandler>()
.WithHandler<PSHostProcessAndRunspaceHandlers>()
.WithHandler<CodeLensHandlers>()
.WithHandler<CodeActionHandler>()
.WithHandler<InvokeExtensionCommandHandler>()
.OnInitialize(
async (languageServer, request) =>
{
var serviceProvider = languageServer.Services;
var workspaceService = serviceProvider.GetService<WorkspaceService>();
// Grab the workspace path from the parameters
workspaceService.WorkspacePath = request.RootPath;
// Set the working directory of the PowerShell session to the workspace path
if (workspaceService.WorkspacePath != null
&& Directory.Exists(workspaceService.WorkspacePath))
{
await serviceProvider.GetService<PowerShellContextService>().SetWorkingDirectoryAsync(
workspaceService.WorkspacePath,
isPathAlreadyEscaped: false);
}
});
logger.LogInformation("Handlers added");
});
_serverStart.SetResult(true);
}
public async Task WaitForShutdown()
{
await _serverStart.Task;
await _languageServer.WaitForExit;
}
private static NamedPipeServerStream CreateNamedPipe(
string inOutPipeName,
string outPipeName,
out NamedPipeServerStream outPipe)
{
// .NET Core implementation is simplest so try that first
if (VersionUtils.IsNetCore)
{
outPipe = outPipeName == null
? null
: new NamedPipeServerStream(
pipeName: outPipeName,
direction: PipeDirection.Out,
maxNumberOfServerInstances: 1,
transmissionMode: PipeTransmissionMode.Byte,
options: (PipeOptions)CurrentUserOnly);
return new NamedPipeServerStream(
pipeName: inOutPipeName,
direction: PipeDirection.InOut,
maxNumberOfServerInstances: 1,
transmissionMode: PipeTransmissionMode.Byte,
options: PipeOptions.Asynchronous | (PipeOptions)CurrentUserOnly);
}
// Now deal with Windows PowerShell
// We need to use reflection to get a nice constructor
var pipeSecurity = new PipeSecurity();
WindowsIdentity identity = WindowsIdentity.GetCurrent();
WindowsPrincipal principal = new WindowsPrincipal(identity);
if (principal.IsInRole(WindowsBuiltInRole.Administrator))
{
// Allow the Administrators group full access to the pipe.
pipeSecurity.AddAccessRule(new PipeAccessRule(
new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null).Translate(typeof(NTAccount)),
PipeAccessRights.FullControl, AccessControlType.Allow));
}
else
{
// Allow the current user read/write access to the pipe.
pipeSecurity.AddAccessRule(new PipeAccessRule(
WindowsIdentity.GetCurrent().User,
PipeAccessRights.ReadWrite, AccessControlType.Allow));
}
outPipe = outPipeName == null
? null
: (NamedPipeServerStream)s_netFrameworkPipeServerConstructor.Invoke(
new object[] {
outPipeName,
PipeDirection.InOut,
1, // maxNumberOfServerInstances
PipeTransmissionMode.Byte,
PipeOptions.Asynchronous,
1024, // inBufferSize
1024, // outBufferSize
pipeSecurity
});
return (NamedPipeServerStream)s_netFrameworkPipeServerConstructor.Invoke(
new object[] {
inOutPipeName,
PipeDirection.InOut,
1, // maxNumberOfServerInstances
PipeTransmissionMode.Byte,
PipeOptions.Asynchronous,
1024, // inBufferSize
1024, // outBufferSize
pipeSecurity
});
}
}
}