forked from Azure/bicep
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathProgram.cs
133 lines (112 loc) · 4.71 KB
/
Program.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
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.IO.Pipes;
using System.Net.Sockets;
using System.Runtime;
using System.Threading;
using System.Threading.Tasks;
using CommandLine;
using Bicep.Core.Utils;
using System.Net;
using System.Diagnostics;
namespace Bicep.LanguageServer
{
public class Program
{
public class CommandLineOptions
{
[Option("pipe", Required = false, HelpText = "The named pipe to connect to for LSP communication")]
public string? Pipe { get; set; }
[Option("socket", Required = false, HelpText = "The TCP port to connect to for LSP communication")]
public int? Socket { get; set; }
[Option("stdio", Required = false, HelpText = "If set, use stdin/stdout for LSP communication")]
public bool Stdio { get; set; }
[Option("wait-for-debugger", Required = false, HelpText = "If set, wait for a dotnet debugger to be attached before starting the server")]
public bool WaitForDebugger { get; set; }
}
public static async Task Main(string[] args)
=> await RunWithCancellationAsync(async cancellationToken =>
{
var profilePath = DirHelper.GetTempPath();
ProfileOptimization.SetProfileRoot(profilePath);
ProfileOptimization.StartProfile("bicepserver.profile");
var parser = new Parser(settings => {
settings.IgnoreUnknownArguments = true;
});
await parser.ParseArguments<CommandLineOptions>(args)
.WithNotParsed((x) => Environment.Exit(1))
.WithParsedAsync(async options => await RunServer(options, cancellationToken));
});
private static async Task RunServer(CommandLineOptions options, CancellationToken cancellationToken)
{
if (options.WaitForDebugger)
{
// exit if we don't have a debugger attached within 5 minutes
var debuggerTimeoutToken = CancellationTokenSource.CreateLinkedTokenSource(
cancellationToken,
new CancellationTokenSource(TimeSpan.FromMinutes(5)).Token).Token;
while (!Debugger.IsAttached)
{
await Task.Delay(100, debuggerTimeoutToken);
}
Debugger.Break();
}
Server server;
if (options.Pipe is { } pipeName)
{
if (pipeName.StartsWith(@"\\.\pipe\"))
{
// VSCode on Windows prefixes the pipe with \\.\pipe\
pipeName = pipeName.Substring(@"\\.\pipe\".Length);
}
var clientPipe = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous);
await clientPipe.ConnectAsync(cancellationToken);
server = new(
options => options
.WithInput(clientPipe)
.WithOutput(clientPipe));
}
else if (options.Socket is { } port)
{
var tcpClient = new TcpClient();
await tcpClient.ConnectAsync(IPAddress.Loopback, port, cancellationToken);
var tcpStream = tcpClient.GetStream();
server = new(
options => options
.WithInput(tcpStream)
.WithOutput(tcpStream)
.RegisterForDisposal(tcpClient));
}
else
{
server = new(
options => options
.WithInput(Console.OpenStandardInput())
.WithOutput(Console.OpenStandardOutput()));
}
await server.RunAsync(cancellationToken);
}
private static async Task RunWithCancellationAsync(Func<CancellationToken, Task> runFunc)
{
var cancellationTokenSource = new CancellationTokenSource();
Console.CancelKeyPress += (sender, e) =>
{
cancellationTokenSource.Cancel();
e.Cancel = true;
};
AppDomain.CurrentDomain.ProcessExit += (sender, e) =>
{
cancellationTokenSource.Cancel();
};
try
{
await runFunc(cancellationTokenSource.Token);
}
catch (OperationCanceledException exception) when (exception.CancellationToken == cancellationTokenSource.Token)
{
// this is expected - no need to rethrow
}
}
}
}