diff --git a/.editorconfig b/.editorconfig index 8aee2863d..3316f2278 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,6 +4,7 @@ indent_style = space indent_size = 4 trim_trailing_whitespace = true insert_final_newline = true +max_line_length = 180 [*.xml] indent_style = space diff --git a/Directory.Build.targets b/Directory.Build.targets index 9df427a15..181a4b228 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -3,6 +3,7 @@ + @@ -33,12 +34,14 @@ + - + + diff --git a/LSP.sln b/LSP.sln index 138361797..0543be637 100644 --- a/LSP.sln +++ b/LSP.sln @@ -57,7 +57,21 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = ".build", ".build\.build.csp EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".build", ".build", "{26522B49-0743-4CBE-BA67-6D17FF65CAB9}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dap.Tests", "test\Dap.Tests\Dap.Tests.csproj", "{F3DF7917-6AD3-4EAA-A549-555032DDC4F8}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{954FB493-FA91-470B-8A78-FA32A7B05E97}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pipeline", "benchmarks\Pipeline\Pipeline.csproj", "{B781CDC6-34BB-4131-B18A-3B1294F53062}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared", "src\Shared\Shared.csproj", "{18FB2302-023B-4F6F-9F6D-099B47B69D9F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dap.Tests", "test\Dap.Tests\Dap.Tests.csproj", "{6D9E5BF4-4666-476B-AC88-D108A80567F6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dap.Client", "src\Dap.Client\Dap.Client.csproj", "{678A4DD2-A656-4DCC-AE78-F9940C82A6E6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dap.Testing", "src\Dap.Testing\Dap.Testing.csproj", "{91919C54-3638-4A3C-963A-327D78368EE3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testing", "src\Testing\Testing.csproj", "{A1EC39EE-AA1F-4EC9-9939-28C3532585C9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonRpc.Testing", "src\JsonRpc.Testing\JsonRpc.Testing.csproj", "{202BA1AB-25DA-44ED-B962-FD82FCC74543}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -195,18 +209,90 @@ Global {28B13787-A442-4D28-BF9A-3D65BF13AAEC}.Release|Any CPU.ActiveCfg = Release|Any CPU {28B13787-A442-4D28-BF9A-3D65BF13AAEC}.Release|x64.ActiveCfg = Release|Any CPU {28B13787-A442-4D28-BF9A-3D65BF13AAEC}.Release|x86.ActiveCfg = Release|Any CPU - {F3DF7917-6AD3-4EAA-A549-555032DDC4F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F3DF7917-6AD3-4EAA-A549-555032DDC4F8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F3DF7917-6AD3-4EAA-A549-555032DDC4F8}.Debug|x64.ActiveCfg = Debug|Any CPU - {F3DF7917-6AD3-4EAA-A549-555032DDC4F8}.Debug|x64.Build.0 = Debug|Any CPU - {F3DF7917-6AD3-4EAA-A549-555032DDC4F8}.Debug|x86.ActiveCfg = Debug|Any CPU - {F3DF7917-6AD3-4EAA-A549-555032DDC4F8}.Debug|x86.Build.0 = Debug|Any CPU - {F3DF7917-6AD3-4EAA-A549-555032DDC4F8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F3DF7917-6AD3-4EAA-A549-555032DDC4F8}.Release|Any CPU.Build.0 = Release|Any CPU - {F3DF7917-6AD3-4EAA-A549-555032DDC4F8}.Release|x64.ActiveCfg = Release|Any CPU - {F3DF7917-6AD3-4EAA-A549-555032DDC4F8}.Release|x64.Build.0 = Release|Any CPU - {F3DF7917-6AD3-4EAA-A549-555032DDC4F8}.Release|x86.ActiveCfg = Release|Any CPU - {F3DF7917-6AD3-4EAA-A549-555032DDC4F8}.Release|x86.Build.0 = Release|Any CPU + {B781CDC6-34BB-4131-B18A-3B1294F53062}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B781CDC6-34BB-4131-B18A-3B1294F53062}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B781CDC6-34BB-4131-B18A-3B1294F53062}.Debug|x64.ActiveCfg = Debug|Any CPU + {B781CDC6-34BB-4131-B18A-3B1294F53062}.Debug|x64.Build.0 = Debug|Any CPU + {B781CDC6-34BB-4131-B18A-3B1294F53062}.Debug|x86.ActiveCfg = Debug|Any CPU + {B781CDC6-34BB-4131-B18A-3B1294F53062}.Debug|x86.Build.0 = Debug|Any CPU + {B781CDC6-34BB-4131-B18A-3B1294F53062}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B781CDC6-34BB-4131-B18A-3B1294F53062}.Release|Any CPU.Build.0 = Release|Any CPU + {B781CDC6-34BB-4131-B18A-3B1294F53062}.Release|x64.ActiveCfg = Release|Any CPU + {B781CDC6-34BB-4131-B18A-3B1294F53062}.Release|x64.Build.0 = Release|Any CPU + {B781CDC6-34BB-4131-B18A-3B1294F53062}.Release|x86.ActiveCfg = Release|Any CPU + {B781CDC6-34BB-4131-B18A-3B1294F53062}.Release|x86.Build.0 = Release|Any CPU + {18FB2302-023B-4F6F-9F6D-099B47B69D9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {18FB2302-023B-4F6F-9F6D-099B47B69D9F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {18FB2302-023B-4F6F-9F6D-099B47B69D9F}.Debug|x64.ActiveCfg = Debug|Any CPU + {18FB2302-023B-4F6F-9F6D-099B47B69D9F}.Debug|x64.Build.0 = Debug|Any CPU + {18FB2302-023B-4F6F-9F6D-099B47B69D9F}.Debug|x86.ActiveCfg = Debug|Any CPU + {18FB2302-023B-4F6F-9F6D-099B47B69D9F}.Debug|x86.Build.0 = Debug|Any CPU + {18FB2302-023B-4F6F-9F6D-099B47B69D9F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {18FB2302-023B-4F6F-9F6D-099B47B69D9F}.Release|Any CPU.Build.0 = Release|Any CPU + {18FB2302-023B-4F6F-9F6D-099B47B69D9F}.Release|x64.ActiveCfg = Release|Any CPU + {18FB2302-023B-4F6F-9F6D-099B47B69D9F}.Release|x64.Build.0 = Release|Any CPU + {18FB2302-023B-4F6F-9F6D-099B47B69D9F}.Release|x86.ActiveCfg = Release|Any CPU + {18FB2302-023B-4F6F-9F6D-099B47B69D9F}.Release|x86.Build.0 = Release|Any CPU + {6D9E5BF4-4666-476B-AC88-D108A80567F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6D9E5BF4-4666-476B-AC88-D108A80567F6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6D9E5BF4-4666-476B-AC88-D108A80567F6}.Debug|x64.ActiveCfg = Debug|Any CPU + {6D9E5BF4-4666-476B-AC88-D108A80567F6}.Debug|x64.Build.0 = Debug|Any CPU + {6D9E5BF4-4666-476B-AC88-D108A80567F6}.Debug|x86.ActiveCfg = Debug|Any CPU + {6D9E5BF4-4666-476B-AC88-D108A80567F6}.Debug|x86.Build.0 = Debug|Any CPU + {6D9E5BF4-4666-476B-AC88-D108A80567F6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6D9E5BF4-4666-476B-AC88-D108A80567F6}.Release|Any CPU.Build.0 = Release|Any CPU + {6D9E5BF4-4666-476B-AC88-D108A80567F6}.Release|x64.ActiveCfg = Release|Any CPU + {6D9E5BF4-4666-476B-AC88-D108A80567F6}.Release|x64.Build.0 = Release|Any CPU + {6D9E5BF4-4666-476B-AC88-D108A80567F6}.Release|x86.ActiveCfg = Release|Any CPU + {6D9E5BF4-4666-476B-AC88-D108A80567F6}.Release|x86.Build.0 = Release|Any CPU + {678A4DD2-A656-4DCC-AE78-F9940C82A6E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {678A4DD2-A656-4DCC-AE78-F9940C82A6E6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {678A4DD2-A656-4DCC-AE78-F9940C82A6E6}.Debug|x64.ActiveCfg = Debug|Any CPU + {678A4DD2-A656-4DCC-AE78-F9940C82A6E6}.Debug|x64.Build.0 = Debug|Any CPU + {678A4DD2-A656-4DCC-AE78-F9940C82A6E6}.Debug|x86.ActiveCfg = Debug|Any CPU + {678A4DD2-A656-4DCC-AE78-F9940C82A6E6}.Debug|x86.Build.0 = Debug|Any CPU + {678A4DD2-A656-4DCC-AE78-F9940C82A6E6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {678A4DD2-A656-4DCC-AE78-F9940C82A6E6}.Release|Any CPU.Build.0 = Release|Any CPU + {678A4DD2-A656-4DCC-AE78-F9940C82A6E6}.Release|x64.ActiveCfg = Release|Any CPU + {678A4DD2-A656-4DCC-AE78-F9940C82A6E6}.Release|x64.Build.0 = Release|Any CPU + {678A4DD2-A656-4DCC-AE78-F9940C82A6E6}.Release|x86.ActiveCfg = Release|Any CPU + {678A4DD2-A656-4DCC-AE78-F9940C82A6E6}.Release|x86.Build.0 = Release|Any CPU + {91919C54-3638-4A3C-963A-327D78368EE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {91919C54-3638-4A3C-963A-327D78368EE3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {91919C54-3638-4A3C-963A-327D78368EE3}.Debug|x64.ActiveCfg = Debug|Any CPU + {91919C54-3638-4A3C-963A-327D78368EE3}.Debug|x64.Build.0 = Debug|Any CPU + {91919C54-3638-4A3C-963A-327D78368EE3}.Debug|x86.ActiveCfg = Debug|Any CPU + {91919C54-3638-4A3C-963A-327D78368EE3}.Debug|x86.Build.0 = Debug|Any CPU + {91919C54-3638-4A3C-963A-327D78368EE3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {91919C54-3638-4A3C-963A-327D78368EE3}.Release|Any CPU.Build.0 = Release|Any CPU + {91919C54-3638-4A3C-963A-327D78368EE3}.Release|x64.ActiveCfg = Release|Any CPU + {91919C54-3638-4A3C-963A-327D78368EE3}.Release|x64.Build.0 = Release|Any CPU + {91919C54-3638-4A3C-963A-327D78368EE3}.Release|x86.ActiveCfg = Release|Any CPU + {91919C54-3638-4A3C-963A-327D78368EE3}.Release|x86.Build.0 = Release|Any CPU + {A1EC39EE-AA1F-4EC9-9939-28C3532585C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1EC39EE-AA1F-4EC9-9939-28C3532585C9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1EC39EE-AA1F-4EC9-9939-28C3532585C9}.Debug|x64.ActiveCfg = Debug|Any CPU + {A1EC39EE-AA1F-4EC9-9939-28C3532585C9}.Debug|x64.Build.0 = Debug|Any CPU + {A1EC39EE-AA1F-4EC9-9939-28C3532585C9}.Debug|x86.ActiveCfg = Debug|Any CPU + {A1EC39EE-AA1F-4EC9-9939-28C3532585C9}.Debug|x86.Build.0 = Debug|Any CPU + {A1EC39EE-AA1F-4EC9-9939-28C3532585C9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1EC39EE-AA1F-4EC9-9939-28C3532585C9}.Release|Any CPU.Build.0 = Release|Any CPU + {A1EC39EE-AA1F-4EC9-9939-28C3532585C9}.Release|x64.ActiveCfg = Release|Any CPU + {A1EC39EE-AA1F-4EC9-9939-28C3532585C9}.Release|x64.Build.0 = Release|Any CPU + {A1EC39EE-AA1F-4EC9-9939-28C3532585C9}.Release|x86.ActiveCfg = Release|Any CPU + {A1EC39EE-AA1F-4EC9-9939-28C3532585C9}.Release|x86.Build.0 = Release|Any CPU + {202BA1AB-25DA-44ED-B962-FD82FCC74543}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {202BA1AB-25DA-44ED-B962-FD82FCC74543}.Debug|Any CPU.Build.0 = Debug|Any CPU + {202BA1AB-25DA-44ED-B962-FD82FCC74543}.Debug|x64.ActiveCfg = Debug|Any CPU + {202BA1AB-25DA-44ED-B962-FD82FCC74543}.Debug|x64.Build.0 = Debug|Any CPU + {202BA1AB-25DA-44ED-B962-FD82FCC74543}.Debug|x86.ActiveCfg = Debug|Any CPU + {202BA1AB-25DA-44ED-B962-FD82FCC74543}.Debug|x86.Build.0 = Debug|Any CPU + {202BA1AB-25DA-44ED-B962-FD82FCC74543}.Release|Any CPU.ActiveCfg = Release|Any CPU + {202BA1AB-25DA-44ED-B962-FD82FCC74543}.Release|Any CPU.Build.0 = Release|Any CPU + {202BA1AB-25DA-44ED-B962-FD82FCC74543}.Release|x64.ActiveCfg = Release|Any CPU + {202BA1AB-25DA-44ED-B962-FD82FCC74543}.Release|x64.Build.0 = Release|Any CPU + {202BA1AB-25DA-44ED-B962-FD82FCC74543}.Release|x86.ActiveCfg = Release|Any CPU + {202BA1AB-25DA-44ED-B962-FD82FCC74543}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -223,7 +309,13 @@ Global {F2C9D555-118E-442B-A953-9A7B58A53F33} = {D764E024-3D3F-4112-B932-2DB722A1BACC} {E1A9123B-A236-4240-8C82-A61BD85C3BF4} = {D764E024-3D3F-4112-B932-2DB722A1BACC} {28B13787-A442-4D28-BF9A-3D65BF13AAEC} = {26522B49-0743-4CBE-BA67-6D17FF65CAB9} - {F3DF7917-6AD3-4EAA-A549-555032DDC4F8} = {2F323ED5-EBF8-45E1-B9D3-C014561B3DDA} + {B781CDC6-34BB-4131-B18A-3B1294F53062} = {954FB493-FA91-470B-8A78-FA32A7B05E97} + {18FB2302-023B-4F6F-9F6D-099B47B69D9F} = {D764E024-3D3F-4112-B932-2DB722A1BACC} + {6D9E5BF4-4666-476B-AC88-D108A80567F6} = {2F323ED5-EBF8-45E1-B9D3-C014561B3DDA} + {678A4DD2-A656-4DCC-AE78-F9940C82A6E6} = {D764E024-3D3F-4112-B932-2DB722A1BACC} + {91919C54-3638-4A3C-963A-327D78368EE3} = {D764E024-3D3F-4112-B932-2DB722A1BACC} + {A1EC39EE-AA1F-4EC9-9939-28C3532585C9} = {D764E024-3D3F-4112-B932-2DB722A1BACC} + {202BA1AB-25DA-44ED-B962-FD82FCC74543} = {D764E024-3D3F-4112-B932-2DB722A1BACC} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D38DD0EC-D095-4BCD-B8AF-2D788AF3B9AE} diff --git a/benchmarks/Pipeline/Class1.cs b/benchmarks/Pipeline/Class1.cs new file mode 100644 index 000000000..03291e6f3 --- /dev/null +++ b/benchmarks/Pipeline/Class1.cs @@ -0,0 +1,84 @@ +using System.Buffers; +using System.Text; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Running; +using System.IO.Pipelines; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using BenchmarkDotNet.Jobs; + +namespace Pipeline +{ + [MemoryDiagnoser] + // [MinColumn, MaxColumn, MeanColumn, MedianColumn] + [SimpleJob(RuntimeMoniker.Net472)] + [SimpleJob(RuntimeMoniker.NetCoreApp31)] + [SimpleJob(RuntimeMoniker.NetCoreApp21)] + public class ClassicVsPipelines + { + private const string sampleCommand = + "Content-Length: 88\r\n\r\n{\"seq\":1,\"type\":\"response\",\"request_seq\":1,\"success\":true,\"command\":\"command\",\"body\":{}}"; + + private const string anotherPayload = + "Content-Length: 894\r\n\r\n{\"edit\":{\"documentChanges\":[{\"textDocument\":{\"version\":1,\"uri\":\"file:///abc/123/d.cs\"},\"edits\":[{\"range\":{\"start\":{\"line\":1,\"character\":1},\"end\":{\"line\":2,\"character\":2}},\"newText\":\"new text\"},{\"range\":{\"start\":{\"line\":3,\"character\":3},\"end\":{\"line\":4,\"character\":4}},\"newText\":\"new text2\"}]},{\"textDocument\":{\"version\":1,\"uri\":\"file:///abc/123/b.cs\"},\"edits\":[{\"range\":{\"start\":{\"line\":1,\"character\":1},\"end\":{\"line\":2,\"character\":2}},\"newText\":\"new text2\"},{\"range\":{\"start\":{\"line\":3,\"character\":3},\"end\":{\"line\":4,\"character\":4}},\"newText\":\"new text3\"}]},{\"kind\":\"create\",\"uri\":\"file:///abc/123/b.cs\",\"options\":{\"overwrite\":true,\"ignoreIfExists\":true}},{\"kind\":\"rename\",\"oldUri\":\"file:///abc/123/b.cs\",\"newUri\":\"file:///abc/123/c.cs\",\"options\":{\"overwrite\":true,\"ignoreIfExists\":true}},{\"kind\":\"delete\",\"uri\":\"file:///abc/123/c.cs\",\"options\":{\"recursive\":false,\"ignoreIfNotExists\":true}}]}}"; + + + private ClassicHandler _classic; + private PipelinesBased _pipelines; + + public ClassicVsPipelines() + { + } + + [Params( + sampleCommand, + anotherPayload + )] + public string Payload { get; set; } + + [Params( + // 10, + 100, + 1000 + )] + public int Count { get; set; } + + public byte[] Bytes { get; set; } + + [GlobalSetup] + public void SetupPipelines() + { + var bytes = Encoding.ASCII.GetBytes(Payload); + Bytes = Enumerable.Range(0, Count).SelectMany(z => bytes).ToArray(); + } + + [Benchmark] + public Task Classic() + { + var pipe = new Pipe(); + pipe.Writer.Write(Bytes); + pipe.Writer.Complete(); + _classic = new ClassicHandler(pipe.Reader.AsStream()); + return _classic.ProcessInputStream(); + } + + [Benchmark] + public Task Pipelines() + { + var pipe = new Pipe(); + pipe.Writer.Write(Bytes); + pipe.Writer.Complete(); + _pipelines = new PipelinesBased(pipe.Reader); + return _pipelines.ProcessInputStream(CancellationToken.None); + } + } + + public class Program + { + public static void Main(string[] args) + { + var summary = BenchmarkRunner.Run(); + } + } +} diff --git a/benchmarks/Pipeline/ClassicHandler.cs b/benchmarks/Pipeline/ClassicHandler.cs new file mode 100644 index 000000000..fd3fa8044 --- /dev/null +++ b/benchmarks/Pipeline/ClassicHandler.cs @@ -0,0 +1,88 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Pipeline +{ + public class ClassicHandler + { + private readonly Stream _input; + public const char CR = '\r'; + public const char LF = '\n'; + public static char[] CRLF = {CR, LF}; + public static char[] HeaderKeys = {CR, LF, ':'}; + public const short MinBuffer = 21; // Minimum size of the buffer "Content-Length: X\r\n\r\n" + + public ClassicHandler(Stream input) + { + _input = input; + } + + // don't be async: We already allocated a seperate thread for this. + public Task ProcessInputStream() + { + // some time to attach a debugger + // System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5)); + + // header is encoded in ASCII + // "Content-Length: 0" counts bytes for the following content + // content is encoded in UTF-8 + while (_input.CanRead) + { + var buffer = new byte[300]; + var current = _input.Read(buffer, 0, MinBuffer); + if (current == 0) return Task.CompletedTask; // no more _input + while (current < MinBuffer || + buffer[current - 4] != CR || buffer[current - 3] != LF || + buffer[current - 2] != CR || buffer[current - 1] != LF) + { + var n = _input.Read(buffer, current, 1); + if (n == 0) return Task.CompletedTask; // no more _input, mitigates endless loop here. + current += n; + } + + var headersContent = System.Text.Encoding.ASCII.GetString(buffer, 0, current); + var headers = headersContent.Split(HeaderKeys, StringSplitOptions.RemoveEmptyEntries); + long length = 0; + for (var i = 1; i < headers.Length; i += 2) + { + // starting at i = 1 instead of 0 won't throw, if we have uneven headers' length + var header = headers[i - 1]; + var value = headers[i].Trim(); + if (header.Equals("Content-Length", StringComparison.OrdinalIgnoreCase)) + { + length = 0; + long.TryParse(value, out length); + } + } + + if (length == 0 || length >= int.MaxValue) + { + HandleRequest(string.Empty, CancellationToken.None); + } + else + { + var requestBuffer = new byte[length]; + var received = 0; + while (received < length) + { + var n = _input.Read(requestBuffer, received, requestBuffer.Length - received); + if (n == 0) return Task.CompletedTask; // no more _input + received += n; + } + + // TODO sometimes: encoding should be based on the respective header (including the wrong "utf8" value) + var payload = System.Text.Encoding.ASCII.GetString(requestBuffer); + HandleRequest(payload, CancellationToken.None); + } + } + + return Task.CompletedTask; + } + + private void HandleRequest(string request, CancellationToken cancellationToken) + { + } + } +} diff --git a/benchmarks/Pipeline/Pipeline.csproj b/benchmarks/Pipeline/Pipeline.csproj new file mode 100644 index 000000000..0af22156a --- /dev/null +++ b/benchmarks/Pipeline/Pipeline.csproj @@ -0,0 +1,16 @@ + + + + net472;netcoreapp2.1;netcoreapp3.1 + Exe + false + + + + + + + + + + diff --git a/benchmarks/Pipeline/PipelinesBased.cs b/benchmarks/Pipeline/PipelinesBased.cs new file mode 100644 index 000000000..4ae600c6e --- /dev/null +++ b/benchmarks/Pipeline/PipelinesBased.cs @@ -0,0 +1,224 @@ +using System; +using System.Buffers; +using System.IO.Pipelines; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Pipeline +{ + public class PipelinesBased + { + public PipelinesBased(PipeReader pipeReader) + { + _pipeReader = pipeReader; + _contentLengthData = "Content-Length".Select(x => (byte) x).ToArray(); + _headersBuffer = new Memory(new byte[HeadersFinishedLength]); + _contentLengthBuffer = new Memory(new byte[ContentLengthLength]); + _contentLengthValueBuffer = new byte[20]; // Max string length of the long value + _contentLengthValueMemory = + new Memory(_contentLengthValueBuffer); // Max string length of the long value + } + + + public static readonly byte[] HeadersFinished = + new byte[] {(byte) '\r', (byte) '\n', (byte) '\r', (byte) '\n'}.ToArray(); + + public const int HeadersFinishedLength = 4; + public static readonly char[] HeaderKeys = {'\r', '\n', ':'}; + public const short MinBuffer = 21; // Minimum size of the buffer "Content-Length: X\r\n\r\n" + public static readonly byte[] ContentLength = "Content-Length".Select(x => (byte) x).ToArray(); + public static readonly int ContentLengthLength = 14; + + private readonly PipeReader _pipeReader; + private readonly Memory _headersBuffer; + private readonly Memory _contentLengthBuffer; + private readonly byte[] _contentLengthValueBuffer; + private readonly Memory _contentLengthValueMemory; + private byte[] _contentLengthData; + + private bool TryParseHeaders(ref ReadOnlySequence buffer, out ReadOnlySequence line) + { + // TODO: This might be simplified with SequenceReader... + // Not sure we can move to purely .netstandard 2.1 + if (buffer.Length < MinBuffer || buffer.Length < HeadersFinishedLength) + { + line = default; + return false; + } + + var rentedSpan = _headersBuffer.Span; + + var start = buffer.PositionOf((byte) '\r'); + do + { + if (!start.HasValue) + { + line = default; + return false; + } + + var next = buffer.Slice(start.Value, buffer.GetPosition(4, start.Value)); + next.CopyTo(rentedSpan); + if (IsEqual(rentedSpan, HeadersFinished)) + { + line = buffer.Slice(0, next.End); + buffer = buffer.Slice(next.End); + return true; + } + + start = buffer.Slice(buffer.GetPosition(HeadersFinishedLength, start.Value)).PositionOf((byte) '\r'); + } while (start.HasValue && buffer.Length > MinBuffer); + + line = default; + return false; + } + + static bool IsEqual(in Span headers, in byte[] bytes) + { + var isEqual = true; + var len = bytes.Length; + for (var i = 0; i < len; i++) + { + if (bytes[i] == headers[i]) continue; + isEqual = false; + break; + } + + return isEqual; + } + + private bool TryParseBodyString(in long length, ref ReadOnlySequence buffer, + out ReadOnlySequence line) + { + if (buffer.Length < length) + { + line = default; + return false; + } + + + line = buffer.Slice(0, length); + buffer = buffer.Slice(length); + return true; + } + + bool TryParseContentLength(ref ReadOnlySequence buffer, out long length) + { + do + { + var colon = buffer.PositionOf((byte) ':'); + if (!colon.HasValue) + { + length = -1; + return false; + } + + var slice = buffer.Slice(0, colon.Value); + slice.CopyTo(_contentLengthBuffer.Span); + + if (IsEqual(_contentLengthBuffer.Span, ContentLength)) + { + var position = buffer.GetPosition(1, colon.Value); + var offset = 1; + + while (buffer.TryGet(ref position, out var memory, true) && !memory.Span.IsEmpty) + { + foreach (var t in memory.Span) + { + if (t == (byte) ' ') + { + offset++; + continue; + } + + break; + } + } + + var lengthSlice = buffer.Slice( + buffer.GetPosition(offset, colon.Value), + buffer.PositionOf((byte) '\r') ?? buffer.End + ); + + var whitespacePosition = lengthSlice.PositionOf((byte) ' '); + if (whitespacePosition.HasValue) + { + lengthSlice = lengthSlice.Slice(0, whitespacePosition.Value); + } + + lengthSlice.CopyTo(_contentLengthValueMemory.Span); + if (long.TryParse(Encoding.ASCII.GetString(_contentLengthValueBuffer), out length)) + { + // Reset the array otherwise smaller numbers will be inflated; + for (var i = 0; i < lengthSlice.Length; i++) _contentLengthValueMemory.Span[i] = 0; + return true; + } + // Reset the array otherwise smaller numbers will be inflated; + for (var i = 0; i < lengthSlice.Length; i++) _contentLengthValueMemory.Span[i] = 0; + + // _logger.LogError("Unable to get length from content length header..."); + return false; + } + else + { + buffer = buffer.Slice(buffer.GetPosition(1, buffer.PositionOf((byte) '\n') ?? buffer.End)); + } + } while (true); + } + + internal async Task ProcessInputStream(CancellationToken cancellationToken) + { + // some time to attach a debugger + // System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5)); + + var headersParsed = false; + long length = 0; + while (!cancellationToken.IsCancellationRequested) + { + var result = await _pipeReader.ReadAsync(cancellationToken); + var buffer = result.Buffer; + + if (!headersParsed) + { + if (TryParseHeaders(ref buffer, out var line)) + { + if (TryParseContentLength(ref line, out length)) + { + headersParsed = true; + } + } + } + + if (headersParsed && length == 0) + { + HandleRequest(new ReadOnlySequence(Array.Empty())); + headersParsed = false; + } + + if (headersParsed) + { + if (TryParseBodyString(length, ref buffer, out var line)) + { + headersParsed = false; + length = 0; + HandleRequest(line); + } + } + + _pipeReader.AdvanceTo(buffer.Start, buffer.End); + + // Stop reading if there's no more data coming. + if (result.IsCompleted && buffer.GetPosition(0, buffer.Start).Equals(buffer.GetPosition(0, buffer.End))) + { + break; + } + } + } + + private void HandleRequest(in ReadOnlySequence request) + { + } + } +} diff --git a/sample/SampleServer/Program.cs b/sample/SampleServer/Program.cs index 81b9ecf3b..5aedb8cd1 100644 --- a/sample/SampleServer/Program.cs +++ b/sample/SampleServer/Program.cs @@ -1,4 +1,7 @@ using System; +using System.Diagnostics; +using System.IO.Pipelines; +using System.IO.Pipes; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -7,6 +10,7 @@ using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Server; using Serilog; +using PipeOptions = System.IO.Pipes.PipeOptions; namespace SampleServer { @@ -28,20 +32,24 @@ static async Task MainAsync(string[] args) Log.Logger = new LoggerConfiguration() .Enrich.FromLogContext() .WriteTo.File("log.txt", rollingInterval: RollingInterval.Day) + .WriteTo.Debug() .MinimumLevel.Verbose() .CreateLogger(); + var pipe = new NamedPipeServerStream("samplepipe", PipeDirection.InOut, -1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); + await pipe.WaitForConnectionAsync(); + Log.Logger.Information("This only goes file..."); IObserver workDone = null; var server = await LanguageServer.From(options => options - .WithInput(Console.OpenStandardInput()) - .WithOutput(Console.OpenStandardOutput()) + .WithInput(pipe) + .WithOutput(pipe) .ConfigureLogging(x => x - .AddSerilog() - .AddLanguageServer() + .AddSerilog(Log.Logger) + .AddLanguageProtocolLogging() .SetMinimumLevel(LogLevel.Debug)) .WithHandler() .WithHandler() diff --git a/sample/SampleServer/SampleServer.csproj b/sample/SampleServer/SampleServer.csproj index 5280c365c..445328d32 100644 --- a/sample/SampleServer/SampleServer.csproj +++ b/sample/SampleServer/SampleServer.csproj @@ -13,12 +13,15 @@ + + + diff --git a/sample/SampleServer/SemanticTokens.cs b/sample/SampleServer/SemanticTokens.cs index 1ca1b3766..b96ccad40 100644 --- a/sample/SampleServer/SemanticTokens.cs +++ b/sample/SampleServer/SemanticTokens.cs @@ -35,7 +35,6 @@ public SemanticTokens(ILogger logger) : base(new SemanticTokensR SemanticTokensParams request, CancellationToken cancellationToken) { var result = await base.Handle(request, cancellationToken); - _logger.LogInformation(JsonConvert.SerializeObject(result)); return result; } @@ -43,7 +42,6 @@ public SemanticTokens(ILogger logger) : base(new SemanticTokensR SemanticTokensRangeParams request, CancellationToken cancellationToken) { var result = await base.Handle(request, cancellationToken); - _logger.LogInformation(JsonConvert.SerializeObject(result)); return result; } @@ -51,7 +49,6 @@ public override async Task Handle(SemanticT CancellationToken cancellationToken) { var result = await base.Handle(request, cancellationToken); - _logger.LogInformation(JsonConvert.SerializeObject(result)); return result; } diff --git a/src/Client/Client.csproj b/src/Client/Client.csproj index b51e0e4a5..d431c3517 100644 --- a/src/Client/Client.csproj +++ b/src/Client/Client.csproj @@ -15,5 +15,7 @@ + + diff --git a/src/Client/Clients/TextDocumentClient.Completions.cs b/src/Client/Clients/TextDocumentClient.Completions.cs deleted file mode 100644 index a32de695e..000000000 --- a/src/Client/Clients/TextDocumentClient.Completions.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using OmniSharp.Extensions.LanguageServer.Protocol; -using OmniSharp.Extensions.LanguageServer.Protocol.Models; - -namespace OmniSharp.Extensions.LanguageServer.Client.Clients -{ - /// - /// Client for the LSP Text Document API. - /// - public partial class TextDocumentClient - { - /// - /// Request completions at the specified document position. - /// - /// - /// The document URI. - /// - /// - /// The target line (0-based). - /// - /// - /// The target column (0-based). - /// - /// - /// An optional that can be used to cancel the request. - /// - /// - /// A that resolves to the completions or null if no completions are available at the specified position. - /// - public Task Completions(DocumentUri documentUri, int line, int column, CancellationToken cancellationToken = default(CancellationToken)) - { - return PositionalRequest(TextDocumentNames.Completion, documentUri, line, column, cancellationToken); - } - } -} diff --git a/src/Client/Clients/TextDocumentClient.Definition.cs b/src/Client/Clients/TextDocumentClient.Definition.cs deleted file mode 100644 index 3380e50bc..000000000 --- a/src/Client/Clients/TextDocumentClient.Definition.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using OmniSharp.Extensions.LanguageServer.Protocol; -using OmniSharp.Extensions.LanguageServer.Protocol.Models; - -namespace OmniSharp.Extensions.LanguageServer.Client.Clients -{ - /// - /// Client for the LSP Text Document API. - /// - public partial class TextDocumentClient - { - /// - /// Request definition at the specified document position. - /// - /// - /// The document URI. - /// - /// - /// The target line (0-based). - /// - /// - /// The target column (0-based). - /// - /// - /// An optional that can be used to cancel the request. - /// - /// - /// A that resolves to the completions or null if no definitions are available at the specified position. - /// - public Task Definition(DocumentUri documentUri, int line, int column, CancellationToken cancellationToken = default(CancellationToken)) - { - return PositionalRequest(TextDocumentNames.Definition, documentUri, line, column, cancellationToken); - } - } -} diff --git a/src/Client/Clients/TextDocumentClient.Diagnostics.cs b/src/Client/Clients/TextDocumentClient.Diagnostics.cs deleted file mode 100644 index 111955f83..000000000 --- a/src/Client/Clients/TextDocumentClient.Diagnostics.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Collections.Generic; -using OmniSharp.Extensions.LanguageServer.Protocol; -using OmniSharp.Extensions.LanguageServer.Protocol.Models; - -namespace OmniSharp.Extensions.LanguageServer.Client.Clients -{ - /// - /// Client for the LSP Text Document API. - /// - public partial class TextDocumentClient - { - /// - /// Register a handler for diagnostics published by the language server. - /// - /// - /// A that is called to publish the diagnostics. - /// - /// - /// An representing the registration. - /// - /// - /// The diagnostics should replace any previously published diagnostics for the specified document. - /// - public IDisposable OnPublishDiagnostics(PublishDiagnosticsHandler handler) - { - if (handler == null) - throw new ArgumentNullException(nameof(handler)); - - return Client.HandleNotification(TextDocumentNames.PublishDiagnostics, notification => - { - if (notification.Diagnostics == null) - if (notification.Diagnostics == null) - return; // Invalid notification. - - var diagnostics = new List(); - if (notification.Diagnostics != null) - diagnostics.AddRange(notification.Diagnostics); - - handler(notification.Uri, diagnostics); - }); - } - } -} diff --git a/src/Client/Clients/TextDocumentClient.DocumentHighlights.cs b/src/Client/Clients/TextDocumentClient.DocumentHighlights.cs deleted file mode 100644 index f91efcc7a..000000000 --- a/src/Client/Clients/TextDocumentClient.DocumentHighlights.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using OmniSharp.Extensions.LanguageServer.Protocol; -using OmniSharp.Extensions.LanguageServer.Protocol.Models; - -namespace OmniSharp.Extensions.LanguageServer.Client.Clients -{ - /// - /// Client for the LSP Text Document API. - /// - public partial class TextDocumentClient - { - /// - /// Request document highlights at the specified document position. - /// - /// - /// The document URI. - /// - /// - /// The target line (0-based). - /// - /// - /// The target column (0-based). - /// - /// - /// An optional that can be used to cancel the request. - /// - /// - /// A that resolves to the completions or null if no document highlights are available at the specified position. - /// - public Task DocumentHighlights(DocumentUri documentUri, int line, int column, CancellationToken cancellationToken = default(CancellationToken)) - { - return PositionalRequest(TextDocumentNames.DocumentHighlight, documentUri, line, column, cancellationToken); - } - } -} diff --git a/src/Client/Clients/TextDocumentClient.FoldingRanges.cs b/src/Client/Clients/TextDocumentClient.FoldingRanges.cs deleted file mode 100644 index 6e8c6f247..000000000 --- a/src/Client/Clients/TextDocumentClient.FoldingRanges.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using OmniSharp.Extensions.LanguageServer.Protocol; -using OmniSharp.Extensions.LanguageServer.Protocol.Models; - -namespace OmniSharp.Extensions.LanguageServer.Client.Clients -{ - /// - /// Client for the LSP Text Document API. - /// - public partial class TextDocumentClient - { - /// - /// Request document highlights at the specified document position. - /// - /// - /// The document URI. - /// - /// - /// The target line (0-based). - /// - /// - /// The target column (0-based). - /// - /// - /// An optional that can be used to cancel the request. - /// - /// - /// A that resolves to the completions or null if no document highlights are available at the specified position. - /// - public async Task> FoldingRanges(DocumentUri documentUri, CancellationToken cancellationToken = default(CancellationToken)) - { - var request = new FoldingRangeRequestParam { - TextDocument = new TextDocumentItem { - Uri = documentUri - } - }; - - return await Client.SendRequest>(TextDocumentNames.FoldingRange, request, cancellationToken).ConfigureAwait(false); - } - } -} diff --git a/src/Client/Clients/TextDocumentClient.Hover.cs b/src/Client/Clients/TextDocumentClient.Hover.cs deleted file mode 100644 index 9b58d7abc..000000000 --- a/src/Client/Clients/TextDocumentClient.Hover.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using OmniSharp.Extensions.LanguageServer.Protocol; -using OmniSharp.Extensions.LanguageServer.Protocol.Models; - -namespace OmniSharp.Extensions.LanguageServer.Client.Clients -{ - /// - /// Client for the LSP Text Document API. - /// - public partial class TextDocumentClient - { - /// - /// Request hover information at the specified document position. - /// - /// - /// The full file-system path of the text document. - /// - /// - /// The target line (0-based). - /// - /// - /// The target column (0-based). - /// - /// - /// An optional that can be used to cancel the request. - /// - /// - /// A that resolves to the hover information or null if no hover information is available at the specified position. - /// - public Task Hover(string filePath, int line, int column, CancellationToken cancellationToken = default(CancellationToken)) - { - if (string.IsNullOrWhiteSpace(filePath)) - throw new ArgumentException("Argument cannot be null, empty, or entirely composed of whitespace: 'filePath'.", nameof(filePath)); - - return Hover(DocumentUri.FromFileSystemPath(filePath), line, column, cancellationToken); - } - - /// - /// Request hover information at the specified document position. - /// - /// - /// The document URI. - /// - /// - /// The target line (0-based). - /// - /// - /// The target column (0-based). - /// - /// - /// An optional that can be used to cancel the request. - /// - /// - /// A that resolves to the hover information or null if no hover information is available at the specified position. - /// - public Task Hover(DocumentUri documentUri, int line, int column, CancellationToken cancellationToken = default(CancellationToken)) - { - return PositionalRequest(TextDocumentNames.Hover, documentUri, line, column, cancellationToken); - } - } -} diff --git a/src/Client/Clients/TextDocumentClient.SignatureHelp.cs b/src/Client/Clients/TextDocumentClient.SignatureHelp.cs deleted file mode 100644 index 54fc6dcfa..000000000 --- a/src/Client/Clients/TextDocumentClient.SignatureHelp.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using OmniSharp.Extensions.LanguageServer.Protocol; -using OmniSharp.Extensions.LanguageServer.Protocol.Models; - -namespace OmniSharp.Extensions.LanguageServer.Client.Clients -{ - /// - /// Client for the LSP Text Document API. - /// - public partial class TextDocumentClient - { - /// - /// Request signature help at the specified document position. - /// - /// - /// The document URI. - /// - /// - /// The target line (0-based). - /// - /// - /// The target column (0-based). - /// - /// - /// An optional that can be used to cancel the request. - /// - /// - /// A that resolves to the completions or null if no completions are available at the specified position. - /// - public Task SignatureHelp(DocumentUri documentUri, int line, int column, CancellationToken cancellationToken = default(CancellationToken)) - { - return PositionalRequest(TextDocumentNames.SignatureHelp, documentUri, line, column, cancellationToken); - } - } -} diff --git a/src/Client/Clients/TextDocumentClient.Sync.cs b/src/Client/Clients/TextDocumentClient.Sync.cs deleted file mode 100644 index f554f9c75..000000000 --- a/src/Client/Clients/TextDocumentClient.Sync.cs +++ /dev/null @@ -1,272 +0,0 @@ -using System; -using System.IO; -using OmniSharp.Extensions.LanguageServer.Protocol; -using OmniSharp.Extensions.LanguageServer.Protocol.Models; - -namespace OmniSharp.Extensions.LanguageServer.Client.Clients -{ - /// - /// Client for the LSP Text Document API. - /// - public partial class TextDocumentClient - { - /// - /// Notify the language server that the client has opened a text document. - /// - /// - /// The full path to the text document. - /// - /// - /// The document language type (e.g. "xml"). - /// - /// - /// The document version (optional). - /// - /// - /// Will automatically populate the document text, if available. - /// - public void DidOpen(string filePath, string languageId, int version = 0) - { - if (string.IsNullOrWhiteSpace(filePath)) - throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(filePath)}.", nameof(filePath)); - - string text = null; - if (File.Exists(filePath)) - text = File.ReadAllText(filePath); - - DidOpen( - DocumentUri.FromFileSystemPath(filePath), - languageId, - text, - version - ); - } - - /// - /// Notify the language server that the client has opened a text document. - /// - /// - /// The full file-system path of the text document. - /// - /// - /// The document language type (e.g. "xml"). - /// - /// - /// The document text (pass null to have the language server retrieve the text itself). - /// - /// - /// The document version (optional). - /// - public void DidOpen(string filePath, string languageId, string text, int version = 0) - { - if (string.IsNullOrWhiteSpace(filePath)) - throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(filePath)}.", nameof(filePath)); - - DidOpen(DocumentUri.FromFileSystemPath(filePath), languageId, text, version); - } - - /// - /// Notify the language server that the client has opened a text document. - /// - /// - /// The document URI. - /// - /// - /// The document language type (e.g. "xml"). - /// - /// - /// The document text. - /// - /// - /// The document version (optional). - /// - public void DidOpen(DocumentUri documentUri, string languageId, string text, int version = 0) - { - if (documentUri == null) - throw new ArgumentNullException(nameof(documentUri)); - - Client.SendNotification(TextDocumentNames.DidOpen, new DidOpenTextDocumentParams - { - TextDocument = new TextDocumentItem - { - Text = text, - LanguageId = languageId, - Version = version, - Uri = documentUri - } - }); - } - - /// - /// Notify the language server that the client has changed a text document. - /// - /// - /// The full path to the text document. - /// - /// - /// The document language type (e.g. "xml"). - /// - /// - /// The document version (optional). - /// - /// - /// This style of notification is used when the client does not support partial updates (i.e. one or more updates with an associated range). - /// - /// Will automatically populate the document text, if available. - /// - public void DidChange(string filePath, string languageId, int version = 0) - { - if (string.IsNullOrWhiteSpace(filePath)) - throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(filePath)}.", nameof(filePath)); - - string text = null; - if (File.Exists(filePath)) - text = File.ReadAllText(filePath); - - DidChange( - DocumentUri.FromFileSystemPath(filePath), - languageId, - text, - version - ); - } - - /// - /// Notify the language server that the client has changed a text document. - /// - /// - /// The full file-system path of the text document. - /// - /// - /// The document language type (e.g. "xml"). - /// - /// - /// The document text (pass null to have the language server retrieve the text itself). - /// - /// - /// The document version (optional). - /// - /// - /// This style of notification is used when the client does not support partial updates (i.e. one or more updates with an associated range). - /// - public void DidChange(string filePath, string languageId, string text, int version = 0) - { - if (string.IsNullOrWhiteSpace(filePath)) - throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(filePath)}.", nameof(filePath)); - - DidChange(DocumentUri.FromFileSystemPath(filePath), languageId, text, version); - } - - /// - /// Notify the language server that the client has changed a text document. - /// - /// - /// The document URI. - /// - /// - /// The document language type (e.g. "xml"). - /// - /// - /// The document text. - /// - /// - /// The document version (optional). - /// - /// - /// This style of notification is used when the client does not support partial updates (i.e. one or more updates with an associated range). - /// - public void DidChange(DocumentUri documentUri, string languageId, string text, int version = 0) - { - if (documentUri == null) - throw new ArgumentNullException(nameof(documentUri)); - - Client.SendNotification(TextDocumentNames.DidChange, new DidChangeTextDocumentParams - { - TextDocument = new VersionedTextDocumentIdentifier - { - Version = version, - Uri = documentUri - }, - ContentChanges = new TextDocumentContentChangeEvent[] - { - new TextDocumentContentChangeEvent - { - Text = text - } - } - }); - } - - /// - /// Notify the language server that the client has closed a text document. - /// - /// - /// The full file-system path of the text document. - /// - public void DidClose(string filePath) - { - if (string.IsNullOrWhiteSpace(filePath)) - throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(filePath)}.", nameof(filePath)); - - DidClose( - DocumentUri.FromFileSystemPath(filePath) - ); - } - - /// - /// Notify the language server that the client has closed a text document. - /// - /// - /// The document URI. - /// - public void DidClose(DocumentUri documentUri) - { - if (documentUri == null) - throw new ArgumentNullException(nameof(documentUri)); - - Client.SendNotification(TextDocumentNames.DidClose, new DidCloseTextDocumentParams - { - TextDocument = new TextDocumentItem - { - Uri = documentUri - } - }); - } - - /// - /// Notify the language server that the client has saved a text document. - /// - /// - /// The full file-system path of the text document. - /// - public void DidSave(string filePath) - { - if (string.IsNullOrWhiteSpace(filePath)) - throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(filePath)}.", nameof(filePath)); - - DidSave( - DocumentUri.FromFileSystemPath(filePath) - ); - } - - /// - /// Notify the language server that the client has saved a text document. - /// - /// - /// The document URI. - /// - public void DidSave(DocumentUri documentUri) - { - if (documentUri == null) - throw new ArgumentNullException(nameof(documentUri)); - - Client.SendNotification(TextDocumentNames.DidSave, new DidSaveTextDocumentParams - { - TextDocument = new TextDocumentItem - { - Uri = documentUri - } - }); - } - } -} diff --git a/src/Client/Clients/TextDocumentClient.cs b/src/Client/Clients/TextDocumentClient.cs deleted file mode 100644 index 33e3c0478..000000000 --- a/src/Client/Clients/TextDocumentClient.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using OmniSharp.Extensions.LanguageServer.Protocol; -using OmniSharp.Extensions.LanguageServer.Protocol.Models; - -namespace OmniSharp.Extensions.LanguageServer.Client.Clients -{ - /// - /// Client for the LSP Text Document API. - /// - public partial class TextDocumentClient - { - /// - /// Create a new . - /// - /// - /// The language client providing the API. - /// - public TextDocumentClient(LanguageClient client) - { - if (client == null) - throw new ArgumentNullException(nameof(client)); - - Client = client; - } - - /// - /// The language client providing the API. - /// - public LanguageClient Client { get; } - - /// - /// Make a request to the server for the specified document position. - /// - /// - /// The response payload type. - /// - /// - /// The name of the operation to invoke. - /// - /// - /// The URI of the target document. - /// - /// - /// The target line numer (0-based). - /// - /// - /// The target column (0-based). - /// - /// - /// A cancellation token that can be used to cancel the request. - /// - /// - /// A representing the request. - /// - async Task PositionalRequest(string method, DocumentUri documentUri, int line, int column, CancellationToken cancellationToken) - { - if (string.IsNullOrWhiteSpace(method)) - throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(method)}.", nameof(method)); - - if (documentUri == null) - throw new ArgumentNullException(nameof(documentUri)); - - var request = new TextDocumentPositionParams - { - TextDocument = new TextDocumentItem - { - Uri = documentUri - }, - Position = new Position - { - Line = line, - Character = column - } - }; - - return await Client.SendRequest(method, request, cancellationToken).ConfigureAwait(false); - } - } -} diff --git a/src/Client/Clients/WindowClient.cs b/src/Client/Clients/WindowClient.cs deleted file mode 100644 index a8965e2e6..000000000 --- a/src/Client/Clients/WindowClient.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using OmniSharp.Extensions.LanguageServer.Protocol; -using OmniSharp.Extensions.LanguageServer.Protocol.Models; - -namespace OmniSharp.Extensions.LanguageServer.Client.Clients -{ - /// - /// Client for the LSP Window API. - /// - public class WindowClient - { - /// - /// Create a new . - /// - /// - /// The language client providing the API. - /// - public WindowClient(LanguageClient client) - { - if (client == null) - throw new ArgumentNullException(nameof(client)); - - Client = client; - } - - /// - /// The language client providing the API. - /// - public LanguageClient Client { get; } - - /// - /// Register a handler for "window/logMessage" notifications from the server. - /// - /// - /// The that will be called for each log message. - /// - /// - /// An representing the registration. - /// - public IDisposable OnLogMessage(LogMessageHandler handler) - { - if (handler == null) - throw new ArgumentNullException(nameof(handler)); - - return Client.HandleNotification(WindowNames.LogMessage, - notification => handler(notification.Message, notification.Type) - ); - } - } -} diff --git a/src/Client/Clients/WorkspaceClient.cs b/src/Client/Clients/WorkspaceClient.cs deleted file mode 100644 index 75a33e2c1..000000000 --- a/src/Client/Clients/WorkspaceClient.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using Newtonsoft.Json.Linq; -using OmniSharp.Extensions.LanguageServer.Protocol; - -namespace OmniSharp.Extensions.LanguageServer.Client.Clients -{ - /// - /// Client for the LSP Workspace API. - /// - public class WorkspaceClient - { - /// - /// Create a new . - /// - /// - /// The language client providing the API. - /// - public WorkspaceClient(LanguageClient client) - { - if (client == null) - throw new ArgumentNullException(nameof(client)); - - Client = client; - } - - /// - /// The language client providing the API. - /// - public LanguageClient Client { get; } - - /// - /// Notify the language server that workspace configuration has changed. - /// - /// - /// A representing the workspace configuration (or a subset thereof). - /// - public void DidChangeConfiguration(JObject configuration) - { - if (configuration == null) - throw new ArgumentNullException(nameof(configuration)); - - Client.SendNotification(WorkspaceNames.DidChangeConfiguration, new JObject( - new JProperty("settings", configuration) - )); - } - } -} diff --git a/src/Client/Dispatcher/LspDispatcher.cs b/src/Client/Dispatcher/LspDispatcher.cs deleted file mode 100644 index e6d400fcc..000000000 --- a/src/Client/Dispatcher/LspDispatcher.cs +++ /dev/null @@ -1,171 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Reactive.Disposables; -using System.Threading; -using System.Threading.Tasks; -using Newtonsoft.Json.Linq; -using OmniSharp.Extensions.JsonRpc; -using OmniSharp.Extensions.LanguageServer.Client.Handlers; - -namespace OmniSharp.Extensions.LanguageServer.Client.Dispatcher -{ - /// - /// Dispatches requests and notifications from a language server to a language client. - /// - public class LspDispatcher - { - /// - /// Invokers for registered handlers. - /// - readonly ConcurrentDictionary _handlers = new ConcurrentDictionary(); - - /// - /// Create a new . - /// - /// - /// The JSON serialiser for notification / request / response payloads. - /// - public LspDispatcher(ISerializer serializer) - { - if (serializer == null) - throw new ArgumentNullException(nameof(serializer)); - - Serializer = serializer; - } - - /// - /// The JSON serialiser to use for notification / request / response payloads. - /// - public ISerializer Serializer { get; set; } - - /// - /// Register a handler invoker. - /// - /// - /// The handler. - /// - /// - /// An representing the registration. - /// - public IDisposable RegisterHandler(IHandler handler) - { - if (handler == null) - throw new ArgumentNullException(nameof(handler)); - - string method = handler.Method; - - if (!_handlers.TryAdd(method, handler)) - throw new InvalidOperationException($"There is already a handler registered for method '{handler.Method}'."); - - return Disposable.Create( - () => _handlers.TryRemove(method, out _) - ); - } - - /// - /// Attempt to handle an empty notification. - /// - /// - /// The notification method name. - /// - /// - /// true, if an empty notification handler was registered for specified method; otherwise, false. - /// - public async Task TryHandleEmptyNotification(string method) - { - if (string.IsNullOrWhiteSpace(method)) - throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(method)}.", nameof(method)); - - if (_handlers.TryGetValue(method, out IHandler handler) && handler is IInvokeEmptyNotificationHandler emptyNotificationHandler) - { - await emptyNotificationHandler.Invoke(); - - return true; - } - - return false; - } - - /// - /// Attempt to handle a notification. - /// - /// - /// The notification method name. - /// - /// - /// The notification message. - /// - /// - /// true, if a notification handler was registered for specified method; otherwise, false. - /// - public async Task TryHandleNotification(string method, JToken notification) - { - if (string.IsNullOrWhiteSpace(method)) - throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(method)}.", nameof(method)); - - if (_handlers.TryGetValue(method, out IHandler handler) && handler is IInvokeNotificationHandler notificationHandler) - { - object notificationPayload = DeserializePayload(notificationHandler.PayloadType, notification); - - await notificationHandler.Invoke(notificationPayload); - - return true; - } - - return false; - } - - /// - /// Attempt to handle a request. - /// - /// - /// The request method name. - /// - /// - /// The request message. - /// - /// - /// A that can be used to cancel the operation. - /// - /// - /// If a registered handler was found, a representing the operation; otherwise, null. - /// - public Task TryHandleRequest(string method, JToken request, CancellationToken cancellationToken) - { - if (string.IsNullOrWhiteSpace(method)) - throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(method)}.", nameof(method)); - - if (_handlers.TryGetValue(method, out IHandler handler) && handler is IInvokeRequestHandler requestHandler) - { - object requestPayload = DeserializePayload(requestHandler.PayloadType, request); - - return requestHandler.Invoke(requestPayload, cancellationToken); - } - - return null; - } - - /// - /// Deserialise a notification / request payload from JSON. - /// - /// - /// The payload's CLR type. - /// - /// - /// JSON representing the payload. - /// - /// - /// The deserialised payload (if one is present and expected). - /// - object DeserializePayload(Type payloadType, JToken payload) - { - if (payloadType == null) - throw new ArgumentNullException(nameof(payloadType)); - - if (payloadType == null || payload == null) - return null; - - return payload.ToObject(payloadType, Serializer.JsonSerializer); - } - } -} diff --git a/src/Client/Dispatcher/LspDispatcherExtensions.cs b/src/Client/Dispatcher/LspDispatcherExtensions.cs deleted file mode 100644 index b492ef8ae..000000000 --- a/src/Client/Dispatcher/LspDispatcherExtensions.cs +++ /dev/null @@ -1,147 +0,0 @@ -using System; -using OmniSharp.Extensions.LanguageServer.Client.Handlers; - -namespace OmniSharp.Extensions.LanguageServer.Client.Dispatcher -{ - /// - /// Extension methods for enabling various styles of handler registration. - /// - public static class LspDispatcherExtensions - { - /// - /// Register a handler for empty notifications. - /// - /// - /// The . - /// - /// - /// The name of the notification method to handle. - /// - /// - /// A delegate that implements the handler. - /// - /// - /// An representing the registration. - /// - public static IDisposable HandleEmptyNotification(this LspDispatcher clientDispatcher, string method, NotificationHandler handler) - { - if (clientDispatcher == null) - throw new ArgumentNullException(nameof(clientDispatcher)); - - if (string.IsNullOrWhiteSpace(method)) - throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(method)}.", nameof(method)); - - if (handler == null) - throw new ArgumentNullException(nameof(handler)); - - return clientDispatcher.RegisterHandler( - new DelegateEmptyNotificationHandler(method, handler) - ); - } - - /// - /// Register a handler for notifications. - /// - /// - /// The notification message type. - /// - /// - /// The . - /// - /// - /// The name of the notification method to handle. - /// - /// - /// A delegate that implements the handler. - /// - /// - /// An representing the registration. - /// - public static IDisposable HandleNotification(this LspDispatcher clientDispatcher, string method, NotificationHandler handler) - { - if (clientDispatcher == null) - throw new ArgumentNullException(nameof(clientDispatcher)); - - if (string.IsNullOrWhiteSpace(method)) - throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(method)}.", nameof(method)); - - if (handler == null) - throw new ArgumentNullException(nameof(handler)); - - return clientDispatcher.RegisterHandler( - new DelegateNotificationHandler(method, handler) - ); - } - - /// - /// Register a handler for requests. - /// - /// - /// The request message type. - /// - /// - /// The . - /// - /// - /// The name of the request method to handle. - /// - /// - /// A delegate that implements the handler. - /// - /// - /// An representing the registration. - /// - public static IDisposable HandleRequest(this LspDispatcher clientDispatcher, string method, RequestHandler handler) - { - if (clientDispatcher == null) - throw new ArgumentNullException(nameof(clientDispatcher)); - - if (string.IsNullOrWhiteSpace(method)) - throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(method)}.", nameof(method)); - - if (handler == null) - throw new ArgumentNullException(nameof(handler)); - - return clientDispatcher.RegisterHandler( - new DelegateRequestHandler(method, handler) - ); - } - - /// - /// Register a handler for requests. - /// - /// - /// The request message type. - /// - /// - /// The response message type. - /// - /// - /// The . - /// - /// - /// The name of the request method to handle. - /// - /// - /// A delegate that implements the handler. - /// - /// - /// An representing the registration. - /// - public static IDisposable HandleRequest(this LspDispatcher clientDispatcher, string method, RequestHandler handler) - { - if (clientDispatcher == null) - throw new ArgumentNullException(nameof(clientDispatcher)); - - if (string.IsNullOrWhiteSpace(method)) - throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(method)}.", nameof(method)); - - if (handler == null) - throw new ArgumentNullException(nameof(handler)); - - return clientDispatcher.RegisterHandler( - new DelegateRequestResponseHandler(method, handler) - ); - } - } -} diff --git a/src/Client/HandlerDelegates.cs b/src/Client/HandlerDelegates.cs deleted file mode 100644 index aad41cc61..000000000 --- a/src/Client/HandlerDelegates.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace OmniSharp.Extensions.LanguageServer.Client -{ - /// - /// A handler for notifications. - /// - /// - /// The notification message type. - /// - /// - /// The notification message. - /// - public delegate void NotificationHandler(TNotification notification); - - /// - /// A handler for requests that return responses. - /// - /// - /// The request message type. - /// - /// - /// The response message type. - /// - /// - /// The request message. - /// - /// - /// A that can be used to cancel the operation. - /// - /// - /// A representing the operation that resolves to the response message. - /// - public delegate Task RequestHandler(TRequest request, CancellationToken cancellationToken); -} diff --git a/src/Client/Handlers/DelegateEmptyNotificationHandler.cs b/src/Client/Handlers/DelegateEmptyNotificationHandler.cs deleted file mode 100644 index 5b8ef95a0..000000000 --- a/src/Client/Handlers/DelegateEmptyNotificationHandler.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace OmniSharp.Extensions.LanguageServer.Client.Handlers -{ - /// - /// A delegate-based handler for empty notifications. - /// - public class DelegateEmptyNotificationHandler - : DelegateHandler, IInvokeEmptyNotificationHandler - { - /// - /// Create a new . - /// - /// - /// The name of the method handled by the handler. - /// - /// - /// The delegate that implements the handler. - /// - public DelegateEmptyNotificationHandler(string method, NotificationHandler handler) - : base(method) - { - if (handler == null) - throw new ArgumentNullException(nameof(handler)); - - Handler = handler; - } - - /// - /// The delegate that implements the handler. - /// - public NotificationHandler Handler { get; } - - /// - /// The expected CLR type of the notification payload (null, since the handler does not use the request payload). - /// - public override Type PayloadType => null; - - /// - /// Invoke the handler. - /// - /// - /// A representing the operation. - /// - public async Task Invoke() - { - await Task.Yield(); - - Handler(); - } - } -} diff --git a/src/Client/Handlers/DelegateHandler.cs b/src/Client/Handlers/DelegateHandler.cs deleted file mode 100644 index 73c31a21a..000000000 --- a/src/Client/Handlers/DelegateHandler.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; - -namespace OmniSharp.Extensions.LanguageServer.Client.Handlers -{ - /// - /// The base class for delegate-based message handlers. - /// - public abstract class DelegateHandler - : IHandler - { - /// - /// Create a new . - /// - /// - /// The name of the method handled by the handler. - /// - protected DelegateHandler(string method) - { - if (string.IsNullOrWhiteSpace(method)) - throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(method)}.", nameof(method)); - - Method = method; - } - - /// - /// The name of the method handled by the handler. - /// - public string Method { get; } - - /// - /// The expected CLR type of the request / notification payload (if any; null if the handler does not use the request payload). - /// - public abstract Type PayloadType { get; } - } -} diff --git a/src/Client/Handlers/DelegateNotificationHandler.cs b/src/Client/Handlers/DelegateNotificationHandler.cs deleted file mode 100644 index e80f20b98..000000000 --- a/src/Client/Handlers/DelegateNotificationHandler.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace OmniSharp.Extensions.LanguageServer.Client.Handlers -{ - /// - /// A delegate-based handler for notifications. - /// - /// - /// The notification message type. - /// - public class DelegateNotificationHandler - : DelegateHandler, IInvokeNotificationHandler - { - /// - /// Create a new . - /// - /// - /// The name of the method handled by the handler. - /// - /// - /// The delegate that implements the handler. - /// - public DelegateNotificationHandler(string method, NotificationHandler handler) - : base(method) - { - if (handler == null) - throw new ArgumentNullException(nameof(handler)); - - Handler = handler; - } - - /// - /// The delegate that implements the handler. - /// - public NotificationHandler Handler { get; } - - /// - /// The expected CLR type of the notification payload. - /// - public override Type PayloadType => typeof(TNotification); - - /// - /// Invoke the handler. - /// - /// - /// The notification message. - /// - /// - /// A representing the operation. - /// - public async Task Invoke(object notification) - { - await Task.Yield(); - - Handler( - (TNotification)notification - ); - } - } -} diff --git a/src/Client/Handlers/DelegateRequestHandler.cs b/src/Client/Handlers/DelegateRequestHandler.cs deleted file mode 100644 index 253adbf01..000000000 --- a/src/Client/Handlers/DelegateRequestHandler.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace OmniSharp.Extensions.LanguageServer.Client.Handlers -{ - /// - /// A delegate-based handler for requests whose responses have no payload (i.e. void return type). - /// - /// - /// The request message type. - /// - public class DelegateRequestHandler - : DelegateHandler, IInvokeRequestHandler - { - /// - /// Create a new . - /// - /// - /// The name of the method handled by the handler. - /// - /// - /// The delegate that implements the handler. - /// - public DelegateRequestHandler(string method, RequestHandler handler) - : base(method) - { - if (handler == null) - throw new ArgumentNullException(nameof(handler)); - - Handler = handler; - } - - /// - /// The delegate that implements the handler. - /// - public RequestHandler Handler { get; } - - /// - /// The expected CLR type of the request payload. - /// - public override Type PayloadType => typeof(TRequest); - - /// - /// Invoke the handler. - /// - /// - /// The request message. - /// - /// - /// A that can be used to cancel the operation. - /// - /// - /// A representing the operation. - /// - public async Task Invoke(object request, CancellationToken cancellationToken) - { - await Handler( - (TRequest)request, - cancellationToken - ); - - return null; - } - } -} diff --git a/src/Client/Handlers/DelegateRequestResponseHandler.cs b/src/Client/Handlers/DelegateRequestResponseHandler.cs deleted file mode 100644 index b19bcef83..000000000 --- a/src/Client/Handlers/DelegateRequestResponseHandler.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace OmniSharp.Extensions.LanguageServer.Client.Handlers -{ - /// - /// A delegate-based handler for requests whose responses have payloads. - /// - /// - /// The request message type. - /// - /// - /// The response message type. - /// - public class DelegateRequestResponseHandler - : DelegateHandler, IInvokeRequestHandler - { - /// - /// Create a new . - /// - /// - /// The name of the method handled by the handler. - /// - /// - /// The delegate that implements the handler. - /// - public DelegateRequestResponseHandler(string method, RequestHandler handler) - : base(method) - { - if (handler == null) - throw new ArgumentNullException(nameof(handler)); - - Handler = handler; - } - - /// - /// The delegate that implements the handler. - /// - public RequestHandler Handler { get; } - - /// - /// The expected CLR type of the request payload. - /// - public override Type PayloadType => typeof(TRequest); - - /// - /// Invoke the handler. - /// - /// - /// The request message. - /// - /// - /// A that can be used to cancel the operation. - /// - /// - /// A representing the operation. - /// - public async Task Invoke(object request, CancellationToken cancellationToken) - { - return await Handler( - (TRequest)request, - cancellationToken - ); - } - } -} diff --git a/src/Client/Handlers/DynamicRegistrationHandler.cs b/src/Client/Handlers/DynamicRegistrationHandler.cs deleted file mode 100644 index 7f4e28db3..000000000 --- a/src/Client/Handlers/DynamicRegistrationHandler.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using OmniSharp.Extensions.LanguageServer.Protocol.Server.Capabilities; - -namespace OmniSharp.Extensions.LanguageServer.Client.Handlers -{ - /// - /// Handler for "client/registerCapability". - /// - /// - /// For now, this handler does nothing other than a void reply; we don't support dynamic registrations yet. - /// - public class DynamicRegistrationHandler - : IInvokeRequestHandler - { - /// - /// Create a new . - /// - public DynamicRegistrationHandler() - { - } - - /// - /// Server capabilities dynamically updated by the handler. - /// - public ServerCapabilities ServerCapabilities { get; set; } = new ServerCapabilities(); - - /// - /// The name of the method handled by the handler. - /// - public string Method => "client/registerCapability"; - - /// - /// The expected CLR type of the request / notification payload (if any; null if the handler does not use the request payload). - /// - public Type PayloadType => null; - - /// - /// Invoke the handler. - /// - /// - /// The request message. - /// - /// - /// A that can be used to cancel the operation. - /// - /// - /// A representing the operation. - /// - public Task Invoke(object request, CancellationToken cancellationToken) - { - // For now, we don't really support dynamic registration but OmniSharp's implementation sends a request even when dynamic registrations are not supported. - - return Task.FromResult(null); - } - } -} diff --git a/src/Client/Handlers/IHandler.cs b/src/Client/Handlers/IHandler.cs deleted file mode 100644 index 90e523a36..000000000 --- a/src/Client/Handlers/IHandler.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace OmniSharp.Extensions.LanguageServer.Client.Handlers -{ - /// - /// Represents a client-side message handler. - /// - public interface IHandler - { - /// - /// The name of the method handled by the handler. - /// - string Method { get; } - - /// - /// The expected CLR type of the request / notification payload (if any; null if the handler does not use the request body). - /// - Type PayloadType { get; } - } -} diff --git a/src/Client/Handlers/IInvokeEmptyNotificationHandler.cs b/src/Client/Handlers/IInvokeEmptyNotificationHandler.cs deleted file mode 100644 index 8f9f073d4..000000000 --- a/src/Client/Handlers/IInvokeEmptyNotificationHandler.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Threading.Tasks; - -namespace OmniSharp.Extensions.LanguageServer.Client.Handlers -{ - /// - /// Represents a handler for empty notifications. - /// - public interface IInvokeEmptyNotificationHandler - : IHandler - { - /// - /// Invoke the handler. - /// - /// - /// A representing the operation. - /// - Task Invoke(); - } -} diff --git a/src/Client/Handlers/IInvokeNotificationHandler.cs b/src/Client/Handlers/IInvokeNotificationHandler.cs deleted file mode 100644 index febda4a78..000000000 --- a/src/Client/Handlers/IInvokeNotificationHandler.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Threading.Tasks; - -namespace OmniSharp.Extensions.LanguageServer.Client.Handlers -{ - /// - /// Represents a handler for notifications. - /// - public interface IInvokeNotificationHandler - : IHandler - { - /// - /// Invoke the handler. - /// - /// - /// The notification message. - /// - /// - /// A representing the operation. - /// - Task Invoke(object notification); - } -} diff --git a/src/Client/Handlers/IInvokeRequestHandler.cs b/src/Client/Handlers/IInvokeRequestHandler.cs deleted file mode 100644 index e2d79fd4e..000000000 --- a/src/Client/Handlers/IInvokeRequestHandler.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace OmniSharp.Extensions.LanguageServer.Client.Handlers -{ - /// - /// Represents a handler for requests. - /// - public interface IInvokeRequestHandler - : IHandler - { - /// - /// Invoke the handler. - /// - /// - /// The request message. - /// - /// - /// A that can be used to cancel the operation. - /// - /// - /// A representing the operation. - /// - Task Invoke(object request, CancellationToken cancellationToken); - } -} diff --git a/src/Client/Handlers/JsonRpcEmptyNotificationHandler.cs b/src/Client/Handlers/JsonRpcEmptyNotificationHandler.cs deleted file mode 100644 index 32930e035..000000000 --- a/src/Client/Handlers/JsonRpcEmptyNotificationHandler.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using OmniSharp.Extensions.JsonRpc; - -namespace OmniSharp.Extensions.LanguageServer.Client.Handlers -{ - /// - /// An empty notification handler that invokes a JSON-RPC . - /// - public class JsonRpcEmptyNotificationHandler - : JsonRpcHandler, IInvokeEmptyNotificationHandler - { - /// - /// Create a new . - /// - /// - /// The name of the method handled by the handler. - /// - /// - /// The underlying JSON-RPC . - /// - public JsonRpcEmptyNotificationHandler(string method, IJsonRpcNotificationHandler handler) - : base(method) - { - if (handler == null) - throw new ArgumentNullException(nameof(handler)); - - Handler = handler; - } - - /// - /// The underlying JSON-RPC . - /// - public IJsonRpcNotificationHandler Handler { get; } - - /// - /// The expected CLR type of the notification payload (null, since the handler does not use the request payload). - /// - public override Type PayloadType => null; - - /// - /// Invoke the handler. - /// - /// - /// A representing the operation. - /// - public Task Invoke() => Handler.Handle(null, CancellationToken.None); - } -} diff --git a/src/Client/Handlers/JsonRpcHandler.cs b/src/Client/Handlers/JsonRpcHandler.cs deleted file mode 100644 index 770d38fce..000000000 --- a/src/Client/Handlers/JsonRpcHandler.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using OmniSharp.Extensions.JsonRpc; - -namespace OmniSharp.Extensions.LanguageServer.Client.Handlers -{ - /// - /// The base class for message handlers based on JSON-RPC s. - /// - public abstract class JsonRpcHandler - : IHandler - { - /// - /// Create a new . - /// - /// - /// The name of the method handled by the handler. - /// - protected JsonRpcHandler(string method) - { - if (string.IsNullOrWhiteSpace(method)) - throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(method)}.", nameof(method)); - - Method = method; - } - - /// - /// The name of the method handled by the handler. - /// - public string Method { get; } - - /// - /// The expected CLR type of the request / notification payload (if any; null if the handler does not use the request payload). - /// - public abstract Type PayloadType { get; } - } -} diff --git a/src/Client/Handlers/JsonRpcNotificationHandler.cs b/src/Client/Handlers/JsonRpcNotificationHandler.cs deleted file mode 100644 index ed7ad6903..000000000 --- a/src/Client/Handlers/JsonRpcNotificationHandler.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using MediatR; -using Newtonsoft.Json.Linq; -using OmniSharp.Extensions.JsonRpc; - -namespace OmniSharp.Extensions.LanguageServer.Client.Handlers -{ - /// - /// A notification handler that invokes a JSON-RPC . - /// - /// - /// The notification message handler. - /// - public class JsonRpcNotificationHandler : JsonRpcHandler, IInvokeNotificationHandler - where TNotification : IRequest - { - /// - /// Create a new . - /// - /// - /// The name of the method handled by the handler. - /// - /// - /// The underlying JSON-RPC . - /// - public JsonRpcNotificationHandler(string method, IJsonRpcNotificationHandler handler) - : base(method) - { - if (handler == null) - throw new ArgumentNullException(nameof(handler)); - - Handler = handler; - } - - /// - /// The underlying JSON-RPC . - /// - public IJsonRpcNotificationHandler Handler { get; } - - /// - /// The expected CLR type of the notification payload. - /// - public override Type PayloadType => typeof(TNotification); - - /// - /// Invoke the handler. - /// - /// - /// A representing the notification parameters. - /// - /// - /// A representing the operation. - /// - public Task Invoke(object notification) => Handler.Handle( - (TNotification)notification, - CancellationToken.None - ); - } -} diff --git a/src/Client/IConfigureClientServer.cs b/src/Client/IConfigureClientServer.cs new file mode 100644 index 000000000..3f2e6e395 --- /dev/null +++ b/src/Client/IConfigureClientServer.cs @@ -0,0 +1,7 @@ +namespace OmniSharp.Extensions.LanguageServer.Client +{ + public interface IConfigureClientServer + { + void Configure(LanguageClientOptions options); + } +} diff --git a/src/Client/ILspClientReceiver.cs b/src/Client/ILspClientReceiver.cs new file mode 100644 index 000000000..c7b75ad11 --- /dev/null +++ b/src/Client/ILspClientReceiver.cs @@ -0,0 +1,10 @@ +using OmniSharp.Extensions.JsonRpc; + +namespace OmniSharp.Extensions.LanguageServer.Client +{ + public interface ILspClientReceiver : IReceiver + { + void Initialized(); + bool ShouldFilterOutput(object value); + } +} diff --git a/src/Client/LanguageClient.cs b/src/Client/LanguageClient.cs index 17bb8e320..6a3fb6f94 100644 --- a/src/Client/LanguageClient.cs +++ b/src/Client/LanguageClient.cs @@ -1,473 +1,365 @@ -using System; -using System.Diagnostics; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Reactive.Threading.Tasks; using System.Threading; using System.Threading.Tasks; +using MediatR; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using OmniSharp.Extensions.LanguageServer.Client.Clients; -using OmniSharp.Extensions.LanguageServer.Client.Dispatcher; -using OmniSharp.Extensions.LanguageServer.Client.Handlers; -using OmniSharp.Extensions.LanguageServer.Client.Processes; -using OmniSharp.Extensions.LanguageServer.Client.Protocol; -using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; +using Newtonsoft.Json.Linq; +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.LanguageServer.Protocol; using OmniSharp.Extensions.LanguageServer.Protocol.Models; -using OmniSharp.Extensions.LanguageServer.Protocol.Server.Capabilities; -using ISerializer = OmniSharp.Extensions.LanguageServer.Protocol.Serialization.ISerializer; -using Serializer = OmniSharp.Extensions.LanguageServer.Protocol.Serialization.Serializer; +using OmniSharp.Extensions.LanguageServer.Protocol.Client; +using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; +using OmniSharp.Extensions.LanguageServer.Protocol.Client.WorkDone; +using OmniSharp.Extensions.LanguageServer.Protocol.Document; +using OmniSharp.Extensions.LanguageServer.Protocol.General; +using OmniSharp.Extensions.LanguageServer.Protocol.Progress; +using OmniSharp.Extensions.LanguageServer.Protocol.Shared; +using OmniSharp.Extensions.LanguageServer.Protocol.Workspace; +using OmniSharp.Extensions.LanguageServer.Shared; namespace OmniSharp.Extensions.LanguageServer.Client { - /// - /// A client for the Language Server Protocol. - /// - /// - /// Note - at this stage, a cannot be reused once has been called; instead, create a new one. - /// - public sealed class LanguageClient - : IDisposable + public class LanguageClient : JsonRpcServerBase, ILanguageClient { - /// - /// The serializer for notification / request / response bodies. - /// - /// - /// TODO: Make this injectable. And what does client version do - do we have to negotiate this? - /// - readonly ISerializer _serializer = new Serializer(ClientVersion.Lsp3); - - /// - /// The dispatcher for incoming requests, notifications, and responses. - /// - readonly LspDispatcher _dispatcher; - - /// - /// The handler for dynamic registration of server capabilities. - /// - /// - /// We don't actually support this yet but some server implementations (e.g. OmniSharp) will freak out if we don't respond to the message, even if we've indicated that we don't support dynamic registrations of server capabilities. - /// - readonly DynamicRegistrationHandler _dynamicRegistrationHandler = new DynamicRegistrationHandler(); - - /// - /// The language server process. - /// - ServerProcess _process; - - /// - /// The underlying LSP connection to the language server process. - /// - LspConnection _connection; - - /// - /// Completion source that callers can await to determine when the language server is ready to use (i.e. initialised). - /// - TaskCompletionSource _readyCompletion = new TaskCompletionSource(); - - /// - /// Create a new . - /// - /// - /// The factory for loggers used by the client and its components. - /// - /// - /// A describing how to start the server process. - /// - public LanguageClient(ILoggerFactory loggerFactory, ProcessStartInfo serverStartInfo) - : this(loggerFactory, new StdioServerProcess(loggerFactory, serverStartInfo)) + private readonly Connection _connection; + private readonly ClientInfo _clientInfo; + private readonly ILspClientReceiver _receiver; + private readonly TextDocumentIdentifiers _textDocumentIdentifiers; + + private readonly IHandlerCollection _collection; + + // private readonly IEnumerable _initializeDelegates; + // private readonly IEnumerable _initializedDelegates; + private readonly IEnumerable _startedDelegates; + private readonly IResponseRouter _responseRouter; + private readonly ISubject _initializeComplete = new AsyncSubject(); + private readonly CompositeDisposable _disposable = new CompositeDisposable(); + private readonly IServiceProvider _serviceProvider; + + // private readonly ILanguageClientConfiguration _configuration; + private readonly IEnumerable _capabilities; + private readonly object _initializationOptions; + private readonly IWorkspaceFoldersManager _workspaceFoldersManager; + private readonly DocumentUri _rootUri; + private readonly InitializeTrace _trace; + private readonly IRegistrationManager _registrationManager; + private readonly ClientCapabilities _clientCapabilities; + private readonly IProgressManager _progressManager; + private readonly IClientWorkDoneManager _workDoneManager; + + public static Task From(Action optionsAction) { + return From(optionsAction, CancellationToken.None); } - /// - /// Create a new . - /// - /// - /// The factory for loggers used by the client and its components. - /// - /// - /// A used to start or connect to the server process. - /// - public LanguageClient(ILoggerFactory loggerFactory, ServerProcess process) - : this(loggerFactory) + public static Task From(LanguageClientOptions options) { - if (process == null) - throw new ArgumentNullException(nameof(process)); - - _process = process; - _process.Exited.Subscribe(x => ServerProcess_Exit()); + return From(options, CancellationToken.None); } - /// - /// Create a new . - /// - /// - /// The logger to use. - /// - LanguageClient(ILoggerFactory loggerFactory) + public static Task From(Action optionsAction, CancellationToken token) { - if (loggerFactory == null) - throw new ArgumentNullException(nameof(loggerFactory)); - - LoggerFactory = loggerFactory; - Log = LoggerFactory.CreateLogger(); - Workspace = new WorkspaceClient(this); - Window = new WindowClient(this); - TextDocument = new TextDocumentClient(this); - - _dispatcher = new LspDispatcher(_serializer); - _dispatcher.RegisterHandler(_dynamicRegistrationHandler); + var options = new LanguageClientOptions(); + optionsAction(options); + return From(options, token); } - /// - /// Dispose of resources being used by the client. - /// - public void Dispose() + public static ILanguageClient PreInit(Action optionsAction) { - var connection = Interlocked.Exchange(ref _connection, null); - connection?.Dispose(); - - var serverProcess = Interlocked.Exchange(ref _process, null); - serverProcess?.Dispose(); + var options = new LanguageClientOptions(); + optionsAction(options); + return PreInit(options); } - /// - /// The factory for loggers used by the client and its components. - /// - ILoggerFactory LoggerFactory { get; } - - /// - /// The client's logger. - /// - ILogger Log { get; } - - /// - /// The LSP Text Document API. - /// - public TextDocumentClient TextDocument { get; } - - /// - /// The LSP Window API. - /// - public WindowClient Window { get; } - - /// - /// The LSP Workspace API. - /// - public WorkspaceClient Workspace { get; } - - /// - /// The client's capabilities. - /// - public ClientCapabilities ClientCapabilities { get; } = new ClientCapabilities + public static async Task From(LanguageClientOptions options, CancellationToken token) { - Workspace = new WorkspaceClientCapabilities - { - DidChangeConfiguration = new DidChangeConfigurationCapability - { - DynamicRegistration = false - } - }, - TextDocument = new TextDocumentClientCapabilities - { - Synchronization = new SynchronizationCapability - { - DidSave = true, - DynamicRegistration = false - }, - Hover = new HoverCapability - { - DynamicRegistration = false - }, - Completion = new CompletionCapability - { - CompletionItem = new CompletionItemCapability - { - SnippetSupport = false - }, - DynamicRegistration = false - } - } - }; - - /// - /// The server's capabilities. - /// - public ServerCapabilities ServerCapabilities => _dynamicRegistrationHandler.ServerCapabilities; - - /// - /// Has the language client been initialised? - /// - public bool IsInitialized { get; private set; } - - /// - /// Is the connection to the language server open? - /// - public bool IsConnected => _connection != null && _connection.IsOpen; - - /// - /// A that completes when the client is ready to handle requests. - /// - public Task IsReady => _readyCompletion.Task; + var server = (LanguageClient)PreInit(options); + await server.Initialize(token); - /// - /// A that completes when the underlying connection has closed and the server has stopped. - /// - public Task HasShutdown - { - get - { - return Task.WhenAll( - _connection.HasHasDisconnected, - _process?.HasExited ?? Task.CompletedTask - ); - } + return server; } /// - /// Initialise the language server. - /// - /// - /// The workspace root. - /// - /// - /// An optional representing additional options to send to the server. - /// - /// - /// An optional that can be used to cancel the operation. - /// - /// - /// A representing initialisation. - /// - /// - /// has already been called. + /// Create the server without connecting to the client /// - /// can only be called once per ; if you have called , you will need to use a new . - /// - public async Task Initialize(string workspaceRoot, object initializationOptions = null, CancellationToken cancellationToken = default(CancellationToken)) + /// Mainly used for unit testing + /// + /// + /// + public static ILanguageClient PreInit(LanguageClientOptions options) { - if (IsInitialized) - throw new InvalidOperationException("Client has already been initialised."); - - try - { - await Start(); - - var initializeParams = new InitializeParams - { - RootPath = workspaceRoot, - Capabilities = ClientCapabilities, - ProcessId = Process.GetCurrentProcess().Id, - InitializationOptions = initializationOptions - }; - - Log.LogDebug("Sending 'initialize' message to language server..."); - - var result = await SendRequest("initialize", initializeParams, cancellationToken).ConfigureAwait(false); - if (result == null) - throw new LspException("Server replied to 'initialize' request with a null response."); - - _dynamicRegistrationHandler.ServerCapabilities = result.Capabilities; - - Log.LogDebug("Sent 'initialize' message to language server."); - - Log.LogDebug("Sending 'initialized' notification to language server..."); - - SendNotification("initialized"); - - Log.LogDebug("Sent 'initialized' notification to language server."); - - IsInitialized = true; - _readyCompletion.TrySetResult(null); - } - catch (Exception initializationError) - { - // Capture the initialisation error so anyone awaiting IsReady will also see it. - _readyCompletion.TrySetException(initializationError); - - throw; - } + return new LanguageClient(options); } - /// - /// Stop the language server. - /// - /// - /// A representing the shutdown operation. - /// - public async Task Shutdown() + internal LanguageClient(LanguageClientOptions options) : base(options) { - var connection = _connection; - if (connection != null) + _capabilities = options.SupportedCapabilities; + _clientCapabilities = options.ClientCapabilities; + var services = options.Services; + services.AddLogging(builder => options.LoggingBuilderAction(builder)); + options.RequestProcessIdentifier ??= (options.SupportsContentModified + ? new RequestProcessIdentifier(RequestProcessType.Parallel) + : new RequestProcessIdentifier(RequestProcessType.Serial)); + // services.AddSingleton, LanguageClientLoggerFilterOptions>(); + + _clientInfo = options.ClientInfo; + _receiver = options.Receiver; + var serializer = options.Serializer; + var supportedCapabilities = new SupportedCapabilities(); + _textDocumentIdentifiers = new TextDocumentIdentifiers(); + var collection = new SharedHandlerCollection(supportedCapabilities, _textDocumentIdentifiers); + services.AddSingleton(collection); + _collection = collection; + // _initializeDelegates = initializeDelegates; + // _initializedDelegates = initializedDelegates; + _startedDelegates = options.StartedDelegates; + _rootUri = options.RootUri; + _trace = options.Trace; + _initializationOptions = options.InitializationOptions; + + services.AddSingleton(_ => + new OutputHandler(options.Output, options.Serializer, options.Receiver.ShouldFilterOutput, _.GetService>())); + services.AddSingleton(_collection); + services.AddSingleton(_textDocumentIdentifiers); + services.AddSingleton(serializer); + services.AddSingleton(serializer); + services.AddSingleton(options.RequestProcessIdentifier); + services.AddSingleton(options.Receiver); + services.AddSingleton(options.Receiver); + services.AddSingleton(this); + services.AddSingleton(); + services.AddSingleton>(_ => _.GetRequiredService()); + services.AddSingleton>(_ => _.GetRequiredService()); + services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(_ => _.GetRequiredService() as IJsonRpcHandler); + services.AddSingleton(); + services.AddSingleton(_ => _.GetRequiredService() as IJsonRpcHandler); + + EnsureAllHandlersAreRegistered(); + + services.AddSingleton(); + services.AddSingleton(_ => _.GetRequiredService()); + if (options.DynamicRegistration) { - if (connection.IsOpen) - { - await connection.SendEmptyRequest("shutdown"); - connection.SendEmptyNotification("exit"); - connection.Disconnect(flushOutgoing: true); - } - - await connection.HasHasDisconnected; + services.AddSingleton(_ => _.GetRequiredService() as IJsonRpcHandler); } - var serverProcess = _process; - if (serverProcess != null) + var workspaceFoldersManager = new WorkspaceFoldersManager(this); + services.AddSingleton(workspaceFoldersManager); + services.AddSingleton(workspaceFoldersManager); + if (options.WorkspaceFolders) { - if (serverProcess.IsRunning) - await serverProcess.Stop(); + services.AddSingleton(workspaceFoldersManager); } - IsInitialized = false; - _readyCompletion = new TaskCompletionSource(); + var serviceProvider = services.BuildServiceProvider(); + _disposable.Add(serviceProvider); + _serviceProvider = serviceProvider; + collection.SetServiceProvider(_serviceProvider); + + _responseRouter = _serviceProvider.GetRequiredService(); + _progressManager = _serviceProvider.GetRequiredService(); + _workDoneManager = _serviceProvider.GetRequiredService(); + _registrationManager = _serviceProvider.GetRequiredService(); + _workspaceFoldersManager = _serviceProvider.GetRequiredService(); + + _connection = new Connection( + options.Input, + _serviceProvider.GetRequiredService(), + options.Receiver, + options.RequestProcessIdentifier, + _serviceProvider.GetRequiredService>(), + _responseRouter, + _serviceProvider.GetRequiredService(), + options.OnUnhandledException ?? (e => { }), + options.CreateResponseException, + options.MaximumRequestTimeout, + options.SupportsContentModified, + options.Concurrency + ); + + // We need to at least create Window here in case any handler does loggin in their constructor + TextDocument = new TextDocumentLanguageClient(this, _serviceProvider); + Client = new ClientLanguageClient(this, _serviceProvider); + General = new GeneralLanguageClient(this, _serviceProvider); + Window = new WindowLanguageClient(this, _serviceProvider); + Workspace = new WorkspaceLanguageClient(this, _serviceProvider); + + workspaceFoldersManager.Add(options.Folders); + + var serviceHandlers = _serviceProvider.GetServices().ToArray(); + var serviceIdentifiers = _serviceProvider.GetServices().ToArray(); + _disposable.Add(_textDocumentIdentifiers.Add(serviceIdentifiers)); + _disposable.Add(_collection.Add(serviceHandlers)); } - /// - /// Register a message handler. - /// - /// - /// The message handler. - /// - /// - /// An representing the registration. - /// - public IDisposable RegisterHandler(IHandler handler) => _dispatcher.RegisterHandler(handler); + public ITextDocumentLanguageClient TextDocument { get; } + public IClientLanguageClient Client { get; } + public IGeneralLanguageClient General { get; } + public IWindowLanguageClient Window { get; } + public IWorkspaceLanguageClient Workspace { get; } + public IProgressManager ProgressManager => _progressManager; + public IClientWorkDoneManager WorkDoneManager => _workDoneManager; + public IRegistrationManager RegistrationManager => _registrationManager; + public IWorkspaceFoldersManager WorkspaceFoldersManager => _workspaceFoldersManager; - /// - /// Send an empty notification to the language server. - /// - /// - /// The notification method name. - /// - public void SendNotification(string method) - { - var connection = _connection; - if (connection == null || !connection.IsOpen) - throw new InvalidOperationException("Not connected to the language server."); + public InitializeParams ClientSettings { get; private set; } + public InitializeResult ServerSettings { get; private set; } - connection.SendEmptyNotification(method); - } + public IServiceProvider Services => _serviceProvider; - /// - /// Send a notification message to the language server. - /// - /// - /// The notification method name. - /// - /// - /// The notification message. - /// - public void SendNotification(string method, object notification) + public async Task Initialize(CancellationToken token) { - var connection = _connection; - if (connection == null || !connection.IsOpen) - throw new InvalidOperationException("Not connected to the language server."); - - connection.SendNotification(method, notification); + var @params = new InitializeParams { + Trace = _trace, + Capabilities = _clientCapabilities, + ClientInfo = _clientInfo, + RootUri = _rootUri, + RootPath = _rootUri?.GetFileSystemPath(), + WorkspaceFolders = new Container(_workspaceFoldersManager.CurrentWorkspaceFolders), + InitializationOptions = _initializationOptions + }; + RegisterCapabilities(@params.Capabilities); + _workDoneManager.Initialize(@params.Capabilities.Window); + + ClientSettings = @params; + + _connection.Open(); + var serverParams = await this.RequestLanguageProtocolInitialize(ClientSettings, token); + _receiver.Initialized(); + + ServerSettings = serverParams; + if (_collection.ContainsHandler(typeof(IRegisterCapabilityHandler))) + RegistrationManager.RegisterCapabilities(serverParams.Capabilities); + + // TODO: pull supported fields and add any static registrations to the registration manager + this.SendLanguageProtocolInitialized(new InitializedParams()); } - /// - /// Send a request to the language server. - /// - /// - /// The request method name. - /// - /// - /// The request message. - /// - /// - /// An optional cancellation token that can be used to cancel the request. - /// - /// - /// A representing the request. - /// - public Task SendRequest(string method, object request, CancellationToken cancellationToken = default(CancellationToken)) + private void RegisterCapabilities(ClientCapabilities capabilities) { - var connection = _connection; - if (connection == null || !connection.IsOpen) - throw new InvalidOperationException("Not connected to the language server."); - - return connection.SendRequest(method, request, cancellationToken); + capabilities.Window ??= new WindowClientCapabilities(); + capabilities.Window.WorkDoneProgress = _collection.ContainsHandler(typeof(IProgressHandler)); + + capabilities.Workspace ??= new WorkspaceClientCapabilities(); + capabilities.Workspace.Configuration = _collection.ContainsHandler(typeof(IConfigurationHandler)); + capabilities.Workspace.Symbol = UseOrTryAndFindCapability(capabilities.Workspace.Symbol); + capabilities.Workspace.ExecuteCommand = UseOrTryAndFindCapability(capabilities.Workspace.ExecuteCommand); + capabilities.Workspace.ApplyEdit = _collection.ContainsHandler(typeof(IApplyWorkspaceEditHandler)); + capabilities.Workspace.WorkspaceEdit = UseOrTryAndFindCapability(capabilities.Workspace.WorkspaceEdit); + capabilities.Workspace.WorkspaceFolders = _collection.ContainsHandler(typeof(IWorkspaceFoldersHandler)); + capabilities.Workspace.DidChangeConfiguration = + UseOrTryAndFindCapability(capabilities.Workspace.DidChangeConfiguration); + capabilities.Workspace.DidChangeWatchedFiles = + UseOrTryAndFindCapability(capabilities.Workspace.DidChangeWatchedFiles); + + capabilities.TextDocument ??= new TextDocumentClientCapabilities(); + capabilities.TextDocument.Synchronization = + UseOrTryAndFindCapability(capabilities.TextDocument.Synchronization); + capabilities.TextDocument.Completion = UseOrTryAndFindCapability(capabilities.TextDocument.Completion); + capabilities.TextDocument.Hover = UseOrTryAndFindCapability(capabilities.TextDocument.Hover); + capabilities.TextDocument.SignatureHelp = + UseOrTryAndFindCapability(capabilities.TextDocument.SignatureHelp); + capabilities.TextDocument.References = UseOrTryAndFindCapability(capabilities.TextDocument.References); + capabilities.TextDocument.DocumentHighlight = + UseOrTryAndFindCapability(capabilities.TextDocument.DocumentHighlight); + capabilities.TextDocument.DocumentSymbol = + UseOrTryAndFindCapability(capabilities.TextDocument.DocumentSymbol); + capabilities.TextDocument.Formatting = UseOrTryAndFindCapability(capabilities.TextDocument.Formatting); + capabilities.TextDocument.RangeFormatting = + UseOrTryAndFindCapability(capabilities.TextDocument.RangeFormatting); + capabilities.TextDocument.OnTypeFormatting = + UseOrTryAndFindCapability(capabilities.TextDocument.OnTypeFormatting); + capabilities.TextDocument.Definition = UseOrTryAndFindCapability(capabilities.TextDocument.Definition); + capabilities.TextDocument.Declaration = UseOrTryAndFindCapability(capabilities.TextDocument.Declaration); + capabilities.TextDocument.CodeAction = UseOrTryAndFindCapability(capabilities.TextDocument.CodeAction); + capabilities.TextDocument.CodeLens = UseOrTryAndFindCapability(capabilities.TextDocument.CodeLens); + capabilities.TextDocument.DocumentLink = UseOrTryAndFindCapability(capabilities.TextDocument.DocumentLink); + capabilities.TextDocument.Rename = UseOrTryAndFindCapability(capabilities.TextDocument.Rename); + capabilities.TextDocument.TypeDefinition = + UseOrTryAndFindCapability(capabilities.TextDocument.TypeDefinition); + capabilities.TextDocument.Implementation = + UseOrTryAndFindCapability(capabilities.TextDocument.Implementation); + capabilities.TextDocument.ColorProvider = + UseOrTryAndFindCapability(capabilities.TextDocument.ColorProvider); + capabilities.TextDocument.FoldingRange = UseOrTryAndFindCapability(capabilities.TextDocument.FoldingRange); + capabilities.TextDocument.SelectionRange = + UseOrTryAndFindCapability(capabilities.TextDocument.SelectionRange); + capabilities.TextDocument.PublishDiagnostics = + UseOrTryAndFindCapability(capabilities.TextDocument.PublishDiagnostics); +#pragma warning disable 618 + capabilities.TextDocument.CallHierarchy = + UseOrTryAndFindCapability(capabilities.TextDocument.CallHierarchy); + capabilities.TextDocument.SemanticTokens = + UseOrTryAndFindCapability(capabilities.TextDocument.SemanticTokens); +#pragma warning restore 618 } - /// - /// Send a request to the language server. - /// - /// - /// The response message type. - /// - /// - /// The request method name. - /// - /// - /// The request message. - /// - /// - /// An optional cancellation token that can be used to cancel the request. - /// - /// - /// A representing the response. - /// - public Task SendRequest(string method, object request, CancellationToken cancellation = default(CancellationToken)) + public async Task Shutdown() { - var connection = _connection; - if (connection == null || !connection.IsOpen) - throw new InvalidOperationException("Not connected to the language server."); + if (_connection.IsOpen) + { + await this.RequestShutdown(); + this.SendExit(); + } - return connection.SendRequest(method, request, cancellation); + await _connection.StopAsync(); + _connection.Dispose(); } - /// - /// Start the language server. - /// - /// - /// A representing the operation. - /// - async Task Start() + private T UseOrTryAndFindCapability(Supports supports) { - if (_process == null) - throw new ObjectDisposedException(GetType().Name); - - if (!_process.IsRunning) + var value = supports.IsSupported + ? supports.Value + : _capabilities.OfType().FirstOrDefault() ?? Activator.CreateInstance(); + if (value is IDynamicCapability dynamicCapability) { - Log.LogDebug("Starting language server..."); - - await _process.Start(); - - Log.LogDebug("Language server is running."); + dynamicCapability.DynamicRegistration = _collection.ContainsHandler(typeof(IRegisterCapabilityHandler)); } - Log.LogDebug("Opening connection to language server..."); - - if (_connection == null) - _connection = new LspConnection(LoggerFactory, input: _process.OutputStream, output: _process.InputStream); + return value; + } - _connection.Connect(_dispatcher); + public IObservable Start => _initializeComplete.AsObservable(); - Log.LogDebug("Connection to language server is open."); + (string method, TaskCompletionSource pendingTask) IResponseRouter.GetRequest(long id) + { + return _responseRouter.GetRequest(id); } - /// - /// Called when the server process has exited. - /// - /// - /// The event sender. - /// - /// - /// The event arguments. - /// - async void ServerProcess_Exit() - { - Log.LogDebug("Server process has exited; language client is shutting down..."); + public Task WasStarted => _initializeComplete.ToTask(); - var connection = Interlocked.Exchange(ref _connection, null); - if (connection != null) - { - using (connection) - { - connection.Disconnect(); - await connection.HasHasDisconnected; - } - } + public void Dispose() + { + _connection?.Dispose(); + _disposable?.Dispose(); + } - await Shutdown(); + public IDictionary Experimental { get; } = new Dictionary(); + object IServiceProvider.GetService(Type serviceType) => _serviceProvider.GetService(serviceType); + protected override IResponseRouter ResponseRouter => _responseRouter; + protected override IHandlersManager HandlersManager => _collection; + public IDisposable Register(Action registryAction) + { + var manager = new CompositeHandlersManager(_collection); + registryAction(new LangaugeClientRegistry(_serviceProvider, manager, _textDocumentIdentifiers)); + return manager.GetDisposable(); + } + } - Log.LogDebug("Language client shutdown complete."); + class LangaugeClientRegistry : InterimLanguageProtocolRegistry, ILanguageClientRegistry + { + public LangaugeClientRegistry(IServiceProvider serviceProvider, CompositeHandlersManager handlersManager, TextDocumentIdentifiers textDocumentIdentifiers) : base(serviceProvider, handlersManager, textDocumentIdentifiers) + { } } } diff --git a/src/Client/LanguageClientOptions.cs b/src/Client/LanguageClientOptions.cs new file mode 100644 index 000000000..33db58c91 --- /dev/null +++ b/src/Client/LanguageClientOptions.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.IO.Pipelines; +using System.Reactive.Disposables; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.JsonRpc.Server; +using OmniSharp.Extensions.LanguageServer.Protocol; +using OmniSharp.Extensions.LanguageServer.Protocol.Client; +using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; +using OmniSharp.Extensions.LanguageServer.Protocol.Document; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Shared; +using OmniSharp.Extensions.LanguageServer.Shared; +using ISerializer = OmniSharp.Extensions.LanguageServer.Protocol.Serialization.ISerializer; + +namespace OmniSharp.Extensions.LanguageServer.Client +{ + public class LanguageClientOptions : LanguageProtocolRpcOptionsBase, ILanguageClientRegistry + { + public ClientCapabilities ClientCapabilities { get; set; } = new ClientCapabilities() { + Experimental = new Dictionary(), + Window = new WindowClientCapabilities(), + Workspace = new WorkspaceClientCapabilities(), + TextDocument = new TextDocumentClientCapabilities() + }; + + public ClientInfo ClientInfo { get; set; } + public DocumentUri RootUri { get; set; } + public bool WorkspaceFolders { get; set; } = true; + public bool DynamicRegistration { get; set; } = true; + public bool ProgressTokens { get; set; } = true; + internal List Folders { get; set; } = new List(); + + public string RootPath + { + get => RootUri.GetFileSystemPath(); + set => RootUri = DocumentUri.FromFileSystemPath(value); + } + + public InitializeTrace Trace { get; set; } + + public object InitializationOptions { get; set; } + + public ISerializer Serializer { get; set; } = new Protocol.Serialization.Serializer(ClientVersion.Lsp3); + public ILspClientReceiver Receiver { get; set; } = new LspClientReceiver(); + internal List SupportedCapabilities { get; set; } = new List(); + + internal readonly List StartedDelegates = new List(); + ILanguageClientRegistry IJsonRpcHandlerRegistry.AddHandler(string method, IJsonRpcHandler handler, JsonRpcHandlerOptions options) => this.AddHandler(method, handler, options); + + ILanguageClientRegistry IJsonRpcHandlerRegistry.AddHandler(string method, Func handlerFunc, JsonRpcHandlerOptions options) => this.AddHandler(method, handlerFunc, options); + + ILanguageClientRegistry IJsonRpcHandlerRegistry.AddHandlers(params IJsonRpcHandler[] handlers) => this.AddHandlers(handlers); + + ILanguageClientRegistry IJsonRpcHandlerRegistry.AddHandler(Func handlerFunc, JsonRpcHandlerOptions options) => this.AddHandler(handlerFunc, options); + + ILanguageClientRegistry IJsonRpcHandlerRegistry.AddHandler(THandler handler, JsonRpcHandlerOptions options) => this.AddHandler(handler, options); + + ILanguageClientRegistry IJsonRpcHandlerRegistry.AddHandler(JsonRpcHandlerOptions options) => this.AddHandler(options); + + ILanguageClientRegistry IJsonRpcHandlerRegistry.AddHandler(string method, JsonRpcHandlerOptions options) => this.AddHandler(method, options); + + ILanguageClientRegistry IJsonRpcHandlerRegistry.AddHandler(Type type, JsonRpcHandlerOptions options) => this.AddHandler(type, options); + + ILanguageClientRegistry IJsonRpcHandlerRegistry.AddHandler(string method, Type type, JsonRpcHandlerOptions options) => this.AddHandler(method, type, options); + + ILanguageClientRegistry IJsonRpcHandlerRegistry.OnJsonRequest(string method, Func> handler, JsonRpcHandlerOptions options) => OnJsonRequest(method, handler, options); + + ILanguageClientRegistry IJsonRpcHandlerRegistry.OnJsonRequest(string method, Func> handler, JsonRpcHandlerOptions options) => OnJsonRequest(method, handler, options); + ILanguageClientRegistry IJsonRpcHandlerRegistry.OnRequest(string method, Func> handler, JsonRpcHandlerOptions options) => OnRequest(method, handler, options); + + ILanguageClientRegistry IJsonRpcHandlerRegistry.OnRequest(string method, Func> handler, JsonRpcHandlerOptions options) => OnRequest(method, handler, options); + + ILanguageClientRegistry IJsonRpcHandlerRegistry.OnRequest(string method, Func> handler, JsonRpcHandlerOptions options) => OnRequest(method, handler, options); + + ILanguageClientRegistry IJsonRpcHandlerRegistry.OnRequest(string method, Func> handler, JsonRpcHandlerOptions options) => OnRequest(method, handler, options); + + ILanguageClientRegistry IJsonRpcHandlerRegistry.OnRequest(string method, Func handler, JsonRpcHandlerOptions options) => OnRequest(method, handler, options); + + ILanguageClientRegistry IJsonRpcHandlerRegistry.OnRequest(string method, Func handler, JsonRpcHandlerOptions options) => OnRequest(method, handler, options); + + ILanguageClientRegistry IJsonRpcHandlerRegistry.OnRequest(string method, Func handler, JsonRpcHandlerOptions options) => OnRequest(method, handler, options); + + ILanguageClientRegistry IJsonRpcHandlerRegistry.OnNotification(string method, Action handler, JsonRpcHandlerOptions options) => OnNotification(method, handler, options); + + ILanguageClientRegistry IJsonRpcHandlerRegistry.OnJsonNotification(string method, Action handler, JsonRpcHandlerOptions options) => OnJsonNotification(method, handler, options); + + ILanguageClientRegistry IJsonRpcHandlerRegistry.OnJsonNotification(string method, Func handler, JsonRpcHandlerOptions options) => OnJsonNotification(method, handler, options); + + ILanguageClientRegistry IJsonRpcHandlerRegistry.OnJsonNotification(string method, Func handler, JsonRpcHandlerOptions options) => OnJsonNotification(method, handler, options); + + ILanguageClientRegistry IJsonRpcHandlerRegistry.OnJsonNotification(string method, Action handler, JsonRpcHandlerOptions options) => OnJsonNotification(method, handler, options); + + ILanguageClientRegistry IJsonRpcHandlerRegistry.OnNotification(string method, Action handler, JsonRpcHandlerOptions options) => OnNotification(method, handler, options); + + ILanguageClientRegistry IJsonRpcHandlerRegistry.OnNotification(string method, Func handler, JsonRpcHandlerOptions options) => OnNotification(method, handler, options); + + ILanguageClientRegistry IJsonRpcHandlerRegistry.OnNotification(string method, Func handler, JsonRpcHandlerOptions options) => OnNotification(method, handler, options); + + ILanguageClientRegistry IJsonRpcHandlerRegistry.OnNotification(string method, Action handler, JsonRpcHandlerOptions options) => OnNotification(method, handler, options); + + ILanguageClientRegistry IJsonRpcHandlerRegistry.OnNotification(string method, Func handler, JsonRpcHandlerOptions options) => OnNotification(method, handler, options); + + ILanguageClientRegistry IJsonRpcHandlerRegistry.OnNotification(string method, Func handler, JsonRpcHandlerOptions options) => OnNotification(method, handler, options); + + public override IRequestProcessIdentifier RequestProcessIdentifier { get; set; } + } +} diff --git a/src/Client/LanguageClientOptionsExtensions.cs b/src/Client/LanguageClientOptionsExtensions.cs new file mode 100644 index 000000000..024e7713f --- /dev/null +++ b/src/Client/LanguageClientOptionsExtensions.cs @@ -0,0 +1,147 @@ +using System; +using System.IO; +using System.IO.Pipelines; +using System.Reflection; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Nerdbank.Streams; +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.JsonRpc.Server; +using OmniSharp.Extensions.LanguageServer.Protocol; +using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Shared; +using ISerializer = OmniSharp.Extensions.LanguageServer.Protocol.Serialization.ISerializer; + +namespace OmniSharp.Extensions.LanguageServer.Client +{ + public static class LanguageClientOptionsExtensions + { + public static LanguageClientOptions WithSerializer(this LanguageClientOptions options, ISerializer serializer) + { + options.Serializer = serializer; + return options; + } + + public static LanguageClientOptions WithReceiver(this LanguageClientOptions options, + ILspClientReceiver serverReceiver) + { + options.Receiver = serverReceiver; + return options; + } + + public static LanguageClientOptions WithServices(this LanguageClientOptions options, + Action servicesAction) + { + servicesAction(options.Services); + return options; + } + + public static LanguageClientOptions WithClientInfo(this LanguageClientOptions options, ClientInfo clientInfo) + { + options.ClientInfo = clientInfo; + return options; + } + + public static LanguageClientOptions WithRootUri(this LanguageClientOptions options, DocumentUri rootUri) + { + options.RootUri = rootUri; + return options; + } + + public static LanguageClientOptions WithRootPath(this LanguageClientOptions options, string rootPath) + { + options.RootPath = rootPath; + return options; + } + + public static LanguageClientOptions WithTrace(this LanguageClientOptions options, InitializeTrace trace) + { + options.Trace = trace; + return options; + } + + public static LanguageClientOptions WithInitializationOptions(this LanguageClientOptions options, + object initializationOptions) + { + options.InitializationOptions = initializationOptions; + return options; + } + + public static LanguageClientOptions WithCapability(this LanguageClientOptions options, params ICapability[] capabilities) + { + options.SupportedCapabilities.AddRange(capabilities); + return options; + } + + public static LanguageClientOptions WithClientCapabilities(this LanguageClientOptions options, ClientCapabilities clientCapabilities) + { + options.ClientCapabilities = clientCapabilities; + return options; + } + + public static LanguageClientOptions OnStarted(this LanguageClientOptions options, + OnClientStartedDelegate @delegate) + { + options.StartedDelegates.Add(@delegate); + return options; + } + + public static LanguageClientOptions ConfigureLogging(this LanguageClientOptions options, + Action builderAction) + { + options.LoggingBuilderAction = builderAction; + return options; + } + + public static LanguageClientOptions AddDefaultLoggingProvider(this LanguageClientOptions options) + { + options.AddDefaultLoggingProvider = true; + return options; + } + + public static LanguageClientOptions ConfigureConfiguration(this LanguageClientOptions options, + Action builderAction) + { + options.ConfigurationBuilderAction = builderAction; + return options; + } + + public static LanguageClientOptions EnableWorkspaceFolders(this LanguageClientOptions options) + { + options.WorkspaceFolders = true; + return options; + } + + public static LanguageClientOptions DisableWorkspaceFolders(this LanguageClientOptions options) + { + options.WorkspaceFolders = false; + return options; + } + + public static LanguageClientOptions EnableDynamicRegistration(this LanguageClientOptions options) + { + options.DynamicRegistration = true; + return options; + } + + public static LanguageClientOptions DisableDynamicRegistration(this LanguageClientOptions options) + { + options.DynamicRegistration = false; + return options; + } + + public static LanguageClientOptions EnableProgressTokens(this LanguageClientOptions options) + { + options.ProgressTokens = true; + return options; + } + + public static LanguageClientOptions DisableProgressTokens(this LanguageClientOptions options) + { + options.ProgressTokens = false; + return options; + } + } +} diff --git a/src/Client/LanguageRegistration.cs b/src/Client/LanguageRegistration.cs deleted file mode 100644 index d01048639..000000000 --- a/src/Client/LanguageRegistration.cs +++ /dev/null @@ -1,216 +0,0 @@ -using System; -using MediatR; -using OmniSharp.Extensions.JsonRpc; -using OmniSharp.Extensions.LanguageServer.Client.Handlers; - -namespace OmniSharp.Extensions.LanguageServer.Client -{ - /// - /// Extension methods for enabling various styles of handler registration. - /// - public static class LanguageRegistration - { - /// - /// Register a handler for empty notifications. - /// - /// - /// The . - /// - /// - /// The name of the notification method to handle. - /// - /// - /// A delegate that implements the handler. - /// - /// - /// An representing the registration. - /// - public static IDisposable HandleNotification(this LanguageClient languageClient, string method, NotificationHandler handler) - { - if (languageClient == null) - throw new ArgumentNullException(nameof(languageClient)); - - if (string.IsNullOrWhiteSpace(method)) - throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(method)}.", nameof(method)); - - if (handler == null) - throw new ArgumentNullException(nameof(handler)); - - return languageClient.RegisterHandler( - new DelegateEmptyNotificationHandler(method, handler) - ); - } - - /// - /// Register a handler for empty notifications. - /// - /// - /// The . - /// - /// - /// The name of the notification method to handle. - /// - /// - /// A JSON-RPC that implements the handler. - /// - /// - /// An representing the registration. - /// - public static IDisposable HandleNotification(this LanguageClient languageClient, string method, IJsonRpcNotificationHandler handler) - { - if (languageClient == null) - throw new ArgumentNullException(nameof(languageClient)); - - if (string.IsNullOrWhiteSpace(method)) - throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(method)}.", nameof(method)); - - if (handler == null) - throw new ArgumentNullException(nameof(handler)); - - return languageClient.RegisterHandler( - new JsonRpcEmptyNotificationHandler(method, handler) - ); - } - - - /// - /// Register a handler for notifications. - /// - /// - /// The notification message type. - /// - /// - /// The . - /// - /// - /// The name of the notification method to handle. - /// - /// - /// A delegate that implements the handler. - /// - /// - /// An representing the registration. - /// - public static IDisposable HandleNotification(this LanguageClient languageClient, string method, NotificationHandler handler) - { - if (languageClient == null) - throw new ArgumentNullException(nameof(languageClient)); - - if (string.IsNullOrWhiteSpace(method)) - throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(method)}.", nameof(method)); - - if (handler == null) - throw new ArgumentNullException(nameof(handler)); - - return languageClient.RegisterHandler( - new DelegateNotificationHandler(method, handler) - ); - } - - /// - /// Register a handler for notifications. - /// - /// - /// The notification message type. - /// - /// - /// The . - /// - /// - /// The name of the notification method to handle. - /// - /// - /// A JSON-RPC that implements the handler. - /// - /// - /// An representing the registration. - /// - public static IDisposable HandleNotification(this LanguageClient languageClient, string method, IJsonRpcNotificationHandler handler) - where TNotification : IRequest - { - if (languageClient == null) - throw new ArgumentNullException(nameof(languageClient)); - - if (string.IsNullOrWhiteSpace(method)) - throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(method)}.", nameof(method)); - - if (handler == null) - throw new ArgumentNullException(nameof(handler)); - - return languageClient.RegisterHandler( - new JsonRpcNotificationHandler(method, handler) - ); - } - - /// - /// Register a handler for requests. - /// - /// - /// The request message type. - /// - /// - /// The . - /// - /// - /// The name of the request method to handle. - /// - /// - /// A delegate that implements the handler. - /// - /// - /// An representing the registration. - /// - public static IDisposable HandleRequest(this LanguageClient languageClient, string method, RequestHandler handler) - { - if (languageClient == null) - throw new ArgumentNullException(nameof(languageClient)); - - if (string.IsNullOrWhiteSpace(method)) - throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(method)}.", nameof(method)); - - if (handler == null) - throw new ArgumentNullException(nameof(handler)); - - return languageClient.RegisterHandler( - new DelegateRequestHandler(method, handler) - ); - } - - /// - /// Register a handler for requests. - /// - /// - /// The request message type. - /// - /// - /// The response message type. - /// - /// - /// The . - /// - /// - /// The name of the request method to handle. - /// - /// - /// A delegate that implements the handler. - /// - /// - /// An representing the registration. - /// - public static IDisposable HandleRequest(this LanguageClient languageClient, string method, RequestHandler handler) - { - if (languageClient == null) - throw new ArgumentNullException(nameof(languageClient)); - - if (string.IsNullOrWhiteSpace(method)) - throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(method)}.", nameof(method)); - - if (handler == null) - throw new ArgumentNullException(nameof(handler)); - - return languageClient.RegisterHandler( - new DelegateRequestResponseHandler(method, handler) - ); - } - } -} diff --git a/src/Client/LogMessageHandler.cs b/src/Client/LogMessageHandler.cs deleted file mode 100644 index 78d29195d..000000000 --- a/src/Client/LogMessageHandler.cs +++ /dev/null @@ -1,15 +0,0 @@ -using OmniSharp.Extensions.LanguageServer.Protocol.Models; - -namespace OmniSharp.Extensions.LanguageServer.Client -{ - /// - /// A handler for log messages sent from the language server to the client. - /// - /// - /// The log message. - /// - /// - /// The log message type. - /// - public delegate void LogMessageHandler(string message, MessageType messageType); -} \ No newline at end of file diff --git a/src/Client/LspClientReceiver.cs b/src/Client/LspClientReceiver.cs new file mode 100644 index 000000000..c67ea87ba --- /dev/null +++ b/src/Client/LspClientReceiver.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using Newtonsoft.Json.Linq; +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.JsonRpc.Client; +using OmniSharp.Extensions.JsonRpc.Server; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Window; + +namespace OmniSharp.Extensions.LanguageServer.Client +{ + public class LspClientReceiver : Receiver, ILspClientReceiver + { + private bool _initialized; + + public override (IEnumerable results, bool hasResponse) GetRequests(JToken container) + { + if (_initialized) return base.GetRequests(container); + + var newResults = new List(); + + // Based on https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#initialize-request + var (results, hasResponse) = base.GetRequests(container); + foreach (var item in results) + { + if (item.IsRequest && + HandlerTypeDescriptorHelper.IsMethodName(item.Request.Method, typeof(IShowMessageRequestHandler))) + { + newResults.Add(item); + } + else if (item.IsResponse) + { + newResults.Add(item); + } + else if (item.IsNotification && + HandlerTypeDescriptorHelper.IsMethodName(item.Notification.Method, + typeof(IShowMessageHandler), + typeof(ILogMessageHandler), + typeof(ITelemetryEventHandler)) + ) + { + newResults.Add(item); + } + } + + return (newResults, hasResponse); + } + + public void Initialized() + { + _initialized = true; + } + + public bool ShouldFilterOutput(object value) + { + if (_initialized) return true; + return value is OutgoingResponse || value is OutgoingRequest v && v.Params is InitializeParams; + } + } +} diff --git a/src/Client/LspException.cs b/src/Client/LspException.cs deleted file mode 100644 index e4ae755b5..000000000 --- a/src/Client/LspException.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Runtime.Serialization; - -namespace OmniSharp.Extensions.LanguageServer.Client -{ - /// - /// Exception raised when a Language Server Protocol error is encountered. - /// - [Serializable] - public class LspException - : Exception - { - /// - /// Create a new . - /// - /// - /// The exception message. - /// - public LspException(string message) - : base(message) - { - } - - /// - /// Create a new . - /// - /// - /// The exception message. - /// - /// - /// The exception that caused this exception to be raised. - /// - public LspException(string message, Exception inner) - : base(message, inner) - { - } - - /// - /// Serialisation constructor. - /// - /// - /// The serialisation data-store. - /// - /// - /// The serialisation streaming context. - /// - protected LspException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - } -} diff --git a/src/Client/NotificationHandler.cs b/src/Client/NotificationHandler.cs deleted file mode 100644 index 7190fc85c..000000000 --- a/src/Client/NotificationHandler.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Threading.Tasks; - -namespace OmniSharp.Extensions.LanguageServer.Client -{ - /// - /// A handler for empty notifications. - /// - /// - /// A representing the operation. - /// - public delegate void NotificationHandler(); -} \ No newline at end of file diff --git a/src/Client/Processes/NamedPipeServerProcess.cs b/src/Client/Processes/NamedPipeServerProcess.cs deleted file mode 100644 index a70a7b217..000000000 --- a/src/Client/Processes/NamedPipeServerProcess.cs +++ /dev/null @@ -1,144 +0,0 @@ -using System.IO; -using System.IO.Pipes; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; - -namespace OmniSharp.Extensions.LanguageServer.Client.Processes -{ - /// - /// A is a that creates named pipe streams to connect a language client to a language server in the same process. - /// - public class NamedPipeServerProcess - : ServerProcess - { - /// - /// Create a new . - /// - /// - /// The base name (prefix) used to create the named pipes. - /// - /// - /// The factory for loggers used by the process and its components. - /// - public NamedPipeServerProcess(string baseName, ILoggerFactory loggerFactory) - : base(loggerFactory) - { - BaseName = baseName; - } - - /// - /// Dispose of resources being used by the . - /// - /// - /// Explicit disposal? - /// - protected override void Dispose(bool disposing) - { - if (disposing) - CloseStreams(); - - base.Dispose(disposing); - } - - /// - /// The base name (prefix) used to create the named pipes. - /// - public string BaseName { get; } - - /// - /// Is the server running? - /// - public override bool IsRunning => ServerStartCompletion.Task.IsCompleted; - - /// - /// A that the client reads messages from. - /// - public NamedPipeClientStream ClientInputStream { get; protected set; } - - /// - /// A that the client writes messages to. - /// - public NamedPipeClientStream ClientOutputStream { get; protected set; } - - /// - /// A that the server reads messages from. - /// - public NamedPipeServerStream ServerInputStream { get; protected set; } - - /// - /// A that the server writes messages to. - /// - public NamedPipeServerStream ServerOutputStream { get; protected set; } - - /// - /// The server's input stream. - /// - public override Stream InputStream => ServerInputStream; - - /// - /// The server's output stream. - /// - public override Stream OutputStream => ServerOutputStream; - - /// - /// Start or connect to the server. - /// - /// - /// A representing the operation. - /// - public override async Task Start() - { - ServerExitCompletion = new TaskCompletionSource(); - - ServerInputStream = new NamedPipeServerStream(BaseName + "_in", PipeDirection.Out, 2, PipeTransmissionMode.Byte, PipeOptions.Asynchronous, inBufferSize: 1024, outBufferSize: 1024); - ServerOutputStream = new NamedPipeServerStream(BaseName + "_out", PipeDirection.In, 2, PipeTransmissionMode.Byte, PipeOptions.Asynchronous, inBufferSize: 1024, outBufferSize: 1024); - ClientInputStream = new NamedPipeClientStream(".", BaseName + "_out", PipeDirection.Out, PipeOptions.Asynchronous); - ClientOutputStream = new NamedPipeClientStream(".", BaseName + "_in", PipeDirection.In, PipeOptions.Asynchronous); - - // Ensure all pipes are connected before proceeding. - await Task.WhenAll( - ServerInputStream.WaitForConnectionAsync(), - ServerOutputStream.WaitForConnectionAsync(), - ClientInputStream.ConnectAsync(), - ClientOutputStream.ConnectAsync() - ); - - ServerStartCompletion.TrySetResult(null); - } - - /// - /// Stop the server. - /// - /// - /// A representing the operation. - /// - public override Task Stop() - { - ServerStartCompletion = new TaskCompletionSource(); - - CloseStreams(); - - ServerExitCompletion.TrySetResult(null); - - return Task.CompletedTask; - } - - /// - /// Close the underlying streams. - /// - void CloseStreams() - { - ClientInputStream?.Dispose(); - ClientInputStream = null; - - ClientOutputStream?.Dispose(); - ClientOutputStream = null; - - ServerInputStream?.Dispose(); - ServerInputStream = null; - - ServerOutputStream?.Dispose(); - ServerOutputStream = null; - } - } -} diff --git a/src/Client/Processes/ServerProcess.cs b/src/Client/Processes/ServerProcess.cs deleted file mode 100644 index e23653c47..000000000 --- a/src/Client/Processes/ServerProcess.cs +++ /dev/null @@ -1,141 +0,0 @@ -using System; -using System.IO; -using System.Reactive.Subjects; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; - -namespace OmniSharp.Extensions.LanguageServer.Client.Processes -{ - /// - /// A is responsible for launching or attaching to a language server, providing access to its input and output streams, and tracking its lifetime. - /// - public abstract class ServerProcess - : IDisposable - { - private readonly ISubject _exitedSubject; - /// - /// Create a new . - /// - /// - /// The factory for loggers used by the process and its components. - /// - protected ServerProcess(ILoggerFactory loggerFactory) - { - if (loggerFactory == null) - throw new ArgumentNullException(nameof(loggerFactory)); - - LoggerFactory = loggerFactory; - Log = LoggerFactory.CreateLogger( - categoryName: GetType().FullName - ); - - ServerStartCompletion = new TaskCompletionSource(); - - ServerExitCompletion = new TaskCompletionSource(); - ServerExitCompletion.SetResult(null); // Start out as if the server has already exited. - - Exited = _exitedSubject = new AsyncSubject(); - } - - /// - /// Finaliser for . - /// - ~ServerProcess() - { - Dispose(false); - } - - /// - /// Dispose of resources being used by the launcher. - /// - public void Dispose() - { - Dispose(true); - } - - /// - /// Dispose of resources being used by the launcher. - /// - /// - /// Explicit disposal? - /// - protected virtual void Dispose(bool disposing) - { - } - - /// - /// The factory for loggers used by the process and its components. - /// - protected ILoggerFactory LoggerFactory { get; } - - /// - /// The process's logger. - /// - protected ILogger Log { get; } - - /// - /// The used to signal server startup. - /// - protected TaskCompletionSource ServerStartCompletion { get; set; } - - /// - /// The used to signal server exit. - /// - protected TaskCompletionSource ServerExitCompletion { get; set; } - - /// - /// Event raised when the server has exited. - /// - public IObservable Exited { get; } - - /// - /// Is the server running? - /// - public abstract bool IsRunning { get; } - - /// - /// A that completes when the server has started. - /// - public Task HasStarted => ServerStartCompletion.Task; - - /// - /// A that completes when the server has exited. - /// - public Task HasExited => ServerExitCompletion.Task; - - /// - /// The server's input stream. - /// - /// - /// The connection will write to the server's input stream, and read from its output stream. - /// - public abstract Stream InputStream { get; } - - /// - /// The server's output stream. - /// - /// - /// The connection will read from the server's output stream, and write to its input stream. - /// - public abstract Stream OutputStream { get; } - - /// - /// Start or connect to the server. - /// - public abstract Task Start(); - - /// - /// Stop or disconnect from the server. - /// - public abstract Task Stop(); - - /// - /// Raise the event. - /// - protected virtual void OnExited() - { - _exitedSubject.OnNext(System.Reactive.Unit.Default); - _exitedSubject.OnCompleted(); - } - } -} diff --git a/src/Client/Processes/StdioServerProcess.cs b/src/Client/Processes/StdioServerProcess.cs deleted file mode 100644 index 8295085e1..000000000 --- a/src/Client/Processes/StdioServerProcess.cs +++ /dev/null @@ -1,137 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; - -namespace OmniSharp.Extensions.LanguageServer.Client.Processes -{ - /// - /// A is a that launches its server as an external process and communicates with it over STDIN / STDOUT. - /// - public class StdioServerProcess - : ServerProcess - { - /// - /// A that describes how to start the server. - /// - readonly ProcessStartInfo _serverStartInfo; - - /// - /// The current server process (if any). - /// - Process _serverProcess; - - /// - /// Create a new . - /// - /// - /// The factory for loggers used by the process and its components. - /// - /// - /// A that describes how to start the server. - /// - public StdioServerProcess(ILoggerFactory loggerFactory, ProcessStartInfo serverStartInfo) - : base(loggerFactory) - { - if (serverStartInfo == null) - throw new ArgumentNullException(nameof(serverStartInfo)); - - _serverStartInfo = serverStartInfo; - } - - /// - /// Dispose of resources being used by the launcher. - /// - /// - /// Explicit disposal? - /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - Process serverProcess = Interlocked.Exchange(ref _serverProcess, null); - if (serverProcess != null) - { - if (!serverProcess.HasExited) - serverProcess.Kill(); - - serverProcess.Dispose(); - } - } - } - - /// - /// Is the server running? - /// - public override bool IsRunning => !ServerExitCompletion.Task.IsCompleted; - - /// - /// The server's input stream. - /// - public override Stream InputStream => _serverProcess?.StandardInput?.BaseStream; - - /// - /// The server's output stream. - /// - public override Stream OutputStream => _serverProcess?.StandardOutput?.BaseStream; - - /// - /// Start or connect to the server. - /// - public override Task Start() - { - ServerExitCompletion = new TaskCompletionSource(); - - _serverStartInfo.CreateNoWindow = true; - _serverStartInfo.UseShellExecute = false; - _serverStartInfo.RedirectStandardInput = true; - _serverStartInfo.RedirectStandardOutput = true; - - Process serverProcess = _serverProcess = new Process - { - StartInfo = _serverStartInfo, - EnableRaisingEvents = true - }; - serverProcess.Exited += ServerProcess_Exit; - - if (!serverProcess.Start()) - throw new InvalidOperationException("Failed to launch language server ."); - - ServerStartCompletion.TrySetResult(null); - - return Task.CompletedTask; - } - - /// - /// Stop or disconnect from the server. - /// - public override async Task Stop() - { - Process serverProcess = Interlocked.Exchange(ref _serverProcess, null); - if (serverProcess != null && !serverProcess.HasExited) - serverProcess.Kill(); - - await ServerExitCompletion.Task; - } - - /// - /// Called when the server process has exited. - /// - /// - /// The event sender. - /// - /// - /// The event arguments. - /// - void ServerProcess_Exit(object sender, EventArgs args) - { - Log.LogDebug("Server process has exited."); - - OnExited(); - ServerExitCompletion.TrySetResult(null); - ServerStartCompletion = new TaskCompletionSource(); - } - } -} diff --git a/src/Client/Protocol/ClientMessage.cs b/src/Client/Protocol/ClientMessage.cs deleted file mode 100644 index 4d0aa8fed..000000000 --- a/src/Client/Protocol/ClientMessage.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using OmniSharp.Extensions.LanguageServer.Protocol.Serialization; - -namespace OmniSharp.Extensions.LanguageServer.Client.Protocol -{ - /// - /// The client-side representation of an LSP message. - /// - public class ClientMessage - { - /// - /// The JSON-RPC protocol version. - /// - [JsonProperty("jsonrpc")] - public string ProtocolVersion => "2.0"; - - /// - /// The request / response Id, if the message represents a request or a response. - /// - [Optional] - public object Id { get; set; } - - /// - /// The JSON-RPC method name. - /// - public string Method { get; set; } - - /// - /// The request / notification message, if the message represents a request or a notification. - /// - [Optional] - public JToken Params { get; set; } - - /// - /// The response message, if the message represents a response. - /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, NullValueHandling = NullValueHandling.Ignore)] - public JToken Result { get; set; } - } -} diff --git a/src/Client/Protocol/ErrorMessage.cs b/src/Client/Protocol/ErrorMessage.cs deleted file mode 100644 index 6d3a9dc85..000000000 --- a/src/Client/Protocol/ErrorMessage.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Newtonsoft.Json.Linq; -using OmniSharp.Extensions.LanguageServer.Protocol.Serialization; - -namespace OmniSharp.Extensions.LanguageServer.Client.Protocol -{ - /// - /// A JSON-RPC error message. - /// - public class ErrorMessage - { - /// - /// The error code. - /// - public int Code { get; set; } - - /// - /// The error message. - /// - public string Message { get; set; } - - /// - /// Optional data associated with the message. - /// - [Optional] - public JToken Data { get; set; } - } -} diff --git a/src/Client/Protocol/LspConnection.cs b/src/Client/Protocol/LspConnection.cs deleted file mode 100644 index e3d94ed9f..000000000 --- a/src/Client/Protocol/LspConnection.cs +++ /dev/null @@ -1,1042 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using OmniSharp.Extensions.JsonRpc; -using OmniSharp.Extensions.LanguageServer.Client.Dispatcher; -using OmniSharp.Extensions.LanguageServer.Client.Handlers; -using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; -using JsonRpcMessages = OmniSharp.Extensions.JsonRpc.Server.Messages; -using Serializer = OmniSharp.Extensions.LanguageServer.Protocol.Serialization.Serializer; - -namespace OmniSharp.Extensions.LanguageServer.Client.Protocol -{ - /// - /// An asynchronous connection using the LSP protocol over s. - /// - public sealed class LspConnection - : IDisposable - { - /// - /// The buffer size to use when receiving headers. - /// - const short HeaderBufferSize = 300; - - /// - /// Minimum size of the buffer for receiving headers ("Content-Length: 1\r\n\r\n"). - /// - const short MinimumHeaderLength = 21; - - /// - /// The length of time to wait for the outgoing message queue to drain. - /// - public static TimeSpan FlushTimeout { get; set; } = TimeSpan.FromSeconds(5); - - /// - /// The encoding used for message headers. - /// - public static Encoding HeaderEncoding = Encoding.ASCII; - - /// - /// The encoding used for message payloads. - /// - public static Encoding PayloadEncoding = Encoding.UTF8; - - /// - /// The queue of outgoing requests. - /// - readonly BlockingCollection _outgoing = new BlockingCollection(new ConcurrentQueue()); - - /// - /// The queue of incoming responses. - /// - readonly BlockingCollection _incoming = new BlockingCollection(new ConcurrentQueue()); - - /// - /// s representing cancellation of requests from the language server (keyed by request Id). - /// - readonly ConcurrentDictionary _requestCancellations = new ConcurrentDictionary(); - - /// - /// s representing completion of responses from the language server (keyed by request Id). - /// - readonly ConcurrentDictionary> _responseCompletions = new ConcurrentDictionary>(); - - /// - /// The input stream. - /// - readonly Stream _input; - - /// - /// The output stream. - /// - readonly Stream _output; - - /// - /// The next available request Id. - /// - int _nextRequestId = 0; - - /// - /// Has the connection been disposed? - /// - bool _disposed; - - /// - /// The cancellation source for the read and write loops. - /// - CancellationTokenSource _cancellationSource; - - /// - /// Cancellation for the read and write loops. - /// - CancellationToken _cancellation; - - /// - /// A representing the stopping of the connection's send, receive, and dispatch loops. - /// - Task _hasDisconnectedTask = Task.CompletedTask; - - /// - /// The used to dispatch messages to handlers. - /// - LspDispatcher _dispatcher; - - /// - /// A representing the connection's receive loop. - /// - Task _sendLoop; - - /// - /// A representing the connection's send loop. - /// - Task _receiveLoop; - - /// - /// A representing the connection's dispatch loop. - /// - Task _dispatchLoop; - - /// - /// Create a new . - /// - /// - /// The factory for loggers used by the connection and its components. - /// - /// - /// The input stream. - /// - /// - /// The output stream. - /// - public LspConnection(ILoggerFactory loggerFactory, Stream input, Stream output) - { - if (loggerFactory == null) - throw new ArgumentNullException(nameof(loggerFactory)); - - if (input == null) - throw new ArgumentNullException(nameof(input)); - - if (!input.CanRead) - throw new ArgumentException("Input stream does not support reading.", nameof(input)); - - if (output == null) - throw new ArgumentNullException(nameof(output)); - - if (!output.CanWrite) - throw new ArgumentException("Output stream does not support reading.", nameof(output)); - - Log = loggerFactory.CreateLogger(); - _input = input; - _output = output; - - // What does client version do? Do we have to negotiate this? - // The connection may change its Serializer instance once connected; this can be propagated to other components as required. - Serializer = new Serializer(ClientVersion.Lsp3); - } - - /// - /// Dispose of resources being used by the connection. - /// - public void Dispose() - { - if (_disposed) - return; - - try - { - Disconnect(); - - _cancellationSource?.Dispose(); - } - finally - { - _disposed = true; - } - } - - /// - /// The connection's logger. - /// - ILogger Log { get; } - - /// - /// The JSON serializer used for notification, request, and response payloads. - /// - public Serializer Serializer { get; } - - /// - /// Is the connection open? - /// - public bool IsOpen => _sendLoop != null || _receiveLoop != null || _dispatchLoop != null; - - /// - /// A task that completes when the connection is closed. - /// - public Task HasHasDisconnected => _hasDisconnectedTask; - - /// - /// Register a message handler. - /// - /// - /// The message handler. - /// - /// - /// An representing the registration. - /// - public IDisposable RegisterHandler(IHandler handler) - { - if (handler == null) - throw new ArgumentNullException(nameof(handler)); - - LspDispatcher dispatcher = _dispatcher; - if (dispatcher == null) - throw new InvalidOperationException("The connection has not been opened."); - - return dispatcher.RegisterHandler(handler); - } - - /// - /// Open the connection. - /// - /// - /// The used to dispatch messages to handlers. - /// - public void Connect(LspDispatcher dispatcher) - { - if (dispatcher == null) - throw new ArgumentNullException(nameof(dispatcher)); - - if (IsOpen) - throw new InvalidOperationException("Connection is already open."); - - _cancellationSource = new CancellationTokenSource(); - _cancellation = _cancellationSource.Token; - - _dispatcher = dispatcher; - _dispatcher.Serializer = Serializer; - _sendLoop = SendLoop(); - _receiveLoop = ReceiveLoop(); - _dispatchLoop = DispatchLoop(); - - _hasDisconnectedTask = Task.WhenAll(_sendLoop, _receiveLoop, _dispatchLoop); - } - - /// - /// Close the connection. - /// - /// - /// If true, stop receiving and block until all outgoing messages have been sent. - /// - public void Disconnect(bool flushOutgoing = false) - { - if (flushOutgoing) - { - // Stop receiving. - _incoming.CompleteAdding(); - - // Wait for the outgoing message queue to drain. - int remainingMessageCount = 0; - DateTime then = DateTime.Now; - while (DateTime.Now - then < FlushTimeout) - { - remainingMessageCount = _outgoing.Count; - if (remainingMessageCount == 0) - break; - - Thread.Sleep( - TimeSpan.FromMilliseconds(200) - ); - } - - if (remainingMessageCount > 0) - Log.LogWarning("Failed to flush outgoing messages ({RemainingMessageCount} messages remaining).", _outgoing.Count); - } - - // Cancel all outstanding requests. - // This should not be necessary because request cancellation tokens should be linked to _cancellationSource, but better to be sure we won't leave a caller hanging. - foreach (TaskCompletionSource responseCompletion in _responseCompletions.Values) - { - responseCompletion.TrySetException( - new OperationCanceledException("The request was canceled because the underlying connection was closed.") - ); - } - - try - { - _cancellationSource?.Cancel(); - } - catch (AggregateException e) when (e.InnerException is ObjectDisposedException) - { - // Swallow object disposed exception - } - _sendLoop = null; - _receiveLoop = null; - _dispatchLoop = null; - _dispatcher = null; - } - - /// - /// Send an empty notification to the language server. - /// - /// - /// The notification method name. - /// - public void SendEmptyNotification(string method) - { - if (string.IsNullOrWhiteSpace(method)) - throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(method)}.", nameof(method)); - - if (!IsOpen) - throw new LspException("Not connected to the language server."); - - _outgoing.TryAdd(new ClientMessage { - // No Id means it's a notification. - Method = method - }); - } - - /// - /// Send a notification message to the language server. - /// - /// - /// The notification method name. - /// - /// - /// The notification message. - /// - public void SendNotification(string method, object notification) - { - if (string.IsNullOrWhiteSpace(method)) - throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(method)}.", nameof(method)); - - if (notification == null) - throw new ArgumentNullException(nameof(notification)); - - if (!IsOpen) - throw new LspException("Not connected to the language server."); - - _outgoing.TryAdd(new ClientMessage { - // No Id means it's a notification. - Method = method, - Params = JToken.FromObject(notification, Serializer.JsonSerializer) - }); - } - - /// - /// Send an empty request to the language server. - /// - /// - /// The request method name. - /// - public Task SendEmptyRequest(string method) - { - return SendRequest(method, EmptyRequest.Instance); - } - - /// - /// Send a request to the language server. - /// - /// - /// The request method name. - /// - /// - /// The request message. - /// - /// - /// An optional cancellation token that can be used to cancel the request. - /// - /// - /// A representing the request. - /// - public async Task SendRequest(string method, object request, CancellationToken cancellationToken = default(CancellationToken)) - { - if (string.IsNullOrWhiteSpace(method)) - throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(method)}.", nameof(method)); - - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (!IsOpen) - throw new LspException("Not connected to the language server."); - - string requestId = Interlocked.Increment(ref _nextRequestId).ToString(); - - var responseCompletion = new TaskCompletionSource(state: requestId); - cancellationToken.Register(() => { - responseCompletion.TrySetException( - new OperationCanceledException("The request was canceled via the supplied cancellation token.", cancellationToken) - ); - - // Send notification telling server to cancel the request, if possible. - if (!_outgoing.IsAddingCompleted) - { - _outgoing.TryAdd(new ClientMessage { - Method = JsonRpcNames.CancelRequest, - Params = new JObject( - new JProperty("id", requestId) - ) - }); - } - }); - - _responseCompletions.TryAdd(requestId, responseCompletion); - - _outgoing.TryAdd(new ClientMessage { - Id = requestId, - Method = method, - Params = request != null ? JToken.FromObject(request, Serializer.JsonSerializer) : null - }); - - await responseCompletion.Task; - } - - /// - /// Send a request to the language server. - /// - /// - /// The response message type. - /// - /// - /// The request method name. - /// - /// - /// The request message. - /// - /// - /// An optional cancellation token that can be used to cancel the request. - /// - /// - /// A representing the response. - /// - public async Task SendRequest(string method, object request, CancellationToken cancellationToken = default(CancellationToken)) - { - if (string.IsNullOrWhiteSpace(method)) - throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(method)}.", nameof(method)); - - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (!IsOpen) - throw new LspException("Not connected to the language server."); - - string requestId = Interlocked.Increment(ref _nextRequestId).ToString(); - - var responseCompletion = new TaskCompletionSource(state: requestId); - cancellationToken.Register(() => { - responseCompletion.TrySetException( - new OperationCanceledException("The request was canceled via the supplied cancellation token.", cancellationToken) - ); - - // Send notification telling server to cancel the request, if possible. - if (!_outgoing.IsAddingCompleted) - { - _outgoing.TryAdd(new ClientMessage { - Method = JsonRpcNames.CancelRequest, - Params = new JObject( - new JProperty("id", requestId) - ) - }); - } - }); - - _responseCompletions.TryAdd(requestId, responseCompletion); - - _outgoing.TryAdd(new ClientMessage { - Id = requestId, - Method = method, - Params = request != null ? JToken.FromObject(request, Serializer.JsonSerializer) : null - }); - - ServerMessage response = await responseCompletion.Task; - - if (response.Result != null) - return response.Result.ToObject(Serializer.JsonSerializer); - else - return default(TResponse); - } - - /// - /// The connection's message-send loop. - /// - /// - /// A representing the loop's activity. - /// - async Task SendLoop() - { - await Task.Yield(); - - Log.LogInformation("Send loop started."); - - try - { - while (_outgoing.TryTake(out object outgoing, -1, _cancellation)) - { - try - { - if (outgoing is ClientMessage message) - { - if (message.Id != null) - Log.LogDebug("Sending outgoing {RequestMethod} request {RequestId}...", message.Method, message.Id); - else - Log.LogDebug("Sending outgoing {RequestMethod} notification...", message.Method); - - await SendMessage(message); - - if (message.Id != null) - Log.LogDebug("Sent outgoing {RequestMethod} request {RequestId}.", message.Method, message.Id); - else - Log.LogDebug("Sent outgoing {RequestMethod} notification.", message.Method); - } - else if (outgoing is RpcError errorResponse) - { - Log.LogDebug("Sending outgoing error response {RequestId} ({ErrorMessage})...", errorResponse.Id, errorResponse.Error?.Message); - - await SendMessage(errorResponse); - - Log.LogDebug("Sent outgoing error response {RequestId}.", errorResponse.Id); - } - else - Log.LogError("Unexpected outgoing message type '{0}'.", outgoing.GetType().AssemblyQualifiedName); - } - catch (Exception sendError) - { - Log.LogError(sendError, "Unexpected error sending outgoing message {@Message}.", outgoing); - } - } - } - catch (OperationCanceledException operationCanceled) - { - // Like tears in rain - if (operationCanceled.CancellationToken != _cancellation) - throw; // time to die - } - finally - { - Log.LogInformation("Send loop terminated."); - } - } - - /// - /// The connection's message-receive loop. - /// - /// - /// A representing the loop's activity. - /// - async Task ReceiveLoop() - { - await Task.Yield(); - - Log.LogInformation("Receive loop started."); - - try - { - while (!_cancellation.IsCancellationRequested && !_incoming.IsAddingCompleted) - { - ServerMessage message = await ReceiveMessage(); - if (message == null) - continue; - - _cancellation.ThrowIfCancellationRequested(); - - try - { - if (message.Id != null) - { - // Request or response. - if (message.Params != null) - { - // Request. - Log.LogDebug("Received {RequestMethod} request {RequestId} from language server: {RequestParameters}", - message.Method, - message.Id, - message.Params?.ToString(Formatting.None) - ); - - // Publish. - if (!_incoming.IsAddingCompleted) - _incoming.TryAdd(message); - } - else - { - // Response. - string requestId = message.Id.ToString(); - if (_responseCompletions.TryGetValue(requestId, out var completion)) - { - if (message.Error != null) - { - Log.LogDebug("Received error response {RequestId} from language server: {@ErrorMessage}", - requestId, - message.Error - ); - - Log.LogDebug("Faulting request {RequestId}.", requestId); - - completion.TrySetException( - CreateLspException(message) - ); - } - else - { - Log.LogDebug("Received response {RequestId} from language server: {ResponseResult}", - requestId, - message.Result?.ToString(Formatting.None) - ); - - Log.LogDebug("Completing request {RequestId}.", requestId); - - completion.TrySetResult(message); - } - } - else - { - Log.LogDebug("Received unexpected response {RequestId} from language server: {ResponseResult}", - requestId, - message.Result?.ToString(Formatting.None) - ); - } - } - } - else - { - // Notification. - Log.LogDebug("Received {NotificationMethod} notification from language server: {NotificationParameters}", - message.Method, - message.Params?.ToString(Formatting.None) - ); - - // Publish. - if (!_incoming.IsAddingCompleted) - _incoming.TryAdd(message); - } - } - catch (Exception dispatchError) - { - Log.LogError(dispatchError, "Unexpected error processing incoming message {@Message}.", message); - } - } - } - catch (OperationCanceledException operationCanceled) - { - // Like tears in rain - if (operationCanceled.CancellationToken != _cancellation) - throw; // time to die - } - finally - { - Log.LogInformation("Receive loop terminated."); - } - } - - /// - /// Send a message to the language server. - /// - /// - /// The type of message to send. - /// - /// - /// The message to send. - /// - /// - /// A representing the operation. - /// - async Task SendMessage(TMessage message) - where TMessage : class - { - if (message == null) - throw new ArgumentNullException(nameof(message)); - - string payload = JsonConvert.SerializeObject(message, Serializer.Settings); - byte[] payloadBuffer = PayloadEncoding.GetBytes(payload); - - byte[] headerBuffer = HeaderEncoding.GetBytes( - $"Content-Length: {payloadBuffer.Length}\r\n\r\n" - ); - - Log.LogDebug("Sending outgoing header ({HeaderSize} bytes)...", headerBuffer.Length); - await _output.WriteAsync(headerBuffer, 0, headerBuffer.Length, _cancellation); - Log.LogDebug("Sent outgoing header ({HeaderSize} bytes).", headerBuffer.Length); - - Log.LogDebug("Sending outgoing payload ({PayloadSize} bytes)...", payloadBuffer.Length); - await _output.WriteAsync(payloadBuffer, 0, payloadBuffer.Length, _cancellation); - Log.LogDebug("Sent outgoing payload ({PayloadSize} bytes).", payloadBuffer.Length); - - Log.LogDebug("Flushing output stream..."); - await _output.FlushAsync(_cancellation); - Log.LogDebug("Flushed output stream."); - } - - /// - /// Receive a message from the language server. - /// - /// - /// A representing the message, - /// - async Task ReceiveMessage() - { - Log.LogDebug("Reading response headers..."); - - byte[] headerBuffer = new byte[HeaderBufferSize]; - int bytesRead = await _input.ReadAsync(headerBuffer, 0, MinimumHeaderLength, _cancellation); - - Log.LogDebug("Read {ByteCount} bytes from input stream.", bytesRead); - - if (bytesRead == 0) - return null; // Stream closed. - - const byte CR = (byte)'\r'; - const byte LF = (byte)'\n'; - - while (bytesRead < MinimumHeaderLength || - headerBuffer[bytesRead - 4] != CR || headerBuffer[bytesRead - 3] != LF || - headerBuffer[bytesRead - 2] != CR || headerBuffer[bytesRead - 1] != LF) - { - Log.LogDebug("Reading additional data from input stream..."); - - // Read single bytes until we've got a valid end-of-header sequence. - var additionalBytesRead = await _input.ReadAsync(headerBuffer, bytesRead, 1, _cancellation); - if (additionalBytesRead == 0) - return null; // no more _input, mitigates endless loop here. - - Log.LogDebug("Read {ByteCount} bytes of additional data from input stream.", additionalBytesRead); - - bytesRead += additionalBytesRead; - } - - string headers = HeaderEncoding.GetString(headerBuffer, 0, bytesRead); - Log.LogDebug("Got raw headers: {Headers}", headers); - - if (string.IsNullOrWhiteSpace(headers)) - return null; // Stream closed. - - Log.LogDebug("Read response headers {Headers}.", headers); - - Dictionary parsedHeaders = ParseHeaders(headers); - - if (!parsedHeaders.TryGetValue("Content-Length", out var contentLengthHeader)) - { - Log.LogDebug("Invalid request headers (missing 'Content-Length' header)."); - - return null; - } - - var contentLength = int.Parse(contentLengthHeader); - - Log.LogDebug("Reading response body ({ExpectedByteCount} bytes expected).", contentLength); - - var requestBuffer = new byte[contentLength]; - var received = 0; - while (received < contentLength) - { - Log.LogDebug("Reading segment of incoming request body ({ReceivedByteCount} of {TotalByteCount} bytes so far)...", received, contentLength); - - var payloadBytesRead = await _input.ReadAsync(requestBuffer, received, requestBuffer.Length - received, _cancellation); - if (payloadBytesRead == 0) - { - Log.LogWarning("Bailing out of reading payload (no_more_input after {ByteCount} bytes)...", received); - - return null; - } - received += payloadBytesRead; - - Log.LogDebug("Read segment of incoming request body ({ReceivedByteCount} of {TotalByteCount} bytes so far).", received, contentLength); - } - - Log.LogDebug("Received entire payload ({ReceivedByteCount} bytes).", received); - - string responseBody = PayloadEncoding.GetString(requestBuffer); - ServerMessage message = JsonConvert.DeserializeObject(responseBody, Serializer.Settings); - - Log.LogDebug("Read response body {ResponseBody}.", responseBody); - - return message; - } - - /// - /// Parse request headers. - /// - /// - /// - /// - /// A containing the header names and values. - /// - private Dictionary ParseHeaders(string rawHeaders) - { - if (rawHeaders == null) - throw new ArgumentNullException(nameof(rawHeaders)); - - var headers = new Dictionary(StringComparer.OrdinalIgnoreCase); // Header names are case-insensitive. - var rawHeaderEntries = rawHeaders.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries); - foreach (string rawHeaderEntry in rawHeaderEntries) - { - string[] nameAndValue = rawHeaderEntry.Split(new char[] { ':' }, count: 2); - if (nameAndValue.Length != 2) - continue; - - headers[nameAndValue[0].Trim()] = nameAndValue[1].Trim(); - } - - return headers; - } - - /// - /// The connection's message-dispatch loop. - /// - /// - /// A representing the loop's activity. - /// - async Task DispatchLoop() - { - await Task.Yield(); - - Log.LogInformation("Dispatch loop started."); - - try - { - while (_incoming.TryTake(out ServerMessage message, -1, _cancellation)) - { - if (message.Id != null) - { - // Request. - if (message.Method == JsonRpcNames.CancelRequest) - CancelRequest(message); - else - DispatchRequest(message); - } - else - { - // Notification. - DispatchNotification(message); - } - } - } - catch (OperationCanceledException operationCanceled) - { - // Like tears in rain - if (operationCanceled.CancellationToken != _cancellation) - throw; // time to die - } - finally - { - Log.LogInformation("Dispatch loop terminated."); - } - } - - /// - /// Dispatch a request. - /// - /// - /// The request message. - /// - private void DispatchRequest(ServerMessage requestMessage) - { - if (requestMessage == null) - throw new ArgumentNullException(nameof(requestMessage)); - - string requestId = requestMessage.Id.ToString(); - Log.LogDebug("Dispatching incoming {RequestMethod} request {RequestId}...", requestMessage.Method, requestId); - - var requestCancellation = CancellationTokenSource.CreateLinkedTokenSource(_cancellation); - _requestCancellations.TryAdd(requestId, requestCancellation); - - Task handlerTask = _dispatcher.TryHandleRequest(requestMessage.Method, requestMessage.Params, requestCancellation.Token); - if (handlerTask == null) - { - Log.LogWarning("Unable to dispatch incoming {RequestMethod} request {RequestId} (no handler registered).", requestMessage.Method, requestId); - - _outgoing.TryAdd( - new JsonRpcMessages.MethodNotFound(requestMessage.Id, requestMessage.Method) - ); - - return; - } - -#pragma warning disable CS4014 // Continuation does the work we need; no need to await it as this would tie up the dispatch loop. - handlerTask.ContinueWith(_ => { - if (handlerTask.IsCanceled) - Log.LogDebug("{RequestMethod} request {RequestId} canceled.", requestMessage.Method, requestId); - else if (handlerTask.IsFaulted) - { - Exception handlerError = handlerTask.Exception.Flatten().InnerExceptions[0]; - - Log.LogError(handlerError, "{RequestMethod} request {RequestId} failed (unexpected error raised by handler).", requestMessage.Method, requestId); - - _outgoing.TryAdd(new RpcError(requestId, - new JsonRpcMessages.ErrorMessage( - code: 500, - message: "Error processing request: " + handlerError.Message, - data: handlerError.ToString() - ) - )); - } - else if (handlerTask.IsCompleted) - { - Log.LogDebug("{RequestMethod} request {RequestId} complete (Result = {@Result}).", requestMessage.Method, requestId, handlerTask.Result); - - _outgoing.TryAdd(new ClientMessage { - Id = requestMessage.Id, - Method = requestMessage.Method, - Result = handlerTask.Result != null ? JToken.FromObject(handlerTask.Result, Serializer.JsonSerializer) : null - }); - } - - _requestCancellations.TryRemove(requestId, out CancellationTokenSource cancellation); - cancellation.Dispose(); - }); -#pragma warning restore CS4014 // Continuation does the work we need; no need to await it as this would tie up the dispatch loop. - - Log.LogDebug("Dispatched incoming {RequestMethod} request {RequestId}.", requestMessage.Method, requestMessage.Id); - } - - /// - /// Cancel a request. - /// - /// - /// The request message. - /// - void CancelRequest(ServerMessage requestMessage) - { - if (requestMessage == null) - throw new ArgumentNullException(nameof(requestMessage)); - - string cancelRequestId = requestMessage.Params?.Value("id")?.ToString(); - if (cancelRequestId != null) - { - if (_requestCancellations.TryRemove(cancelRequestId, out CancellationTokenSource requestCancellation)) - { - Log.LogDebug("Cancel request {RequestId}", requestMessage.Id); - requestCancellation.Cancel(); - requestCancellation.Dispose(); - } - else - Log.LogDebug("Received cancellation message for non-existent (or already-completed) request "); - } - else - { - Log.LogWarning("Received invalid request cancellation message {MessageId} (missing 'id' parameter).", requestMessage.Id); - - _outgoing.TryAdd( - new JsonRpcMessages.InvalidParams(requestMessage.Id) - ); - } - } - - /// - /// Dispatch a notification. - /// - /// - /// The notification message. - /// - void DispatchNotification(ServerMessage notificationMessage) - { - if (notificationMessage == null) - throw new ArgumentNullException(nameof(notificationMessage)); - - Log.LogDebug("Dispatching incoming {NotificationMethod} notification...", notificationMessage.Method); - - Task handlerTask; - if (notificationMessage.Params != null) - handlerTask = _dispatcher.TryHandleNotification(notificationMessage.Method, notificationMessage.Params); - else - handlerTask = _dispatcher.TryHandleEmptyNotification(notificationMessage.Method); - -#pragma warning disable CS4014 // Continuation does the work we need; no need to await it as this would tie up the dispatch loop. - handlerTask.ContinueWith(completedHandler => { - if (handlerTask.IsCanceled) - Log.LogDebug("{NotificationMethod} notification canceled.", notificationMessage.Method); - else if (handlerTask.IsFaulted) - { - Exception handlerError = handlerTask.Exception.Flatten().InnerExceptions[0]; - - Log.LogError(handlerError, "Failed to dispatch {NotificationMethod} notification (unexpected error raised by handler).", notificationMessage.Method); - } - else if (handlerTask.IsCompleted) - { - Log.LogDebug("{NotificationMethod} notification complete.", notificationMessage.Method); - - if (completedHandler.Result) - Log.LogDebug("Dispatched incoming {NotificationMethod} notification.", notificationMessage.Method); - else - Log.LogDebug("Ignored incoming {NotificationMethod} notification (no handler registered).", notificationMessage.Method); - } - }); -#pragma warning restore CS4014 // Continuation does the work we need; no need to await it as this would tie up the dispatch loop. - } - - /// - /// Create an to represent the specified message. - /// - /// - /// The ( must be populated). - /// - /// - /// The new . - /// - static LspException CreateLspException(ServerMessage message) - { - if (message == null) - throw new ArgumentNullException(nameof(message)); - - Trace.Assert(message.Error != null, "message.Error != null"); - - string requestId = message.Id?.ToString(); - - switch (message.Error.Code) - { - case LspErrorCodes.InvalidRequest: - { - return new LspInvalidRequestException(requestId); - } - case LspErrorCodes.InvalidParameters: - { - return new LspInvalidParametersException(requestId); - } - case LspErrorCodes.InternalError: - { - return new LspInternalErrorException(requestId); - } - case LspErrorCodes.MethodNotSupported: - { - return new LspMethodNotSupportedException(requestId, message.Method); - } - case LspErrorCodes.RequestCancelled: - { - return new LspRequestCancelledException(requestId); - } - default: - { - string exceptionMessage = $"Error processing request '{message.Id}' ({message.Error.Code}): {message.Error.Message}"; - - return new LspRequestException(exceptionMessage, requestId, message.Error.Code); - } - } - } - } -} diff --git a/src/Client/Protocol/LspConnectionExtensions.cs b/src/Client/Protocol/LspConnectionExtensions.cs deleted file mode 100644 index a9f961d73..000000000 --- a/src/Client/Protocol/LspConnectionExtensions.cs +++ /dev/null @@ -1,147 +0,0 @@ -using System; -using OmniSharp.Extensions.LanguageServer.Client.Handlers; - -namespace OmniSharp.Extensions.LanguageServer.Client.Protocol -{ - /// - /// Extension methods for enabling various styles of handler registration. - /// - public static class LspConnectionExtensions - { - /// - /// Register a handler for empty notifications. - /// - /// - /// The . - /// - /// - /// The name of the notification method to handle. - /// - /// - /// A delegate that implements the handler. - /// - /// - /// An representing the registration. - /// - public static IDisposable HandleEmptyNotification(this LspConnection clientConnection, string method, NotificationHandler handler) - { - if (clientConnection == null) - throw new ArgumentNullException(nameof(clientConnection)); - - if (string.IsNullOrWhiteSpace(method)) - throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(method)}.", nameof(method)); - - if (handler == null) - throw new ArgumentNullException(nameof(handler)); - - return clientConnection.RegisterHandler( - new DelegateEmptyNotificationHandler(method, handler) - ); - } - - /// - /// Register a handler for notifications. - /// - /// - /// The notification message type. - /// - /// - /// The . - /// - /// - /// The name of the notification method to handle. - /// - /// - /// A delegate that implements the handler. - /// - /// - /// An representing the registration. - /// - public static IDisposable HandleNotification(this LspConnection clientConnection, string method, NotificationHandler handler) - { - if (clientConnection == null) - throw new ArgumentNullException(nameof(clientConnection)); - - if (string.IsNullOrWhiteSpace(method)) - throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(method)}.", nameof(method)); - - if (handler == null) - throw new ArgumentNullException(nameof(handler)); - - return clientConnection.RegisterHandler( - new DelegateNotificationHandler(method, handler) - ); - } - - /// - /// Register a handler for requests. - /// - /// - /// The request message type. - /// - /// - /// The . - /// - /// - /// The name of the request method to handle. - /// - /// - /// A delegate that implements the handler. - /// - /// - /// An representing the registration. - /// - public static IDisposable HandleRequest(this LspConnection clientConnection, string method, RequestHandler handler) - { - if (clientConnection == null) - throw new ArgumentNullException(nameof(clientConnection)); - - if (string.IsNullOrWhiteSpace(method)) - throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(method)}.", nameof(method)); - - if (handler == null) - throw new ArgumentNullException(nameof(handler)); - - return clientConnection.RegisterHandler( - new DelegateRequestHandler(method, handler) - ); - } - - /// - /// Register a handler for requests. - /// - /// - /// The request message type. - /// - /// - /// The response message type. - /// - /// - /// The . - /// - /// - /// The name of the request method to handle. - /// - /// - /// A delegate that implements the handler. - /// - /// - /// An representing the registration. - /// - public static IDisposable HandleRequest(this LspConnection clientConnection, string method, RequestHandler handler) - { - if (clientConnection == null) - throw new ArgumentNullException(nameof(clientConnection)); - - if (string.IsNullOrWhiteSpace(method)) - throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(method)}.", nameof(method)); - - if (handler == null) - throw new ArgumentNullException(nameof(handler)); - - return clientConnection.RegisterHandler( - new DelegateRequestResponseHandler(method, handler) - ); - } - } -} diff --git a/src/Client/Protocol/ServerMessage.cs b/src/Client/Protocol/ServerMessage.cs deleted file mode 100644 index 60bcd99ce..000000000 --- a/src/Client/Protocol/ServerMessage.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using OmniSharp.Extensions.LanguageServer.Protocol.Serialization; - -namespace OmniSharp.Extensions.LanguageServer.Client.Protocol -{ - /// - /// The server-side representation of an LSP message. - /// - public class ServerMessage - { - /// - /// The JSON-RPC protocol version. - /// - [JsonProperty("jsonrpc")] - public string ProtocolVersion { get; set; } = "2.0"; - - /// - /// The request / response Id, if the message represents a request or a response. - /// - [Optional] - public object Id { get; set; } - - /// - /// The JSON-RPC method name. - /// - public string Method { get; set; } - - /// - /// The request / notification message, if the message represents a request or a notification. - /// - [Optional] - public JToken Params { get; set; } - - /// - /// The response message, if the message represents a response. - /// - [Optional] - public JToken Result { get; set; } - - /// - /// The response error (if any). - /// - [JsonProperty("error", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] - public ErrorMessage Error { get; set; } - } -} diff --git a/src/Client/PublishDiagnosticsHandler.cs b/src/Client/PublishDiagnosticsHandler.cs deleted file mode 100644 index 7296cb154..000000000 --- a/src/Client/PublishDiagnosticsHandler.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Collections.Generic; -using OmniSharp.Extensions.LanguageServer.Protocol; -using OmniSharp.Extensions.LanguageServer.Protocol.Models; - -namespace OmniSharp.Extensions.LanguageServer.Client -{ - /// - /// A handler for diagnostics published by the language server. - /// - /// - /// The URI of the document that the diagnostics apply to. - /// - /// - /// A list of s. - /// - /// - /// The diagnostics should replace any previously published diagnostics for the specified document. - /// - public delegate void PublishDiagnosticsHandler(DocumentUri documentUri, List diagnostics); -} diff --git a/src/Client/RegistrationManager.cs b/src/Client/RegistrationManager.cs new file mode 100644 index 000000000..aed6cae84 --- /dev/null +++ b/src/Client/RegistrationManager.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol; +using OmniSharp.Extensions.LanguageServer.Protocol.Client; +using OmniSharp.Extensions.LanguageServer.Protocol.Server.Capabilities; +using OmniSharp.Extensions.LanguageServer.Protocol.Shared; +using OmniSharp.Extensions.LanguageServer.Shared; +using ISerializer = OmniSharp.Extensions.LanguageServer.Protocol.Serialization.ISerializer; + +namespace OmniSharp.Extensions.LanguageServer.Client +{ + class RegistrationManager : IRegisterCapabilityHandler, IUnregisterCapabilityHandler, IRegistrationManager, IDisposable + { + private readonly ISerializer _serializer; + private readonly ILogger _logger; + private readonly ConcurrentDictionary _registrations; + private readonly ReplaySubject> _registrationSubject; + + public RegistrationManager(ISerializer serializer, ILogger logger) + { + _serializer = serializer; + _logger = logger; + _registrations = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + _registrationSubject = new ReplaySubject>(1); + } + + Task IRequestHandler.Handle(RegistrationParams request, CancellationToken cancellationToken) + { + Register(request.Registrations.ToArray()); + _registrationSubject.OnNext(_registrations.Values); + return Unit.Task; + } + + Task IRequestHandler.Handle(UnregistrationParams request, CancellationToken cancellationToken) + { + foreach (var item in request.Unregisterations ?? new UnregistrationContainer()) + { + _registrations.TryRemove(item.Id, out _); + } + + _registrationSubject.OnNext(_registrations.Values); + return Unit.Task; + } + + public void RegisterCapabilities(ServerCapabilities serverCapabilities) + { + foreach (var registrationOptions in LspHandlerDescriptorHelpers.GetStaticRegistrationOptions( + serverCapabilities)) + { + var descriptor = LspHandlerTypeDescriptorHelper.GetHandlerTypeForRegistrationOptions(registrationOptions); + if (descriptor == null) + { + _logger.LogWarning("Unable to find handler type descriptor for the given {@RegistrationOptions}", registrationOptions); + continue; + } + + var reg = new Registration() { + Id = registrationOptions.Id, + Method = descriptor.Method, + RegisterOptions = registrationOptions + }; + _registrations.AddOrUpdate(registrationOptions.Id, (x) => reg, (a, b) => reg); + } + + if (serverCapabilities.Workspace == null) + { + _registrationSubject.OnNext(_registrations.Values); + return; + } + + foreach (var registrationOptions in LspHandlerDescriptorHelpers.GetStaticRegistrationOptions(serverCapabilities + .Workspace)) + { + var descriptor = LspHandlerTypeDescriptorHelper.GetHandlerTypeForRegistrationOptions(registrationOptions); + if (descriptor == null) + { + // TODO: Log this + continue; + } + + var reg = new Registration() { + Id = registrationOptions.Id, + Method = descriptor.Method, + RegisterOptions = registrationOptions + }; + _registrations.AddOrUpdate(registrationOptions.Id, (x) => reg, (a, b) => reg); + } + } + + private void Register(params Registration[] registrations) + { + foreach (var registration in registrations) + { + Register(registration); + } + } + + private void Register(Registration registration) + { + var typeDescriptor = LspHandlerTypeDescriptorHelper.GetHandlerTypeDescriptor(registration.Method); + if (typeDescriptor == null) + { + _registrations.AddOrUpdate(registration.Id, (x) => registration, (a, b) => registration); + return; + } + + var deserializedRegistration = new Registration() { + Id = registration.Id, + Method = registration.Method, + RegisterOptions = registration.RegisterOptions is JToken token + ? token.ToObject(typeDescriptor.RegistrationType, _serializer.JsonSerializer) + : registration.RegisterOptions + }; + _registrations.AddOrUpdate(deserializedRegistration.Id, (x) => deserializedRegistration, (a, b) => deserializedRegistration); + } + + public IObservable> Registrations => _registrationSubject.AsObservable(); + public IEnumerable CurrentRegistrations => _registrations.Values; + + public IEnumerable GetRegistrationsForMethod(string method) => _registrations.Select(z => z.Value).Where(x => x.Method == method); + + public IEnumerable GetRegistrationsMatchingSelector(DocumentSelector documentSelector) => + _registrations + .Select(z => z.Value) + .Where(x => x.RegisterOptions is ITextDocumentRegistrationOptions ro && ro.DocumentSelector + .Join(documentSelector, + z => z.HasLanguage ? z.Language : + z.HasScheme ? z.Scheme : + z.HasPattern ? z.Pattern : string.Empty, + z => z.HasLanguage ? z.Language : + z.HasScheme ? z.Scheme : + z.HasPattern ? z.Pattern : string.Empty, (a, b) => a) + .Any(x => x.HasLanguage || x.HasPattern || x.HasScheme) + ); + + public void Dispose() => _registrationSubject.Dispose(); + } +} diff --git a/src/Client/RequestHandler.cs b/src/Client/RequestHandler.cs deleted file mode 100644 index 2217269cb..000000000 --- a/src/Client/RequestHandler.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace OmniSharp.Extensions.LanguageServer.Client -{ - /// - /// A handler for requests. - /// - /// - /// The request message type. - /// - /// - /// The request message. - /// - /// - /// A that can be used to cancel the operation. - /// - /// - /// A representing the operation. - /// - public delegate Task RequestHandler(TRequest request, CancellationToken cancellationToken); -} \ No newline at end of file diff --git a/src/Client/Utilities/DocumentUri.cs b/src/Client/Utilities/DocumentUri.cs deleted file mode 100644 index 91e543270..000000000 --- a/src/Client/Utilities/DocumentUri.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace OmniSharp.Extensions.LanguageServer.Client.Utilities -{ -} diff --git a/src/Client/WorkspaceFoldersManager.cs b/src/Client/WorkspaceFoldersManager.cs new file mode 100644 index 000000000..575967d78 --- /dev/null +++ b/src/Client/WorkspaceFoldersManager.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Subjects; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol; +using OmniSharp.Extensions.LanguageServer.Protocol.Client; +using OmniSharp.Extensions.LanguageServer.Protocol.Workspace; + +namespace OmniSharp.Extensions.LanguageServer.Client +{ + class WorkspaceFoldersManager : IWorkspaceFoldersHandler, IWorkspaceFoldersManager, IDisposable + { + private readonly ILanguageClient _client; + private readonly ConcurrentDictionary _workspaceFolders; + private readonly ReplaySubject> _workspaceFoldersSubject; + + public WorkspaceFoldersManager(ILanguageClient client) + { + _client = client; + _workspaceFolders = new ConcurrentDictionary(DocumentUri.Comparer); + _workspaceFoldersSubject = new ReplaySubject>(1); + } + + Task> IRequestHandler>. + Handle(WorkspaceFolderParams request, CancellationToken cancellationToken) + { + return Task.FromResult(new Container(_workspaceFolders.Values)); + } + + public void Add(DocumentUri uri, string name) + { + Add(new WorkspaceFolder() {Name = name, Uri = uri}); + } + + public void Add(IEnumerable workspaceFolders) + { + if (!workspaceFolders.Any()) return; + + foreach (var item in workspaceFolders) + { + _workspaceFolders.AddOrUpdate(item.Uri, _ => item, (a, b) => item); + } + + _client.Workspace.DidChangeWorkspaceFolders(new DidChangeWorkspaceFoldersParams() { + Event = new WorkspaceFoldersChangeEvent() { + Added = new Container(workspaceFolders) + } + }); + _workspaceFoldersSubject.OnNext(_workspaceFolders.Values); + } + + public void Add(params WorkspaceFolder[] workspaceFolders) + { + if (!workspaceFolders.Any()) return; + + foreach (var item in workspaceFolders) + { + _workspaceFolders.AddOrUpdate(item.Uri, _ => item, (a, b) => item); + } + + _client.Workspace.DidChangeWorkspaceFolders(new DidChangeWorkspaceFoldersParams() { + Event = new WorkspaceFoldersChangeEvent() { + Added = new Container(workspaceFolders) + } + }); + _workspaceFoldersSubject.OnNext(_workspaceFolders.Values); + } + + public void Remove(DocumentUri uri) + { + if (_workspaceFolders.TryRemove(uri, out var item)) + { + _client.Workspace.DidChangeWorkspaceFolders(new DidChangeWorkspaceFoldersParams() { + Event = new WorkspaceFoldersChangeEvent() { + Removed = new Container(item) + } + }); + _workspaceFoldersSubject.OnNext(_workspaceFolders.Values); + } + } + + public void Remove(string name) + { + var items = _workspaceFolders.Values.Where(z => z.Name == name).ToArray(); + if (items.Length > 0) + { + foreach (var item in items) + { + _workspaceFolders.TryRemove(item.Uri, out _); + } + _client.Workspace.DidChangeWorkspaceFolders(new DidChangeWorkspaceFoldersParams() { + Event = new WorkspaceFoldersChangeEvent() { + Removed = new Container(items) + } + }); + _workspaceFoldersSubject.OnNext(_workspaceFolders.Values); + } + } + + public void Remove(WorkspaceFolder workspaceFolder) + { + Remove(workspaceFolder.Uri); + } + + public IObservable> WorkspaceFolders => _workspaceFoldersSubject; + + public IEnumerable CurrentWorkspaceFolders => _workspaceFolders.Values; + + public void Dispose() + { + } + } +} diff --git a/src/Dap.Client/Dap.Client.csproj b/src/Dap.Client/Dap.Client.csproj new file mode 100644 index 000000000..a3ddb4123 --- /dev/null +++ b/src/Dap.Client/Dap.Client.csproj @@ -0,0 +1,19 @@ + + + + netstandard2.1;netstandard2.0 + AnyCPU + OmniSharp.Extensions.DebugAdapter.Client + OmniSharp.Extensions.DebugAdapter.Client + You can use this package to interact with a Debug Adapter using the debug adapter protocol + + + + + + + + + + + diff --git a/src/JsonRpc/DapReciever.cs b/src/Dap.Protocol/DapReceiver.cs similarity index 98% rename from src/JsonRpc/DapReciever.cs rename to src/Dap.Protocol/DapReceiver.cs index 31528749e..e0dc0b85f 100644 --- a/src/JsonRpc/DapReciever.cs +++ b/src/Dap.Protocol/DapReceiver.cs @@ -83,7 +83,7 @@ protected virtual Renor GetRenor(JToken @object) { return new ServerResponse(requestSequence, bodyValue); } - return new ServerError(requestSequence, bodyValue); + return new ServerError(requestSequence, bodyValue.ToObject()); } throw new NotSupportedException($"Message type {messageType} is not supported"); diff --git a/src/Dap.Protocol/DebugAdapterConverters/DapClientNotificationConverter.cs b/src/Dap.Protocol/DebugAdapterConverters/DapClientNotificationConverter.cs index 07094f0f7..146b9cc2b 100644 --- a/src/Dap.Protocol/DebugAdapterConverters/DapClientNotificationConverter.cs +++ b/src/Dap.Protocol/DebugAdapterConverters/DapClientNotificationConverter.cs @@ -5,7 +5,7 @@ namespace OmniSharp.Extensions.DebugAdapter.Protocol.DebugAdapterConverters { - class DapClientNotificationConverter : JsonConverter + class DapClientNotificationConverter : JsonConverter { private readonly ISerializer _serializer; @@ -16,13 +16,13 @@ public DapClientNotificationConverter(ISerializer serializer) public override bool CanRead => false; - public override Notification ReadJson(JsonReader reader, Type objectType, Notification existingValue, + public override OutgoingNotification ReadJson(JsonReader reader, Type objectType, OutgoingNotification existingValue, bool hasExistingValue, JsonSerializer serializer) { throw new NotImplementedException(); } - public override void WriteJson(JsonWriter writer, Notification value, JsonSerializer serializer) + public override void WriteJson(JsonWriter writer, OutgoingNotification value, JsonSerializer serializer) { writer.WriteStartObject(); writer.WritePropertyName("seq"); diff --git a/src/Dap.Protocol/DebugAdapterConverters/DapClientRequestConverter.cs b/src/Dap.Protocol/DebugAdapterConverters/DapClientRequestConverter.cs index 29594953f..315eb7206 100644 --- a/src/Dap.Protocol/DebugAdapterConverters/DapClientRequestConverter.cs +++ b/src/Dap.Protocol/DebugAdapterConverters/DapClientRequestConverter.cs @@ -4,16 +4,16 @@ namespace OmniSharp.Extensions.DebugAdapter.Protocol.DebugAdapterConverters { - class DapClientRequestConverter : JsonConverter + class DapClientRequestConverter : JsonConverter { public override bool CanRead => false; - public override Request ReadJson(JsonReader reader, Type objectType, Request existingValue, + public override OutgoingRequest ReadJson(JsonReader reader, Type objectType, OutgoingRequest existingValue, bool hasExistingValue, JsonSerializer serializer) { throw new NotImplementedException(); } - public override void WriteJson(JsonWriter writer, Request value, JsonSerializer serializer) + public override void WriteJson(JsonWriter writer, OutgoingRequest value, JsonSerializer serializer) { writer.WriteStartObject(); writer.WritePropertyName("seq"); diff --git a/src/Dap.Protocol/DebugAdapterConverters/DapClientResponseConverter.cs b/src/Dap.Protocol/DebugAdapterConverters/DapClientResponseConverter.cs index ee4ad7aaf..7b425deed 100644 --- a/src/Dap.Protocol/DebugAdapterConverters/DapClientResponseConverter.cs +++ b/src/Dap.Protocol/DebugAdapterConverters/DapClientResponseConverter.cs @@ -5,7 +5,7 @@ namespace OmniSharp.Extensions.DebugAdapter.Protocol.DebugAdapterConverters { - class DapClientResponseConverter : JsonConverter + class DapClientResponseConverter : JsonConverter { private readonly ISerializer _serializer; @@ -15,13 +15,13 @@ public DapClientResponseConverter(ISerializer serializer) } public override bool CanRead => false; - public override Response ReadJson(JsonReader reader, Type objectType, Response existingValue, + public override OutgoingResponse ReadJson(JsonReader reader, Type objectType, OutgoingResponse existingValue, bool hasExistingValue, JsonSerializer serializer) { throw new NotImplementedException(); } - public override void WriteJson(JsonWriter writer, Response value, JsonSerializer serializer) + public override void WriteJson(JsonWriter writer, OutgoingResponse value, JsonSerializer serializer) { writer.WriteStartObject(); writer.WritePropertyName("seq"); diff --git a/src/Dap.Protocol/Events/BreakpointExtensions.cs b/src/Dap.Protocol/Events/BreakpointExtensions.cs index a37e4e991..16bb600ee 100644 --- a/src/Dap.Protocol/Events/BreakpointExtensions.cs +++ b/src/Dap.Protocol/Events/BreakpointExtensions.cs @@ -17,22 +17,22 @@ public abstract class BreakpointHandler : IBreakpointHandler public static class BreakpointExtensions { - public static IDisposable OnBreakpoint(this IDebugAdapterClientRegistry registry, Action handler) + public static IDebugAdapterClientRegistry OnBreakpoint(this IDebugAdapterClientRegistry registry, Action handler) { return registry.AddHandler(EventNames.Breakpoint, NotificationHandler.For(handler)); } - public static IDisposable OnBreakpoint(this IDebugAdapterClientRegistry registry, Action handler) + public static IDebugAdapterClientRegistry OnBreakpoint(this IDebugAdapterClientRegistry registry, Action handler) { return registry.AddHandler(EventNames.Breakpoint, NotificationHandler.For(handler)); } - public static IDisposable OnBreakpoint(this IDebugAdapterClientRegistry registry, Func handler) + public static IDebugAdapterClientRegistry OnBreakpoint(this IDebugAdapterClientRegistry registry, Func handler) { return registry.AddHandler(EventNames.Breakpoint, NotificationHandler.For(handler)); } - public static IDisposable OnBreakpoint(this IDebugAdapterClientRegistry registry, Func handler) + public static IDebugAdapterClientRegistry OnBreakpoint(this IDebugAdapterClientRegistry registry, Func handler) { return registry.AddHandler(EventNames.Breakpoint, NotificationHandler.For(handler)); } diff --git a/src/Dap.Protocol/Events/CapabilitiesExtensions.cs b/src/Dap.Protocol/Events/CapabilitiesExtensions.cs index 238e1639a..3ae6d6223 100644 --- a/src/Dap.Protocol/Events/CapabilitiesExtensions.cs +++ b/src/Dap.Protocol/Events/CapabilitiesExtensions.cs @@ -17,22 +17,22 @@ public abstract class CapabilitiesHandler : ICapabilitiesHandler public static class CapabilitiesExtensions { - public static IDisposable OnCapabilities(this IDebugAdapterClientRegistry registry, Action handler) + public static IDebugAdapterClientRegistry OnCapabilities(this IDebugAdapterClientRegistry registry, Action handler) { return registry.AddHandler(EventNames.Capabilities, NotificationHandler.For(handler)); } - public static IDisposable OnCapabilities(this IDebugAdapterClientRegistry registry, Action handler) + public static IDebugAdapterClientRegistry OnCapabilities(this IDebugAdapterClientRegistry registry, Action handler) { return registry.AddHandler(EventNames.Capabilities, NotificationHandler.For(handler)); } - public static IDisposable OnCapabilities(this IDebugAdapterClientRegistry registry, Func handler) + public static IDebugAdapterClientRegistry OnCapabilities(this IDebugAdapterClientRegistry registry, Func handler) { return registry.AddHandler(EventNames.Capabilities, NotificationHandler.For(handler)); } - public static IDisposable OnCapabilities(this IDebugAdapterClientRegistry registry, Func handler) + public static IDebugAdapterClientRegistry OnCapabilities(this IDebugAdapterClientRegistry registry, Func handler) { return registry.AddHandler(EventNames.Capabilities, NotificationHandler.For(handler)); } diff --git a/src/Dap.Protocol/Events/ContinuedExtensions.cs b/src/Dap.Protocol/Events/ContinuedExtensions.cs index 4a53e4f28..a94ecea3f 100644 --- a/src/Dap.Protocol/Events/ContinuedExtensions.cs +++ b/src/Dap.Protocol/Events/ContinuedExtensions.cs @@ -17,22 +17,22 @@ public abstract class ContinuedHandler : IContinuedHandler public static class ContinuedExtensions { - public static IDisposable OnContinued(this IDebugAdapterClientRegistry registry, Action handler) + public static IDebugAdapterClientRegistry OnContinued(this IDebugAdapterClientRegistry registry, Action handler) { return registry.AddHandler(EventNames.Continued, NotificationHandler.For(handler)); } - public static IDisposable OnContinued(this IDebugAdapterClientRegistry registry, Action handler) + public static IDebugAdapterClientRegistry OnContinued(this IDebugAdapterClientRegistry registry, Action handler) { return registry.AddHandler(EventNames.Continued, NotificationHandler.For(handler)); } - public static IDisposable OnContinued(this IDebugAdapterClientRegistry registry, Func handler) + public static IDebugAdapterClientRegistry OnContinued(this IDebugAdapterClientRegistry registry, Func handler) { return registry.AddHandler(EventNames.Continued, NotificationHandler.For(handler)); } - public static IDisposable OnContinued(this IDebugAdapterClientRegistry registry, Func handler) + public static IDebugAdapterClientRegistry OnContinued(this IDebugAdapterClientRegistry registry, Func handler) { return registry.AddHandler(EventNames.Continued, NotificationHandler.For(handler)); } diff --git a/src/Dap.Protocol/Events/ExitedExtensions.cs b/src/Dap.Protocol/Events/ExitedExtensions.cs index e1944e99a..c409f0c88 100644 --- a/src/Dap.Protocol/Events/ExitedExtensions.cs +++ b/src/Dap.Protocol/Events/ExitedExtensions.cs @@ -17,22 +17,22 @@ public abstract class ExitedHandler : IExitedHandler public static class ExitedExtensions { - public static IDisposable OnExited(this IDebugAdapterClientRegistry registry, Action handler) + public static IDebugAdapterClientRegistry OnExited(this IDebugAdapterClientRegistry registry, Action handler) { return registry.AddHandler(EventNames.Exited, NotificationHandler.For(handler)); } - public static IDisposable OnExited(this IDebugAdapterClientRegistry registry, Action handler) + public static IDebugAdapterClientRegistry OnExited(this IDebugAdapterClientRegistry registry, Action handler) { return registry.AddHandler(EventNames.Exited, NotificationHandler.For(handler)); } - public static IDisposable OnExited(this IDebugAdapterClientRegistry registry, Func handler) + public static IDebugAdapterClientRegistry OnExited(this IDebugAdapterClientRegistry registry, Func handler) { return registry.AddHandler(EventNames.Exited, NotificationHandler.For(handler)); } - public static IDisposable OnExited(this IDebugAdapterClientRegistry registry, Func handler) + public static IDebugAdapterClientRegistry OnExited(this IDebugAdapterClientRegistry registry, Func handler) { return registry.AddHandler(EventNames.Exited, NotificationHandler.For(handler)); } diff --git a/src/Dap.Protocol/Events/InitializedExtensions.cs b/src/Dap.Protocol/Events/InitializedExtensions.cs index e51be006a..f595391c0 100644 --- a/src/Dap.Protocol/Events/InitializedExtensions.cs +++ b/src/Dap.Protocol/Events/InitializedExtensions.cs @@ -17,22 +17,22 @@ public abstract class InitializedHandler : IInitializedHandler public static class InitializedExtensions { - public static IDisposable OnInitialized(this IDebugAdapterClientRegistry registry, Action handler) + public static IDebugAdapterClientRegistry OnInitialized(this IDebugAdapterClientRegistry registry, Action handler) { return registry.AddHandler(EventNames.Initialized, NotificationHandler.For(handler)); } - public static IDisposable OnInitialized(this IDebugAdapterClientRegistry registry, Action handler) + public static IDebugAdapterClientRegistry OnInitialized(this IDebugAdapterClientRegistry registry, Action handler) { return registry.AddHandler(EventNames.Initialized, NotificationHandler.For(handler)); } - public static IDisposable OnInitialized(this IDebugAdapterClientRegistry registry, Func handler) + public static IDebugAdapterClientRegistry OnInitialized(this IDebugAdapterClientRegistry registry, Func handler) { return registry.AddHandler(EventNames.Initialized, NotificationHandler.For(handler)); } - public static IDisposable OnInitialized(this IDebugAdapterClientRegistry registry, Func handler) + public static IDebugAdapterClientRegistry OnInitialized(this IDebugAdapterClientRegistry registry, Func handler) { return registry.AddHandler(EventNames.Initialized, NotificationHandler.For(handler)); } diff --git a/src/Dap.Protocol/Events/LoadedSourceExtensions.cs b/src/Dap.Protocol/Events/LoadedSourceExtensions.cs index 2e5838605..c309eb89c 100644 --- a/src/Dap.Protocol/Events/LoadedSourceExtensions.cs +++ b/src/Dap.Protocol/Events/LoadedSourceExtensions.cs @@ -17,22 +17,22 @@ public abstract class LoadedSourceHandler : ILoadedSourceHandler public static class LoadedSourceExtensions { - public static IDisposable OnLoadedSource(this IDebugAdapterClientRegistry registry, Action handler) + public static IDebugAdapterClientRegistry OnLoadedSource(this IDebugAdapterClientRegistry registry, Action handler) { return registry.AddHandler(EventNames.LoadedSource, NotificationHandler.For(handler)); } - public static IDisposable OnLoadedSource(this IDebugAdapterClientRegistry registry, Action handler) + public static IDebugAdapterClientRegistry OnLoadedSource(this IDebugAdapterClientRegistry registry, Action handler) { return registry.AddHandler(EventNames.LoadedSource, NotificationHandler.For(handler)); } - public static IDisposable OnLoadedSource(this IDebugAdapterClientRegistry registry, Func handler) + public static IDebugAdapterClientRegistry OnLoadedSource(this IDebugAdapterClientRegistry registry, Func handler) { return registry.AddHandler(EventNames.LoadedSource, NotificationHandler.For(handler)); } - public static IDisposable OnLoadedSource(this IDebugAdapterClientRegistry registry, Func handler) + public static IDebugAdapterClientRegistry OnLoadedSource(this IDebugAdapterClientRegistry registry, Func handler) { return registry.AddHandler(EventNames.LoadedSource, NotificationHandler.For(handler)); } diff --git a/src/Dap.Protocol/Events/ModuleExtensions.cs b/src/Dap.Protocol/Events/ModuleExtensions.cs index 039503c1b..86e54ea5c 100644 --- a/src/Dap.Protocol/Events/ModuleExtensions.cs +++ b/src/Dap.Protocol/Events/ModuleExtensions.cs @@ -17,22 +17,22 @@ public abstract class ModuleHandler : IModuleHandler public static class ModuleExtensions { - public static IDisposable OnModule(this IDebugAdapterClientRegistry registry, Action handler) + public static IDebugAdapterClientRegistry OnModule(this IDebugAdapterClientRegistry registry, Action handler) { return registry.AddHandler(EventNames.Module, NotificationHandler.For(handler)); } - public static IDisposable OnModule(this IDebugAdapterClientRegistry registry, Action handler) + public static IDebugAdapterClientRegistry OnModule(this IDebugAdapterClientRegistry registry, Action handler) { return registry.AddHandler(EventNames.Module, NotificationHandler.For(handler)); } - public static IDisposable OnModule(this IDebugAdapterClientRegistry registry, Func handler) + public static IDebugAdapterClientRegistry OnModule(this IDebugAdapterClientRegistry registry, Func handler) { return registry.AddHandler(EventNames.Module, NotificationHandler.For(handler)); } - public static IDisposable OnModule(this IDebugAdapterClientRegistry registry, Func handler) + public static IDebugAdapterClientRegistry OnModule(this IDebugAdapterClientRegistry registry, Func handler) { return registry.AddHandler(EventNames.Module, NotificationHandler.For(handler)); } diff --git a/src/Dap.Protocol/Events/OutputExtensions.cs b/src/Dap.Protocol/Events/OutputExtensions.cs index 99013313e..9d8afe07d 100644 --- a/src/Dap.Protocol/Events/OutputExtensions.cs +++ b/src/Dap.Protocol/Events/OutputExtensions.cs @@ -16,22 +16,22 @@ public abstract class OutputHandler : IOutputHandler public static class OutputExtensions { - public static IDisposable OnOutput(this IDebugAdapterClientRegistry registry, Action handler) + public static IDebugAdapterClientRegistry OnOutput(this IDebugAdapterClientRegistry registry, Action handler) { return registry.AddHandler(EventNames.Output, NotificationHandler.For(handler)); } - public static IDisposable OnOutput(this IDebugAdapterClientRegistry registry, Action handler) + public static IDebugAdapterClientRegistry OnOutput(this IDebugAdapterClientRegistry registry, Action handler) { return registry.AddHandler(EventNames.Output, NotificationHandler.For(handler)); } - public static IDisposable OnOutput(this IDebugAdapterClientRegistry registry, Func handler) + public static IDebugAdapterClientRegistry OnOutput(this IDebugAdapterClientRegistry registry, Func handler) { return registry.AddHandler(EventNames.Output, NotificationHandler.For(handler)); } - public static IDisposable OnOutput(this IDebugAdapterClientRegistry registry, Func handler) + public static IDebugAdapterClientRegistry OnOutput(this IDebugAdapterClientRegistry registry, Func handler) { return registry.AddHandler(EventNames.Output, NotificationHandler.For(handler)); } diff --git a/src/Dap.Protocol/Events/ProcessExtensions.cs b/src/Dap.Protocol/Events/ProcessExtensions.cs index 1cdc58c9e..43bcacebb 100644 --- a/src/Dap.Protocol/Events/ProcessExtensions.cs +++ b/src/Dap.Protocol/Events/ProcessExtensions.cs @@ -17,22 +17,22 @@ public abstract class ProcessHandler : IProcessHandler public static class ProcessExtensions { - public static IDisposable OnProcess(this IDebugAdapterClientRegistry registry, Action handler) + public static IDebugAdapterClientRegistry OnProcess(this IDebugAdapterClientRegistry registry, Action handler) { return registry.AddHandler(EventNames.Process, NotificationHandler.For(handler)); } - public static IDisposable OnProcess(this IDebugAdapterClientRegistry registry, Action handler) + public static IDebugAdapterClientRegistry OnProcess(this IDebugAdapterClientRegistry registry, Action handler) { return registry.AddHandler(EventNames.Process, NotificationHandler.For(handler)); } - public static IDisposable OnProcess(this IDebugAdapterClientRegistry registry, Func handler) + public static IDebugAdapterClientRegistry OnProcess(this IDebugAdapterClientRegistry registry, Func handler) { return registry.AddHandler(EventNames.Process, NotificationHandler.For(handler)); } - public static IDisposable OnProcess(this IDebugAdapterClientRegistry registry, Func handler) + public static IDebugAdapterClientRegistry OnProcess(this IDebugAdapterClientRegistry registry, Func handler) { return registry.AddHandler(EventNames.Process, NotificationHandler.For(handler)); } diff --git a/src/Dap.Protocol/Events/StoppedExtensions.cs b/src/Dap.Protocol/Events/StoppedExtensions.cs index ebc5d8dce..92b95dfdf 100644 --- a/src/Dap.Protocol/Events/StoppedExtensions.cs +++ b/src/Dap.Protocol/Events/StoppedExtensions.cs @@ -17,22 +17,22 @@ public abstract class StoppedHandler : IStoppedHandler public static class StoppedExtensions { - public static IDisposable OnStopped(this IDebugAdapterClientRegistry registry, Action handler) + public static IDebugAdapterClientRegistry OnStopped(this IDebugAdapterClientRegistry registry, Action handler) { return registry.AddHandler(EventNames.Stopped, NotificationHandler.For(handler)); } - public static IDisposable OnStopped(this IDebugAdapterClientRegistry registry, Action handler) + public static IDebugAdapterClientRegistry OnStopped(this IDebugAdapterClientRegistry registry, Action handler) { return registry.AddHandler(EventNames.Stopped, NotificationHandler.For(handler)); } - public static IDisposable OnStopped(this IDebugAdapterClientRegistry registry, Func handler) + public static IDebugAdapterClientRegistry OnStopped(this IDebugAdapterClientRegistry registry, Func handler) { return registry.AddHandler(EventNames.Stopped, NotificationHandler.For(handler)); } - public static IDisposable OnStopped(this IDebugAdapterClientRegistry registry, Func handler) + public static IDebugAdapterClientRegistry OnStopped(this IDebugAdapterClientRegistry registry, Func handler) { return registry.AddHandler(EventNames.Stopped, NotificationHandler.For(handler)); } diff --git a/src/Dap.Protocol/Events/TerminatedExtensions.cs b/src/Dap.Protocol/Events/TerminatedExtensions.cs index 8b21674c1..4207e044e 100644 --- a/src/Dap.Protocol/Events/TerminatedExtensions.cs +++ b/src/Dap.Protocol/Events/TerminatedExtensions.cs @@ -17,22 +17,22 @@ public abstract class TerminatedHandler : ITerminatedHandler public static class TerminatedExtensions { - public static IDisposable OnTerminated(this IDebugAdapterClientRegistry registry, Action handler) + public static IDebugAdapterClientRegistry OnTerminated(this IDebugAdapterClientRegistry registry, Action handler) { return registry.AddHandler(EventNames.Terminated, NotificationHandler.For(handler)); } - public static IDisposable OnTerminated(this IDebugAdapterClientRegistry registry, Action handler) + public static IDebugAdapterClientRegistry OnTerminated(this IDebugAdapterClientRegistry registry, Action handler) { return registry.AddHandler(EventNames.Terminated, NotificationHandler.For(handler)); } - public static IDisposable OnTerminated(this IDebugAdapterClientRegistry registry, Func handler) + public static IDebugAdapterClientRegistry OnTerminated(this IDebugAdapterClientRegistry registry, Func handler) { return registry.AddHandler(EventNames.Terminated, NotificationHandler.For(handler)); } - public static IDisposable OnTerminated(this IDebugAdapterClientRegistry registry, Func handler) + public static IDebugAdapterClientRegistry OnTerminated(this IDebugAdapterClientRegistry registry, Func handler) { return registry.AddHandler(EventNames.Terminated, NotificationHandler.For(handler)); } diff --git a/src/Dap.Protocol/Events/ThreadExtensions.cs b/src/Dap.Protocol/Events/ThreadExtensions.cs index 21ca81161..805851602 100644 --- a/src/Dap.Protocol/Events/ThreadExtensions.cs +++ b/src/Dap.Protocol/Events/ThreadExtensions.cs @@ -17,22 +17,22 @@ public abstract class ThreadHandler : IThreadHandler public static class ThreadExtensions { - public static IDisposable OnThread(this IDebugAdapterClientRegistry registry, Action handler) + public static IDebugAdapterClientRegistry OnThread(this IDebugAdapterClientRegistry registry, Action handler) { return registry.AddHandler(EventNames.Thread, NotificationHandler.For(handler)); } - public static IDisposable OnThread(this IDebugAdapterClientRegistry registry, Action handler) + public static IDebugAdapterClientRegistry OnThread(this IDebugAdapterClientRegistry registry, Action handler) { return registry.AddHandler(EventNames.Thread, NotificationHandler.For(handler)); } - public static IDisposable OnThread(this IDebugAdapterClientRegistry registry, Func handler) + public static IDebugAdapterClientRegistry OnThread(this IDebugAdapterClientRegistry registry, Func handler) { return registry.AddHandler(EventNames.Thread, NotificationHandler.For(handler)); } - public static IDisposable OnThread(this IDebugAdapterClientRegistry registry, Func handler) + public static IDebugAdapterClientRegistry OnThread(this IDebugAdapterClientRegistry registry, Func handler) { return registry.AddHandler(EventNames.Thread, NotificationHandler.For(handler)); } diff --git a/src/Dap.Protocol/IDebugAdapterClientRegistry.cs b/src/Dap.Protocol/IDebugAdapterClientRegistry.cs index 820837631..f00f02c61 100644 --- a/src/Dap.Protocol/IDebugAdapterClientRegistry.cs +++ b/src/Dap.Protocol/IDebugAdapterClientRegistry.cs @@ -3,8 +3,7 @@ namespace OmniSharp.Extensions.DebugAdapter.Protocol { - public interface IDebugAdapterClientRegistry : IJsonRpcHandlerRegistry + public interface IDebugAdapterClientRegistry : IJsonRpcHandlerRegistry { - IDisposable AddHandler(Func handlerFunc) where T : IJsonRpcHandler; } } diff --git a/src/Dap.Protocol/IDebugAdapterServerRegistry.cs b/src/Dap.Protocol/IDebugAdapterServerRegistry.cs index bbaa7bfaa..95886b008 100644 --- a/src/Dap.Protocol/IDebugAdapterServerRegistry.cs +++ b/src/Dap.Protocol/IDebugAdapterServerRegistry.cs @@ -3,8 +3,7 @@ namespace OmniSharp.Extensions.DebugAdapter.Protocol { - public interface IDebugAdapterServerRegistry : IJsonRpcHandlerRegistry + public interface IDebugAdapterServerRegistry : IJsonRpcHandlerRegistry { - IDisposable AddHandler(Func handlerFunc) where T : IJsonRpcHandler; } } diff --git a/src/Dap.Protocol/Requests/IAttachHandler.cs b/src/Dap.Protocol/Requests/IAttachHandler.cs index 0a48a9d75..b694b5802 100644 --- a/src/Dap.Protocol/Requests/IAttachHandler.cs +++ b/src/Dap.Protocol/Requests/IAttachHandler.cs @@ -15,12 +15,12 @@ public abstract class AttachHandler : IAttachHandler public static class AttachExtensions { - public static IDisposable OnAttach(this IDebugAdapterServerRegistry registry, Func> handler) + public static IDebugAdapterServerRegistry OnAttach(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Attach, RequestHandler.For(handler)); } - public static IDisposable OnAttach(this IDebugAdapterServerRegistry registry, Func> handler) + public static IDebugAdapterServerRegistry OnAttach(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Attach, RequestHandler.For(handler)); } diff --git a/src/Dap.Protocol/Requests/ICompletionsHandler.cs b/src/Dap.Protocol/Requests/ICompletionsHandler.cs index 3ce17e12d..faf0de387 100644 --- a/src/Dap.Protocol/Requests/ICompletionsHandler.cs +++ b/src/Dap.Protocol/Requests/ICompletionsHandler.cs @@ -15,12 +15,12 @@ public abstract class CompletionsHandler : ICompletionsHandler public static class CompletionsExtensions { - public static IDisposable OnCompletions(this IDebugAdapterServerRegistry registry, Func> handler) + public static IDebugAdapterServerRegistry OnCompletions(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Completions, RequestHandler.For(handler)); } - public static IDisposable OnCompletions(this IDebugAdapterServerRegistry registry, Func> handler) + public static IDebugAdapterServerRegistry OnCompletions(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Completions, RequestHandler.For(handler)); } diff --git a/src/Dap.Protocol/Requests/IConfigurationDoneHandler.cs b/src/Dap.Protocol/Requests/IConfigurationDoneHandler.cs index 7d28ea0fa..c696a0080 100644 --- a/src/Dap.Protocol/Requests/IConfigurationDoneHandler.cs +++ b/src/Dap.Protocol/Requests/IConfigurationDoneHandler.cs @@ -19,13 +19,13 @@ public abstract Task Handle(ConfigurationDoneArgument public static class ConfigurationDoneExtensions { - public static IDisposable OnConfigurationDone(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnConfigurationDone(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.ConfigurationDone, RequestHandler.For(handler)); } - public static IDisposable OnConfigurationDone(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnConfigurationDone(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.ConfigurationDone, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/IContinueHandler.cs b/src/Dap.Protocol/Requests/IContinueHandler.cs index 0d220b4f3..dd4c00595 100644 --- a/src/Dap.Protocol/Requests/IContinueHandler.cs +++ b/src/Dap.Protocol/Requests/IContinueHandler.cs @@ -17,13 +17,13 @@ public abstract class ContinueHandler : IContinueHandler public static class ContinueExtensions { - public static IDisposable OnContinue(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnContinue(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Continue, RequestHandler.For(handler)); } - public static IDisposable OnContinue(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnContinue(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Continue, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/IDataBreakpointInfoHandler.cs b/src/Dap.Protocol/Requests/IDataBreakpointInfoHandler.cs index 00a7ec69f..eb3ea46f8 100644 --- a/src/Dap.Protocol/Requests/IDataBreakpointInfoHandler.cs +++ b/src/Dap.Protocol/Requests/IDataBreakpointInfoHandler.cs @@ -19,13 +19,13 @@ public abstract Task Handle(DataBreakpointInfoArgume public static class DataBreakpointInfoExtensions { - public static IDisposable OnDataBreakpointInfo(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnDataBreakpointInfo(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.DataBreakpointInfo, RequestHandler.For(handler)); } - public static IDisposable OnDataBreakpointInfo(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnDataBreakpointInfo(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.DataBreakpointInfo, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/IDisassembleHandler.cs b/src/Dap.Protocol/Requests/IDisassembleHandler.cs index aa4fa3542..5b597ac22 100644 --- a/src/Dap.Protocol/Requests/IDisassembleHandler.cs +++ b/src/Dap.Protocol/Requests/IDisassembleHandler.cs @@ -18,13 +18,13 @@ public abstract Task Handle(DisassembleArguments request, public static class DisassembleExtensions { - public static IDisposable OnDisassemble(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnDisassemble(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Disassemble, RequestHandler.For(handler)); } - public static IDisposable OnDisassemble(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnDisassemble(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Disassemble, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/IDisconnectHandler.cs b/src/Dap.Protocol/Requests/IDisconnectHandler.cs index 6b8cbff03..e39985702 100644 --- a/src/Dap.Protocol/Requests/IDisconnectHandler.cs +++ b/src/Dap.Protocol/Requests/IDisconnectHandler.cs @@ -18,13 +18,13 @@ public abstract Task Handle(DisconnectArguments request, public static class DisconnectExtensions { - public static IDisposable OnDisconnect(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnDisconnect(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Disconnect, RequestHandler.For(handler)); } - public static IDisposable OnDisconnect(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnDisconnect(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Disconnect, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/IEvaluateHandler.cs b/src/Dap.Protocol/Requests/IEvaluateHandler.cs index 4cb8ff3fb..b853c05dc 100644 --- a/src/Dap.Protocol/Requests/IEvaluateHandler.cs +++ b/src/Dap.Protocol/Requests/IEvaluateHandler.cs @@ -18,13 +18,13 @@ public abstract class EvaluateHandler : IEvaluateHandler public static class EvaluateExtensions { - public static IDisposable OnEvaluate(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnEvaluate(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Evaluate, RequestHandler.For(handler)); } - public static IDisposable OnEvaluate(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnEvaluate(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Evaluate, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/IExceptionInfoHandler.cs b/src/Dap.Protocol/Requests/IExceptionInfoHandler.cs index 01a79ec29..25d356980 100644 --- a/src/Dap.Protocol/Requests/IExceptionInfoHandler.cs +++ b/src/Dap.Protocol/Requests/IExceptionInfoHandler.cs @@ -18,13 +18,13 @@ public abstract Task Handle(ExceptionInfoArguments reques public static class ExceptionInfoExtensions { - public static IDisposable OnExceptionInfo(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnExceptionInfo(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.ExceptionInfo, RequestHandler.For(handler)); } - public static IDisposable OnExceptionInfo(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnExceptionInfo(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.ExceptionInfo, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/IGotoHandler.cs b/src/Dap.Protocol/Requests/IGotoHandler.cs index 6c5e12e91..79b947c00 100644 --- a/src/Dap.Protocol/Requests/IGotoHandler.cs +++ b/src/Dap.Protocol/Requests/IGotoHandler.cs @@ -17,13 +17,13 @@ public abstract class GotoHandler : IGotoHandler public static class GotoExtensions { - public static IDisposable OnGoto(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnGoto(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Goto, RequestHandler.For(handler)); } - public static IDisposable OnGoto(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnGoto(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Goto, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/IGotoTargetsHandler.cs b/src/Dap.Protocol/Requests/IGotoTargetsHandler.cs index b67bc1c15..1e7c8852b 100644 --- a/src/Dap.Protocol/Requests/IGotoTargetsHandler.cs +++ b/src/Dap.Protocol/Requests/IGotoTargetsHandler.cs @@ -19,13 +19,13 @@ public abstract Task Handle(GotoTargetsArguments request, public static class GotoTargetsExtensions { - public static IDisposable OnGotoTargets(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnGotoTargets(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.GotoTargets, RequestHandler.For(handler)); } - public static IDisposable OnGotoTargets(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnGotoTargets(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.GotoTargets, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/IInitializeHandler.cs b/src/Dap.Protocol/Requests/IInitializeHandler.cs index 9b26745e2..b5a13985b 100644 --- a/src/Dap.Protocol/Requests/IInitializeHandler.cs +++ b/src/Dap.Protocol/Requests/IInitializeHandler.cs @@ -18,13 +18,13 @@ public abstract Task Handle(InitializeRequestArguments reque public static class InitializeExtensions { - public static IDisposable OnInitialize(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnInitialize(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Initialize, RequestHandler.For(handler)); } - public static IDisposable OnInitialize(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnInitialize(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Initialize, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/ILaunchHandler.cs b/src/Dap.Protocol/Requests/ILaunchHandler.cs index c72f1c960..c75eba748 100644 --- a/src/Dap.Protocol/Requests/ILaunchHandler.cs +++ b/src/Dap.Protocol/Requests/ILaunchHandler.cs @@ -18,13 +18,13 @@ public abstract Task public static class LaunchExtensions { - public static IDisposable OnLaunch(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnLaunch(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Launch, RequestHandler.For(handler)); } - public static IDisposable OnLaunch(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnLaunch(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Launch, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/ILoadedSourcesHandler.cs b/src/Dap.Protocol/Requests/ILoadedSourcesHandler.cs index 958db8e3b..09bdaaaf3 100644 --- a/src/Dap.Protocol/Requests/ILoadedSourcesHandler.cs +++ b/src/Dap.Protocol/Requests/ILoadedSourcesHandler.cs @@ -18,13 +18,13 @@ public abstract Task Handle(LoadedSourcesArguments reques public static class LoadedSourcesExtensions { - public static IDisposable OnLoadedSources(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnLoadedSources(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.LoadedSources, RequestHandler.For(handler)); } - public static IDisposable OnLoadedSources(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnLoadedSources(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.LoadedSources, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/IModulesHandler.cs b/src/Dap.Protocol/Requests/IModulesHandler.cs index 75858b69a..a42f199ef 100644 --- a/src/Dap.Protocol/Requests/IModulesHandler.cs +++ b/src/Dap.Protocol/Requests/IModulesHandler.cs @@ -17,13 +17,13 @@ public abstract class ModulesHandler : IModulesHandler public static class ModulesExtensions { - public static IDisposable OnModules(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnModules(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Modules, RequestHandler.For(handler)); } - public static IDisposable OnModules(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnModules(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Modules, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/INextHandler.cs b/src/Dap.Protocol/Requests/INextHandler.cs index 81b5918bc..54b89e9c2 100644 --- a/src/Dap.Protocol/Requests/INextHandler.cs +++ b/src/Dap.Protocol/Requests/INextHandler.cs @@ -17,13 +17,13 @@ public abstract class NextHandler : INextHandler public static class NextExtensions { - public static IDisposable OnNext(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnNext(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Next, RequestHandler.For(handler)); } - public static IDisposable OnNext(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnNext(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Next, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/IPauseHandler.cs b/src/Dap.Protocol/Requests/IPauseHandler.cs index c21c0393c..b0ec68302 100644 --- a/src/Dap.Protocol/Requests/IPauseHandler.cs +++ b/src/Dap.Protocol/Requests/IPauseHandler.cs @@ -17,13 +17,13 @@ public abstract class PauseHandler : IPauseHandler public static class PauseExtensions { - public static IDisposable OnPause(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnPause(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Pause, RequestHandler.For(handler)); } - public static IDisposable OnPause(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnPause(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Pause, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/IReadMemoryHandler.cs b/src/Dap.Protocol/Requests/IReadMemoryHandler.cs index f9ea1f215..2037fd830 100644 --- a/src/Dap.Protocol/Requests/IReadMemoryHandler.cs +++ b/src/Dap.Protocol/Requests/IReadMemoryHandler.cs @@ -18,13 +18,13 @@ public abstract Task Handle(ReadMemoryArguments request, public static class ReadMemoryExtensions { - public static IDisposable OnReadMemory(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnReadMemory(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.ReadMemory, RequestHandler.For(handler)); } - public static IDisposable OnReadMemory(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnReadMemory(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.ReadMemory, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/IRestartFrameHandler.cs b/src/Dap.Protocol/Requests/IRestartFrameHandler.cs index b734db008..d946a64f3 100644 --- a/src/Dap.Protocol/Requests/IRestartFrameHandler.cs +++ b/src/Dap.Protocol/Requests/IRestartFrameHandler.cs @@ -18,13 +18,13 @@ public abstract Task Handle(RestartFrameArguments request, public static class RestartFrameExtensions { - public static IDisposable OnRestartFrame(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnRestartFrame(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.RestartFrame, RequestHandler.For(handler)); } - public static IDisposable OnRestartFrame(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnRestartFrame(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.RestartFrame, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/IRestartHandler.cs b/src/Dap.Protocol/Requests/IRestartHandler.cs index 8b94d93e1..3d16941cf 100644 --- a/src/Dap.Protocol/Requests/IRestartHandler.cs +++ b/src/Dap.Protocol/Requests/IRestartHandler.cs @@ -17,13 +17,13 @@ public abstract class RestartHandler : IRestartHandler public static class RestartExtensions { - public static IDisposable OnRestart(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnRestart(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Restart, RequestHandler.For(handler)); } - public static IDisposable OnRestart(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnRestart(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Restart, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/IReverseContinueHandler.cs b/src/Dap.Protocol/Requests/IReverseContinueHandler.cs index 4f77d1fc9..a968f49b4 100644 --- a/src/Dap.Protocol/Requests/IReverseContinueHandler.cs +++ b/src/Dap.Protocol/Requests/IReverseContinueHandler.cs @@ -18,13 +18,13 @@ public abstract Task Handle(ReverseContinueArguments re public static class ReverseContinueExtensions { - public static IDisposable OnReverseContinue(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnReverseContinue(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.ReverseContinue, RequestHandler.For(handler)); } - public static IDisposable OnReverseContinue(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnReverseContinue(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.ReverseContinue, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/IRunInTerminalHandler.cs b/src/Dap.Protocol/Requests/IRunInTerminalHandler.cs index 076bf86cd..76d8864a4 100644 --- a/src/Dap.Protocol/Requests/IRunInTerminalHandler.cs +++ b/src/Dap.Protocol/Requests/IRunInTerminalHandler.cs @@ -17,13 +17,13 @@ public abstract class RunInTerminalHandler : IRunInTerminalHandler public static class RunInTerminalExtensions { - public static IDisposable OnRunInTerminal(this IDebugAdapterClientRegistry registry, + public static IDebugAdapterClientRegistry OnRunInTerminal(this IDebugAdapterClientRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.RunInTerminal, RequestHandler.For(handler)); } - public static IDisposable OnRunInTerminal(this IDebugAdapterClientRegistry registry, + public static IDebugAdapterClientRegistry OnRunInTerminal(this IDebugAdapterClientRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.RunInTerminal, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/IScopesHandler.cs b/src/Dap.Protocol/Requests/IScopesHandler.cs index bc61ab433..407d2a8d2 100644 --- a/src/Dap.Protocol/Requests/IScopesHandler.cs +++ b/src/Dap.Protocol/Requests/IScopesHandler.cs @@ -17,13 +17,13 @@ public abstract class ScopesHandler : IScopesHandler public static class ScopesExtensions { - public static IDisposable OnScopes(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnScopes(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Scopes, RequestHandler.For(handler)); } - public static IDisposable OnScopes(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnScopes(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Scopes, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/ISetBreakpointsHandler.cs b/src/Dap.Protocol/Requests/ISetBreakpointsHandler.cs index c312a914f..75bf1bbb6 100644 --- a/src/Dap.Protocol/Requests/ISetBreakpointsHandler.cs +++ b/src/Dap.Protocol/Requests/ISetBreakpointsHandler.cs @@ -18,13 +18,13 @@ public abstract Task Handle(SetBreakpointsArguments requ public static class SetBreakpointsExtensions { - public static IDisposable OnSetBreakpoints(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnSetBreakpoints(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.SetBreakpoints, RequestHandler.For(handler)); } - public static IDisposable OnSetBreakpoints(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnSetBreakpoints(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.SetBreakpoints, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/ISetDataBreakpointsHandler.cs b/src/Dap.Protocol/Requests/ISetDataBreakpointsHandler.cs index ebe79c037..34aef6dd7 100644 --- a/src/Dap.Protocol/Requests/ISetDataBreakpointsHandler.cs +++ b/src/Dap.Protocol/Requests/ISetDataBreakpointsHandler.cs @@ -19,13 +19,13 @@ public abstract Task Handle(SetDataBreakpointsArgume public static class SetDataBreakpointsExtensions { - public static IDisposable OnSetDataBreakpoints(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnSetDataBreakpoints(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.SetDataBreakpoints, RequestHandler.For(handler)); } - public static IDisposable OnSetDataBreakpoints(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnSetDataBreakpoints(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.SetDataBreakpoints, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/ISetExceptionBreakpointsHandler.cs b/src/Dap.Protocol/Requests/ISetExceptionBreakpointsHandler.cs index e0cf63319..6e212a92c 100644 --- a/src/Dap.Protocol/Requests/ISetExceptionBreakpointsHandler.cs +++ b/src/Dap.Protocol/Requests/ISetExceptionBreakpointsHandler.cs @@ -19,13 +19,13 @@ public abstract Task Handle(SetExceptionBreakpo public static class SetExceptionBreakpointsExtensions { - public static IDisposable OnSetExceptionBreakpoints(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnSetExceptionBreakpoints(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.SetExceptionBreakpoints, RequestHandler.For(handler)); } - public static IDisposable OnSetExceptionBreakpoints(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnSetExceptionBreakpoints(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.SetExceptionBreakpoints, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/ISetExpressionHandler.cs b/src/Dap.Protocol/Requests/ISetExpressionHandler.cs index b2453fe4f..d207fb1e1 100644 --- a/src/Dap.Protocol/Requests/ISetExpressionHandler.cs +++ b/src/Dap.Protocol/Requests/ISetExpressionHandler.cs @@ -18,13 +18,13 @@ public abstract Task Handle(SetExpressionArguments reques public static class SetExpressionExtensions { - public static IDisposable OnSetExpression(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnSetExpression(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.SetExpression, RequestHandler.For(handler)); } - public static IDisposable OnSetExpression(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnSetExpression(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.SetExpression, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/ISetFunctionBreakpointsHandler.cs b/src/Dap.Protocol/Requests/ISetFunctionBreakpointsHandler.cs index 5007ce721..261d036c0 100644 --- a/src/Dap.Protocol/Requests/ISetFunctionBreakpointsHandler.cs +++ b/src/Dap.Protocol/Requests/ISetFunctionBreakpointsHandler.cs @@ -20,13 +20,13 @@ public abstract Task Handle(SetFunctionBreakpoin public static class SetFunctionBreakpointsExtensions { - public static IDisposable OnSetFunctionBreakpoints(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnSetFunctionBreakpoints(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.SetFunctionBreakpoints, RequestHandler.For(handler)); } - public static IDisposable OnSetFunctionBreakpoints(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnSetFunctionBreakpoints(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.SetFunctionBreakpoints, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/ISetVariableHandler.cs b/src/Dap.Protocol/Requests/ISetVariableHandler.cs index 5e2dcf6e7..c529e0668 100644 --- a/src/Dap.Protocol/Requests/ISetVariableHandler.cs +++ b/src/Dap.Protocol/Requests/ISetVariableHandler.cs @@ -18,13 +18,13 @@ public abstract Task Handle(SetVariableArguments request, public static class SetVariableExtensions { - public static IDisposable OnSetVariable(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnSetVariable(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.SetVariable, RequestHandler.For(handler)); } - public static IDisposable OnSetVariable(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnSetVariable(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.SetVariable, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/ISourceHandler.cs b/src/Dap.Protocol/Requests/ISourceHandler.cs index 1cadf8021..23d7eb569 100644 --- a/src/Dap.Protocol/Requests/ISourceHandler.cs +++ b/src/Dap.Protocol/Requests/ISourceHandler.cs @@ -17,13 +17,13 @@ public abstract class SourceHandler : ISourceHandler public static class SourceExtensions { - public static IDisposable OnSource(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnSource(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Source, RequestHandler.For(handler)); } - public static IDisposable OnSource(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnSource(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Source, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/IStackTraceHandler.cs b/src/Dap.Protocol/Requests/IStackTraceHandler.cs index 31ba537e5..2b20ee9f5 100644 --- a/src/Dap.Protocol/Requests/IStackTraceHandler.cs +++ b/src/Dap.Protocol/Requests/IStackTraceHandler.cs @@ -18,13 +18,13 @@ public abstract Task Handle(StackTraceArguments request, public static class StackTraceExtensions { - public static IDisposable OnStackTrace(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnStackTrace(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.StackTrace, RequestHandler.For(handler)); } - public static IDisposable OnStackTrace(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnStackTrace(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.StackTrace, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/IStepBackHandler.cs b/src/Dap.Protocol/Requests/IStepBackHandler.cs index 32147296e..ada8ec031 100644 --- a/src/Dap.Protocol/Requests/IStepBackHandler.cs +++ b/src/Dap.Protocol/Requests/IStepBackHandler.cs @@ -17,13 +17,13 @@ public abstract class StepBackHandler : IStepBackHandler public static class StepBackExtensions { - public static IDisposable OnStepBack(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnStepBack(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.StepBack, RequestHandler.For(handler)); } - public static IDisposable OnStepBack(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnStepBack(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.StepBack, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/IStepInHandler.cs b/src/Dap.Protocol/Requests/IStepInHandler.cs index 28b7d8374..20b456501 100644 --- a/src/Dap.Protocol/Requests/IStepInHandler.cs +++ b/src/Dap.Protocol/Requests/IStepInHandler.cs @@ -17,13 +17,13 @@ public abstract class StepInHandler : IStepInHandler public static class StepInExtensions { - public static IDisposable OnStepIn(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnStepIn(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.StepIn, RequestHandler.For(handler)); } - public static IDisposable OnStepIn(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnStepIn(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.StepIn, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/IStepInTargetsHandler.cs b/src/Dap.Protocol/Requests/IStepInTargetsHandler.cs index 825ca3c1b..fe4d7f98e 100644 --- a/src/Dap.Protocol/Requests/IStepInTargetsHandler.cs +++ b/src/Dap.Protocol/Requests/IStepInTargetsHandler.cs @@ -18,13 +18,13 @@ public abstract Task Handle(StepInTargetsArguments reques public static class StepInTargetsExtensions { - public static IDisposable OnStepInTargets(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnStepInTargets(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.StepInTargets, RequestHandler.For(handler)); } - public static IDisposable OnStepInTargets(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnStepInTargets(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.StepInTargets, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/IStepOutHandler.cs b/src/Dap.Protocol/Requests/IStepOutHandler.cs index 9d8db342c..a7032c0aa 100644 --- a/src/Dap.Protocol/Requests/IStepOutHandler.cs +++ b/src/Dap.Protocol/Requests/IStepOutHandler.cs @@ -17,13 +17,13 @@ public abstract class StepOutHandler : IStepOutHandler public static class StepOutExtensions { - public static IDisposable OnStepOut(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnStepOut(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.StepOut, RequestHandler.For(handler)); } - public static IDisposable OnStepOut(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnStepOut(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.StepOut, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/ITerminateHandler.cs b/src/Dap.Protocol/Requests/ITerminateHandler.cs index fee59150a..0c3ed6d30 100644 --- a/src/Dap.Protocol/Requests/ITerminateHandler.cs +++ b/src/Dap.Protocol/Requests/ITerminateHandler.cs @@ -17,13 +17,13 @@ public abstract class TerminateHandler : ITerminateHandler public static class TerminateExtensions { - public static IDisposable OnTerminate(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnTerminate(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Terminate, RequestHandler.For(handler)); } - public static IDisposable OnTerminate(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnTerminate(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Terminate, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/ITerminateThreadsHandler.cs b/src/Dap.Protocol/Requests/ITerminateThreadsHandler.cs index 52d53247f..6f5b613ee 100644 --- a/src/Dap.Protocol/Requests/ITerminateThreadsHandler.cs +++ b/src/Dap.Protocol/Requests/ITerminateThreadsHandler.cs @@ -19,13 +19,13 @@ public abstract Task Handle(TerminateThreadsArguments public static class TerminateThreadsExtensions { - public static IDisposable OnTerminateThreads(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnTerminateThreads(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.TerminateThreads, RequestHandler.For(handler)); } - public static IDisposable OnTerminateThreads(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnTerminateThreads(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.TerminateThreads, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/IThreadsHandler.cs b/src/Dap.Protocol/Requests/IThreadsHandler.cs index 32078c8f1..46b80f978 100644 --- a/src/Dap.Protocol/Requests/IThreadsHandler.cs +++ b/src/Dap.Protocol/Requests/IThreadsHandler.cs @@ -17,13 +17,13 @@ public abstract class ThreadsHandler : IThreadsHandler public static class ThreadsExtensions { - public static IDisposable OnThreads(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnThreads(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Threads, RequestHandler.For(handler)); } - public static IDisposable OnThreads(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnThreads(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Threads, RequestHandler.For(handler)); diff --git a/src/Dap.Protocol/Requests/IVariablesHandler.cs b/src/Dap.Protocol/Requests/IVariablesHandler.cs index f4db74826..64885e1ee 100644 --- a/src/Dap.Protocol/Requests/IVariablesHandler.cs +++ b/src/Dap.Protocol/Requests/IVariablesHandler.cs @@ -17,13 +17,13 @@ public abstract class VariablesHandler : IVariablesHandler public static class VariablesExtensions { - public static IDisposable OnVariables(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnVariables(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Variables, RequestHandler.For(handler)); } - public static IDisposable OnVariables(this IDebugAdapterServerRegistry registry, + public static IDebugAdapterServerRegistry OnVariables(this IDebugAdapterServerRegistry registry, Func> handler) { return registry.AddHandler(RequestNames.Variables, RequestHandler.For(handler)); diff --git a/src/Dap.Server/Class1.cs b/src/Dap.Server/Class1.cs deleted file mode 100644 index ed83232d9..000000000 --- a/src/Dap.Server/Class1.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Dap.Server -{ - public class Class1 - { - } -} diff --git a/src/Dap.Server/Dap.Server.csproj b/src/Dap.Server/Dap.Server.csproj index 1570d1008..80552db1c 100644 --- a/src/Dap.Server/Dap.Server.csproj +++ b/src/Dap.Server/Dap.Server.csproj @@ -9,6 +9,7 @@ + diff --git a/src/Dap.Testing/Dap.Testing.csproj b/src/Dap.Testing/Dap.Testing.csproj new file mode 100644 index 000000000..56c3825df --- /dev/null +++ b/src/Dap.Testing/Dap.Testing.csproj @@ -0,0 +1,17 @@ + + + + netstandard2.1;netstandard2.0 + AnyCPU + OmniSharp.Extensions.DebugAdapter.Testing + OmniSharp.Extensions.DebugAdapter.Testing + You can use this package to test a debug adapter protocol client or server + + + + + + + + + diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 6479ccd3e..e1bd0de96 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -21,6 +21,15 @@ <_Parameter1>Lsp.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f + + <_Parameter1>OmniSharp.Extensions.LanguageProtocol.Testing, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f + + + <_Parameter1>OmniSharp.Extensions.DebugAdapter.Testing, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f + + + <_Parameter1>OmniSharp.Extensions.JsonRpc.Testing, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f + <_Parameter1>DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7 diff --git a/src/JsonRpc.Testing/AggregateSettler.cs b/src/JsonRpc.Testing/AggregateSettler.cs new file mode 100644 index 000000000..7967a6b6b --- /dev/null +++ b/src/JsonRpc.Testing/AggregateSettler.cs @@ -0,0 +1,33 @@ +using System; +using System.Reactive; +using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; +using System.Threading.Tasks; + +namespace OmniSharp.Extensions.JsonRpc.Testing +{ + class AggregateSettler : ISettler + { + private readonly ISettler[] _settlers; + + public AggregateSettler(params ISettler[] settlers) + { + _settlers = settlers; + } + + public Task SettleNext() + { + return _settlers.ToObservable() + .Select(z => z.Settle()) + .Switch() + .Take(1) + //.Amb(Observable.Timer(_waitTime + _waitTime).Select(z => Unit.Value)) + .ToTask(); + } + + public IObservable Settle() => + _settlers.ToObservable() + .Select(z => z.Settle()) + .Switch(); + } +} diff --git a/src/JsonRpc.Testing/IRequestSettler.cs b/src/JsonRpc.Testing/IRequestSettler.cs new file mode 100644 index 000000000..df34e935f --- /dev/null +++ b/src/JsonRpc.Testing/IRequestSettler.cs @@ -0,0 +1,8 @@ +namespace OmniSharp.Extensions.JsonRpc.Testing +{ + public interface IRequestSettler + { + void OnStartRequest(); + void OnEndRequest(); + } +} diff --git a/src/JsonRpc.Testing/ISettler.cs b/src/JsonRpc.Testing/ISettler.cs new file mode 100644 index 000000000..8b1138ade --- /dev/null +++ b/src/JsonRpc.Testing/ISettler.cs @@ -0,0 +1,12 @@ +using System; +using System.Reactive; +using System.Threading.Tasks; + +namespace OmniSharp.Extensions.JsonRpc.Testing +{ + public interface ISettler + { + Task SettleNext(); + IObservable Settle(); + } +} diff --git a/src/JsonRpc.Testing/JsonRpc.Testing.csproj b/src/JsonRpc.Testing/JsonRpc.Testing.csproj new file mode 100644 index 000000000..5c6ce8413 --- /dev/null +++ b/src/JsonRpc.Testing/JsonRpc.Testing.csproj @@ -0,0 +1,22 @@ + + + + netstandard2.1;netstandard2.0 + AnyCPU + OmniSharp.Extensions.JsonRpc.Testing + OmniSharp.Extensions.JsonRpc.Testing + You can use this package to test a Json Rpc Server + + + + + + + + <_Parameter1>OmniSharp.Extensions.LanguageProtocol.Testing, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f + + + <_Parameter1>OmniSharp.Extensions.DebugAdapter.Testing, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f + + + diff --git a/src/JsonRpc.Testing/JsonRpcServerTestBase.cs b/src/JsonRpc.Testing/JsonRpcServerTestBase.cs new file mode 100644 index 000000000..96df1544d --- /dev/null +++ b/src/JsonRpc.Testing/JsonRpcServerTestBase.cs @@ -0,0 +1,75 @@ +using System; +using System.IO.Pipelines; +using System.Threading.Tasks; +using MediatR; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace OmniSharp.Extensions.JsonRpc.Testing +{ + /// + /// This is a test class that is designed to allow you configure an in memory lsp client and server to do testing of handlers or behaviors. + /// + public abstract class JsonRpcServerTestBase : JsonRpcTestBase + { + private JsonRpcServer _client; + private JsonRpcServer _server; + + public JsonRpcServerTestBase(JsonRpcTestOptions testOptions) : base(testOptions) + { + } + + protected virtual void ConfigureClientInputOutput(PipeReader inMemoryReader, PipeWriter inMemoryWriter, JsonRpcServerOptions options) + { + options.WithInput(inMemoryReader).WithOutput(inMemoryWriter); + } + + protected virtual void ConfigureServerInputOutput(PipeReader inMemoryReader, PipeWriter inMemoryWriter, JsonRpcServerOptions options) + { + options.WithInput(inMemoryReader).WithOutput(inMemoryWriter); + } + + protected virtual async Task<(JsonRpcServer client, JsonRpcServer server)> Initialize( + Action clientOptionsAction, + Action serverOptionsAction) + { + var clientPipe = new Pipe(); + var serverPipe = new Pipe(); + + var clientTask = JsonRpcServer.From(options => { + options + .Services + .AddTransient(typeof(IPipelineBehavior<,>), typeof(SettlePipeline<,>)) + .AddSingleton(ServerEvents as IRequestSettler) + .AddLogging(x => { + x.SetMinimumLevel(LogLevel.Trace); + x.Services.AddSingleton(TestOptions.ClientLoggerFactory); + }); + ConfigureClientInputOutput(serverPipe.Reader, clientPipe.Writer, options); + clientOptionsAction(options); + }, CancellationToken); + + var serverTask = JsonRpcServer.From(options => { + options + .Services + .AddTransient(typeof(IPipelineBehavior<,>), typeof(SettlePipeline<,>)) + .AddSingleton(ServerEvents as IRequestSettler) + .AddLogging(x => { + x.SetMinimumLevel(LogLevel.Trace); + x.Services.AddSingleton(TestOptions.ServerLoggerFactory); + }); + ConfigureServerInputOutput(clientPipe.Reader, serverPipe.Writer, options); + serverOptionsAction(options); + }, CancellationToken); + + await Task.WhenAll(clientTask, serverTask); + _client = clientTask.Result; + _server = serverTask.Result; + + Disposable.Add(_client); + Disposable.Add(_server); + + return (_client, _server); + } + } +} diff --git a/src/JsonRpc.Testing/JsonRpcTestBase.cs b/src/JsonRpc.Testing/JsonRpcTestBase.cs new file mode 100644 index 000000000..f0892237f --- /dev/null +++ b/src/JsonRpc.Testing/JsonRpcTestBase.cs @@ -0,0 +1,39 @@ +using System; +using System.Diagnostics; +using System.Reactive; +using System.Reactive.Disposables; +using System.Threading; +using System.Threading.Tasks; + +namespace OmniSharp.Extensions.JsonRpc.Testing +{ + public abstract class JsonRpcTestBase + { + private readonly CancellationTokenSource _cancellationTokenSource; + + public JsonRpcTestBase(JsonRpcTestOptions testOptions) + { + TestOptions = testOptions; + Disposable = new CompositeDisposable {testOptions.ClientLoggerFactory, testOptions.ServerLoggerFactory}; + + _cancellationTokenSource = new CancellationTokenSource(); + if (!Debugger.IsAttached) + { + _cancellationTokenSource.CancelAfter(testOptions.TestTimeout); + } + + ClientEvents = new Settler(TestOptions.SettleTimeSpan, TestOptions.SettleTimeout, CancellationToken); + ServerEvents = new Settler(TestOptions.SettleTimeSpan, TestOptions.SettleTimeout, CancellationToken); + Events = new AggregateSettler(ClientEvents, ServerEvents); + } + + protected CompositeDisposable Disposable { get; } + protected ISettler ClientEvents { get; } + protected ISettler ServerEvents { get; } + protected ISettler Events { get; } + protected JsonRpcTestOptions TestOptions { get; } + protected CancellationToken CancellationToken => _cancellationTokenSource.Token; + protected Task SettleNext() => Events.SettleNext(); + protected IObservable Settle() => Events.Settle(); + } +} diff --git a/src/JsonRpc.Testing/JsonRpcTestOptions.cs b/src/JsonRpc.Testing/JsonRpcTestOptions.cs new file mode 100644 index 000000000..60706ff04 --- /dev/null +++ b/src/JsonRpc.Testing/JsonRpcTestOptions.cs @@ -0,0 +1,29 @@ +using System; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace OmniSharp.Extensions.JsonRpc.Testing +{ + public sealed class JsonRpcTestOptions + { + public JsonRpcTestOptions() + { + + } + public JsonRpcTestOptions(ILoggerFactory loggerFactory) + { + ServerLoggerFactory = ClientLoggerFactory = loggerFactory; + } + public JsonRpcTestOptions(ILoggerFactory clientLoggerFactory, ILoggerFactory serverLoggerFactory) + { + ClientLoggerFactory = clientLoggerFactory; + ServerLoggerFactory = serverLoggerFactory; + } + + public ILoggerFactory ClientLoggerFactory { get; internal set; } = NullLoggerFactory.Instance; + public ILoggerFactory ServerLoggerFactory { get; internal set; } = NullLoggerFactory.Instance; + public TimeSpan SettleTimeSpan { get; internal set; } = TimeSpan.FromMilliseconds(50); + public TimeSpan SettleTimeout { get; internal set; } = TimeSpan.FromMilliseconds(500); + public TimeSpan TestTimeout { get; internal set; } = TimeSpan.FromSeconds(30); + } +} diff --git a/src/JsonRpc.Testing/JsonRpcTestOptionsExtensions.cs b/src/JsonRpc.Testing/JsonRpcTestOptionsExtensions.cs new file mode 100644 index 000000000..7f98a288d --- /dev/null +++ b/src/JsonRpc.Testing/JsonRpcTestOptionsExtensions.cs @@ -0,0 +1,34 @@ +using System; +using Microsoft.Extensions.Logging; + +namespace OmniSharp.Extensions.JsonRpc.Testing +{ + public static class JsonRpcTestOptionsExtensions + { + public static JsonRpcTestOptions WithServerLoggerFactory(this JsonRpcTestOptions options, ILoggerFactory serverLoggerFactory) + { + options.ServerLoggerFactory = serverLoggerFactory; + return options; + } + public static JsonRpcTestOptions WithClientLoggerFactory(this JsonRpcTestOptions options, ILoggerFactory clientLoggerFactory) + { + options.ClientLoggerFactory = clientLoggerFactory; + return options; + } + public static JsonRpcTestOptions WithSettleTimeSpan(this JsonRpcTestOptions options, TimeSpan settleTimeSpan) + { + options.SettleTimeSpan = settleTimeSpan; + return options; + } + public static JsonRpcTestOptions WithSettleTimeout(this JsonRpcTestOptions options, TimeSpan timeout) + { + options.SettleTimeout = timeout; + return options; + } + public static JsonRpcTestOptions WithTestTimeout(this JsonRpcTestOptions options, TimeSpan testTimeout) + { + options.TestTimeout = testTimeout; + return options; + } + } +} diff --git a/src/JsonRpc.Testing/SettlePipeline.cs b/src/JsonRpc.Testing/SettlePipeline.cs new file mode 100644 index 000000000..124fbaa2e --- /dev/null +++ b/src/JsonRpc.Testing/SettlePipeline.cs @@ -0,0 +1,30 @@ +using System.Threading; +using System.Threading.Tasks; +using MediatR; + +namespace OmniSharp.Extensions.JsonRpc.Testing +{ + class SettlePipeline : IPipelineBehavior + where T : IRequest + { + private readonly IRequestSettler _settler; + + public SettlePipeline(IRequestSettler settler) + { + _settler = settler; + } + + async Task IPipelineBehavior.Handle(T request, CancellationToken cancellationToken, RequestHandlerDelegate next) + { + _settler.OnStartRequest(); + try + { + return await next(); + } + finally + { + _settler.OnEndRequest(); + } + } + } +} diff --git a/src/JsonRpc.Testing/Settler.cs b/src/JsonRpc.Testing/Settler.cs new file mode 100644 index 000000000..dba89a368 --- /dev/null +++ b/src/JsonRpc.Testing/Settler.cs @@ -0,0 +1,57 @@ +using System; +using System.Reactive; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Reactive.Threading.Tasks; +using System.Threading; +using System.Threading.Tasks; + +namespace OmniSharp.Extensions.JsonRpc.Testing +{ + class Settler : ISettler, IRequestSettler + { + private readonly CancellationToken _cancellationToken; + private readonly IObservable _settle; + private readonly IObserver _requester; + + public Settler(TimeSpan waitTime, TimeSpan timeout, CancellationToken cancellationToken) + { + _cancellationToken = cancellationToken; + var subject = new Subject(); + var data = subject; + _settle = data + .StartWith(0) + .Scan(0, (acc, next) => { + acc += next; + return acc; + }) + .Select(z => z <= 0 ? Observable.Timer(waitTime).Select(_ => Unit.Default).Timeout(timeout, Observable.Return(Unit.Default)) : Observable.Never()) + .Switch() + .Replay(1) + .RefCount(); + _requester = subject; + } + + public Task SettleNext() + { + return _settle + .Take(1) + .ToTask(_cancellationToken); + } + + public IObservable Settle() + { + return _settle; + } + + void IRequestSettler.OnStartRequest() + { + _requester.OnNext(1); + } + + void IRequestSettler.OnEndRequest() + { + _requester.OnNext(-1); + } + } +} diff --git a/src/JsonRpc/CancelRequestHandler.cs b/src/JsonRpc/CancelRequestHandler.cs deleted file mode 100644 index f75ef5e0e..000000000 --- a/src/JsonRpc/CancelRequestHandler.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using MediatR; - -namespace OmniSharp.Extensions.JsonRpc -{ - public class CancelRequestHandler : ICancelRequestHandler - { - private readonly IRequestRouter _requestRouter; - - public CancelRequestHandler(IRequestRouter requestRouter) - { - _requestRouter = requestRouter; - } - - public Task Handle(CancelParams notification, CancellationToken token) - { - _requestRouter.CancelRequest(notification.Id); - return Unit.Task; - } - } -} diff --git a/src/JsonRpc/Client/Notification.cs b/src/JsonRpc/Client/OutgoingNotification.cs similarity index 79% rename from src/JsonRpc/Client/Notification.cs rename to src/JsonRpc/Client/OutgoingNotification.cs index 47c6a1dc6..59b23e54b 100644 --- a/src/JsonRpc/Client/Notification.cs +++ b/src/JsonRpc/Client/OutgoingNotification.cs @@ -1,6 +1,6 @@ namespace OmniSharp.Extensions.JsonRpc.Client { - public class Notification + public class OutgoingNotification { public string Method { get; set; } diff --git a/src/JsonRpc/Client/Request.cs b/src/JsonRpc/Client/OutgoingRequest.cs similarity index 89% rename from src/JsonRpc/Client/Request.cs rename to src/JsonRpc/Client/OutgoingRequest.cs index c6d6fa15a..20e746eb5 100644 --- a/src/JsonRpc/Client/Request.cs +++ b/src/JsonRpc/Client/OutgoingRequest.cs @@ -2,7 +2,7 @@ namespace OmniSharp.Extensions.JsonRpc.Client { - public class Request + public class OutgoingRequest { public object Id { get; set; } diff --git a/src/JsonRpc/Client/Response.cs b/src/JsonRpc/Client/OutgoingResponse.cs similarity index 70% rename from src/JsonRpc/Client/Response.cs rename to src/JsonRpc/Client/OutgoingResponse.cs index cd180c942..230039856 100644 --- a/src/JsonRpc/Client/Response.cs +++ b/src/JsonRpc/Client/OutgoingResponse.cs @@ -2,15 +2,15 @@ namespace OmniSharp.Extensions.JsonRpc.Client { - public class Response + public class OutgoingResponse { - public Response(object id, ServerRequest request) + public OutgoingResponse(object id, ServerRequest request) { Id = id; Request = request; } - public Response(object id, object result, ServerRequest request) + public OutgoingResponse(object id, object result, ServerRequest request) { Id = id; Result = result; diff --git a/src/JsonRpc/CompositeHandlersManager.cs b/src/JsonRpc/CompositeHandlersManager.cs new file mode 100644 index 000000000..c7caa2851 --- /dev/null +++ b/src/JsonRpc/CompositeHandlersManager.cs @@ -0,0 +1,32 @@ +using System; +using System.Reactive.Disposables; + +namespace OmniSharp.Extensions.JsonRpc +{ + public class CompositeHandlersManager : IHandlersManager + { + private readonly IHandlersManager _parent; + private CompositeDisposable _compositeDisposable = new CompositeDisposable(); + + public CompositeHandlersManager(IHandlersManager parent) + { + _parent = parent; + } + + public IDisposable Add(IJsonRpcHandler handler, JsonRpcHandlerOptions options) + { + var result = _parent.Add(handler, options); + _compositeDisposable.Add(result); + return result; + } + + public IDisposable Add(string method, IJsonRpcHandler handler, JsonRpcHandlerOptions options) + { + var result = _parent.Add(method, handler, options); + _compositeDisposable.Add(result); + return result; + } + + public CompositeDisposable GetDisposable() => _compositeDisposable; + } +} diff --git a/src/JsonRpc/Connection.cs b/src/JsonRpc/Connection.cs index 7bac4e3a6..7132bacee 100644 --- a/src/JsonRpc/Connection.cs +++ b/src/JsonRpc/Connection.cs @@ -1,22 +1,28 @@ using System; -using System.IO; +using System.IO.Pipelines; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using OmniSharp.Extensions.JsonRpc.Server; namespace OmniSharp.Extensions.JsonRpc { public class Connection : IDisposable { - private readonly IInputHandler _inputHandler; + private readonly InputHandler _inputHandler; + public bool IsOpen { get; private set; } public Connection( - Stream input, + PipeReader input, IOutputHandler outputHandler, IReceiver receiver, IRequestProcessIdentifier requestProcessIdentifier, IRequestRouter requestRouter, IResponseRouter responseRouter, ILoggerFactory loggerFactory, - ISerializer serializer, + Action onUnhandledException, + Func getException, + TimeSpan requestTimeout, + bool supportContentModified, int? concurrency) { _inputHandler = new InputHandler( @@ -27,7 +33,10 @@ public Connection( requestRouter, responseRouter, loggerFactory, - serializer, + onUnhandledException, + getException, + requestTimeout, + supportContentModified, concurrency ); } @@ -36,11 +45,15 @@ public void Open() { // TODO: Throw if called twice? _inputHandler.Start(); + IsOpen = true; } + public Task StopAsync() => _inputHandler.StopAsync(); + public void Dispose() { _inputHandler?.Dispose(); + IsOpen = false; } } } diff --git a/src/JsonRpc/ContentModified.cs b/src/JsonRpc/ContentModified.cs new file mode 100644 index 000000000..edae4abc7 --- /dev/null +++ b/src/JsonRpc/ContentModified.cs @@ -0,0 +1,11 @@ +using OmniSharp.Extensions.JsonRpc.Server; +using OmniSharp.Extensions.JsonRpc.Server.Messages; + +namespace OmniSharp.Extensions.JsonRpc +{ + public class ContentModified : RpcError + { + internal ContentModified() : base(null, new ErrorMessage(ErrorCodes.ContentModified, "Content Modified")) { } + internal ContentModified(object id ) : base(id, new ErrorMessage(ErrorCodes.ContentModified, "Content Modified")) { } + } +} diff --git a/src/JsonRpc/DelegatingHandlers.cs b/src/JsonRpc/DelegatingHandlers.cs index b604d1ad3..871bc4738 100644 --- a/src/JsonRpc/DelegatingHandlers.cs +++ b/src/JsonRpc/DelegatingHandlers.cs @@ -67,10 +67,10 @@ public Notification(Func handler) _handler = handler; } - Task IRequestHandler.Handle(TParams request, CancellationToken cancellationToken) + async Task IRequestHandler.Handle(TParams request, CancellationToken cancellationToken) { - _handler(request, cancellationToken); - return Unit.Task; + await _handler(request, cancellationToken); + return Unit.Value; } } } diff --git a/src/JsonRpc/DelegatingJsonNotificationHandler.cs b/src/JsonRpc/DelegatingJsonNotificationHandler.cs new file mode 100644 index 000000000..9ec5f0de6 --- /dev/null +++ b/src/JsonRpc/DelegatingJsonNotificationHandler.cs @@ -0,0 +1,24 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Newtonsoft.Json.Linq; + +namespace OmniSharp.Extensions.JsonRpc +{ + public class DelegatingJsonNotificationHandler : IJsonRpcNotificationHandler> + { + private readonly Func _handler; + + public DelegatingJsonNotificationHandler(Func handler) + { + _handler = handler; + } + + public async Task Handle(DelegatingNotification request, CancellationToken cancellationToken) + { + await _handler.Invoke(request.Value, cancellationToken); + return Unit.Value; + } + } +} diff --git a/src/JsonRpc/DelegatingJsonRequestHandler.cs b/src/JsonRpc/DelegatingJsonRequestHandler.cs new file mode 100644 index 000000000..45bd610f9 --- /dev/null +++ b/src/JsonRpc/DelegatingJsonRequestHandler.cs @@ -0,0 +1,23 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; + +namespace OmniSharp.Extensions.JsonRpc +{ + public class DelegatingJsonRequestHandler : IJsonRpcRequestHandler, JToken> + { + private readonly Func> _handler; + + public DelegatingJsonRequestHandler(Func> handler) + { + _handler = handler; + } + + public async Task Handle(DelegatingRequest request, CancellationToken cancellationToken) + { + var response = await _handler.Invoke(request.Value, cancellationToken); + return response; + } + } +} diff --git a/src/JsonRpc/ErrorResponse.cs b/src/JsonRpc/ErrorResponse.cs index 27fb83178..0d8a6655c 100644 --- a/src/JsonRpc/ErrorResponse.cs +++ b/src/JsonRpc/ErrorResponse.cs @@ -10,22 +10,22 @@ public ErrorResponse(RpcError error) Error = error; } - public ErrorResponse(Response response) + public ErrorResponse(OutgoingResponse outgoingResponse) { - Response = response; + Response = outgoingResponse; Error = null; } public bool IsResponse => Response != null; - public Response Response { get; } + public OutgoingResponse Response { get; } public bool IsError => Error != null; public RpcError Error { get; } public object Value => IsResponse ? (object)Response : IsError ? Error : null; - public static implicit operator ErrorResponse(Response response) + public static implicit operator ErrorResponse(OutgoingResponse outgoingResponse) { - return new ErrorResponse(response); + return new ErrorResponse(outgoingResponse); } public static implicit operator ErrorResponse(RpcError error) diff --git a/src/JsonRpc/HandlerCollection.cs b/src/JsonRpc/HandlerCollection.cs index f901746b5..c5a7c527c 100644 --- a/src/JsonRpc/HandlerCollection.cs +++ b/src/JsonRpc/HandlerCollection.cs @@ -9,16 +9,23 @@ namespace OmniSharp.Extensions.JsonRpc { - [DebuggerDisplay("{Method}")] - public class HandlerCollection : IEnumerable + public class HandlerCollection : IEnumerable, IHandlersManager { internal readonly List _handlers = new List(); + public HandlerCollection() { } + + public HandlerCollection(IEnumerable handlers) + { + Add(handlers.ToArray()); + } + + [DebuggerDisplay("{Method}")] internal class HandlerInstance : IHandlerDescriptor, IDisposable { private readonly Action _disposeAction; - public HandlerInstance(string method, IJsonRpcHandler handler, Type handlerInterface, Type @params, Type response, Action disposeAction) + public HandlerInstance(string method, IJsonRpcHandler handler, Type handlerInterface, Type @params, Type response, RequestProcessType? requestProcessType, Action disposeAction) { _disposeAction = disposeAction; Handler = handler; @@ -37,9 +44,17 @@ public HandlerInstance(string method, IJsonRpcHandler handler, Type handlerInter typeof(DelegatingRequest<>).IsAssignableFrom(@params.GetGenericTypeDefinition()) || typeof(DelegatingNotification<>).IsAssignableFrom(@params.GetGenericTypeDefinition()) ); + + IsNotification = typeof(IJsonRpcNotificationHandler).IsAssignableFrom(handlerInterface) || handlerInterface + .GetInterfaces().Any(z => + z.IsGenericType && typeof(IJsonRpcNotificationHandler<>).IsAssignableFrom(z.GetGenericTypeDefinition())); + IsRequest = !IsNotification; + RequestProcessType = requestProcessType; } public IJsonRpcHandler Handler { get; } + public bool IsNotification { get; } + public bool IsRequest { get; } public Type HandlerType { get; } public Type ImplementationType { get; } public string Method { get; } @@ -47,6 +62,7 @@ public HandlerInstance(string method, IJsonRpcHandler handler, Type handlerInter public Type Response { get; } public bool HasReturnType { get; } public bool IsDelegatingHandler { get; } + public RequestProcessType? RequestProcessType { get; } public void Dispose() { @@ -75,12 +91,18 @@ public IDisposable Add(params IJsonRpcHandler[] handlers) var cd = new CompositeDisposable(); foreach (var handler in handlers) { - cd.Add(Add(GetMethodName(handler.GetType()), handler)); + if (_handlers.Any(z => z.Handler == handler)) continue; + cd.Add(Add(GetMethodName(handler.GetType()), handler, null)); } return cd; } - public IDisposable Add(string method, IJsonRpcHandler handler) + public IDisposable Add(IJsonRpcHandler handler, JsonRpcHandlerOptions options) + { + return Add(GetMethodName(handler.GetType()), handler, options); + } + + public IDisposable Add(string method, IJsonRpcHandler handler, JsonRpcHandlerOptions options) { var type = handler.GetType(); var @interface = GetHandlerInterface(type); @@ -98,7 +120,14 @@ public IDisposable Add(string method, IJsonRpcHandler handler) } } - var h = new HandlerInstance(method, handler, @interface, @params, response, () => Remove(handler)); + var requestProcessType = + options?.RequestProcessType ?? + type.GetCustomAttributes(true) + .Concat(@interface.GetCustomAttributes(true)) + .OfType() + .FirstOrDefault()?.Type; + + var h = new HandlerInstance(method, handler, @interface, @params, response, requestProcessType, () => Remove(handler)); _handlers.Add(h); return h; } diff --git a/src/JsonRpc/HandlerTypeDescriptor.cs b/src/JsonRpc/HandlerTypeDescriptor.cs new file mode 100644 index 000000000..d186b0b9e --- /dev/null +++ b/src/JsonRpc/HandlerTypeDescriptor.cs @@ -0,0 +1,59 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using MediatR; + +namespace OmniSharp.Extensions.JsonRpc +{ + [DebuggerDisplay("{" + nameof(Method) + "}")] + class HandlerTypeDescriptor : IHandlerTypeDescriptor + { + public HandlerTypeDescriptor(Type handlerType) + { + var method = handlerType.GetCustomAttribute(); + Method = method.Method; + Direction = method.Direction; + HandlerType = handlerType; + InterfaceType = HandlerTypeDescriptorHelper.GetHandlerInterface(handlerType); + + ParamsType = InterfaceType.IsGenericType ? InterfaceType.GetGenericArguments()[0] : typeof(EmptyRequest); + HasParamsType = ParamsType != null && ParamsType != typeof(EmptyRequest); + + IsNotification = typeof(IJsonRpcNotificationHandler).IsAssignableFrom(handlerType) || handlerType + .GetInterfaces().Any(z => + z.IsGenericType && typeof(IJsonRpcNotificationHandler<>).IsAssignableFrom(z.GetGenericTypeDefinition())); + IsRequest = !IsNotification; + + var requestInterface = ParamsType? + .GetInterfaces() + .FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IRequest<>)); + if (requestInterface != null) + ResponseType = requestInterface.GetGenericArguments()[0]; + HasResponseType = ResponseType != null && ResponseType != typeof(Unit); + + var processAttributes = HandlerType + .GetCustomAttributes(true) + .Concat(HandlerType.GetCustomAttributes(true)) + .Concat(InterfaceType.GetInterfaces().SelectMany(x => x.GetCustomAttributes(true))) + .Concat(HandlerType.GetInterfaces().SelectMany(x => x.GetCustomAttributes(true))) + .OfType() + .ToArray(); + RequestProcessType = processAttributes + .FirstOrDefault()?.Type; + } + + public string Method { get; } + public Direction Direction { get; } + public RequestProcessType? RequestProcessType { get; } + public bool IsRequest { get; } + public Type HandlerType { get; } + public Type InterfaceType { get; } + public bool IsNotification { get; } + public bool HasParamsType { get; } + public Type ParamsType { get; } + public bool HasResponseType { get; } + public Type ResponseType { get; } + public override string ToString() => $"{Method}"; + } +} diff --git a/src/JsonRpc/HandlerTypeDescriptorHelper.cs b/src/JsonRpc/HandlerTypeDescriptorHelper.cs new file mode 100644 index 000000000..aa36a0deb --- /dev/null +++ b/src/JsonRpc/HandlerTypeDescriptorHelper.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Immutable; +using System.Linq; +using System.Reflection; + +namespace OmniSharp.Extensions.JsonRpc +{ + public static class HandlerTypeDescriptorHelper + { + private static readonly ConcurrentDictionary MethodNames = + new ConcurrentDictionary(); + + internal static readonly ImmutableSortedDictionary KnownHandlers; + + static HandlerTypeDescriptorHelper() + { + try + { + KnownHandlers = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(x => { + try + { + return x.GetTypes(); + } + catch + { + return Enumerable.Empty(); + } + }) + .Where(z => z.IsInterface && typeof(IJsonRpcHandler).IsAssignableFrom(z)) + .Where(z => z.GetCustomAttributes().Any()) + .Select(GetMethodType) + .Distinct() + .ToLookup(x => x.GetCustomAttribute().Method) + .Select(x => new HandlerTypeDescriptor(x.First()) as IHandlerTypeDescriptor) + .ToImmutableSortedDictionary(x => x.Method, x => x, StringComparer.Ordinal); + } + catch (Exception e) + { + throw new AggregateException($"Failed", e); + } + } + + public static IHandlerTypeDescriptor GetHandlerTypeDescriptor(string method) + { + return KnownHandlers.TryGetValue(method, out var descriptor) ? descriptor : null; + } + + public static IHandlerTypeDescriptor GetHandlerTypeDescriptor() + { + return KnownHandlers.Values.FirstOrDefault(x => x.InterfaceType == typeof(T)) ?? + GetHandlerTypeDescriptor(GetMethodName(typeof(T))); + } + + public static IHandlerTypeDescriptor GetHandlerTypeDescriptor(Type type) + { + var @default = KnownHandlers.Values.FirstOrDefault(x => x.InterfaceType == type); + if (@default != null) + { + return @default; + } + + var methodName = GetMethodName(type); + if (string.IsNullOrWhiteSpace(methodName)) return null; + return GetHandlerTypeDescriptor(methodName); + } + + public static string GetMethodName() + where T : IJsonRpcHandler + { + return GetMethodName(typeof(T)); + } + + public static bool IsMethodName(string name, params Type[] types) + { + return types.Any(z => GetMethodName(z).Equals(name)); + } + + public static string GetMethodName(Type type) + { + if (MethodNames.TryGetValue(type, out var method)) return method; + + // Custom method + var attribute = type.GetCustomAttribute(); + if (attribute is null) + { + attribute = type + .GetInterfaces() + .Select(t => t.GetCustomAttribute()) + .FirstOrDefault(x => x != null); + } + + var handler = KnownHandlers.Values.FirstOrDefault(z => + z.InterfaceType == type || z.HandlerType == type || z.ParamsType == type); + if (handler != null) + { + return handler.Method; + } + + + // TODO: Log unknown method name + if (attribute is null) + { + return null; + } + + MethodNames.TryAdd(type, attribute.Method); + return attribute.Method; + } + + internal static Type GetMethodType(Type type) + { + // Custom method + if (type.GetTypeInfo().GetCustomAttributes().Any()) + { + return type; + } + + return type.GetTypeInfo() + .ImplementedInterfaces + .FirstOrDefault(t => t.GetCustomAttributes().Any()); + } + + private static readonly Type[] HandlerTypes = { typeof(IJsonRpcNotificationHandler), typeof(IJsonRpcNotificationHandler<>), typeof(IJsonRpcRequestHandler<>), typeof(IJsonRpcRequestHandler<,>), }; + + private static bool IsValidInterface(Type type) + { + if (type.GetTypeInfo().IsGenericType) + { + return HandlerTypes.Contains(type.GetGenericTypeDefinition()); + } + return HandlerTypes.Contains(type); + } + + public static Type GetHandlerInterface(Type type) + { + if (IsValidInterface(type)) return type; + return type?.GetTypeInfo() + .ImplementedInterfaces + .First(IsValidInterface); + } + + public static Type UnwrapGenericType(Type genericType, Type type) + { + return type?.GetTypeInfo() + .ImplementedInterfaces + .FirstOrDefault(x => x.GetTypeInfo().IsGenericType && x.GetTypeInfo().GetGenericTypeDefinition() == genericType) + ?.GetTypeInfo() + ?.GetGenericArguments()[0]; + } + } +} diff --git a/src/JsonRpc/ICancelRequestHandler.cs b/src/JsonRpc/ICancelRequestHandler.cs deleted file mode 100644 index e160b22f7..000000000 --- a/src/JsonRpc/ICancelRequestHandler.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using MediatR; - - -namespace OmniSharp.Extensions.JsonRpc -{ - [Parallel, Method(JsonRpcNames.CancelRequest, Direction.Bidirectional)] - public interface ICancelRequestHandler : IJsonRpcNotificationHandler { } - - public abstract class CancelRequestHandler : ICancelRequestHandler - { - public abstract Task Handle(CancelParams request, CancellationToken cancellationToken); - } - - public static class CancelRequestExtensions - { - public static IDisposable OnCancelRequest( - this IJsonRpcHandlerRegistry registry, - Action handler) - { - return registry.AddHandler(JsonRpcNames.CancelRequest, NotificationHandler.For(handler)); - } - - public static IDisposable OnCancelRequest( - this IJsonRpcHandlerRegistry registry, - Func handler) - { - return registry.AddHandler(JsonRpcNames.CancelRequest, NotificationHandler.For(handler)); - } - } -} diff --git a/src/JsonRpc/IHandlerDescriptor.cs b/src/JsonRpc/IHandlerDescriptor.cs index 685c137d1..a75712eef 100644 --- a/src/JsonRpc/IHandlerDescriptor.cs +++ b/src/JsonRpc/IHandlerDescriptor.cs @@ -12,5 +12,6 @@ public interface IHandlerDescriptor bool HasReturnType { get; } bool IsDelegatingHandler { get; } IJsonRpcHandler Handler { get; } + RequestProcessType? RequestProcessType { get; } } } diff --git a/src/JsonRpc/IHandlerTypeDescriptor.cs b/src/JsonRpc/IHandlerTypeDescriptor.cs new file mode 100644 index 000000000..adbe81890 --- /dev/null +++ b/src/JsonRpc/IHandlerTypeDescriptor.cs @@ -0,0 +1,19 @@ +using System; + +namespace OmniSharp.Extensions.JsonRpc +{ + public interface IHandlerTypeDescriptor + { + string Method { get; } + Direction Direction { get; } + RequestProcessType? RequestProcessType { get; } + Type InterfaceType { get; } + bool IsNotification { get; } + bool IsRequest { get; } + Type HandlerType { get; } + bool HasParamsType { get; } + Type ParamsType { get; } + bool HasResponseType { get; } + Type ResponseType { get; } + } +} diff --git a/src/JsonRpc/IHandlersManager.cs b/src/JsonRpc/IHandlersManager.cs new file mode 100644 index 000000000..ce152f4df --- /dev/null +++ b/src/JsonRpc/IHandlersManager.cs @@ -0,0 +1,10 @@ +using System; + +namespace OmniSharp.Extensions.JsonRpc +{ + public interface IHandlersManager + { + IDisposable Add(IJsonRpcHandler handler, JsonRpcHandlerOptions options); + IDisposable Add(string method, IJsonRpcHandler handler, JsonRpcHandlerOptions options); + } +} diff --git a/src/JsonRpc/IInputHandler.cs b/src/JsonRpc/IInputHandler.cs index ea3c17a79..0d5f12518 100644 --- a/src/JsonRpc/IInputHandler.cs +++ b/src/JsonRpc/IInputHandler.cs @@ -1,9 +1,10 @@ -using System; +using System.Threading.Tasks; namespace OmniSharp.Extensions.JsonRpc { - public interface IInputHandler : IDisposable + public interface IInputHandler { void Start(); + Task StopAsync(); } -} \ No newline at end of file +} diff --git a/src/JsonRpc/IJsonRpcHandler.cs b/src/JsonRpc/IJsonRpcHandler.cs index 92ce9ddd9..a1bca668f 100644 --- a/src/JsonRpc/IJsonRpcHandler.cs +++ b/src/JsonRpc/IJsonRpcHandler.cs @@ -1,4 +1,6 @@ -namespace OmniSharp.Extensions.JsonRpc +using System; + +namespace OmniSharp.Extensions.JsonRpc { /// /// A simple marker interface to use for storing handlings (which will be cast out later) diff --git a/src/JsonRpc/IJsonRpcHandlerInstance.cs b/src/JsonRpc/IJsonRpcHandlerInstance.cs new file mode 100644 index 000000000..2b4938fc8 --- /dev/null +++ b/src/JsonRpc/IJsonRpcHandlerInstance.cs @@ -0,0 +1,10 @@ +using System; + +namespace OmniSharp.Extensions.JsonRpc +{ + public interface IJsonRpcHandlerInstance + where TRegistry : IJsonRpcHandlerRegistry + { + public IDisposable Register(Action registryAction); + } +} \ No newline at end of file diff --git a/src/JsonRpc/IJsonRpcHandlerRegistry.cs b/src/JsonRpc/IJsonRpcHandlerRegistry.cs index dd0161660..7636d2fd9 100644 --- a/src/JsonRpc/IJsonRpcHandlerRegistry.cs +++ b/src/JsonRpc/IJsonRpcHandlerRegistry.cs @@ -1,12 +1,50 @@ using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json.Linq; namespace OmniSharp.Extensions.JsonRpc { - public interface IJsonRpcHandlerRegistry + public class JsonRpcHandlerOptions { - IDisposable AddHandler(string method, IJsonRpcHandler handler); - IDisposable AddHandler(string method, Func handlerFunc); - IDisposable AddHandlers(params IJsonRpcHandler[] handlers); - IDisposable AddHandler() where T : IJsonRpcHandler; + public RequestProcessType RequestProcessType { get; set; } + } + public interface IJsonRpcHandlerRegistry {} + public interface IJsonRpcHandlerRegistry : IJsonRpcHandlerRegistry where T : IJsonRpcHandlerRegistry + { + T AddHandler(string method, IJsonRpcHandler handler, JsonRpcHandlerOptions options = null); + T AddHandler(string method, Func handlerFunc, JsonRpcHandlerOptions options = null); + T AddHandlers(params IJsonRpcHandler[] handlers); + T AddHandler(Func handlerFunc, JsonRpcHandlerOptions options = null) where THandler : IJsonRpcHandler; + T AddHandler(THandler handler, JsonRpcHandlerOptions options = null) where THandler : IJsonRpcHandler; + T AddHandler(JsonRpcHandlerOptions options = null) where TTHandler : IJsonRpcHandler; + T AddHandler(string method, JsonRpcHandlerOptions options = null) where TTHandler : IJsonRpcHandler; + T AddHandler(Type type, JsonRpcHandlerOptions options = null); + T AddHandler(string method, Type type, JsonRpcHandlerOptions options = null); + + T OnJsonRequest(string method, Func> handler, JsonRpcHandlerOptions options = null); + T OnJsonRequest(string method, Func> handler, JsonRpcHandlerOptions options = null); + T OnRequest(string method, Func> handler, JsonRpcHandlerOptions options = null); + T OnRequest(string method, Func> handler, JsonRpcHandlerOptions options = null); + T OnRequest(string method, Func> handler, JsonRpcHandlerOptions options = null); + T OnRequest(string method, Func> handler, JsonRpcHandlerOptions options = null); + T OnRequest(string method, Func handler, JsonRpcHandlerOptions options = null); + T OnRequest(string method, Func handler, JsonRpcHandlerOptions options = null); + T OnRequest(string method, Func handler, JsonRpcHandlerOptions options = null); + T OnJsonNotification(string method, Action handler, JsonRpcHandlerOptions options = null); + T OnJsonNotification(string method, Action handler, JsonRpcHandlerOptions options = null); + T OnJsonNotification(string method, Func handler, JsonRpcHandlerOptions options = null); + T OnJsonNotification(string method, Func handler, JsonRpcHandlerOptions options = null); + T OnNotification(string method, Action handler, JsonRpcHandlerOptions options = null); + T OnNotification(string method, Action handler, JsonRpcHandlerOptions options = null); + T OnNotification(string method, Func handler, JsonRpcHandlerOptions options = null); + T OnNotification(string method, Func handler, JsonRpcHandlerOptions options = null); + T OnNotification(string method, Action handler, JsonRpcHandlerOptions options = null); + T OnNotification(string method, Func handler, JsonRpcHandlerOptions options = null); + T OnNotification(string method, Func handler, JsonRpcHandlerOptions options = null); } } diff --git a/src/JsonRpc/IJsonRpcServer.cs b/src/JsonRpc/IJsonRpcServer.cs index 830e8c985..dcd4a82f9 100644 --- a/src/JsonRpc/IJsonRpcServer.cs +++ b/src/JsonRpc/IJsonRpcServer.cs @@ -2,7 +2,7 @@ namespace OmniSharp.Extensions.JsonRpc { - public interface IJsonRpcServer : IResponseRouter, IDisposable, IJsonRpcHandlerRegistry + public interface IJsonRpcServer : IResponseRouter, IJsonRpcHandlerInstance, IDisposable { } diff --git a/src/JsonRpc/IJsonRpcServerOptions.cs b/src/JsonRpc/IJsonRpcServerOptions.cs new file mode 100644 index 000000000..7a2ebf2ff --- /dev/null +++ b/src/JsonRpc/IJsonRpcServerOptions.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.IO.Pipelines; +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; +using OmniSharp.Extensions.JsonRpc.Serialization; +using OmniSharp.Extensions.JsonRpc.Server; + +namespace OmniSharp.Extensions.JsonRpc +{ + public interface IJsonRpcServerOptions + { + PipeReader Input { get; set; } + PipeWriter Output { get; set; } + IServiceCollection Services { get; set; } + IRequestProcessIdentifier RequestProcessIdentifier { get; set; } + int? Concurrency { get; set; } + Action OnUnhandledException { get; set; } + Func CreateResponseException { get; set; } + bool SupportsContentModified { get; set; } + TimeSpan MaximumRequestTimeout { get; set; } + void RegisterForDisposal(IDisposable disposable); + IDisposable RegisteredDisposables { get; } + IEnumerable Assemblies { get; } + } +} diff --git a/src/JsonRpc/IJsonRpcServerRegistry.cs b/src/JsonRpc/IJsonRpcServerRegistry.cs new file mode 100644 index 000000000..e5b8f64ad --- /dev/null +++ b/src/JsonRpc/IJsonRpcServerRegistry.cs @@ -0,0 +1,6 @@ +namespace OmniSharp.Extensions.JsonRpc +{ + public interface IJsonRpcServerRegistry : IJsonRpcHandlerRegistry + { + } +} \ No newline at end of file diff --git a/src/JsonRpc/IOutputHandler.cs b/src/JsonRpc/IOutputHandler.cs index 89366dc60..40906c18e 100644 --- a/src/JsonRpc/IOutputHandler.cs +++ b/src/JsonRpc/IOutputHandler.cs @@ -1,13 +1,12 @@ using System; -using System.Threading; using System.Threading.Tasks; namespace OmniSharp.Extensions.JsonRpc { public interface IOutputHandler : IDisposable { - void Start(); - void Send(object value, CancellationToken cancellationToken); - Task WaitForShutdown(); + void Send(object value); + Task StopAsync(); + } } diff --git a/src/JsonRpc/IReciever.cs b/src/JsonRpc/IReceiver.cs similarity index 100% rename from src/JsonRpc/IReciever.cs rename to src/JsonRpc/IReceiver.cs diff --git a/src/JsonRpc/IRequestContext.cs b/src/JsonRpc/IRequestContext.cs index 74bde8ebf..d34d34679 100644 --- a/src/JsonRpc/IRequestContext.cs +++ b/src/JsonRpc/IRequestContext.cs @@ -4,4 +4,4 @@ public interface IRequestContext { IHandlerDescriptor Descriptor { get; set; } } -} \ No newline at end of file +} diff --git a/src/JsonRpc/IRequestRouter.cs b/src/JsonRpc/IRequestRouter.cs index a78683d26..58f3fedf9 100644 --- a/src/JsonRpc/IRequestRouter.cs +++ b/src/JsonRpc/IRequestRouter.cs @@ -8,10 +8,6 @@ namespace OmniSharp.Extensions.JsonRpc public interface IRequestRouter { IServiceProvider ServiceProvider { get; } - Task RouteNotification(Notification notification, CancellationToken token); - Task RouteRequest(Request request, CancellationToken token); - void CancelRequest(object id); - void StartRequest(object id); } public interface IRequestRouter : IRequestRouter diff --git a/src/JsonRpc/IResponseRouter.cs b/src/JsonRpc/IResponseRouter.cs index aff7f5921..8c60ce311 100644 --- a/src/JsonRpc/IResponseRouter.cs +++ b/src/JsonRpc/IResponseRouter.cs @@ -13,6 +13,6 @@ public interface IResponseRouter IResponseRouterReturns SendRequest(string method, T @params); IResponseRouterReturns SendRequest(string method); Task SendRequest(IRequest request, CancellationToken cancellationToken); - TaskCompletionSource GetRequest(long id); + (string method, TaskCompletionSource pendingTask) GetRequest(long id); } } diff --git a/src/JsonRpc/IReturning.cs b/src/JsonRpc/IResponseRouterReturns.cs similarity index 100% rename from src/JsonRpc/IReturning.cs rename to src/JsonRpc/IResponseRouterReturns.cs diff --git a/src/JsonRpc/IScheduler.cs b/src/JsonRpc/IScheduler.cs deleted file mode 100644 index e54d9ab15..000000000 --- a/src/JsonRpc/IScheduler.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Reactive; -using System.Reactive.Linq; -using System.Threading.Tasks; - -namespace OmniSharp.Extensions.JsonRpc -{ - public interface IScheduler : IDisposable - { - void Start(); - void Add(RequestProcessType type, string name, IObservable request); - } - - public static class SchedulerExtensions - { - public static void Add(this IScheduler scheduler, RequestProcessType type, string name, Func request) - { - scheduler.Add(type, name, Observable.FromAsync(request)); - } - } -} diff --git a/src/JsonRpc/InputHandler.cs b/src/JsonRpc/InputHandler.cs index 013439ede..a511e605d 100644 --- a/src/JsonRpc/InputHandler.cs +++ b/src/JsonRpc/InputHandler.cs @@ -1,173 +1,405 @@ using System; +using System.Buffers; +using System.Collections.Concurrent; +using System.Collections.Generic; using System.IO; +using System.IO.Pipelines; using System.Linq; +using System.Reactive; +using System.Reactive.Concurrency; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Reactive.Threading.Tasks; +using System.Text; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json.Linq; using Microsoft.Extensions.Logging; +using Nerdbank.Streams; +using Newtonsoft.Json; using OmniSharp.Extensions.JsonRpc.Server; using OmniSharp.Extensions.JsonRpc.Server.Messages; +using Notification = OmniSharp.Extensions.JsonRpc.Server.Notification; namespace OmniSharp.Extensions.JsonRpc { - public class InputHandler : IInputHandler + public class InputHandler : IInputHandler, IDisposable { - public const char CR = '\r'; - public const char LF = '\n'; - public static char[] CRLF = { CR, LF }; - public static char[] HeaderKeys = { CR, LF, ':' }; + public static readonly byte[] HeadersFinished = + new byte[] {(byte) '\r', (byte) '\n', (byte) '\r', (byte) '\n'}.ToArray(); + + public const int HeadersFinishedLength = 4; + public static readonly char[] HeaderKeys = {'\r', '\n', ':'}; public const short MinBuffer = 21; // Minimum size of the buffer "Content-Length: X\r\n\r\n" + public static readonly byte[] ContentLength = "Content-Length".Select(x => (byte) x).ToArray(); + public static readonly int ContentLengthLength = 14; - private readonly Stream _input; + private readonly PipeReader _pipeReader; private readonly IOutputHandler _outputHandler; private readonly IReceiver _receiver; private readonly IRequestProcessIdentifier _requestProcessIdentifier; - private Thread _inputThread; private readonly IRequestRouter _requestRouter; private readonly IResponseRouter _responseRouter; - private readonly ISerializer _serializer; + private readonly Action _unhandledInputProcessException; + private readonly Func _getException; + private readonly TimeSpan _requestTimeout; private readonly ILogger _logger; - private readonly IScheduler _scheduler; + private readonly ProcessScheduler _scheduler; + private readonly Memory _headersBuffer; + private readonly Memory _contentLengthBuffer; + private readonly byte[] _contentLengthValueBuffer; + private readonly Memory _contentLengthValueMemory; + private readonly CancellationTokenSource _stopProcessing; + private readonly CompositeDisposable _disposable; + private readonly AsyncSubject _inputActive; + + private readonly ConcurrentDictionary _requests = + new ConcurrentDictionary(); + + private readonly Subject> _inputQueue; public InputHandler( - Stream input, + PipeReader pipeReader, IOutputHandler outputHandler, IReceiver receiver, IRequestProcessIdentifier requestProcessIdentifier, IRequestRouter requestRouter, IResponseRouter responseRouter, ILoggerFactory loggerFactory, - ISerializer serializer, + Action unhandledInputProcessException, + Func getException, + TimeSpan requestTimeout, + bool supportContentModified, int? concurrency - ) + ) { - if (!input.CanRead) throw new ArgumentException($"must provide a readable stream for {nameof(input)}", nameof(input)); - _input = input; + _pipeReader = pipeReader; _outputHandler = outputHandler; _receiver = receiver; _requestProcessIdentifier = requestProcessIdentifier; _requestRouter = requestRouter; _responseRouter = responseRouter; - _serializer = serializer; + _unhandledInputProcessException = unhandledInputProcessException; + _getException = getException; + _requestTimeout = requestTimeout; _logger = loggerFactory.CreateLogger(); - _scheduler = new ProcessScheduler(loggerFactory, concurrency); - _inputThread = new Thread(ProcessInputStream) { IsBackground = true, Name = "ProcessInputStream" }; + _scheduler = new ProcessScheduler( + loggerFactory, + supportContentModified, + concurrency, + requestTimeout, + TaskPoolScheduler.Default + // new EventLoopScheduler(_ => new Thread(_) {IsBackground = true, Name = "InputHandler"}) + ); + _headersBuffer = new Memory(new byte[HeadersFinishedLength]); + _contentLengthBuffer = new Memory(new byte[ContentLengthLength]); + _contentLengthValueBuffer = new byte[20]; // Max string length of the long value + _contentLengthValueMemory = + new Memory(_contentLengthValueBuffer); // Max string length of the long value + _stopProcessing = new CancellationTokenSource(); + + _disposable = new CompositeDisposable { + Disposable.Create(() => _stopProcessing.Cancel()), + _stopProcessing, + _scheduler, + }; + + _inputActive = new AsyncSubject(); + _inputQueue = new Subject>(); } public void Start() { - _scheduler.Start(); - _outputHandler.Start(); - _inputThread.Start(); + _disposable.Add( + Observable.FromAsync(() => ProcessInputStream(_stopProcessing.Token)) + .Do(_ => { }, e => _logger.LogCritical(e, "unhandled exception")) + .Subscribe(_inputActive)); + _disposable.Add( + _inputQueue + .Concat() + .Subscribe() + ); } - // don't be async: We already allocated a seperate thread for this. - private void ProcessInputStream() + public async Task StopAsync() { - // some time to attach a debugger - // System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5)); + await _outputHandler.StopAsync(); + await _pipeReader.CompleteAsync(); + } - // header is encoded in ASCII - // "Content-Length: 0" counts bytes for the following content - // content is encoded in UTF-8 - while (true) + public void Dispose() + { + _disposable.Dispose(); + _pipeReader.Complete(); + _outputHandler.Dispose(); + } + + public Task InputCompleted => _inputActive.ToTask(); + + private bool TryParseHeaders(ref ReadOnlySequence buffer, out ReadOnlySequence line) + { + if (buffer.Length < MinBuffer || buffer.Length < HeadersFinishedLength) + { + line = default; + return false; + } + + var rentedSpan = _headersBuffer.Span; + + var start = buffer.PositionOf((byte) '\r'); + do { - try + if (!start.HasValue) { - if (_inputThread == null) return; - - var buffer = new byte[300]; - var current = _input.Read(buffer, 0, MinBuffer); - if (current == 0) return; // no more _input - while (current < MinBuffer || - buffer[current - 4] != CR || buffer[current - 3] != LF || - buffer[current - 2] != CR || buffer[current - 1] != LF) - { - var n = _input.Read(buffer, current, 1); - if (n == 0) return; // no more _input, mitigates endless loop here. - current += n; - } + line = default; + return false; + } + + var startSlice = buffer.Slice(start.Value); + if (startSlice.Length < HeadersFinishedLength) + { + line = default; + return false; + } + + var next = buffer.Slice(start.Value, buffer.GetPosition(HeadersFinishedLength, start.Value)); + next.CopyTo(rentedSpan); + if (IsEqual(rentedSpan, HeadersFinished)) + { + line = buffer.Slice(0, next.End); + buffer = buffer.Slice(next.End); + return true; + } + + start = buffer.Slice(buffer.GetPosition(HeadersFinishedLength, start.Value)).PositionOf((byte) '\r'); + } while (start.HasValue && buffer.Length > MinBuffer); + + line = default; + return false; + } + + static bool IsEqual(in Span headers, in byte[] bytes) + { + var isEqual = true; + var len = bytes.Length; + for (var i = 0; i < len; i++) + { + if (bytes[i] == headers[i]) continue; + isEqual = false; + break; + } + + return isEqual; + } + + private bool TryParseBodyString(in long length, ref ReadOnlySequence buffer, + out ReadOnlySequence line) + { + if (buffer.Length < length) + { + line = default; + return false; + } - var headersContent = System.Text.Encoding.ASCII.GetString(buffer, 0, current); - var headers = headersContent.Split(HeaderKeys, StringSplitOptions.RemoveEmptyEntries); - long length = 0; - for (var i = 1; i < headers.Length; i += 2) + + line = buffer.Slice(0, length); + buffer = buffer.Slice(length); + return true; + } + + bool TryParseContentLength(ref ReadOnlySequence buffer, out long length) + { + do + { + var colon = buffer.PositionOf((byte) ':'); + if (!colon.HasValue) + { + length = -1; + return false; + } + + var slice = buffer.Slice(0, colon.Value); + slice.CopyTo(_contentLengthBuffer.Span); + + if (IsEqual(_contentLengthBuffer.Span, ContentLength)) + { + var position = buffer.GetPosition(1, colon.Value); + var offset = 1; + + while (buffer.TryGet(ref position, out var memory, true) && !memory.Span.IsEmpty) { - // starting at i = 1 instead of 0 won't throw, if we have uneven headers' length - var header = headers[i - 1]; - var value = headers[i].Trim(); - if (header.Equals("Content-Length", StringComparison.OrdinalIgnoreCase)) + foreach (var t in memory.Span) { - length = 0; - long.TryParse(value, out length); + if (t == (byte) ' ') + { + offset++; + continue; + } + + break; } } - if (length == 0 || length >= int.MaxValue) + var lengthSlice = buffer.Slice( + buffer.GetPosition(offset, colon.Value), + buffer.PositionOf((byte) '\r') ?? buffer.End + ); + + var whitespacePosition = lengthSlice.PositionOf((byte) ' '); + if (whitespacePosition.HasValue) { - HandleRequest(string.Empty, CancellationToken.None); + lengthSlice = lengthSlice.Slice(0, whitespacePosition.Value); } - else + + lengthSlice.CopyTo(_contentLengthValueMemory.Span); + if (long.TryParse(Encoding.ASCII.GetString(_contentLengthValueBuffer), out length)) { - var requestBuffer = new byte[length]; - var received = 0; - while (received < length) - { - var n = _input.Read(requestBuffer, received, requestBuffer.Length - received); - if (n == 0) return; // no more _input - received += n; - } - // TODO sometimes: encoding should be based on the respective header (including the wrong "utf8" value) - var payload = System.Text.Encoding.UTF8.GetString(requestBuffer); - HandleRequest(payload, CancellationToken.None); + // Reset the array otherwise smaller numbers will be inflated; + for (var i = 0; i < lengthSlice.Length; i++) _contentLengthValueMemory.Span[i] = 0; + return true; } + + // Reset the array otherwise smaller numbers will be inflated; + for (var i = 0; i < lengthSlice.Length; i++) _contentLengthValueMemory.Span[i] = 0; + + _logger.LogError("Unable to get length from content length header..."); + return false; } - catch (IOException) + else { - _logger.LogError("Input stream has been closed."); - break; + buffer = buffer.Slice(buffer.GetPosition(1, buffer.PositionOf((byte) '\n') ?? buffer.End)); } + } while (true); + } + + internal async Task ProcessInputStream(CancellationToken cancellationToken) + { + // some time to attach a debugger + // System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5)); + ReadOnlySequence buffer = default; + try + { + var headersParsed = false; + long length = 0; + do + { + var result = await _pipeReader.ReadAsync(cancellationToken); + buffer = result.Buffer; + + var dataParsed = true; + do + { + dataParsed = false; + if (!headersParsed) + { + if (TryParseHeaders(ref buffer, out var line)) + { + if (TryParseContentLength(ref line, out length)) + { + headersParsed = true; + } + } + } + + if (headersParsed && length == 0) + { + HandleRequest(new ReadOnlySequence(Array.Empty())); + headersParsed = false; + } + + if (headersParsed) + { + if (TryParseBodyString(length, ref buffer, out var line)) + { + headersParsed = false; + length = 0; + HandleRequest(line); + dataParsed = true; + } + } + } while (!buffer.IsEmpty && dataParsed); + + _pipeReader.AdvanceTo(buffer.Start, buffer.End); + + // Stop reading if there's no more data coming. + if (result.IsCompleted && buffer.IsEmpty) + { + break; + } + } while (!cancellationToken.IsCancellationRequested); + } + catch (Exception e) + { + var outerException = new InputProcessingException(Encoding.UTF8.GetString(buffer.ToArray()), e); + _unhandledInputProcessException(outerException); + throw outerException; + } + finally + { + await _outputHandler.StopAsync(); + await _pipeReader.CompleteAsync(); } } - private void HandleRequest(string request, CancellationToken cancellationToken) + private void HandleRequest(in ReadOnlySequence request) { JToken payload; try { - payload = JToken.Parse(request); + using var textReader = new StreamReader(request.AsStream()); + using var reader = new JsonTextReader(textReader); + payload = JToken.Load(reader); } catch { - _outputHandler.Send(new ParseError(), cancellationToken); + _outputHandler.Send(new ParseError()); return; } if (!_receiver.IsValid(payload)) { - _outputHandler.Send(new InvalidRequest(), cancellationToken); + _outputHandler.Send(new InvalidRequest()); return; } + // using (_logger.TimeDebug("InputHandler is handling the request")) + // { var (requests, hasResponse) = _receiver.GetRequests(payload); if (hasResponse) { foreach (var response in requests.Where(x => x.IsResponse).Select(x => x.Response)) { + // _logger.LogDebug("Handling Response for request {ResponseId}", response.Id); var id = response.Id is string s ? long.Parse(s) : response.Id is long l ? l : -1; - if (id < 0) continue; - - var tcs = _responseRouter.GetRequest(id); - if (tcs is null) continue; - - if (response is ServerResponse serverResponse) + if (id < 0) { - tcs.SetResult(serverResponse.Result); + // _logger.LogDebug("Id was out of range, skipping request {ResponseId}", response.Id); + continue; } - else if (response is ServerError serverError) + + var (method, tcs) = _responseRouter.GetRequest(id); + if (tcs is null) { - tcs.SetException(new JsonRpcException(serverError)); + // _logger.LogDebug("Request {ResponseId} was not found in the response router, unable to complete", response.Id); + continue; } + + _inputQueue.OnNext(Observable.Create(observer => { + if (response is ServerResponse serverResponse) + { + // _logger.LogDebug("Setting successful Response for {ResponseId}", response.Id); + tcs.TrySetResult(serverResponse.Result); + } + else if (response is ServerError serverError) + { + // _logger.LogDebug("Setting error for {ResponseId}", response.Id); + tcs.TrySetException(DefaultErrorParser(method, serverError, _getException)); + } + + observer.OnCompleted(); + return Disposable.Empty; + })); } return; @@ -177,89 +409,167 @@ private void HandleRequest(string request, CancellationToken cancellationToken) { if (item.IsRequest) { + // _logger.LogDebug("Handling Request {Method} {ResponseId}", item.Request.Method, item.Request.Id); var descriptor = _requestRouter.GetDescriptor(item.Request); - if (descriptor is null) continue; + if (descriptor is null) + { + _logger.LogDebug("Request handler was not found (or not setup) {Method} {ResponseId}", item.Request.Method, item.Request.Id); + _outputHandler.Send(new MethodNotFound(item.Request.Id, item.Request.Method)); + return; + } + var type = _requestProcessIdentifier.Identify(descriptor); - _requestRouter.StartRequest(item.Request.Id); - _scheduler.Add( - type, - item.Request.Method, - async () => { - try - { - var result = await _requestRouter.RouteRequest(descriptor, item.Request, cancellationToken); - if (result.IsError && result.Error is RequestCancelled) - { - return; - } - _outputHandler.Send(result.Value, cancellationToken); - } - catch (Exception e) - { - _logger.LogCritical(Events.UnhandledRequest, e, "Unhandled exception executing request {Method}@{Id}", item.Request.Method, item.Request.Id); - // TODO: Should we rethrow or swallow? - // If an exception happens... the whole system could be in a bad state, hence this throwing currently. - throw; - } - } - ); + _scheduler.Add(type, $"{item.Request.Method}:{item.Request.Id}", RouteRequest(descriptor, item.Request)); } if (item.IsNotification) { - - var descriptor = _requestRouter.GetDescriptor(item.Notification); - if (descriptor is null) continue; - // We need to special case cancellation so that we can cancel any request that is currently in flight. - if (descriptor.Method == JsonRpcNames.CancelRequest) + if (item.Notification.Method == JsonRpcNames.CancelRequest) { + _logger.LogDebug("Found cancellation request {Method}", item.Notification.Method); var cancelParams = item.Notification.Params?.ToObject(); - if (cancelParams == null) { continue; } - _requestRouter.CancelRequest(cancelParams.Id); + if (cancelParams == null) + { + _logger.LogDebug("Got incorrect cancellation params", item.Notification.Method); + continue; + } + + _logger.LogDebug("Cancelling pending request", item.Notification.Method); + if (_requests.TryGetValue(cancelParams.Id, out var d)) + { + d.cancellationTokenSource.Cancel(); + } + continue; } + // _logger.LogDebug("Handling Request {Method}", item.Notification.Method); + var descriptor = _requestRouter.GetDescriptor(item.Notification); + if (descriptor is null) + { + _logger.LogDebug("Notification handler was not found (or not setup) {Method}", item.Notification.Method); + // TODO: Figure out a good way to send this feedback back. + // _outputHandler.Send(new RpcError(null, new ErrorMessage(-32601, $"Method not found - {item.Notification.Method}"))); + return; + } + var type = _requestProcessIdentifier.Identify(descriptor); - _scheduler.Add( - type, - item.Notification.Method, - DoNotification(descriptor, item.Notification) - ); + _scheduler.Add(type, item.Notification.Method, RouteNotification(descriptor, item.Notification)); } if (item.IsError) { - // TODO: - _outputHandler.Send(item.Error, cancellationToken); + _outputHandler.Send(item.Error); } } + } - Func DoNotification(IHandlerDescriptor descriptor, Notification notification) - { - return async () => { - try - { - await _requestRouter.RouteNotification(descriptor, notification, CancellationToken.None); - } - catch (Exception e) - { - _logger.LogCritical(Events.UnhandledNotification, e, "Unhandled exception executing notification {Method}", notification.Method); - // TODO: Should we rethrow or swallow? - // If an exception happens... the whole system could be in a bad state, hence this throwing currently. - throw; - } - }; - } + private SchedulerDelegate RouteRequest(IHandlerDescriptor descriptor, Request request) + { + // start request, create cts, etc + var cts = new CancellationTokenSource(); + _requests.TryAdd(request.Id, (cts, descriptor)); + + return (contentModifiedToken, scheduler) => Observable.Create(observer => { + // ITS A RACE! + var sub = Observable.Amb( + contentModifiedToken.Select(_ => { + _logger.LogTrace("Request {Id} was abandoned due to content be modified", request.Id); + return new ErrorResponse(new ContentModified(request.Id)); + }), + Observable.Timer(_requestTimeout, scheduler).Select(z => new ErrorResponse(new RequestCancelled(request.Id))), + Observable.FromAsync(async (ct) => { + using var timer = _logger.TimeDebug("Processing request {Method} {ResponseId}", request.Method, request.Id); + ct.Register(cts.Cancel); + // ObservableToToken(contentModifiedToken).Register(cts.Cancel); + try + { + return await _requestRouter.RouteRequest(descriptor, request, cts.Token); + } + catch (OperationCanceledException) + { + _logger.LogTrace("Request {Id} was cancelled", request.Id); + return new RequestCancelled(request.Id); + } + catch (RpcErrorException e) + { + _logger.LogCritical(Events.UnhandledRequest, e, "Failed to handle request {Method} {RequestId}", request.Method, request.Id); + return new RpcError(request.Id, new ErrorMessage(e.Code, e.Message, e.Error)); + } + catch (Exception e) + { + _logger.LogCritical(Events.UnhandledRequest, e, "Failed to handle request {Method} {RequestId}", request.Method, request.Id); + return new InternalError(request.Id, e.ToString()); + } + }) + ) + .Subscribe(observer); + return new CompositeDisposable() { + sub, + Disposable.Create(() => { + if (_requests.TryRemove(request.Id, out var v)) + { + v.cancellationTokenSource.Dispose(); + } + }) + }; + }) + .Select(response => { + _outputHandler.Send(response.Value); + return Unit.Default; + }); } + private SchedulerDelegate RouteNotification(IHandlerDescriptor descriptor, Notification notification) + { + return (contentModifiedToken, scheduler) => + // ITS A RACE! + Observable.Amb( + contentModifiedToken + .Do(_ => { _logger.LogTrace("Notification was abandoned due to content be modified"); }), + Observable.Timer(_requestTimeout, scheduler) + .Select(z => Unit.Default) + .Do(z => _logger.LogTrace("Notification was cancelled due to timeout") + ), + Observable.FromAsync(async (ct) => { + using var timer = _logger.TimeDebug("Processing notification {Method}", notification.Method); + try + { + await _requestRouter.RouteNotification(descriptor, notification, ct); + } + catch (OperationCanceledException) + { + _logger.LogTrace("Notification was cancelled"); + } + catch (Exception e) + { + _logger.LogCritical(Events.UnhandledRequest, e, "Failed to handle request {Method}", notification.Method); + } + }) + ) + ; + } - public void Dispose() + private static Exception DefaultErrorParser(string method, ServerError error, Func customHandler) { - _scheduler.Dispose(); - _outputHandler.Dispose(); - _inputThread = null; - _input?.Dispose(); + return error.Error?.Code switch { + ErrorCodes.ServerNotInitialized => new ServerNotInitializedException(error.Id), + ErrorCodes.MethodNotSupported => new MethodNotSupportedException(error.Id, method ?? "UNKNOWN"), + ErrorCodes.InvalidRequest => new InvalidRequestException(error.Id), + ErrorCodes.InvalidParameters => new InvalidParametersException(error.Id), + ErrorCodes.InternalError => new InternalErrorException(error.Id, error.Error.Data?.ToString() ?? string.Empty), + ErrorCodes.ParseError => new ParseErrorException(error.Id), + ErrorCodes.RequestCancelled => new RequestCancelledException(error.Id), + ErrorCodes.ContentModified => new ContentModifiedException(error.Id), + ErrorCodes.UnknownErrorCode => new UnknownErrorException(error.Id), + ErrorCodes.Exception => new JsonRpcException(ErrorCodes.Exception, error.Id, error.Error.Message ?? string.Empty, error.Error.Data.ToString()), + _ => customHandler?.Invoke(error, method) ?? + new JsonRpcException( + error.Error?.Code ?? ErrorCodes.UnknownErrorCode, error.Id, error.Error?.Message ?? string.Empty, + error.Error?.Data.ToString() ?? string.Empty + ) + }; } } } diff --git a/src/JsonRpc/InputProcessingException.cs b/src/JsonRpc/InputProcessingException.cs new file mode 100644 index 000000000..031681bf4 --- /dev/null +++ b/src/JsonRpc/InputProcessingException.cs @@ -0,0 +1,12 @@ +using System; + +namespace OmniSharp.Extensions.JsonRpc +{ + public class InputProcessingException : Exception + { + public InputProcessingException(string message, Exception innerException) : base($"There was an error processing input the contents of the buffer were '{message}'", innerException) + { + + } + } +} \ No newline at end of file diff --git a/src/JsonRpc/InterimJsonRpcServerRegistry.cs b/src/JsonRpc/InterimJsonRpcServerRegistry.cs new file mode 100644 index 000000000..f25b6bc9e --- /dev/null +++ b/src/JsonRpc/InterimJsonRpcServerRegistry.cs @@ -0,0 +1,73 @@ +using System; +using Microsoft.Extensions.DependencyInjection; + +namespace OmniSharp.Extensions.JsonRpc +{ + public class InterimJsonRpcServerRegistry : JsonRpcCommonMethodsBase where T : IJsonRpcHandlerRegistry + { + protected readonly IServiceProvider _serviceProvider; + private readonly IHandlersManager _handlersManager; + + public InterimJsonRpcServerRegistry(IServiceProvider serviceProvider, IHandlersManager handlersManager) + { + _serviceProvider = serviceProvider; + _handlersManager = handlersManager; + } + + public sealed override T AddHandler(string method, IJsonRpcHandler handler, JsonRpcHandlerOptions options) + { + _handlersManager.Add(method, handler, options); + return (T) (object) this; + } + + public sealed override T AddHandler(string method, Func handlerFunc, JsonRpcHandlerOptions options) + { + _handlersManager.Add(method, handlerFunc(_serviceProvider), options); + return (T) (object) this; + } + + public sealed override T AddHandler(Func handlerFunc, JsonRpcHandlerOptions options) + { + _handlersManager.Add(handlerFunc(_serviceProvider), options); + return (T) (object) this; + } + + public sealed override T AddHandlers(params IJsonRpcHandler[] handlers) + { + foreach (var handler in handlers) + { + _handlersManager.Add(handler, null); + } + + return (T) (object) this; + } + + public sealed override T AddHandler(THandler handler, JsonRpcHandlerOptions options) + { + _handlersManager.Add(handler, options); + return (T) (object) this; + } + + public sealed override T AddHandler(JsonRpcHandlerOptions options) + { + return AddHandler(typeof(THandler), options); + } + + public sealed override T AddHandler(string method, JsonRpcHandlerOptions options) + { + return AddHandler(method, typeof(THandler), options); + } + + public sealed override T AddHandler(Type type, JsonRpcHandlerOptions options) + { + _handlersManager.Add(ActivatorUtilities.CreateInstance(_serviceProvider, type) as IJsonRpcHandler, options); + return (T) (object) this; + } + + public sealed override T AddHandler(string method, Type type, JsonRpcHandlerOptions options) + { + _handlersManager.Add(method, ActivatorUtilities.CreateInstance(_serviceProvider, type) as IJsonRpcHandler, options); + return (T) (object) this; + } + } +} diff --git a/src/JsonRpc/JsonRpc.csproj b/src/JsonRpc/JsonRpc.csproj index bbae7d6c0..6c8e67ac4 100644 --- a/src/JsonRpc/JsonRpc.csproj +++ b/src/JsonRpc/JsonRpc.csproj @@ -4,7 +4,7 @@ AnyCPU OmniSharp.Extensions.JsonRpc OmniSharp.Extensions.JsonRpc - Primitives for working with JsonRpc. This library is used as the base for communication with language servers + Primitives for working with JsonRpc. This library is used as the base for communication with language servers @@ -16,5 +16,18 @@ + + + + + + <_Parameter1>OmniSharp.Extensions.LanguageProtocol, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f + + + <_Parameter1>OmniSharp.Extensions.JsonRpc.Testing, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f + + + <_Parameter1>OmniSharp.Extensions.LanguageServer.Shared, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f + diff --git a/src/JsonRpc/JsonRpcCancelRequestExtensions.cs b/src/JsonRpc/JsonRpcCancelRequestExtensions.cs new file mode 100644 index 000000000..3d542a3df --- /dev/null +++ b/src/JsonRpc/JsonRpcCancelRequestExtensions.cs @@ -0,0 +1,24 @@ + + + + +namespace OmniSharp.Extensions.JsonRpc +{ + public static class JsonRpcCancelRequestExtensions + { + public static void CancelRequest(this IResponseRouter mediator, CancelParams @params) + { + mediator.SendNotification(@params); + } + + public static void CancelRequest(this IResponseRouter mediator, string id) + { + mediator.SendNotification(new CancelParams() { Id = id }); + } + + public static void CancelRequest(this IResponseRouter mediator, long id) + { + mediator.SendNotification(new CancelParams() { Id = id }); + } + } +} diff --git a/src/JsonRpc/JsonRpcCommonMethodsBase.cs b/src/JsonRpc/JsonRpcCommonMethodsBase.cs new file mode 100644 index 000000000..7ca6cba0e --- /dev/null +++ b/src/JsonRpc/JsonRpcCommonMethodsBase.cs @@ -0,0 +1,146 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json.Linq; + +namespace OmniSharp.Extensions.JsonRpc +{ + public abstract class JsonRpcCommonMethodsBase : IJsonRpcHandlerRegistry where T : IJsonRpcHandlerRegistry + { + #region OnRequest / OnNotification + + + public T OnJsonRequest(string method, Func> handler, JsonRpcHandlerOptions options = null) + { + return AddHandler(method, _ => new DelegatingJsonRequestHandler(handler), options); + } + + public T OnJsonRequest(string method, Func> handler, JsonRpcHandlerOptions options = null) + { + return OnJsonRequest(method, (request, ct) => handler(request), options); + } + + public T OnRequest(string method, Func> handler, JsonRpcHandlerOptions options = null) + { + return OnRequest(method, (value, cancellationToken) => handler(value), options); + } + + public T OnRequest(string method, Func> handler, JsonRpcHandlerOptions options = null) + { + return OnRequest(method, (value, cancellationToken) => handler(), options); + } + + public T OnRequest(string method, Func> handler, JsonRpcHandlerOptions options = null) + { + return AddHandler(method, _ => new DelegatingRequestHandler(_.GetRequiredService(), handler), options); + } + + public T OnRequest(string method, Func> handler, JsonRpcHandlerOptions options = null) + { + return OnRequest(method, (value, cancellationToken) => handler(cancellationToken), options); + } + + public T OnRequest(string method, Func handler, JsonRpcHandlerOptions options = null) + { + return OnRequest(method, (value, cancellationToken) => handler(value), options); + } + + public T OnRequest(string method, Func handler, JsonRpcHandlerOptions options = null) + { + return AddHandler(method, _ => new DelegatingRequestHandler(_.GetRequiredService(), handler), options); + } + + public T OnRequest(string method, Func handler, JsonRpcHandlerOptions options = null) + { + return OnRequest(method, (value, cancellationToken) => handler(cancellationToken), options); + } + + public T OnJsonNotification(string method, Action handler, JsonRpcHandlerOptions options = null) + { + return OnJsonNotification(method, (value, cancellationToken) => { + handler(value, cancellationToken); + return Task.CompletedTask; + }, options); + } + + public T OnJsonNotification(string method, Action handler, JsonRpcHandlerOptions options = null) + { + return OnJsonNotification(method, (value, cancellationToken) => { + handler(value); + return Task.CompletedTask; + }, options); + } + + public T OnJsonNotification(string method, Func handler, JsonRpcHandlerOptions options = null) + { + return AddHandler(method, _ => new DelegatingJsonNotificationHandler(handler), options); + } + + public T OnJsonNotification(string method, Func handler, JsonRpcHandlerOptions options = null) + { + return OnJsonNotification(method, (value, cancellationToken) => handler(value), options); + } + + public T OnNotification(string method, Action handler, JsonRpcHandlerOptions options = null) + { + return OnNotification(method, (value, cancellationToken) => { + handler(value, cancellationToken); + return Task.CompletedTask; + }, options); + } + + public T OnNotification(string method, Action handler, JsonRpcHandlerOptions options = null) + { + return OnNotification(method, (value, cancellationToken) => { + handler(value); + return Task.CompletedTask; + }, options); + } + + public T OnNotification(string method, Func handler, JsonRpcHandlerOptions options = null) + { + return AddHandler(method, _ => new DelegatingNotificationHandler(_.GetRequiredService(), handler), options); + } + + public T OnNotification(string method, Func handler, JsonRpcHandlerOptions options = null) + { + return OnNotification(method, (value, cancellationToken) => handler(value), options); + } + + public T OnNotification(string method, Action handler, JsonRpcHandlerOptions options = null) + { + return OnNotification(method, (value, cancellationToken) => { + handler(); + return Task.CompletedTask; + }, options); + } + + public T OnNotification(string method, Func handler, JsonRpcHandlerOptions options = null) + { + return AddHandler(method, _ => new DelegatingNotificationHandler(_.GetRequiredService(), (unit, token) => handler(token)), options); + } + + public T OnNotification(string method, Func handler, JsonRpcHandlerOptions options = null) + { + return OnNotification(method, (cancellationToken) => handler(), options); + } + + #endregion + + #region AddHandler + + public abstract T AddHandler(string method, IJsonRpcHandler handler, JsonRpcHandlerOptions options = null); + public abstract T AddHandler(string method, Func handlerFunc, JsonRpcHandlerOptions options = null); + public abstract T AddHandlers(params IJsonRpcHandler[] handlers); + public abstract T AddHandler(Func handlerFunc, JsonRpcHandlerOptions options = null) where THandler : IJsonRpcHandler; + public abstract T AddHandler(THandler handler, JsonRpcHandlerOptions options = null) where THandler : IJsonRpcHandler; + public abstract T AddHandler(JsonRpcHandlerOptions options = null) where THandler : IJsonRpcHandler; + public abstract T AddHandler(string method, JsonRpcHandlerOptions options = null) where THandler : IJsonRpcHandler; + public abstract T AddHandler(Type type, JsonRpcHandlerOptions options = null); + public abstract T AddHandler(string method, Type type, JsonRpcHandlerOptions options = null); + + #endregion + } +} diff --git a/src/JsonRpc/JsonRpcHandlerInstance.cs b/src/JsonRpc/JsonRpcHandlerInstance.cs new file mode 100644 index 000000000..123e0cd70 --- /dev/null +++ b/src/JsonRpc/JsonRpcHandlerInstance.cs @@ -0,0 +1,6 @@ +namespace OmniSharp.Extensions.JsonRpc +{ + public class JsonRpcHandlerInstance + { + } +} \ No newline at end of file diff --git a/src/JsonRpc/JsonRpcOptionsRegistryBase.cs b/src/JsonRpc/JsonRpcOptionsRegistryBase.cs new file mode 100644 index 000000000..f3cdf07a0 --- /dev/null +++ b/src/JsonRpc/JsonRpcOptionsRegistryBase.cs @@ -0,0 +1,122 @@ +using System; +using Microsoft.Extensions.DependencyInjection; + +namespace OmniSharp.Extensions.JsonRpc +{ + public abstract class JsonRpcOptionsRegistryBase : JsonRpcCommonMethodsBase where T : IJsonRpcHandlerRegistry + { + public IServiceCollection Services { get; set; } = new ServiceCollection(); + + #region AddHandler + + public sealed override T AddHandler(string method, IJsonRpcHandler handler, JsonRpcHandlerOptions options = null) + { + Services.AddSingleton(Named(method, handler, options)); + return (T) (object) this; + } + + public sealed override T AddHandler(string method, Func handlerFunc, JsonRpcHandlerOptions options = null) + { + Services.AddSingleton(Named(method, handlerFunc, options)); + return (T) (object) this; + } + + public sealed override T AddHandler(Func handlerFunc, JsonRpcHandlerOptions options = null) + { + Services.AddSingleton(Unnamed(_ => handlerFunc(_), options)); + return (T) (object) this; + } + + public sealed override T AddHandlers(params IJsonRpcHandler[] handlers) + { + foreach (var item in handlers) + { + Services.AddSingleton(item); + } + + return (T) (object) this; + } + + public sealed override T AddHandler(JsonRpcHandlerOptions options = null) + { + return AddHandler(typeof(THandler), options); + } + + public sealed override T AddHandler(THandler handler, JsonRpcHandlerOptions options = null) + { + Services.AddSingleton(Unnamed(handler, options)); + return (T) (object) this; + } + + public sealed override T AddHandler(string method, JsonRpcHandlerOptions options = null) + { + return AddHandler(method, typeof(THandler), options); + } + + public sealed override T AddHandler(Type type, JsonRpcHandlerOptions options = null) + { + Services.AddSingleton(Unnamed(type, options)); + return (T) (object) this; + } + + public sealed override T AddHandler(string method, Type type, JsonRpcHandlerOptions options = null) + { + Services.AddSingleton(Named(method, type, options)); + return (T) (object) this; + } + + private Func Unnamed(IJsonRpcHandler handler, JsonRpcHandlerOptions options) + { + return _ => { + _.GetRequiredService().Add( handler, options); + return handler; + }; + } + + private Func Unnamed(Func factory, JsonRpcHandlerOptions options) + { + return _ => { + var instance = factory(_); + _.GetRequiredService().Add( instance, options); + return instance; + }; + } + + private Func Unnamed(Type handlerType, JsonRpcHandlerOptions options) + { + return _ => { + var instance = ActivatorUtilities.CreateInstance(_, handlerType) as IJsonRpcHandler; + _.GetRequiredService().Add( instance, options); + return instance; + }; + } + + private Func Named(string method, IJsonRpcHandler handler, JsonRpcHandlerOptions options) + { + return _ => { + _.GetRequiredService().Add(method, handler, options); + return handler; + }; + } + + private Func Named(string method, Func factory, JsonRpcHandlerOptions options) + { + return _ => { + var instance = factory(_); + _.GetRequiredService().Add(method, instance, options); + return instance; + }; + } + + private Func Named(string method, Type handlerType, JsonRpcHandlerOptions options) + { + return _ => { + var instance = ActivatorUtilities.CreateInstance(_, handlerType) as IJsonRpcHandler; + _.GetRequiredService().Add(method, instance, options); + return instance; + }; + } + + #endregion + } +} diff --git a/src/JsonRpc/JsonRpcServer.cs b/src/JsonRpc/JsonRpcServer.cs index 4ba491cde..713278e88 100644 --- a/src/JsonRpc/JsonRpcServer.cs +++ b/src/JsonRpc/JsonRpcServer.cs @@ -1,220 +1,124 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Reflection; -using System.Threading; +using System.Reactive.Concurrency; using System.Threading.Tasks; -using MediatR; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Newtonsoft.Json.Linq; using System.Reactive.Disposables; +using System.Threading; +using Microsoft.Extensions.DependencyInjection.Extensions; namespace OmniSharp.Extensions.JsonRpc { - - public class JsonRpcServer : IJsonRpcServer + public class JsonRpcServer : JsonRpcServerBase, IJsonRpcServer, IDisposable { private readonly Connection _connection; - private readonly IRequestRouter _requestRouter; - private readonly IReceiver _receiver; - private readonly ISerializer _serializer; private readonly HandlerCollection _collection; - private readonly List<(string method, Func)> _namedHandlers = new List<(string method, Func)>(); - private readonly IResponseRouter _responseRouter; + private readonly CompositeDisposable _disposable; private readonly IServiceProvider _serviceProvider; - public static Task From(Action optionsAction) + public static Task From(Action optionsAction, CancellationToken cancellationToken) { var options = new JsonRpcServerOptions(); optionsAction(options); - return From(options); + return From(options, cancellationToken); + } + public static Task From(Action optionsAction) + { + var options = new JsonRpcServerOptions(); + optionsAction(options); + return From(options, CancellationToken.None); } - public static async Task From(JsonRpcServerOptions options) + public static async Task From(JsonRpcServerOptions options, CancellationToken cancellationToken) { - var server = new JsonRpcServer( - options.Input, - options.Output, - options.Receiver, - options.RequestProcessIdentifier, - options.LoggerFactory, - options.Serializer, - options.Services, - options.HandlerTypes.Select(x => x.Assembly) - .Distinct().Concat(options.HandlerAssemblies), - options.Handlers, - options.NamedHandlers, - options.NamedServiceHandlers, - options.Concurrency - ); + var server = new JsonRpcServer(options); - await server.Initialize(); + await server.Initialize(cancellationToken); return server; } - internal JsonRpcServer( - Stream input, - Stream output, - IReceiver receiver, - IRequestProcessIdentifier requestProcessIdentifier, - ILoggerFactory loggerFactory, - ISerializer serializer, - IServiceCollection services, - IEnumerable assemblies, - IEnumerable handlers, - IEnumerable<(string name, IJsonRpcHandler handler)> namedHandlers, - IEnumerable<(string name, Func handlerFunc)> namedServiceHandlers, - int? concurrency) + public static Task From(JsonRpcServerOptions options) { - var outputHandler = new OutputHandler(output, serializer, loggerFactory.CreateLogger()); + return From(options, CancellationToken.None); + } + internal JsonRpcServer(JsonRpcServerOptions options) : base(options) + { + var outputHandler = new OutputHandler( + options.Output, + options.Serializer, + options.LoggerFactory.CreateLogger() + ); + var services = options.Services; services.AddLogging(); - _receiver = receiver; - _serializer = serializer; - _collection = new HandlerCollection(); + var receiver = options.Receiver; + var serializer = options.Serializer; + _disposable = options.CompositeDisposable; services.AddSingleton(outputHandler); - services.AddSingleton(_collection); - services.AddSingleton(_serializer); - services.AddSingleton(_serializer); - services.AddSingleton(requestProcessIdentifier); - services.AddSingleton(_receiver); - services.AddSingleton(loggerFactory); + services.AddSingleton(serializer); + services.AddSingleton(serializer); + services.AddSingleton(options.RequestProcessIdentifier); + services.AddSingleton(receiver); + services.AddSingleton(options.LoggerFactory); - services.AddJsonRpcMediatR(assemblies); + services.AddJsonRpcMediatR(options.Assemblies); services.AddSingleton(this); services.AddSingleton, RequestRouter>(); services.AddSingleton(); + _collection = new HandlerCollection(); + services.AddSingleton(_collection); + services.AddSingleton(_ => _.GetRequiredService()); + + EnsureAllHandlersAreRegistered(); + + var serviceProvider = services.BuildServiceProvider(); + _disposable.Add(serviceProvider); + _serviceProvider = serviceProvider; + HandlersManager = _collection; - var foundHandlers = services - .Where(x => typeof(IJsonRpcHandler).IsAssignableFrom(x.ServiceType) && x.ServiceType != typeof(IJsonRpcHandler)) - .ToArray(); - - // Handlers are created at the start and maintained as a singleton - foreach (var handler in foundHandlers) - { - services.Remove(handler); - - if (handler.ImplementationFactory != null) - services.Add(ServiceDescriptor.Singleton(typeof(IJsonRpcHandler), handler.ImplementationFactory)); - else if (handler.ImplementationInstance != null) - services.Add(ServiceDescriptor.Singleton(typeof(IJsonRpcHandler), handler.ImplementationInstance)); - else - services.Add(ServiceDescriptor.Singleton(typeof(IJsonRpcHandler), handler.ImplementationType)); - } - - _serviceProvider = services.BuildServiceProvider(); - - var serviceHandlers = _serviceProvider.GetServices().ToArray(); - _collection.Add(serviceHandlers); - _collection.Add(handlers.ToArray()); - foreach (var (name, handler) in namedHandlers) - { - _collection.Add(name, handler); - } - foreach (var (name, handlerFunc) in namedServiceHandlers) - { - _collection.Add(name, handlerFunc(_serviceProvider)); - } - - _requestRouter = _serviceProvider.GetRequiredService>(); - _collection.Add(new CancelRequestHandler(_requestRouter)); - _responseRouter = _serviceProvider.GetRequiredService(); + var requestRouter = _serviceProvider.GetRequiredService>(); + var router = ResponseRouter = _serviceProvider.GetRequiredService(); _connection = new Connection( - input, + options.Input, outputHandler, - receiver, - requestProcessIdentifier, - _requestRouter, - _responseRouter, - loggerFactory, - serializer, - concurrency + options.Receiver, + options.RequestProcessIdentifier, + requestRouter, + router, + options.LoggerFactory, + options.OnUnhandledException ?? (e => { }), + options.CreateResponseException, + options.MaximumRequestTimeout, + options.SupportsContentModified, + options.Concurrency ); + _disposable.Add(_connection); + _collection.Add(_serviceProvider.GetRequiredService>().ToArray()); } - public IDisposable AddHandler(string method, IJsonRpcHandler handler) - { - return _collection.Add(method, handler); - } - - public IDisposable AddHandler(string method, Func handlerFunc) - { - _namedHandlers.Add((method, handlerFunc)); - return Disposable.Empty; - } - - public IDisposable AddHandlers(params IJsonRpcHandler[] handlers) - { - return _collection.Add(handlers); - } - - public IDisposable AddHandler(string method, Type handlerType) - { - return _collection.Add(method, ActivatorUtilities.CreateInstance(_serviceProvider, handlerType) as IJsonRpcHandler); - } - - public IDisposable AddHandler() - where T : IJsonRpcHandler - { - return AddHandlers(typeof(T)); - } - - public IDisposable AddHandlers(params Type[] handlerTypes) - { - return _collection.Add( - handlerTypes - .Select(handlerType => ActivatorUtilities.CreateInstance(_serviceProvider, handlerType) as IJsonRpcHandler) - .ToArray()); - } - - private async Task Initialize() + private async Task Initialize(CancellationToken cancellationToken) { await Task.Yield(); _connection.Open(); } - public void SendNotification(string method) - { - _responseRouter.SendNotification(method); - } - - public void SendNotification(string method, T @params) - { - _responseRouter.SendNotification(method, @params); - } - - public void SendNotification(IRequest @params) - { - _responseRouter.SendNotification(@params); - } - - public Task SendRequest(IRequest @params, CancellationToken cancellationToken) - { - return _responseRouter.SendRequest(@params, cancellationToken); - } - - public IResponseRouterReturns SendRequest(string method, T @params) - { - return _responseRouter.SendRequest(method, @params); - } - - public IResponseRouterReturns SendRequest(string method) + public void Dispose() { - return _responseRouter.SendRequest(method); + _disposable.Dispose(); } - public TaskCompletionSource GetRequest(long id) + public IDisposable Register(Action registryAction) { - return _responseRouter.GetRequest(id); + var manager = new CompositeHandlersManager(_collection); + registryAction(new JsonRpcServerRegistry(_serviceProvider, manager)); + return manager.GetDisposable(); } - public void Dispose() - { - _connection?.Dispose(); - } + protected override IResponseRouter ResponseRouter { get; } + protected override IHandlersManager HandlersManager { get; } } } diff --git a/src/JsonRpc/JsonRpcServerBase.cs b/src/JsonRpc/JsonRpcServerBase.cs new file mode 100644 index 000000000..6a0ed813b --- /dev/null +++ b/src/JsonRpc/JsonRpcServerBase.cs @@ -0,0 +1,79 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json.Linq; + +namespace OmniSharp.Extensions.JsonRpc +{ + public abstract class JsonRpcServerBase: IResponseRouter + { + private readonly IJsonRpcServerOptions _options; + + public JsonRpcServerBase(IJsonRpcServerOptions options) + { + _options = options; + } + protected abstract IResponseRouter ResponseRouter { get; } + protected abstract IHandlersManager HandlersManager { get; } + + public void SendNotification(string method) + { + ResponseRouter.SendNotification(method); + } + + public void SendNotification(string method, T @params) + { + ResponseRouter.SendNotification(method, @params); + } + + public void SendNotification(IRequest @params) + { + ResponseRouter.SendNotification(@params); + } + + public Task SendRequest(IRequest @params, CancellationToken cancellationToken) + { + return ResponseRouter.SendRequest(@params, cancellationToken); + } + + public IResponseRouterReturns SendRequest(string method, T @params) + { + return ResponseRouter.SendRequest(method, @params); + } + + public IResponseRouterReturns SendRequest(string method) + { + return ResponseRouter.SendRequest(method); + } + + (string method, TaskCompletionSource pendingTask) IResponseRouter.GetRequest(long id) + { + return ResponseRouter.GetRequest(id); + } + + protected void EnsureAllHandlersAreRegistered() + { + var foundHandlers = _options.Services + .Where(x => typeof(IJsonRpcHandler).IsAssignableFrom(x.ServiceType) && + x.ServiceType != typeof(IJsonRpcHandler)) + .ToArray(); + + // Handlers are created at the start and maintained as a singleton + foreach (var handler in foundHandlers) + { + _options.Services.Remove(handler); + + if (handler.ImplementationFactory != null) + _options.Services.Add(ServiceDescriptor.Singleton(typeof(IJsonRpcHandler), handler.ImplementationFactory)); + else if (handler.ImplementationInstance != null) + _options.Services.Add(ServiceDescriptor.Singleton(typeof(IJsonRpcHandler), handler.ImplementationInstance)); + else + _options.Services.Add(ServiceDescriptor.Singleton(typeof(IJsonRpcHandler), handler.ImplementationType)); + } + + _options.Services.AddJsonRpcMediatR(_options.Assemblies.Distinct()); + } + } +} diff --git a/src/JsonRpc/JsonRpcServerExtensions.cs b/src/JsonRpc/JsonRpcServerExtensions.cs deleted file mode 100644 index 7668a56b3..000000000 --- a/src/JsonRpc/JsonRpcServerExtensions.cs +++ /dev/null @@ -1,144 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using MediatR; -using Microsoft.Extensions.DependencyInjection; - -namespace OmniSharp.Extensions.JsonRpc -{ - public static class JsonRpcServerExtensions - { - public static IDisposable OnRequest( - this IJsonRpcHandlerRegistry registry, - string method, - Func> handler) - { - return registry.OnRequest(method, (value, cancellationToken) => handler(value)); - } - - public static IDisposable OnRequest( - this IJsonRpcHandlerRegistry registry, - string method, - Func> handler) - { - return registry.OnRequest(method, (value, cancellationToken) => handler()); - } - - public static IDisposable OnRequest( - this IJsonRpcHandlerRegistry registry, - string method, - Func> handler) - { - return registry.AddHandler(method, _ => new DelegatingRequestHandler(_.GetRequiredService(), handler)); - } - - public static IDisposable OnRequest( - this IJsonRpcHandlerRegistry registry, - string method, - Func> handler) - { - return registry.OnRequest(method, (value, cancellationToken) => handler(cancellationToken)); - } - - public static IDisposable OnRequest( - this IJsonRpcHandlerRegistry registry, - string method, - Func handler) - { - return registry.OnRequest(method, (value, cancellationToken) => handler(value)); - } - - public static IDisposable OnRequest( - this IJsonRpcHandlerRegistry registry, - string method, - Func handler) - { - return registry.AddHandler(method, _ => new DelegatingRequestHandler(_.GetRequiredService(), handler)); - } - - public static IDisposable OnRequest( - this IJsonRpcHandlerRegistry registry, - string method, - Func handler) - { - return registry.OnRequest(method, (value, cancellationToken) => handler(cancellationToken)); - } - - public static IDisposable OnNotification( - this IJsonRpcHandlerRegistry registry, - string method, - Action handler) - { - return registry.OnNotification(method, (value, cancellationToken) => { - handler(value, cancellationToken); - return Task.CompletedTask; - }); - } - - public static IDisposable OnNotification( - this IJsonRpcHandlerRegistry registry, - string method, - Action handler) - { - return registry.OnNotification(method, (value, cancellationToken) => { - handler(); - return Task.CompletedTask; - }); - } - - public static IDisposable OnNotification( - this IJsonRpcHandlerRegistry registry, - string method, - Action handler) - { - return registry.OnNotification(method, (value, cancellationToken) => { - handler(value); - return Task.CompletedTask; - }); - } - - public static IDisposable OnNotification( - this IJsonRpcHandlerRegistry registry, - string method, - Func handler) - { - return registry.AddHandler(method, _ => new DelegatingNotificationHandler(_.GetRequiredService(), handler)); - } - - public static IDisposable OnNotification( - this IJsonRpcHandlerRegistry registry, - string method, - Func handler) - { - return registry.OnNotification(method, (value, cancellationToken) => handler(value)); - } - - public static IDisposable OnNotification( - this IJsonRpcHandlerRegistry registry, - string method, - Func handler) - { - return registry.AddHandler(method, _ => new DelegatingNotificationHandler(_.GetRequiredService(), (unit, token) => handler(token))); - } - - public static IDisposable OnNotification( - this IJsonRpcHandlerRegistry registry, - string method, - Func handler) - { - return registry.OnNotification(method, (cancellationToken) => handler()); - } - - /// - /// Set maximum number of allowed parallel actions - /// - /// - /// - /// - public static JsonRpcServerOptions WithConcurrency(this JsonRpcServerOptions options, int? concurrency) - { - options.Concurrency = concurrency; - return options; - } - } -} diff --git a/src/JsonRpc/JsonRpcServerOptions.cs b/src/JsonRpc/JsonRpcServerOptions.cs index 1de53b60c..dee845674 100644 --- a/src/JsonRpc/JsonRpcServerOptions.cs +++ b/src/JsonRpc/JsonRpcServerOptions.cs @@ -1,56 +1,26 @@ -using System; using System.Collections.Generic; -using System.IO; +using System.Diagnostics; +using System.IO.Pipelines; using System.Reactive.Disposables; using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using OmniSharp.Extensions.JsonRpc.Serialization; +using OmniSharp.Extensions.JsonRpc.Server; namespace OmniSharp.Extensions.JsonRpc { - public class JsonRpcServerOptions : IJsonRpcHandlerRegistry + public class JsonRpcServerOptions : JsonRpcServerOptionsBase { - public JsonRpcServerOptions() - { - } - - public Stream Input { get; set; } - public Stream Output { get; set; } - public ILoggerFactory LoggerFactory { get; set; } = new LoggerFactory(); public ISerializer Serializer { get; set; } = new JsonRpcSerializer(); - public IRequestProcessIdentifier RequestProcessIdentifier { get; set; } = new ParallelRequestProcessIdentifier(); public IReceiver Receiver { get; set; } = new Receiver(); - public IServiceCollection Services { get; set; } = new ServiceCollection(); - internal List Handlers { get; set; } = new List(); - internal List<(string name, IJsonRpcHandler handler)> NamedHandlers { get; set; } = new List<(string name, IJsonRpcHandler handler)>(); - internal List<(string name, Func handlerFunc)> NamedServiceHandlers { get; set; } = new List<(string name, Func handlerFunc)>(); - internal List HandlerTypes { get; set; } = new List(); - internal List HandlerAssemblies { get; set; } = new List(); - internal int? Concurrency { get; set; } - - public IDisposable AddHandler(string method, IJsonRpcHandler handler) - { - NamedHandlers.Add((method, handler)); - return Disposable.Empty; - } - public IDisposable AddHandler(string method, Func handlerFunc) + public JsonRpcServerOptions WithReceiver(IReceiver receiver) { - NamedServiceHandlers.Add((method, handlerFunc)); - return Disposable.Empty; + Receiver = receiver; + return this; } - public IDisposable AddHandlers(params IJsonRpcHandler[] handlers) - { - Handlers.AddRange(handlers); - return Disposable.Empty; - } - - public IDisposable AddHandler() where T : IJsonRpcHandler - { - HandlerTypes.Add(typeof(T)); - return Disposable.Empty; - } + public override IRequestProcessIdentifier RequestProcessIdentifier { get; set; } = new ParallelRequestProcessIdentifier(); } } diff --git a/src/JsonRpc/JsonRpcServerOptionsBase.cs b/src/JsonRpc/JsonRpcServerOptionsBase.cs new file mode 100644 index 000000000..5e84209b3 --- /dev/null +++ b/src/JsonRpc/JsonRpcServerOptionsBase.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Pipelines; +using System.Linq; +using System.Reactive.Disposables; +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Nerdbank.Streams; +using OmniSharp.Extensions.JsonRpc.Server; + +namespace OmniSharp.Extensions.JsonRpc +{ + public abstract class JsonRpcServerOptionsBase : JsonRpcOptionsRegistryBase, IJsonRpcServerOptions where T : IJsonRpcHandlerRegistry + { + public PipeReader Input { get; set; } + public PipeWriter Output { get; set; } + public ILoggerFactory LoggerFactory { get; set; } = new LoggerFactory(); + public IEnumerable Assemblies { get; set; } = Enumerable.Empty(); + public abstract IRequestProcessIdentifier RequestProcessIdentifier { get; set; } + public int? Concurrency { get; set; } + public Func CreateResponseException { get; set; } + public Action OnUnhandledException { get; set; } + public bool SupportsContentModified { get; set; } = true; + public TimeSpan MaximumRequestTimeout { get; set; } = TimeSpan.FromMinutes(5); + internal CompositeDisposable CompositeDisposable { get; } = new CompositeDisposable(); + IDisposable IJsonRpcServerOptions.RegisteredDisposables => CompositeDisposable; + + public void RegisterForDisposal(IDisposable disposable) + { + CompositeDisposable.Add(disposable); + } + + public T WithAssemblies(IEnumerable assemblies) + { + Assemblies = Assemblies.Concat(assemblies); + return (T)(object) this; + } + + public T WithAssemblies(params Assembly[] assemblies) + { + Assemblies = Assemblies.Concat(assemblies); + return (T)(object) this; + } + + public T WithInput(Stream input) + { + Input = input.UsePipeReader(); + RegisterForDisposal(input); + return (T)(object) this; + } + public T WithInput(PipeReader input) + { + Input = input; + return (T)(object) this; + } + + public T WithOutput(Stream output) + { + Output = output.UsePipeWriter(); + RegisterForDisposal(output); + return (T)(object) this; + } + + public T WithOutput(PipeWriter output) + { + Output = output; + return (T)(object) this; + } + + public T WithPipe(Pipe pipe) + { + Input = pipe.Reader; + Output = pipe.Writer; + return (T)(object) this; + } + + public T WithLoggerFactory(ILoggerFactory loggerFactory) + { + LoggerFactory = loggerFactory; + return (T)(object) this; + } + + public T WithRequestProcessIdentifier(IRequestProcessIdentifier requestProcessIdentifier) + { + RequestProcessIdentifier = requestProcessIdentifier; + return (T)(object) this; + } + + public T WithHandler(JsonRpcHandlerOptions options = null) + where THandler : class, IJsonRpcHandler + { + return AddHandler(options); + } + + public T WithHandler(THandler handler, JsonRpcHandlerOptions options = null) + where THandler : class, IJsonRpcHandler + { + return AddHandler(handler, options); + } + + public T WithHandlersFrom(Type type, JsonRpcHandlerOptions options = null) + { + return AddHandler(type, options); + } + + public T WithHandlersFrom(TypeInfo typeInfo, JsonRpcHandlerOptions options = null) + { + return AddHandler(typeInfo.AsType(), options); + } + + public T WithServices(Action servicesAction) + { + servicesAction(Services); + return (T)(object) this; + } + + public T WithResponseExceptionFactory(Func handler) + { + CreateResponseException = handler; + return (T)(object) this; + } + + public T WithUnhandledExceptionHandler(Action handler) + { + OnUnhandledException = handler; + return (T)(object) this; + } + + public T WithContentModifiedSupport(bool supportsContentModified) + { + SupportsContentModified = supportsContentModified; + return (T)(object) this; + } + + public T WithMaximumRequestTimeout(TimeSpan maximumRequestTimeout) + { + MaximumRequestTimeout = maximumRequestTimeout; + return (T)(object) this; + } + } +} diff --git a/src/JsonRpc/JsonRpcServerOptionsExtensions.cs b/src/JsonRpc/JsonRpcServerOptionsExtensions.cs index d97c42734..1ee614650 100644 --- a/src/JsonRpc/JsonRpcServerOptionsExtensions.cs +++ b/src/JsonRpc/JsonRpcServerOptionsExtensions.cs @@ -1,85 +1,20 @@ using System; using System.IO; +using System.IO.Pipelines; using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Nerdbank.Streams; +using OmniSharp.Extensions.JsonRpc.Server; namespace OmniSharp.Extensions.JsonRpc { public static class JsonRpcServerOptionsExtensions { - public static JsonRpcServerOptions WithInput(this JsonRpcServerOptions options, Stream input) - { - options.Input = input; - return options; - } - - public static JsonRpcServerOptions WithOutput(this JsonRpcServerOptions options, Stream output) - { - options.Output = output; - return options; - } - - public static JsonRpcServerOptions WithLoggerFactory(this JsonRpcServerOptions options, ILoggerFactory loggerFactory) - { - options.LoggerFactory = loggerFactory; - return options; - } - - public static JsonRpcServerOptions WithRequestProcessIdentifier(this JsonRpcServerOptions options, IRequestProcessIdentifier requestProcessIdentifier) - { - options.RequestProcessIdentifier = requestProcessIdentifier; - return options; - } - public static JsonRpcServerOptions WithSerializer(this JsonRpcServerOptions options, ISerializer serializer) { options.Serializer = serializer; return options; } - - public static JsonRpcServerOptions WithReciever(this JsonRpcServerOptions options, IReceiver receiver) - { - options.Receiver = receiver; - return options; - } - - public static JsonRpcServerOptions WithHandler(this JsonRpcServerOptions options) - where T : class, IJsonRpcHandler - { - options.Services.AddSingleton(); - return options; - } - - public static JsonRpcServerOptions WithHandler(this JsonRpcServerOptions options, T handler) - where T : IJsonRpcHandler - { - options.Services.AddSingleton(handler); - return options; - } - - public static JsonRpcServerOptions WithHandlersFrom(this JsonRpcServerOptions options, Type type) - { - options.HandlerTypes.Add(type); - return options; - } - - public static JsonRpcServerOptions WithHandlersFrom(this JsonRpcServerOptions options, TypeInfo typeInfo) - { - options.HandlerTypes.Add(typeInfo.AsType()); - return options; - } - - public static JsonRpcServerOptions WithHandlersFrom(this JsonRpcServerOptions options, Assembly assembly) - { - options.HandlerAssemblies.Add(assembly); - return options; - } - - public static JsonRpcServerOptions WithServices(this JsonRpcServerOptions options, Action servicesAction) - { - servicesAction(options.Services); - return options; - } } } diff --git a/src/JsonRpc/JsonRpcServerRegistry.cs b/src/JsonRpc/JsonRpcServerRegistry.cs new file mode 100644 index 000000000..d1d0fdf7e --- /dev/null +++ b/src/JsonRpc/JsonRpcServerRegistry.cs @@ -0,0 +1,11 @@ +using System; + +namespace OmniSharp.Extensions.JsonRpc +{ + class JsonRpcServerRegistry : InterimJsonRpcServerRegistry, IJsonRpcServerRegistry + { + public JsonRpcServerRegistry(IServiceProvider serviceProvider, CompositeHandlersManager handlersManager) : base(serviceProvider, handlersManager) + { + } + } +} \ No newline at end of file diff --git a/src/JsonRpc/NoopResponseRouter.cs b/src/JsonRpc/NoopResponseRouter.cs new file mode 100644 index 000000000..2f51ab80e --- /dev/null +++ b/src/JsonRpc/NoopResponseRouter.cs @@ -0,0 +1,58 @@ +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Newtonsoft.Json.Linq; + +namespace OmniSharp.Extensions.JsonRpc +{ + /// + /// Used top ensure noop behaviors don't throw if they consume a router + /// + class NoopResponseRouter : IResponseRouter + { + private NoopResponseRouter() {} + public static NoopResponseRouter Instance = new NoopResponseRouter(); + + public void SendNotification(string method) + { + + } + + public void SendNotification(string method, T @params) + { + + } + + public void SendNotification(IRequest request) + { + + } + + public IResponseRouterReturns SendRequest(string method, T @params) => new Impl(); + + public IResponseRouterReturns SendRequest(string method) => new Impl(); + + public Task SendRequest(IRequest request, CancellationToken cancellationToken) + { + return Task.FromResult(default); + } + + (string method, TaskCompletionSource pendingTask) IResponseRouter.GetRequest(long id) + { + return ("UNKNOWN", new TaskCompletionSource()); + } + + class Impl : IResponseRouterReturns + { + public Task Returning(CancellationToken cancellationToken) + { + return Task.FromResult(default); + } + + public Task ReturningVoid(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + } + } +} diff --git a/src/JsonRpc/NotificationHandler.cs b/src/JsonRpc/NotificationHandler.cs index e36075644..31d1262a0 100644 --- a/src/JsonRpc/NotificationHandler.cs +++ b/src/JsonRpc/NotificationHandler.cs @@ -1,18 +1,16 @@ -using System; +using System; using System.Threading; using System.Threading.Tasks; using MediatR; - - namespace OmniSharp.Extensions.JsonRpc { - public static class NotificationHandler { - + public static class NotificationHandler + { public static DelegatingHandlers.Notification For(Action handler) where TParams : IRequest { - return new DelegatingHandlers.Notification( handler); + return new DelegatingHandlers.Notification(handler); } public static DelegatingHandlers.Notification For(Func handler) @@ -20,6 +18,7 @@ public static DelegatingHandlers.Notification For(Func(handler); } + public static DelegatingHandlers.Notification For(Action handler) where TParams : IRequest { diff --git a/src/JsonRpc/OutputHandler.cs b/src/JsonRpc/OutputHandler.cs index d2636c4e0..88cd696e2 100644 --- a/src/JsonRpc/OutputHandler.cs +++ b/src/JsonRpc/OutputHandler.cs @@ -1,89 +1,121 @@ using System; -using System.Collections.Concurrent; -using System.IO; +using System.ComponentModel; +using System.IO.Pipelines; +using System.Reactive.Concurrency; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using System.Reactive.Subjects; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using Nerdbank.Streams; namespace OmniSharp.Extensions.JsonRpc { public class OutputHandler : IOutputHandler { - private readonly Stream _output; + private readonly PipeWriter _pipeWriter; private readonly ISerializer _serializer; - private readonly ILogger _outputHandler; - private readonly Thread _thread; - private readonly BlockingCollection _queue; - private readonly CancellationTokenSource _cancel; + private readonly ILogger _logger; + private readonly Subject _queue; private readonly TaskCompletionSource _outputIsFinished; + private readonly CompositeDisposable _disposable; - public OutputHandler(Stream output, ISerializer serializer, ILogger outputHandler) + public OutputHandler( + PipeWriter pipeWriter, + ISerializer serializer, + Func objectFilter, + IScheduler scheduler, + ILogger logger) { - if (!output.CanWrite) - throw new ArgumentException($"must provide a writable stream for {nameof(output)}", nameof(output)); - _output = output; + _pipeWriter = pipeWriter; _serializer = serializer; - _outputHandler = outputHandler; - _queue = new BlockingCollection(); - _cancel = new CancellationTokenSource(); + _logger = logger; + _queue = new Subject(); _outputIsFinished = new TaskCompletionSource(); - _thread = new Thread(ProcessOutputQueue) {IsBackground = true, Name = "ProcessOutputQueue"}; + + _disposable = new CompositeDisposable { + _queue + .ObserveOn(scheduler) + .Where(objectFilter) + .Select(value => Observable.FromAsync(ct => ProcessOutputStream(value, ct))) + .Concat() + .Subscribe(), + _queue + }; } - public void Start() + public OutputHandler( + PipeWriter pipeWriter, + ISerializer serializer, + Func objectFilter, + ILogger logger) : this( + pipeWriter, + serializer, + objectFilter, + new EventLoopScheduler(_ => new Thread(_) {IsBackground = true, Name = "OutputHandler"}), + // TaskPoolScheduler.Default, + logger) { - _thread.Start(); } - public void Send(object value, CancellationToken cancellationToken) + public OutputHandler( + PipeWriter pipeWriter, + ISerializer serializer, + ILogger logger) : this( + pipeWriter, + serializer, + _ => true, + new EventLoopScheduler(_ => new Thread(_) {IsBackground = true, Name = "OutputHandler"}), + // TaskPoolScheduler.Default, + logger) { - if (!cancellationToken.IsCancellationRequested) - _queue.Add(value); } - private void ProcessOutputQueue() + public void Send(object value) { - var token = _cancel.Token; - try - { - while (!token.IsCancellationRequested) - { - if (_queue.TryTake(out var value, Timeout.Infinite, token)) - { - var content = _serializer.SerializeObject(value); - var contentBytes = System.Text.Encoding.UTF8.GetBytes(content); + if (_queue.IsDisposed) return; + _queue.OnNext(value); + } - // TODO: Is this lsp specific?? - var sb = new StringBuilder(); - sb.Append($"Content-Length: {contentBytes.Length}\r\n"); - sb.Append($"\r\n"); - var headerBytes = System.Text.Encoding.UTF8.GetBytes(sb.ToString()); + public async Task StopAsync() + { + await _pipeWriter.CompleteAsync(); + _disposable.Dispose(); + } - // only one write to _output - using (var ms = new MemoryStream(headerBytes.Length + contentBytes.Length)) - { - ms.Write(headerBytes, 0, headerBytes.Length); - ms.Write(contentBytes, 0, contentBytes.Length); - if (!token.IsCancellationRequested) - { - _output.Write(ms.ToArray(), 0, (int) ms.Position); - } - } + /// + /// For unit test use only + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + internal async Task WriteAndFlush() + { + await _pipeWriter.FlushAsync(); + await _pipeWriter.CompleteAsync(); + } - _output.Flush(); - } - } + private async Task ProcessOutputStream(object value, CancellationToken cancellationToken) + { + try + { + // TODO: this will be part of the serialization refactor to make streaming first class + var content = _serializer.SerializeObject(value); + var contentBytes = Encoding.UTF8.GetBytes(content).AsMemory(); + await _pipeWriter.WriteAsync(Encoding.UTF8.GetBytes($"Content-Length: {contentBytes.Length}\r\n\r\n"), cancellationToken); + await _pipeWriter.WriteAsync(contentBytes, cancellationToken); + await _pipeWriter.FlushAsync(cancellationToken); } - catch (OperationCanceledException ex) when (ex.CancellationToken != token) + catch (OperationCanceledException ex) when (ex.CancellationToken != cancellationToken) { - _outputHandler.LogTrace(ex, "Cancellation happened"); - _outputIsFinished.TrySetException(ex); + _logger.LogTrace(ex, "Cancellation happened"); + Error(ex); } catch (Exception e) { - _outputHandler.LogTrace(e, "Could not write to output handler, perhaps serialization failed?"); - _outputIsFinished.TrySetException(e); + _logger.LogTrace(e, "Could not write to output handler, perhaps serialization failed?"); + Error(e); } } @@ -92,13 +124,16 @@ public Task WaitForShutdown() return _outputIsFinished.Task; } + private void Error(Exception ex) + { + _outputIsFinished.TrySetResult(ex); + _disposable.Dispose(); + } + public void Dispose() { _outputIsFinished.TrySetResult(null); - _cancel.Cancel(); - _thread.Join(); - _cancel.Dispose(); - _output.Dispose(); + _disposable.Dispose(); } } } diff --git a/src/JsonRpc/ParallelRequestProcessIdentifier.cs b/src/JsonRpc/ParallelRequestProcessIdentifier.cs index e12c2e49e..b44045129 100644 --- a/src/JsonRpc/ParallelRequestProcessIdentifier.cs +++ b/src/JsonRpc/ParallelRequestProcessIdentifier.cs @@ -4,7 +4,7 @@ public class ParallelRequestProcessIdentifier : IRequestProcessIdentifier { public RequestProcessType Identify(IHandlerDescriptor descriptor) { - return RequestProcessType.Parallel; + return descriptor.RequestProcessType ?? RequestProcessType.Parallel; } } } diff --git a/src/JsonRpc/ProcessScheduler.cs b/src/JsonRpc/ProcessScheduler.cs index 6cee7554e..701c01468 100644 --- a/src/JsonRpc/ProcessScheduler.cs +++ b/src/JsonRpc/ProcessScheduler.cs @@ -4,69 +4,76 @@ using System.Reactive.Disposables; using System.Reactive.Linq; using System.Reactive.Subjects; -using System.Threading; using Microsoft.Extensions.Logging; +using OmniSharp.Extensions.JsonRpc.Server; namespace OmniSharp.Extensions.JsonRpc { - public class ProcessScheduler : IScheduler + class ProcessScheduler : IDisposable { - private readonly int? _concurrency; - private readonly ILogger _logger; - private readonly IObserver<(RequestProcessType type, string name, IObservable request)> _enqueue; - private readonly IObservable<(RequestProcessType type, string name, IObservable request)> _queue; - private bool _disposed = false; + private readonly IObserver<(RequestProcessType type, string name, SchedulerDelegate request)> _enqueue; private readonly CompositeDisposable _disposable = new CompositeDisposable(); - private readonly System.Reactive.Concurrency.IScheduler _scheduler; - public ProcessScheduler(ILoggerFactory loggerFactory, int? concurrency) : this(loggerFactory, concurrency, - new EventLoopScheduler( - _ => new Thread(_) {IsBackground = true, Name = "ProcessRequestQueue"})) + public ProcessScheduler( + ILoggerFactory loggerFactory, + bool supportContentModified, + int? concurrency, + TimeSpan requestTimeout, + IScheduler scheduler) { - } - - internal ProcessScheduler(ILoggerFactory loggerFactory, int? concurrency, - System.Reactive.Concurrency.IScheduler scheduler) - { - _concurrency = concurrency; - _logger = loggerFactory.CreateLogger(); + var concurrency1 = concurrency; + var logger = loggerFactory.CreateLogger(); - var subject = new Subject<(RequestProcessType type, string name, IObservable request)>(); + var subject = new Subject<(RequestProcessType type, string name, SchedulerDelegate request)>(); _disposable.Add(subject); _enqueue = subject; - _scheduler = scheduler; - _queue = subject; - } - public void Start() - { var obs = Observable.Create(observer => { var cd = new CompositeDisposable(); var observableQueue = - new BehaviorSubject<(RequestProcessType type, ReplaySubject> observer)>(( - RequestProcessType.Serial, new ReplaySubject>(int.MaxValue))); + new BehaviorSubject<(RequestProcessType type, ReplaySubject> observer, Subject contentModifiedSource)>(( + RequestProcessType.Serial, new ReplaySubject>(int.MaxValue), supportContentModified ? new Subject() : null)); - cd.Add(_queue.Subscribe(item => { + cd.Add(subject.Subscribe(item => { if (observableQueue.Value.type != item.type) { + logger.LogDebug("Swapping from {From} to {To}", observableQueue.Value.type, item.type); + if (supportContentModified && observableQueue.Value.type == RequestProcessType.Parallel) + { + logger.LogDebug("Cancelling any outstanding requests (switch from parallel to serial)"); + observableQueue.Value.contentModifiedSource.OnNext(Unit.Default); + observableQueue.Value.contentModifiedSource.OnCompleted(); + } + logger.LogDebug("Completing existing request process type {Type}", observableQueue.Value.type); observableQueue.Value.observer.OnCompleted(); - observableQueue.OnNext((item.type, new ReplaySubject>(int.MaxValue))); + observableQueue.OnNext((item.type, new ReplaySubject>(int.MaxValue), supportContentModified ? new Subject() : null)); } - observableQueue.Value.observer.OnNext(HandleRequest(item.name, item.request)); + logger.LogDebug("Queueing {Type}:{Name} request for processing", item.type, item.name); + observableQueue.Value.observer.OnNext(HandleRequest(item.name, item.request(observableQueue.Value.contentModifiedSource ?? Observable.Never(), scheduler))); })); cd.Add(observableQueue .Select(item => { - var (type, replay) = item; + var (type, replay, _) = item; if (type == RequestProcessType.Serial) + { + // logger.LogDebug("Changing to serial processing"); return replay.Concat(); - - return _concurrency.HasValue - ? replay.Merge(_concurrency.Value) - : replay.Merge(); + } + + if (concurrency1.HasValue) + { + // logger.LogDebug("Changing to parallel processing with concurrency of {Concurrency}", concurrency1.Value); + return replay.Merge(concurrency1.Value); + } + else + { + // logger.LogDebug("Changing to parallel processing with concurrency of {Concurrency}", "Unlimited"); + return replay.Merge(); + } }) .Concat() .Subscribe(observer) @@ -76,31 +83,47 @@ public void Start() }); _disposable.Add(obs - // .ObserveOn(_scheduler) + .ObserveOn(scheduler) .Subscribe(_ => { }) ); IObservable HandleRequest(string name, IObservable request) { return request - .Catch(ex => Observable.Empty()) + .Catch(ex => { + logger.LogDebug(ex, "Request {Name} was explicitly cancelled", name); + return Observable.Empty(); + }) + .Catch(ex => { + logger.LogDebug(ex, "Request {Name} was cancelled, due to content being modified", name); + return Observable.Empty(); + }) + .Catch(ex => { + logger.LogDebug(ex, "Request {Name} was cancelled, due to timeout", name); + return Observable.Empty(); + }) .Catch(ex => { - _logger.LogCritical(Events.UnhandledException, ex, "Unhandled exception executing {Name}", - name); + logger.LogCritical(Events.UnhandledException, ex, "Unhandled exception executing {Name}", name); return Observable.Empty(); - }); + }) + // .Do(v => { + // logger.LogDebug("Request {Name} was processed", name); + // }, (ex) => { + // logger.LogCritical(Events.UnhandledException, ex, "Request {Name} encountered and unhandled exception", name); + // }, () => { + // logger.LogDebug("Request {Name} was completed", name); + // }) + ; } } - public void Add(RequestProcessType type, string name, IObservable request) + public void Add(RequestProcessType type, string name, SchedulerDelegate request) { _enqueue.OnNext((type, name, request)); } public void Dispose() { - if (_disposed) return; - _disposed = true; _disposable.Dispose(); } } diff --git a/src/JsonRpc/Reciever.cs b/src/JsonRpc/Receiver.cs similarity index 95% rename from src/JsonRpc/Reciever.cs rename to src/JsonRpc/Receiver.cs index 5cc7bc6d1..f1bae0089 100644 --- a/src/JsonRpc/Reciever.cs +++ b/src/JsonRpc/Receiver.cs @@ -66,10 +66,10 @@ protected virtual Renor GetRenor(JToken @object) return new ServerResponse(requestId, response); } - if (hasRequestId && request.TryGetValue("error", out var errorResponse)) + if (request.TryGetValue("error", out var errorResponse)) { // TODO: this doesn't seem right. - return new ServerError(requestId, errorResponse); + return new ServerError(requestId, errorResponse.ToObject()); } var method = request["method"]?.Value(); diff --git a/src/JsonRpc/RequestCancelled.cs b/src/JsonRpc/RequestCancelled.cs index 11388d2df..fa6d482f0 100644 --- a/src/JsonRpc/RequestCancelled.cs +++ b/src/JsonRpc/RequestCancelled.cs @@ -1,9 +1,11 @@ -using OmniSharp.Extensions.JsonRpc.Server.Messages; +using OmniSharp.Extensions.JsonRpc.Server; +using OmniSharp.Extensions.JsonRpc.Server.Messages; namespace OmniSharp.Extensions.JsonRpc { public class RequestCancelled : RpcError { - internal RequestCancelled() : base(null, new ErrorMessage(-32800, "Request Cancelled")) { } + internal RequestCancelled() : base(null, new ErrorMessage(ErrorCodes.RequestCancelled, "Request Cancelled")) { } + internal RequestCancelled(object id) : base(id, new ErrorMessage(ErrorCodes.RequestCancelled, "Request Cancelled")) { } } } diff --git a/src/JsonRpc/RequestContext.cs b/src/JsonRpc/RequestContext.cs index f4af90fe5..727a82d61 100644 --- a/src/JsonRpc/RequestContext.cs +++ b/src/JsonRpc/RequestContext.cs @@ -4,4 +4,4 @@ class RequestContext : IRequestContext { public IHandlerDescriptor Descriptor { get; set; } } -} \ No newline at end of file +} diff --git a/src/JsonRpc/RequestHandler.cs b/src/JsonRpc/RequestHandler.cs index 19ae28d82..4e404bdd5 100644 --- a/src/JsonRpc/RequestHandler.cs +++ b/src/JsonRpc/RequestHandler.cs @@ -1,10 +1,8 @@ -using System; +using System; using System.Threading; using System.Threading.Tasks; using MediatR; - - namespace OmniSharp.Extensions.JsonRpc { public static class RequestHandler { @@ -35,4 +33,4 @@ public static DelegatingHandlers.Request For(Func(handler); } } -} +} \ No newline at end of file diff --git a/src/JsonRpc/RequestRouterBase.cs b/src/JsonRpc/RequestRouterBase.cs index 1368588fc..db0d6ca75 100644 --- a/src/JsonRpc/RequestRouterBase.cs +++ b/src/JsonRpc/RequestRouterBase.cs @@ -19,8 +19,6 @@ public abstract class RequestRouterBase : IRequestRouter _requests = new ConcurrentDictionary(); - public RequestRouterBase(ISerializer serializer, IServiceProvider serviceProvider, IServiceScopeFactory serviceScopeFactory, ILogger logger) { @@ -34,196 +32,108 @@ public RequestRouterBase(ISerializer serializer, IServiceProvider serviceProvide public async Task RouteNotification(TDescriptor descriptor, Notification notification, CancellationToken token) { - using (_logger.TimeDebug("Routing Notification {Method}", notification.Method)) + using var debug = _logger.TimeDebug("Routing Notification {Method}", notification.Method); + using var _ = _logger.BeginScope(new[] { + new KeyValuePair("Method", notification.Method), + new KeyValuePair("Params", notification.Params?.ToString()) + }); + using var scope = _serviceScopeFactory.CreateScope(); + var context = scope.ServiceProvider.GetRequiredService(); + context.Descriptor = descriptor; + var mediator = scope.ServiceProvider.GetRequiredService(); + + if (descriptor.Params is null) + { + await HandleNotification(mediator, descriptor, EmptyRequest.Instance, token); + } + else { - using (_logger.BeginScope(new[] { - new KeyValuePair( "Method", notification.Method), - new KeyValuePair( "Params", notification.Params?.ToString()) - })) - using (var scope = _serviceScopeFactory.CreateScope()) + _logger.LogDebug("Converting params for Notification {Method} to {Type}", notification.Method, descriptor.Params.FullName); + object @params; + if (descriptor.IsDelegatingHandler) { - var context = scope.ServiceProvider.GetRequiredService(); - context.Descriptor = descriptor; - var mediator = scope.ServiceProvider.GetRequiredService(); - - try - { - if (descriptor.Params is null) - { - await HandleNotification(mediator, descriptor, EmptyRequest.Instance, token); - } - else - { - _logger.LogDebug("Converting params for Notification {Method} to {Type}", notification.Method, descriptor.Params.FullName); - object @params; - if (descriptor.IsDelegatingHandler) - { - // new DelegatingRequest(); - var o = notification.Params?.ToObject(descriptor.Params.GetGenericArguments()[0], _serializer.JsonSerializer); - @params = Activator.CreateInstance(descriptor.Params, new object[] { o }); - } - else - { - @params = notification.Params?.ToObject(descriptor.Params, _serializer.JsonSerializer); - } - - await HandleNotification(mediator, descriptor, @params ?? Activator.CreateInstance(descriptor.Params), token); - } - } - catch (Exception e) - { - _logger.LogCritical(Events.UnhandledRequest, e, "Failed to handle request {Method}", notification.Method); - } + // new DelegatingRequest(); + var o = notification.Params?.ToObject(descriptor.Params.GetGenericArguments()[0], _serializer.JsonSerializer); + @params = Activator.CreateInstance(descriptor.Params, new object[] {o}); } + else + { + @params = notification.Params?.ToObject(descriptor.Params, _serializer.JsonSerializer); + } + + await HandleNotification(mediator, descriptor, @params ?? Activator.CreateInstance(descriptor.Params), token); } } public virtual async Task RouteRequest(TDescriptor descriptor, Request request, CancellationToken token) { - using (_logger.TimeDebug("Routing Request ({Id}) {Method}", request.Id, request.Method)) + using var debug = _logger.TimeDebug("Routing Request ({Id}) {Method}", request.Id, request.Method); + using var _ = _logger.BeginScope(new[] { + new KeyValuePair("Id", request.Id?.ToString()), + new KeyValuePair("Method", request.Method), + new KeyValuePair("Params", request.Params?.ToString()) + }); + using var scope = _serviceScopeFactory.CreateScope(); + var context = scope.ServiceProvider.GetRequiredService(); + context.Descriptor = descriptor; + var mediator = scope.ServiceProvider.GetRequiredService(); + + token.ThrowIfCancellationRequested(); + + // To avoid boxing, the best way to compare generics for equality is with EqualityComparer.Default. + // This respects IEquatable (without boxing) as well as object.Equals, and handles all the Nullable "lifted" nuances. + // https://stackoverflow.com/a/864860 + if (EqualityComparer.Default.Equals(descriptor, default)) { - using (_logger.BeginScope(new[] { - new KeyValuePair( "Id", request.Id?.ToString()), - new KeyValuePair( "Method", request.Method), - new KeyValuePair( "Params", request.Params?.ToString()) - })) - using (var scope = _serviceScopeFactory.CreateScope()) - { - var context = scope.ServiceProvider.GetRequiredService(); - context.Descriptor = descriptor; - var mediator = scope.ServiceProvider.GetRequiredService(); - - var id = GetId(request.Id); - if (!_requests.TryGetValue(id, out var cts)) - { - cts = new CancellationTokenSource(); - _requests.TryAdd(id, cts); - } - token.Register(cts.Cancel); - - // TODO: Try / catch for Internal Error - try - { - if (cts.IsCancellationRequested) - { - _logger.LogDebug("Request {Id} was cancelled", id); - return new RequestCancelled(); - } - - // To avoid boxing, the best way to compare generics for equality is with EqualityComparer.Default. - // This respects IEquatable (without boxing) as well as object.Equals, and handles all the Nullable "lifted" nuances. - // https://stackoverflow.com/a/864860 - if (EqualityComparer.Default.Equals(descriptor, default)) - { - _logger.LogDebug("descriptor not found for Request ({Id}) {Method}", request.Id, request.Method); - return new MethodNotFound(request.Id, request.Method); - } - - object @params; - try - { - _logger.LogTrace("Converting params for Request ({Id}) {Method} to {Type}", request.Id, request.Method, descriptor.Params.FullName); - if (descriptor.IsDelegatingHandler) - { - // new DelegatingRequest(); - var o = request.Params?.ToObject(descriptor.Params.GetGenericArguments()[0], _serializer.JsonSerializer); - @params = Activator.CreateInstance(descriptor.Params, new object[] { o }); - } - else - { - @params = request.Params?.ToObject(descriptor.Params, _serializer.JsonSerializer); - } - } - catch (Exception cannotDeserializeRequestParams) - { - _logger.LogError(new EventId(-32602), cannotDeserializeRequestParams, "Failed to deserialize request parameters."); - return new InvalidParams(request.Id); - } - - var result = HandleRequest(mediator, descriptor, @params ?? EmptyRequest.Instance, cts.Token); - await result; - - object responseValue = null; - if (result.GetType().GetTypeInfo().IsGenericType) - { - var property = typeof(Task<>) - .MakeGenericType(result.GetType().GetTypeInfo().GetGenericArguments()[0]).GetTypeInfo() - .GetProperty(nameof(Task.Result), BindingFlags.Public | BindingFlags.Instance); - - responseValue = property.GetValue(result); - if (responseValue?.GetType() == typeof(Unit)) - { - responseValue = null; - } - _logger.LogTrace("Response value was {Type}", responseValue?.GetType().FullName); - } - - return new JsonRpc.Client.Response(request.Id, responseValue, request); - } - catch (OperationCanceledException) - { - _logger.LogDebug("Request {Id} was cancelled", id); - return new RequestCancelled(); - } - catch (RpcErrorException e) - { - _logger.LogCritical(Events.UnhandledRequest, e, "Failed to handle notification {Method}", request.Method); - return new RpcError(id, new ErrorMessage(e.Code, e.Message, e.Error)); - } - catch (Exception e) - { - _logger.LogCritical(Events.UnhandledRequest, e, "Failed to handle notification {Method}", request.Method); - return new InternalError(id, e.ToString()); - } - finally - { - _requests.TryRemove(id, out var _); - } - } + _logger.LogTrace("descriptor not found for Request ({Id}) {Method}", request.Id, request.Method); + return new MethodNotFound(request.Id, request.Method); } - } - public void CancelRequest(object id) - { - if (_requests.TryGetValue(GetId(id), out var cts)) + object @params; + try { - _logger.LogTrace("Request {Id} was cancelled", id); - cts.Cancel(); + _logger.LogTrace("Converting params for Request ({Id}) {Method} to {Type}", request.Id, request.Method, descriptor.Params.FullName); + if (descriptor.IsDelegatingHandler) + { + // new DelegatingRequest(); + var o = request.Params?.ToObject(descriptor.Params.GetGenericArguments()[0], _serializer.JsonSerializer); + @params = Activator.CreateInstance(descriptor.Params, new object[] {o}); + } + else + { + @params = request.Params?.ToObject(descriptor.Params, _serializer.JsonSerializer); + } } - else + catch (Exception cannotDeserializeRequestParams) { - _logger.LogDebug("Request {Id} was not found to cancel, stubbing it in.", id); + _logger.LogError(new EventId(-32602), cannotDeserializeRequestParams, "Failed to deserialize request parameters."); + return new InvalidParams(request.Id); } - } - public void StartRequest(object id) - { - _requests.TryAdd(GetId(id), new CancellationTokenSource()); - } + token.ThrowIfCancellationRequested(); - private string GetId(object id) - { - if (id is string s) - { - return s; - } + var result = HandleRequest(mediator, descriptor, @params ?? Activator.CreateInstance(descriptor.Params), token); + await result; + + token.ThrowIfCancellationRequested(); - if (id is long l) + object responseValue = null; + if (result.GetType().GetTypeInfo().IsGenericType) { - return l.ToString(); - } + var property = typeof(Task<>) + .MakeGenericType(result.GetType().GetTypeInfo().GetGenericArguments()[0]).GetTypeInfo() + .GetProperty(nameof(Task.Result), BindingFlags.Public | BindingFlags.Instance); - return id?.ToString(); - } + responseValue = property.GetValue(result); + if (responseValue?.GetType() == typeof(Unit)) + { + responseValue = null; + } - Task IRequestRouter.RouteNotification(Notification notification, CancellationToken token) - { - return RouteNotification(GetDescriptor(notification), notification, token); - } + _logger.LogTrace("Response value was {Type}", responseValue?.GetType().FullName); + } - Task IRequestRouter.RouteRequest(Request request, CancellationToken token) - { - return RouteRequest(GetDescriptor(request), request, token); + return new JsonRpc.Client.OutgoingResponse(request.Id, responseValue, request); } public abstract TDescriptor GetDescriptor(Notification notification); @@ -241,24 +151,24 @@ Task IRequestRouter.RouteRequest(Request request, CancellationTok public static Task HandleNotification(IMediator mediator, IHandlerDescriptor handler, object @params, CancellationToken token) { - return (Task)SendRequestUnit + return (Task) SendRequestUnit .MakeGenericMethod(handler.Params ?? typeof(EmptyRequest)) - .Invoke(null, new object[] { mediator, @params, token }); + .Invoke(null, new object[] {mediator, @params, token}); } public static Task HandleRequest(IMediator mediator, IHandlerDescriptor descriptor, object @params, CancellationToken token) { if (!descriptor.HasReturnType) { - return (Task)SendRequestUnit + return (Task) SendRequestUnit .MakeGenericMethod(descriptor.Params) - .Invoke(null, new object[] { mediator, @params, token }); + .Invoke(null, new object[] {mediator, @params, token}); } else { - return (Task)SendRequestResponse + return (Task) SendRequestResponse .MakeGenericMethod(descriptor.Params, descriptor.Response) - .Invoke(null, new object[] { mediator, @params, token }); + .Invoke(null, new object[] {mediator, @params, token}); } } diff --git a/src/JsonRpc/ResponseRouter.cs b/src/JsonRpc/ResponseRouter.cs index 7af147650..df5def7be 100644 --- a/src/JsonRpc/ResponseRouter.cs +++ b/src/JsonRpc/ResponseRouter.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using MediatR; using Newtonsoft.Json.Linq; +using OmniSharp.Extensions.JsonRpc.Server; namespace OmniSharp.Extensions.JsonRpc { @@ -13,8 +14,8 @@ public class ResponseRouter : IResponseRouter internal readonly IOutputHandler OutputHandler; internal readonly ISerializer Serializer; - internal readonly ConcurrentDictionary> Requests = - new ConcurrentDictionary>(); + internal readonly ConcurrentDictionary pendingTask)> Requests = + new ConcurrentDictionary pendingTask)>(); internal static readonly ConcurrentDictionary MethodCache = new ConcurrentDictionary(); @@ -27,17 +28,17 @@ public ResponseRouter(IOutputHandler outputHandler, ISerializer serializer) public void SendNotification(string method) { - OutputHandler.Send(new Client.Notification() { + OutputHandler.Send(new Client.OutgoingNotification() { Method = method - }, CancellationToken.None); + }); } public void SendNotification(string method, T @params) { - OutputHandler.Send(new Client.Notification() { + OutputHandler.Send(new Client.OutgoingNotification() { Method = method, Params = @params - }, CancellationToken.None); + }); } public void SendNotification(IRequest @params) @@ -52,7 +53,7 @@ public Task SendRequest(IRequest @params, Cance public IResponseRouterReturns SendRequest(string method) { - return new ResponseRouterReturnsImpl(this, method, null); + return new ResponseRouterReturnsImpl(this, method, new object()); } public IResponseRouterReturns SendRequest(string method, T @params) @@ -60,7 +61,7 @@ public IResponseRouterReturns SendRequest(string method, T @params) return new ResponseRouterReturnsImpl(this, method, @params); } - public TaskCompletionSource GetRequest(long id) + public (string method, TaskCompletionSource pendingTask) GetRequest(long id) { Requests.TryGetValue(id, out var source); return source; @@ -100,13 +101,19 @@ public async Task Returning(CancellationToken cancellation { var nextId = _router.Serializer.GetNextId(); var tcs = new TaskCompletionSource(); - _router.Requests.TryAdd(nextId, tcs); + _router.Requests.TryAdd(nextId, (_method, tcs)); - _router.OutputHandler.Send(new Client.Request() { + cancellationToken.ThrowIfCancellationRequested(); + + _router.OutputHandler.Send(new Client.OutgoingRequest() { Method = _method, Params = _params, Id = nextId - }, cancellationToken); + }); + cancellationToken.Register(() => { + if (tcs.Task.IsCompleted) return; + _router.CancelRequest(new CancelParams() {Id = nextId}); + }); try { @@ -120,7 +127,7 @@ public async Task Returning(CancellationToken cancellation } finally { - _router.Requests.TryRemove(nextId, out var _); + _router.Requests.TryRemove(nextId, out _); } } diff --git a/src/JsonRpc/SchedulerDelegate.cs b/src/JsonRpc/SchedulerDelegate.cs new file mode 100644 index 000000000..1e263c327 --- /dev/null +++ b/src/JsonRpc/SchedulerDelegate.cs @@ -0,0 +1,8 @@ +using System; +using System.Reactive; +using System.Reactive.Concurrency; + +namespace OmniSharp.Extensions.JsonRpc +{ + delegate IObservable SchedulerDelegate(IObservable contentModifiedToken, IScheduler scheduler); +} diff --git a/src/JsonRpc/SerialRequestProcessIdentifier.cs b/src/JsonRpc/SerialRequestProcessIdentifier.cs index a3332cc66..94ff5a617 100644 --- a/src/JsonRpc/SerialRequestProcessIdentifier.cs +++ b/src/JsonRpc/SerialRequestProcessIdentifier.cs @@ -4,7 +4,7 @@ public class SerialRequestProcessIdentifier : IRequestProcessIdentifier { public RequestProcessType Identify(IHandlerDescriptor descriptor) { - return RequestProcessType.Serial; + return descriptor.RequestProcessType ?? RequestProcessType.Serial; } } } diff --git a/src/JsonRpc/Serialization/Converters/ClientNotificationConverter.cs b/src/JsonRpc/Serialization/Converters/ClientNotificationConverter.cs index b6bd3975d..a25783a32 100644 --- a/src/JsonRpc/Serialization/Converters/ClientNotificationConverter.cs +++ b/src/JsonRpc/Serialization/Converters/ClientNotificationConverter.cs @@ -4,16 +4,16 @@ namespace OmniSharp.Extensions.JsonRpc.Serialization.Converters { - public class ClientNotificationConverter : JsonConverter + public class ClientNotificationConverter : JsonConverter { public override bool CanRead => false; - public override Notification ReadJson(JsonReader reader, Type objectType, Notification existingValue, + public override OutgoingNotification ReadJson(JsonReader reader, Type objectType, OutgoingNotification existingValue, bool hasExistingValue, JsonSerializer serializer) { throw new NotImplementedException(); } - public override void WriteJson(JsonWriter writer, Notification value, JsonSerializer serializer) + public override void WriteJson(JsonWriter writer, OutgoingNotification value, JsonSerializer serializer) { writer.WriteStartObject(); writer.WritePropertyName("jsonrpc"); diff --git a/src/JsonRpc/Serialization/Converters/ClientRequestConverter.cs b/src/JsonRpc/Serialization/Converters/ClientRequestConverter.cs index 5a8b2e27a..4a2b3b7f5 100644 --- a/src/JsonRpc/Serialization/Converters/ClientRequestConverter.cs +++ b/src/JsonRpc/Serialization/Converters/ClientRequestConverter.cs @@ -4,16 +4,16 @@ namespace OmniSharp.Extensions.JsonRpc.Serialization.Converters { - public class ClientRequestConverter : JsonConverter + public class ClientRequestConverter : JsonConverter { public override bool CanRead => false; - public override Request ReadJson(JsonReader reader, Type objectType, Request existingValue, + public override OutgoingRequest ReadJson(JsonReader reader, Type objectType, OutgoingRequest existingValue, bool hasExistingValue, JsonSerializer serializer) { throw new NotImplementedException(); } - public override void WriteJson(JsonWriter writer, Request value, JsonSerializer serializer) + public override void WriteJson(JsonWriter writer, OutgoingRequest value, JsonSerializer serializer) { writer.WriteStartObject(); writer.WritePropertyName("jsonrpc"); diff --git a/src/JsonRpc/Serialization/Converters/ClientResponseConverter.cs b/src/JsonRpc/Serialization/Converters/ClientResponseConverter.cs index f174fa109..c32468419 100644 --- a/src/JsonRpc/Serialization/Converters/ClientResponseConverter.cs +++ b/src/JsonRpc/Serialization/Converters/ClientResponseConverter.cs @@ -4,16 +4,16 @@ namespace OmniSharp.Extensions.JsonRpc.Serialization.Converters { - public class ClientResponseConverter : JsonConverter + public class ClientResponseConverter : JsonConverter { public override bool CanRead => false; - public override Response ReadJson(JsonReader reader, Type objectType, Response existingValue, + public override OutgoingResponse ReadJson(JsonReader reader, Type objectType, OutgoingResponse existingValue, bool hasExistingValue, JsonSerializer serializer) { throw new NotImplementedException(); } - public override void WriteJson(JsonWriter writer, Response value, JsonSerializer serializer) + public override void WriteJson(JsonWriter writer, OutgoingResponse value, JsonSerializer serializer) { writer.WriteStartObject(); writer.WritePropertyName("jsonrpc"); diff --git a/src/JsonRpc/Server/ContentModifiedException.cs b/src/JsonRpc/Server/ContentModifiedException.cs new file mode 100644 index 000000000..b544c6f89 --- /dev/null +++ b/src/JsonRpc/Server/ContentModifiedException.cs @@ -0,0 +1,90 @@ +using System; +using System.Runtime.Serialization; +using System.Threading.Tasks; + +namespace OmniSharp.Extensions.JsonRpc.Server +{ + /// + /// Exception raised when request parameters are invalid according to the target method. + /// + [Serializable] + public class ContentModifiedException + : TaskCanceledException, IRequestException + { + /// + /// Create a new . + /// + /// + /// The LSP / JSON-RPC request Id (if known). + /// + public ContentModifiedException(object requestId) + : this(ErrorCodes.ContentModified, requestId.ToString(), "Content not modified.", null) + { + } + + /// + /// Create a new . + /// + /// + /// The LSP / JSON-RPC request Id (if known). + /// + /// + /// The exception that caused this exception to be raised. + /// + public ContentModifiedException(object requestId, Exception inner) + : this(ErrorCodes.ContentModified, requestId.ToString(), "Content not modified.", inner) + { + } + + /// + /// Create a new . + /// + /// + /// The LSP / JSON-RPC error code. + /// + /// + /// The exception message. + /// + /// + /// The LSP / JSON-RPC request Id (if known). + /// + /// + /// The exception that caused this exception to be raised. + /// + public ContentModifiedException(int errorCode, string message, string requestId, Exception inner) : base(message, inner) + { + RequestId = !string.IsNullOrWhiteSpace(requestId) ? requestId : UnknownRequestId; + ErrorCode = errorCode; + } + + /// + /// Serialisation constructor. + /// + /// + /// The serialisation data-store. + /// + /// + /// The serialisation streaming context. + /// + protected ContentModifiedException(SerializationInfo info, StreamingContext context) + { + RequestId = info.GetString(nameof(RequestId)); + ErrorCode = info.GetInt32(nameof(ErrorCode)); + } + + /// + /// The LSP / JSON-RPC request Id (if known). + /// + public object RequestId { get; } + + /// + /// The LSP / JSON-RPC error code. + /// + public int ErrorCode { get; } + + /// + /// The request Id used when no valid request Id was supplied. + /// + public const string UnknownRequestId = "(unknown)"; + } +} diff --git a/src/Client/LspErrorCodes.cs b/src/JsonRpc/Server/ErrorCodes.cs similarity index 81% rename from src/Client/LspErrorCodes.cs rename to src/JsonRpc/Server/ErrorCodes.cs index a1be78995..3368bc4e4 100644 --- a/src/Client/LspErrorCodes.cs +++ b/src/JsonRpc/Server/ErrorCodes.cs @@ -1,14 +1,19 @@ -namespace OmniSharp.Extensions.LanguageServer.Client +namespace OmniSharp.Extensions.JsonRpc.Server { /// /// Well-known LSP error codes. /// - public static class LspErrorCodes + public static class ErrorCodes { /// /// No error code was supplied. /// - public static readonly int None = -32001; + public const int UnknownErrorCode = -32001; + + /// + /// An exception was thrown by a .net server / client + /// + public const int Exception = -32050; /// /// Server has not been initialised. @@ -50,4 +55,4 @@ public static class LspErrorCodes /// public const int ContentModified = -32801; } -} +} \ No newline at end of file diff --git a/src/JsonRpc/Server/IRequestException.cs b/src/JsonRpc/Server/IRequestException.cs new file mode 100644 index 000000000..352edb1f3 --- /dev/null +++ b/src/JsonRpc/Server/IRequestException.cs @@ -0,0 +1,15 @@ +namespace OmniSharp.Extensions.JsonRpc.Server +{ + public interface IRequestException + { + /// + /// The LSP / JSON-RPC request Id (if known). + /// + public object RequestId { get; } + + /// + /// The LSP / JSON-RPC error code. + /// + public int ErrorCode { get; } + } +} diff --git a/src/Client/LspInternalErrorException.cs b/src/JsonRpc/Server/InternalErrorException.cs similarity index 61% rename from src/Client/LspInternalErrorException.cs rename to src/JsonRpc/Server/InternalErrorException.cs index a71727298..3a655ad56 100644 --- a/src/Client/LspInternalErrorException.cs +++ b/src/JsonRpc/Server/InternalErrorException.cs @@ -1,23 +1,24 @@ using System; using System.Runtime.Serialization; -namespace OmniSharp.Extensions.LanguageServer.Client +namespace OmniSharp.Extensions.JsonRpc.Server { /// /// Exception raised when an internal error has occurred in the language server. /// [Serializable] - public class LspInternalErrorException - : LspRequestException + public class InternalErrorException + : RequestException { /// - /// Create a new . + /// Create a new . /// /// /// The LSP / JSON-RPC request Id (if known). /// - public LspInternalErrorException(string requestId) - : base("Internal error.", requestId, LspErrorCodes.InternalError) + /// + public InternalErrorException(object requestId, string message) + : base(ErrorCodes.InternalError, requestId, "Internal error. " + message) { } @@ -30,9 +31,9 @@ public LspInternalErrorException(string requestId) /// /// The serialisation streaming context. /// - protected LspInternalErrorException(SerializationInfo info, StreamingContext context) + protected InternalErrorException(SerializationInfo info, StreamingContext context) : base(info, context) { } } -} \ No newline at end of file +} diff --git a/src/JsonRpc/Server/InvalidParametersException.cs b/src/JsonRpc/Server/InvalidParametersException.cs new file mode 100644 index 000000000..0d86f4b98 --- /dev/null +++ b/src/JsonRpc/Server/InvalidParametersException.cs @@ -0,0 +1,38 @@ +using System; +using System.Runtime.Serialization; + +namespace OmniSharp.Extensions.JsonRpc.Server +{ + /// + /// Exception raised when request parameters are invalid according to the target method. + /// + [Serializable] + public class InvalidParametersException + : RequestException + { + /// + /// Create a new . + /// + /// + /// The LSP / JSON-RPC request Id (if known). + /// + public InvalidParametersException(object requestId) + : base(ErrorCodes.InvalidParameters, requestId, "Invalid parameters.") + { + } + + /// + /// Serialisation constructor. + /// + /// + /// The serialisation data-store. + /// + /// + /// The serialisation streaming context. + /// + protected InvalidParametersException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/src/Client/LspInvalidRequestException.cs b/src/JsonRpc/Server/InvalidRequestException.cs similarity index 62% rename from src/Client/LspInvalidRequestException.cs rename to src/JsonRpc/Server/InvalidRequestException.cs index 70c3940ae..3d9740654 100644 --- a/src/Client/LspInvalidRequestException.cs +++ b/src/JsonRpc/Server/InvalidRequestException.cs @@ -1,23 +1,23 @@ using System; using System.Runtime.Serialization; -namespace OmniSharp.Extensions.LanguageServer.Client +namespace OmniSharp.Extensions.JsonRpc.Server { /// /// Exception raised an LSP request is invalid. /// [Serializable] - public class LspInvalidRequestException - : LspRequestException + public class InvalidRequestException + : RequestException { /// - /// Create a new . + /// Create a new . /// /// /// The LSP / JSON-RPC request Id (if known). /// - public LspInvalidRequestException(string requestId) - : base("Invalid request.", requestId, LspErrorCodes.InvalidRequest) + public InvalidRequestException(object requestId) + : base(ErrorCodes.InvalidRequest, requestId, "Invalid request.") { } @@ -30,9 +30,9 @@ public LspInvalidRequestException(string requestId) /// /// The serialisation streaming context. /// - protected LspInvalidRequestException(SerializationInfo info, StreamingContext context) + protected InvalidRequestException(SerializationInfo info, StreamingContext context) : base(info, context) { } } -} \ No newline at end of file +} diff --git a/src/JsonRpc/Server/JsonRpcException.cs b/src/JsonRpc/Server/JsonRpcException.cs index f85ac3c5b..f14f6832a 100644 --- a/src/JsonRpc/Server/JsonRpcException.cs +++ b/src/JsonRpc/Server/JsonRpcException.cs @@ -1,17 +1,12 @@ -using System; -using Newtonsoft.Json.Linq; - namespace OmniSharp.Extensions.JsonRpc.Server { - public class JsonRpcException : Exception + public class JsonRpcException : RequestException { - public JsonRpcException(ServerError error) + public JsonRpcException(int code, object requestId, string message, string data) : base(code, requestId, message) { - Error = error.Error; - Id = error.Id; + Error = data; } - public JToken Error { get; } - public object Id { get; } + public string Error { get; } } } diff --git a/src/JsonRpc/Server/JsonRpcInvalidRequestException.cs b/src/JsonRpc/Server/JsonRpcInvalidRequestException.cs deleted file mode 100644 index e64592b1a..000000000 --- a/src/JsonRpc/Server/JsonRpcInvalidRequestException.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; - -namespace OmniSharp.Extensions.JsonRpc.Server -{ - public class JsonRpcInvalidRequestException : Exception - { - public JsonRpcInvalidRequestException() - { - } - - public JsonRpcInvalidRequestException(string message) : base(message) - { - } - - public JsonRpcInvalidRequestException(string message, Exception innerException) : base(message, innerException) - { - } - - //protected JsonRpcInvalidRequestException(SerializationInfo info, StreamingContext context) : base(info, context) - //{ - //} - } -} diff --git a/src/JsonRpc/Server/Messages/InternalError.cs b/src/JsonRpc/Server/Messages/InternalError.cs index 889543278..fa87ee647 100644 --- a/src/JsonRpc/Server/Messages/InternalError.cs +++ b/src/JsonRpc/Server/Messages/InternalError.cs @@ -3,7 +3,7 @@ namespace OmniSharp.Extensions.JsonRpc.Server.Messages public class InternalError : RpcError { public InternalError() : this(null) { } - public InternalError(object id) : base(id, new ErrorMessage(-32602, "Internal Error")) { } - public InternalError(object id, string message) : base(id, new ErrorMessage(-32602, "Internal Error - " + message)) { } + public InternalError(object id) : base(id, new ErrorMessage(ErrorCodes.InternalError, "Internal Error")) { } + public InternalError(object id, string message) : base(id, new ErrorMessage(ErrorCodes.InternalError, "Internal Error - " + message)) { } } } diff --git a/src/Client/LspMethodNotSupportedException.cs b/src/JsonRpc/Server/MethodNotSupportedException.cs similarity index 77% rename from src/Client/LspMethodNotSupportedException.cs rename to src/JsonRpc/Server/MethodNotSupportedException.cs index 0028d6cfe..f4dbae165 100644 --- a/src/Client/LspMethodNotSupportedException.cs +++ b/src/JsonRpc/Server/MethodNotSupportedException.cs @@ -1,17 +1,17 @@ using System; using System.Runtime.Serialization; -namespace OmniSharp.Extensions.LanguageServer.Client +namespace OmniSharp.Extensions.JsonRpc.Server { /// /// Exception raised when an LSP request is made for a method not supported by the remote process. /// [Serializable] - public class LspMethodNotSupportedException - : LspRequestException + public class MethodNotSupportedException + : RequestException { /// - /// Create a new . + /// Create a new . /// /// /// The LSP / JSON-RPC request Id (if known). @@ -19,8 +19,8 @@ public class LspMethodNotSupportedException /// /// The name of the target method. /// - public LspMethodNotSupportedException(string method, string requestId) - : base($"Method not found: '{method}'.", requestId, LspErrorCodes.MethodNotSupported) + public MethodNotSupportedException(object requestId, string method) + : base(ErrorCodes.MethodNotSupported, requestId, $"Method not found: '{method}'.") { Method = !string.IsNullOrWhiteSpace(method) ? method : "(unknown)"; } @@ -34,7 +34,7 @@ public LspMethodNotSupportedException(string method, string requestId) /// /// The serialisation streaming context. /// - protected LspMethodNotSupportedException(SerializationInfo info, StreamingContext context) + protected MethodNotSupportedException(SerializationInfo info, StreamingContext context) : base(info, context) { Method = info.GetString("Method"); @@ -61,4 +61,4 @@ public override void GetObjectData(SerializationInfo info, StreamingContext cont /// public string Method { get; } } -} \ No newline at end of file +} diff --git a/src/JsonRpc/Server/Notification.cs b/src/JsonRpc/Server/Notification.cs index cd5f2587b..65d4abea6 100644 --- a/src/JsonRpc/Server/Notification.cs +++ b/src/JsonRpc/Server/Notification.cs @@ -4,7 +4,7 @@ namespace OmniSharp.Extensions.JsonRpc.Server { public class Notification : IMethodWithParams { - internal Notification( + public Notification( string method, JToken @params) { diff --git a/src/Client/LspParseErrorException.cs b/src/JsonRpc/Server/ParseErrorException.cs similarity index 63% rename from src/Client/LspParseErrorException.cs rename to src/JsonRpc/Server/ParseErrorException.cs index 1aac390a5..7287cb135 100644 --- a/src/Client/LspParseErrorException.cs +++ b/src/JsonRpc/Server/ParseErrorException.cs @@ -1,23 +1,23 @@ using System; using System.Runtime.Serialization; -namespace OmniSharp.Extensions.LanguageServer.Client +namespace OmniSharp.Extensions.JsonRpc.Server { /// /// Exception raised when an LSP request could not be parsed. /// [Serializable] - public class LspParseErrorException - : LspRequestException + public class ParseErrorException + : RequestException { /// - /// Create a new . + /// Create a new . /// /// /// The LSP / JSON-RPC request Id (if known). /// - public LspParseErrorException(string requestId) - : base("Error parsing request.", requestId, LspErrorCodes.ParseError) + public ParseErrorException(object requestId) + : base( ErrorCodes.ParseError, requestId, "Error parsing request.") { } @@ -30,9 +30,9 @@ public LspParseErrorException(string requestId) /// /// The serialisation streaming context. /// - protected LspParseErrorException(SerializationInfo info, StreamingContext context) + protected ParseErrorException(SerializationInfo info, StreamingContext context) : base(info, context) { } } -} \ No newline at end of file +} diff --git a/src/JsonRpc/Server/Request.cs b/src/JsonRpc/Server/Request.cs index 1852190de..75405e36d 100644 --- a/src/JsonRpc/Server/Request.cs +++ b/src/JsonRpc/Server/Request.cs @@ -4,7 +4,7 @@ namespace OmniSharp.Extensions.JsonRpc.Server { public class Request : IMethodWithParams { - internal Request( + public Request( object id, string method, JToken @params) diff --git a/src/JsonRpc/Server/RequestCancelledException.cs b/src/JsonRpc/Server/RequestCancelledException.cs new file mode 100644 index 000000000..de8600f5f --- /dev/null +++ b/src/JsonRpc/Server/RequestCancelledException.cs @@ -0,0 +1,90 @@ +using System; +using System.Runtime.Serialization; +using System.Threading.Tasks; + +namespace OmniSharp.Extensions.JsonRpc.Server +{ + /// + /// Exception raised when an LSP request is cancelled. + /// + [Serializable] + public class RequestCancelledException + : TaskCanceledException, IRequestException + { + /// + /// Create a new . + /// + /// + /// The LSP / JSON-RPC request Id (if known). + /// + public RequestCancelledException(object requestId) + : this(ErrorCodes.RequestCancelled, requestId.ToString(), "Request was cancelled.", null) + { + } + + /// + /// Create a new . + /// + /// + /// The LSP / JSON-RPC request Id (if known). + /// + /// + /// The exception that caused this exception to be raised. + /// + public RequestCancelledException(object requestId, Exception inner) + : this(ErrorCodes.RequestCancelled, requestId.ToString(), "Request was cancelled.", inner) + { + } + + /// + /// Create a new . + /// + /// + /// The LSP / JSON-RPC error code. + /// + /// + /// The exception message. + /// + /// + /// The LSP / JSON-RPC request Id (if known). + /// + /// + /// The exception that caused this exception to be raised. + /// + public RequestCancelledException(int errorCode, string message, string requestId, Exception inner) : base(message, inner) + { + RequestId = !string.IsNullOrWhiteSpace(requestId) ? requestId : UnknownRequestId; + ErrorCode = errorCode; + } + + /// + /// Serialisation constructor. + /// + /// + /// The serialisation data-store. + /// + /// + /// The serialisation streaming context. + /// + protected RequestCancelledException(SerializationInfo info, StreamingContext context) + { + RequestId = info.GetString(nameof(RequestId)); + ErrorCode = info.GetInt32(nameof(ErrorCode)); + } + + /// + /// The LSP / JSON-RPC request Id (if known). + /// + public object RequestId { get; } + + /// + /// The LSP / JSON-RPC error code. + /// + public int ErrorCode { get; } + + /// + /// The request Id used when no valid request Id was supplied. + /// + public const string UnknownRequestId = "(unknown)"; + } +} diff --git a/src/Client/LspRequestException.cs b/src/JsonRpc/Server/RequestException.cs similarity index 56% rename from src/Client/LspRequestException.cs rename to src/JsonRpc/Server/RequestException.cs index a50d814d5..b50fa51aa 100644 --- a/src/Client/LspRequestException.cs +++ b/src/JsonRpc/Server/RequestException.cs @@ -1,14 +1,13 @@ using System; using System.Runtime.Serialization; -namespace OmniSharp.Extensions.LanguageServer.Client +namespace OmniSharp.Extensions.JsonRpc.Server { /// /// Exception raised when a Language Server Protocol error is encountered while processing a request. /// [Serializable] - public class LspRequestException - : LspException + public class RequestException: Exception, IRequestException { /// /// The request Id used when no valid request Id was supplied. @@ -16,72 +15,39 @@ public class LspRequestException public const string UnknownRequestId = "(unknown)"; /// - /// Create a new without an error code (). + /// Create a new . /// - /// - /// The exception message. + /// + /// The LSP / JSON-RPC error code. /// /// /// The LSP / JSON-RPC request Id (if known). /// - public LspRequestException(string message, string requestId) - : this(message, requestId, LspErrorCodes.None) - { - } - - /// - /// Create a new . - /// /// /// The exception message. /// - /// - /// The LSP / JSON-RPC request Id (if known). - /// - /// - /// The LSP / JSON-RPC error code. - /// - public LspRequestException(string message, string requestId, int errorCode) - : base(message) + public RequestException(int errorCode, object requestId, string message) : base(message) { - RequestId = !string.IsNullOrWhiteSpace(requestId) ? requestId : UnknownRequestId; + RequestId = requestId ?? UnknownRequestId; ErrorCode = errorCode; } /// - /// Create a new . + /// Create a new . /// - /// - /// The exception message. - /// - /// - /// The LSP / JSON-RPC request Id (if known). - /// - /// - /// The exception that caused this exception to be raised. + /// + /// The LSP / JSON-RPC error code. /// - public LspRequestException(string message, string requestId, Exception inner) - : this(message, requestId, LspErrorCodes.None, inner) - { - } - - /// - /// Create a new . - /// /// /// The exception message. /// /// /// The LSP / JSON-RPC request Id (if known). /// - /// - /// The LSP / JSON-RPC error code. - /// /// /// The exception that caused this exception to be raised. /// - public LspRequestException(string message, string requestId, int errorCode, Exception inner) - : base(message, inner) + public RequestException(int errorCode, string message, string requestId, Exception inner) : base(message, inner) { RequestId = !string.IsNullOrWhiteSpace(requestId) ? requestId : UnknownRequestId; ErrorCode = errorCode; @@ -96,8 +62,7 @@ public LspRequestException(string message, string requestId, int errorCode, Exce /// /// The serialisation streaming context. /// - protected LspRequestException(SerializationInfo info, StreamingContext context) - : base(info, context) + protected RequestException(SerializationInfo info, StreamingContext context) { RequestId = info.GetString(nameof(RequestId)); ErrorCode = info.GetInt32(nameof(ErrorCode)); @@ -123,16 +88,11 @@ public override void GetObjectData(SerializationInfo info, StreamingContext cont /// /// The LSP / JSON-RPC request Id (if known). /// - public string RequestId { get; } + public object RequestId { get; } /// /// The LSP / JSON-RPC error code. /// public int ErrorCode { get; } - - /// - /// Does the represent an LSP / JSON-RPC protocol error? - /// - public bool IsProtocolError => ErrorCode != LspErrorCodes.None; } -} \ No newline at end of file +} diff --git a/src/JsonRpc/Server/ServerError.cs b/src/JsonRpc/Server/ServerError.cs index 4bf902b70..cf6e63f56 100644 --- a/src/JsonRpc/Server/ServerError.cs +++ b/src/JsonRpc/Server/ServerError.cs @@ -1,14 +1,17 @@ -using Newtonsoft.Json.Linq; - namespace OmniSharp.Extensions.JsonRpc.Server { public class ServerError : ResponseBase { - public ServerError(object id, JToken result) : base(id) + public ServerError(ServerErrorResult result) : this(null, result) + { + Error = result; + } + + public ServerError(object id, ServerErrorResult result) : base(id) { Error = result; } - public JToken Error { get; set; } + public ServerErrorResult Error { get; } } -} \ No newline at end of file +} diff --git a/src/JsonRpc/Server/ServerErrorResult.cs b/src/JsonRpc/Server/ServerErrorResult.cs new file mode 100644 index 000000000..9f8c415d2 --- /dev/null +++ b/src/JsonRpc/Server/ServerErrorResult.cs @@ -0,0 +1,26 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace OmniSharp.Extensions.JsonRpc.Server +{ + public class ServerErrorResult + { + [JsonConstructor] + public ServerErrorResult(int code, string message, JToken data) + { + Code = code; + Message = message; + Data = data; + } + public ServerErrorResult(int code, string message) + { + Code = code; + Message = message; + } + + public int Code { get; set; } + public string Message { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public JToken Data { get; set; } + } +} \ No newline at end of file diff --git a/src/Client/LspInvalidParametersException.cs b/src/JsonRpc/Server/ServerNotInitializedException.cs similarity index 65% rename from src/Client/LspInvalidParametersException.cs rename to src/JsonRpc/Server/ServerNotInitializedException.cs index 4f99aaead..6c1efe8a7 100644 --- a/src/Client/LspInvalidParametersException.cs +++ b/src/JsonRpc/Server/ServerNotInitializedException.cs @@ -1,23 +1,23 @@ using System; using System.Runtime.Serialization; -namespace OmniSharp.Extensions.LanguageServer.Client +namespace OmniSharp.Extensions.JsonRpc.Server { /// /// Exception raised when request parameters are invalid according to the target method. /// [Serializable] - public class LspInvalidParametersException - : LspRequestException + public class ServerNotInitializedException + : RequestException { /// - /// Create a new . + /// Create a new . /// /// /// The LSP / JSON-RPC request Id (if known). /// - public LspInvalidParametersException(string requestId) - : base("Invalid parameters.", requestId, LspErrorCodes.InvalidParameters) + public ServerNotInitializedException(object requestId) + : base(ErrorCodes.ServerNotInitialized, requestId, "Server not initialized.") { } @@ -30,9 +30,9 @@ public LspInvalidParametersException(string requestId) /// /// The serialisation streaming context. /// - protected LspInvalidParametersException(SerializationInfo info, StreamingContext context) + protected ServerNotInitializedException(SerializationInfo info, StreamingContext context) : base(info, context) { } } -} \ No newline at end of file +} diff --git a/src/Client/LspRequestCancelledException.cs b/src/JsonRpc/Server/UnknownErrorException.cs similarity index 56% rename from src/Client/LspRequestCancelledException.cs rename to src/JsonRpc/Server/UnknownErrorException.cs index 4b6578e04..94de172d8 100644 --- a/src/Client/LspRequestCancelledException.cs +++ b/src/JsonRpc/Server/UnknownErrorException.cs @@ -1,23 +1,23 @@ using System; using System.Runtime.Serialization; -namespace OmniSharp.Extensions.LanguageServer.Client +namespace OmniSharp.Extensions.JsonRpc.Server { /// - /// Exception raised when an LSP request is cancelled. + /// Exception raised when request parameters are invalid according to the target method. /// [Serializable] - public class LspRequestCancelledException - : LspRequestException + public class UnknownErrorException + : RequestException { /// - /// Create a new . + /// Create a new . /// /// /// The LSP / JSON-RPC request Id (if known). /// - public LspRequestCancelledException(string requestId) - : base("Request was cancelled.", requestId, LspErrorCodes.RequestCancelled) + public UnknownErrorException(object requestId) + : base(ErrorCodes.ContentModified, requestId, "Content not modified.") { } @@ -30,9 +30,9 @@ public LspRequestCancelledException(string requestId) /// /// The serialisation streaming context. /// - protected LspRequestCancelledException(SerializationInfo info, StreamingContext context) + protected UnknownErrorException(SerializationInfo info, StreamingContext context) : base(info, context) { } } -} \ No newline at end of file +} diff --git a/src/Protocol/Client/ClientProxyBase.cs b/src/Protocol/Client/ClientProxyBase.cs index cbd54ff4c..b4e9ce463 100644 --- a/src/Protocol/Client/ClientProxyBase.cs +++ b/src/Protocol/Client/ClientProxyBase.cs @@ -33,7 +33,7 @@ public ClientProxyBase(IClientProxy proxy, IServiceProvider serviceProvider) public Task SendRequest(IRequest request, CancellationToken cancellationToken) => _proxy.SendRequest(request, cancellationToken); - public TaskCompletionSource GetRequest(long id) => _proxy.GetRequest(id); + (string method, TaskCompletionSource pendingTask) IResponseRouter.GetRequest(long id) => _proxy.GetRequest(id); object IServiceProvider.GetService(Type serviceType) => _serviceProvider.GetService(serviceType); public IProgressManager ProgressManager => _proxy.ProgressManager; public IClientWorkDoneManager WorkDoneManager => _proxy.WorkDoneManager; diff --git a/src/Protocol/Client/ILanguageClient.cs b/src/Protocol/Client/ILanguageClient.cs index 8fa1608b6..3f19e0a80 100644 --- a/src/Protocol/Client/ILanguageClient.cs +++ b/src/Protocol/Client/ILanguageClient.cs @@ -1,10 +1,11 @@ using System; using System.Threading; using System.Threading.Tasks; +using OmniSharp.Extensions.JsonRpc; namespace OmniSharp.Extensions.LanguageServer.Protocol.Client { - public interface ILanguageClient : IClientProxy, ILanguageClientRegistry, IDisposable + public interface ILanguageClient : IClientProxy, IJsonRpcHandlerInstance, IDisposable { ITextDocumentLanguageClient TextDocument { get; } IClientLanguageClient Client { get; } diff --git a/src/Protocol/Client/ILanguageClientRegistry.cs b/src/Protocol/Client/ILanguageClientRegistry.cs index 378ac4716..093470482 100644 --- a/src/Protocol/Client/ILanguageClientRegistry.cs +++ b/src/Protocol/Client/ILanguageClientRegistry.cs @@ -4,11 +4,7 @@ namespace OmniSharp.Extensions.LanguageServer.Protocol.Client { - public interface ILanguageClientRegistry : IJsonRpcHandlerRegistry + public interface ILanguageClientRegistry : IJsonRpcHandlerRegistry { - IDisposable AddTextDocumentIdentifier(params ITextDocumentIdentifier[] handlers); - IDisposable AddTextDocumentIdentifier() where T : ITextDocumentIdentifier; - - IDisposable AddHandler(Func handlerFunc) where T : IJsonRpcHandler; } } diff --git a/src/Protocol/Client/IRegisterCapabilityHandler.cs b/src/Protocol/Client/IRegisterCapabilityHandler.cs index 68d4a3d59..d5ba9a09a 100644 --- a/src/Protocol/Client/IRegisterCapabilityHandler.cs +++ b/src/Protocol/Client/IRegisterCapabilityHandler.cs @@ -20,13 +20,13 @@ public abstract class RegisterCapabilityHandler : IRegisterCapabilityHandler public static class RegisterCapabilityExtensions { - public static IDisposable OnRegisterCapability(this ILanguageClientRegistry registry, + public static ILanguageClientRegistry OnRegisterCapability(this ILanguageClientRegistry registry, Func handler) { return registry.AddHandler(ClientNames.RegisterCapability, RequestHandler.For(handler)); } - public static IDisposable OnRegisterCapability(this ILanguageClientRegistry registry, + public static ILanguageClientRegistry OnRegisterCapability(this ILanguageClientRegistry registry, Func handler) { return registry.AddHandler(ClientNames.RegisterCapability, RequestHandler.For(handler)); diff --git a/src/Protocol/Client/IUnregisterCapabilityHandler.cs b/src/Protocol/Client/IUnregisterCapabilityHandler.cs index 6a2da1f4f..5075c05de 100644 --- a/src/Protocol/Client/IUnregisterCapabilityHandler.cs +++ b/src/Protocol/Client/IUnregisterCapabilityHandler.cs @@ -18,13 +18,13 @@ public abstract class UnregisterCapabilityHandler : IUnregisterCapabilityHandler public static class UnregisterCapabilityExtensions { - public static IDisposable OnUnregisterCapability(this ILanguageClientRegistry registry, + public static ILanguageClientRegistry OnUnregisterCapability(this ILanguageClientRegistry registry, Func handler) { return registry.AddHandler(ClientNames.UnregisterCapability, RequestHandler.For(handler)); } - public static IDisposable OnUnregisterCapability(this ILanguageClientRegistry registry, + public static ILanguageClientRegistry OnUnregisterCapability(this ILanguageClientRegistry registry, Func handler) { return registry.AddHandler(ClientNames.UnregisterCapability, RequestHandler.For(handler)); diff --git a/src/Protocol/Client/WorkDone/ClientWorkDoneManager.cs b/src/Protocol/Client/WorkDone/ClientWorkDoneManager.cs index 10bb92cda..c5cc17f12 100644 --- a/src/Protocol/Client/WorkDone/ClientWorkDoneManager.cs +++ b/src/Protocol/Client/WorkDone/ClientWorkDoneManager.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.Concurrent; +using System.Collections.Immutable; +using System.Reactive.Disposables; using System.Threading; using System.Threading.Tasks; -using DynamicData; using MediatR; using Newtonsoft.Json.Linq; using OmniSharp.Extensions.JsonRpc; @@ -14,19 +16,18 @@ namespace OmniSharp.Extensions.LanguageServer.Protocol.Client.WorkDone { class ClientWorkDoneManager : IClientWorkDoneManager, IWorkDoneProgressCreateHandler { - private readonly IResponseRouter _router; + private readonly ILanguageClient _router; private readonly ISerializer _serializer; private readonly IProgressManager _progressManager; private bool _supported; - private readonly ISourceCache, ProgressToken> _pendingWork; + private readonly ConcurrentDictionary> _pendingWork; - public ClientWorkDoneManager(IResponseRouter router, ISerializer serializer, IProgressManager progressManager) + public ClientWorkDoneManager(ILanguageClient router, ISerializer serializer, IProgressManager progressManager) { _router = router; _serializer = serializer; _progressManager = progressManager; - _pendingWork = new SourceCache, ProgressToken>(x => x.ProgressToken); - PendingWork = _pendingWork.AsObservableCache(); + _pendingWork = new ConcurrentDictionary>(); } public void Initialize(WindowClientCapabilities windowClientCapabilities) @@ -36,13 +37,25 @@ public void Initialize(WindowClientCapabilities windowClientCapabilities) } public bool IsSupported => _supported; - public IObservableCache, ProgressToken> PendingWork { get; } public IProgressObservable Monitor(ProgressToken progressToken) { - var data = _progressManager.Monitor(progressToken, Parse); - _pendingWork.AddOrUpdate(data); - data.Subscribe(_ => { }, () => { _pendingWork.RemoveKey(data.ProgressToken); }); + if (_pendingWork.TryGetValue(progressToken, out var currentValue)) + { + return currentValue; + } + + var data = new WorkDoneObservable( + _progressManager.Monitor(progressToken, Parse), + Disposable.Create(() => _router.Window.SendWorkDoneProgressCancel(progressToken)) + ); + _pendingWork.AddOrUpdate(progressToken, x => data, (a, b) => data); + data.Subscribe(_ => { }, () => { + if (_pendingWork.TryRemove(data.ProgressToken, out var item)) + { + item.Dispose(); + } + }); return data; } @@ -63,4 +76,28 @@ private WorkDoneProgress Parse(JToken token) }; } } + + class WorkDoneObservable : IProgressObservable + { + private readonly IProgressObservable _progressObservable; + private readonly IDisposable _triggerCancellation; + + public WorkDoneObservable(IProgressObservable progressObservable, IDisposable triggerCancellation) + { + _progressObservable = progressObservable; + _triggerCancellation = triggerCancellation; + } + + public ProgressToken ProgressToken => _progressObservable.ProgressToken; + + public Type ParamsType => _progressObservable.ParamsType; + + public void Dispose() + { + _triggerCancellation.Dispose(); + _progressObservable.Dispose(); + } + + public IDisposable Subscribe(IObserver observer) => _progressObservable.Subscribe(observer); + } } diff --git a/src/Protocol/Client/WorkDone/IClientWorkDoneManager.cs b/src/Protocol/Client/WorkDone/IClientWorkDoneManager.cs index 7d3d318f9..527e668d1 100644 --- a/src/Protocol/Client/WorkDone/IClientWorkDoneManager.cs +++ b/src/Protocol/Client/WorkDone/IClientWorkDoneManager.cs @@ -1,5 +1,4 @@ -using DynamicData; -using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; +using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Protocol.Progress; @@ -9,7 +8,6 @@ public interface IClientWorkDoneManager { void Initialize(WindowClientCapabilities windowClientCapabilities); bool IsSupported { get; } - IObservableCache, ProgressToken> PendingWork { get; } IProgressObservable Monitor(ProgressToken progressToken); } } diff --git a/src/Protocol/DelegatingHandlers.cs b/src/Protocol/DelegatingHandlers.cs index cc7cad4fc..9fc124343 100644 --- a/src/Protocol/DelegatingHandlers.cs +++ b/src/Protocol/DelegatingHandlers.cs @@ -13,6 +13,7 @@ namespace OmniSharp.Extensions.LanguageServer.Protocol { + public static class LanguageProtocolDelegatingHandlers { public sealed class Request : @@ -709,7 +710,7 @@ async Task IRequestHandler.Handle(TParams request } public sealed class Notification : - IJsonRpcRequestHandler, + IJsonRpcNotificationHandler, IRegistration, ICapability where TParams : IRequest where TRegistrationOptions : class, new() @@ -762,7 +763,7 @@ async Task IRequestHandler.Handle(TParams request, Cancella } public sealed class Notification : - IJsonRpcRequestHandler, + IJsonRpcNotificationHandler, IRegistration where TParams : IRequest where TRegistrationOptions : class, new() @@ -812,7 +813,7 @@ async Task IRequestHandler.Handle(TParams request, Cancella } public sealed class NotificationCapability : - IJsonRpcRequestHandler, ICapability + IJsonRpcNotificationHandler, ICapability where TParams : IRequest where TCapability : ICapability { diff --git a/src/Protocol/Document/ICodeActionHandler.cs b/src/Protocol/Document/ICodeActionHandler.cs index 8a3b14f04..496bb2580 100644 --- a/src/Protocol/Document/ICodeActionHandler.cs +++ b/src/Protocol/Document/ICodeActionHandler.cs @@ -38,8 +38,7 @@ public abstract Task Handle(CodeActionParams reque public static class CodeActionExtensions { - public static IDisposable OnCodeAction( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnCodeAction(this ILanguageServerRegistry registry, Func> handler, CodeActionRegistrationOptions registrationOptions) @@ -50,8 +49,7 @@ public static IDisposable OnCodeAction( CodeActionRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnCodeAction( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnCodeAction(this ILanguageServerRegistry registry, Func> handler, CodeActionRegistrationOptions registrationOptions) { @@ -61,8 +59,7 @@ public static IDisposable OnCodeAction( CodeActionRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnCodeAction( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnCodeAction(this ILanguageServerRegistry registry, Func> handler, CodeActionRegistrationOptions registrationOptions) { @@ -72,8 +69,7 @@ public static IDisposable OnCodeAction( CodeActionRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnCodeAction( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnCodeAction(this ILanguageServerRegistry registry, Action>, CodeActionCapability, CancellationToken> handler, CodeActionRegistrationOptions registrationOptions) @@ -86,8 +82,7 @@ public static IDisposable OnCodeAction( registrationOptions, _.GetService(), x => new CommandOrCodeActionContainer(x))); } - public static IDisposable OnCodeAction( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnCodeAction(this ILanguageServerRegistry registry, Action>, CodeActionCapability> handler, CodeActionRegistrationOptions registrationOptions) @@ -100,8 +95,7 @@ public static IDisposable OnCodeAction( registrationOptions, _.GetService(), x => new CommandOrCodeActionContainer(x))); } - public static IDisposable OnCodeAction( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnCodeAction(this ILanguageServerRegistry registry, Action>, CancellationToken> handler, CodeActionRegistrationOptions registrationOptions) { @@ -113,8 +107,7 @@ public static IDisposable OnCodeAction( _.GetService(), x => new CommandOrCodeActionContainer(x))); } - public static IDisposable OnCodeAction( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnCodeAction(this ILanguageServerRegistry registry, Action>> handler, CodeActionRegistrationOptions registrationOptions) { diff --git a/src/Protocol/Document/ICodeLensHandler.cs b/src/Protocol/Document/ICodeLensHandler.cs index acf95027b..653fb732c 100644 --- a/src/Protocol/Document/ICodeLensHandler.cs +++ b/src/Protocol/Document/ICodeLensHandler.cs @@ -14,14 +14,19 @@ namespace OmniSharp.Extensions.LanguageServer.Protocol.Document { [Parallel, Method(TextDocumentNames.CodeLens, Direction.ClientToServer)] - public interface ICodeLensHandler : IJsonRpcRequestHandler, IRegistration, ICapability { } + public interface ICodeLensHandler : IJsonRpcRequestHandler, IRegistration, ICapability + { + } [Parallel, Method(TextDocumentNames.CodeLensResolve, Direction.ClientToServer)] - public interface ICodeLensResolveHandler : ICanBeResolvedHandler { } + public interface ICodeLensResolveHandler : ICanBeResolvedHandler + { + } public abstract class CodeLensHandler : ICodeLensHandler, ICodeLensResolveHandler { private readonly CodeLensRegistrationOptions _options; + public CodeLensHandler(CodeLensRegistrationOptions registrationOptions) { _options = registrationOptions; @@ -37,8 +42,7 @@ public CodeLensHandler(CodeLensRegistrationOptions registrationOptions) public static class CodeLensExtensions { - public static IDisposable OnCodeLens( - this ILanguageServerRegistry registry, + public static ILanguageServerRegistry OnCodeLens(this ILanguageServerRegistry registry, Func> handler, CodeLensRegistrationOptions registrationOptions) { @@ -51,8 +55,7 @@ public static IDisposable OnCodeLens( registrationOptions)); } - public static IDisposable OnCodeLens( - this ILanguageServerRegistry registry, + public static ILanguageServerRegistry OnCodeLens(this ILanguageServerRegistry registry, Func> handler, Func canResolve, Func> resolveHandler, @@ -63,21 +66,20 @@ public static IDisposable OnCodeLens( canResolve ??= item => registrationOptions.ResolveProvider; resolveHandler ??= (link, cap, token) => Task.FromException(new NotImplementedException()); - return new CompositeDisposable( - registry.AddHandler(TextDocumentNames.CodeLens, - new LanguageProtocolDelegatingHandlers.Request( - handler, - registrationOptions)), - registry.AddHandler(TextDocumentNames.CodeLensResolve, - new LanguageProtocolDelegatingHandlers.CanBeResolved( - resolveHandler, - canResolve, - registrationOptions)) - ); + return registry.AddHandler(TextDocumentNames.CodeLens, + new LanguageProtocolDelegatingHandlers.Request( + handler, + registrationOptions)) + .AddHandler(TextDocumentNames.CodeLensResolve, + new LanguageProtocolDelegatingHandlers.CanBeResolved( + resolveHandler, + canResolve, + registrationOptions)) + ; } - public static IDisposable OnCodeLens( - this ILanguageServerRegistry registry, + + public static ILanguageServerRegistry OnCodeLens(this ILanguageServerRegistry registry, Func> handler, CodeLensRegistrationOptions registrationOptions) { @@ -90,8 +92,7 @@ public static IDisposable OnCodeLens( registrationOptions)); } - public static IDisposable OnCodeLens( - this ILanguageServerRegistry registry, + public static ILanguageServerRegistry OnCodeLens(this ILanguageServerRegistry registry, Func> handler, Func canResolve, Func> resolveHandler, @@ -102,21 +103,20 @@ public static IDisposable OnCodeLens( canResolve ??= item => registrationOptions.ResolveProvider; resolveHandler ??= (link, token) => Task.FromException(new NotImplementedException()); - return new CompositeDisposable( - registry.AddHandler(TextDocumentNames.CodeLens, - new LanguageProtocolDelegatingHandlers.RequestRegistration( - handler, - registrationOptions)), - registry.AddHandler(TextDocumentNames.CodeLensResolve, - new LanguageProtocolDelegatingHandlers.CanBeResolved( - resolveHandler, - canResolve, - registrationOptions)) - ); + return registry.AddHandler(TextDocumentNames.CodeLens, + new LanguageProtocolDelegatingHandlers.RequestRegistration( + handler, + registrationOptions)) + .AddHandler(TextDocumentNames.CodeLensResolve, + new LanguageProtocolDelegatingHandlers.CanBeResolved( + resolveHandler, + canResolve, + registrationOptions)) + ; } - public static IDisposable OnCodeLens( - this ILanguageServerRegistry registry, + + public static ILanguageServerRegistry OnCodeLens(this ILanguageServerRegistry registry, Func> handler, CodeLensRegistrationOptions registrationOptions) { @@ -129,8 +129,7 @@ public static IDisposable OnCodeLens( registrationOptions)); } - public static IDisposable OnCodeLens( - this ILanguageServerRegistry registry, + public static ILanguageServerRegistry OnCodeLens(this ILanguageServerRegistry registry, Func> handler, Func canResolve, Func> resolveHandler, @@ -141,22 +140,20 @@ public static IDisposable OnCodeLens( canResolve ??= item => registrationOptions.ResolveProvider; resolveHandler ??= (link) => Task.FromException(new NotImplementedException()); - return new CompositeDisposable( - registry.AddHandler(TextDocumentNames.CodeLens, - new LanguageProtocolDelegatingHandlers.RequestRegistration( - handler, - registrationOptions)), - registry.AddHandler(TextDocumentNames.CodeLensResolve, - new LanguageProtocolDelegatingHandlers.CanBeResolved( - resolveHandler, - canResolve, - registrationOptions)) - ); + return registry.AddHandler(TextDocumentNames.CodeLens, + new LanguageProtocolDelegatingHandlers.RequestRegistration( + handler, + registrationOptions)) + .AddHandler(TextDocumentNames.CodeLensResolve, + new LanguageProtocolDelegatingHandlers.CanBeResolved( + resolveHandler, + canResolve, + registrationOptions)) + ; } - public static IDisposable OnCodeLens( - this ILanguageServerRegistry registry, + public static ILanguageServerRegistry OnCodeLens(this ILanguageServerRegistry registry, Action>, CodeLensCapability, CancellationToken> handler, CodeLensRegistrationOptions registrationOptions) { @@ -171,8 +168,7 @@ public static IDisposable OnCodeLens( x => new CodeLensContainer(x))); } - public static IDisposable OnCodeLens( - this ILanguageServerRegistry registry, + public static ILanguageServerRegistry OnCodeLens(this ILanguageServerRegistry registry, Action>, CodeLensCapability, CancellationToken> handler, Func canResolve, Func> resolveHandler, @@ -183,30 +179,30 @@ public static IDisposable OnCodeLens( canResolve ??= item => registrationOptions.ResolveProvider; resolveHandler ??= (link, cap, token) => Task.FromException(new NotImplementedException()); - return new CompositeDisposable( + return registry.AddHandler(TextDocumentNames.CodeLens, - _ => new LanguageProtocolDelegatingHandlers.PartialResults( - handler, - registrationOptions, - _.GetRequiredService(), - x => new CodeLensContainer(x))), - registry.AddHandler(TextDocumentNames.CodeLensResolve, - new LanguageProtocolDelegatingHandlers.CanBeResolved( - resolveHandler, - canResolve, - registrationOptions)) - ); + _ => new LanguageProtocolDelegatingHandlers.PartialResults( + handler, + registrationOptions, + _.GetRequiredService(), + x => new CodeLensContainer(x))) + .AddHandler(TextDocumentNames.CodeLensResolve, + new LanguageProtocolDelegatingHandlers.CanBeResolved( + resolveHandler, + canResolve, + registrationOptions)) + ; } - public static IDisposable OnCodeLens( - this ILanguageServerRegistry registry, + + public static ILanguageServerRegistry OnCodeLens(this ILanguageServerRegistry registry, Action>, CancellationToken> handler, CodeLensRegistrationOptions registrationOptions) { registrationOptions ??= new CodeLensRegistrationOptions(); return registry.AddHandler(TextDocumentNames.CodeLens, - _=> new LanguageProtocolDelegatingHandlers.PartialResults new LanguageProtocolDelegatingHandlers.PartialResults( handler, registrationOptions, @@ -214,8 +210,7 @@ public static IDisposable OnCodeLens( x => new CodeLensContainer(x))); } - public static IDisposable OnCodeLens( - this ILanguageServerRegistry registry, + public static ILanguageServerRegistry OnCodeLens(this ILanguageServerRegistry registry, Action>, CancellationToken> handler, Func canResolve, Func> resolveHandler, @@ -226,30 +221,29 @@ public static IDisposable OnCodeLens( canResolve ??= item => registrationOptions.ResolveProvider; resolveHandler ??= (link, token) => Task.FromException(new NotImplementedException()); - return new CompositeDisposable( - registry.AddHandler(TextDocumentNames.CodeLens, - _=> new LanguageProtocolDelegatingHandlers.PartialResults( - handler, - registrationOptions, - _.GetRequiredService(), - x => new CodeLensContainer(x))), - registry.AddHandler(TextDocumentNames.CodeLensResolve, - new LanguageProtocolDelegatingHandlers.CanBeResolved( - resolveHandler, - canResolve, - registrationOptions)) - ); + return registry.AddHandler(TextDocumentNames.CodeLens, + _ => new LanguageProtocolDelegatingHandlers.PartialResults( + handler, + registrationOptions, + _.GetRequiredService(), + x => new CodeLensContainer(x))) + .AddHandler(TextDocumentNames.CodeLensResolve, + new LanguageProtocolDelegatingHandlers.CanBeResolved( + resolveHandler, + canResolve, + registrationOptions)) + ; } - public static IDisposable OnCodeLens( - this ILanguageServerRegistry registry, + + public static ILanguageServerRegistry OnCodeLens(this ILanguageServerRegistry registry, Action>> handler, CodeLensRegistrationOptions registrationOptions) { registrationOptions ??= new CodeLensRegistrationOptions(); return registry.AddHandler(TextDocumentNames.CodeLens, - _=> new LanguageProtocolDelegatingHandlers.PartialResults new LanguageProtocolDelegatingHandlers.PartialResults( handler, registrationOptions, @@ -257,8 +251,7 @@ public static IDisposable OnCodeLens( x => new CodeLensContainer(x))); } - public static IDisposable OnCodeLens( - this ILanguageServerRegistry registry, + public static ILanguageServerRegistry OnCodeLens(this ILanguageServerRegistry registry, Action>> handler, Func canResolve, Func> resolveHandler, @@ -269,20 +262,19 @@ public static IDisposable OnCodeLens( canResolve ??= item => registrationOptions.ResolveProvider; resolveHandler ??= (link) => Task.FromException(new NotImplementedException()); - return new CompositeDisposable( - registry.AddHandler(TextDocumentNames.CodeLens, - _=> new LanguageProtocolDelegatingHandlers.PartialResults( - handler, - registrationOptions, - _.GetRequiredService(), - x => new CodeLensContainer(x))), - registry.AddHandler(TextDocumentNames.CodeLensResolve, - new LanguageProtocolDelegatingHandlers.CanBeResolved( - resolveHandler, - canResolve, - registrationOptions)) - ); + return registry.AddHandler(TextDocumentNames.CodeLens, + _ => new LanguageProtocolDelegatingHandlers.PartialResults( + handler, + registrationOptions, + _.GetRequiredService(), + x => new CodeLensContainer(x))) + .AddHandler(TextDocumentNames.CodeLensResolve, + new LanguageProtocolDelegatingHandlers.CanBeResolved( + resolveHandler, + canResolve, + registrationOptions)) + ; } public static IRequestProgressObservable, CodeLensContainer> RequestCodeLens( diff --git a/src/Protocol/Document/IColorPresentationHandler.cs b/src/Protocol/Document/IColorPresentationHandler.cs index 830aa35b9..97fbcd840 100644 --- a/src/Protocol/Document/IColorPresentationHandler.cs +++ b/src/Protocol/Document/IColorPresentationHandler.cs @@ -28,8 +28,7 @@ public ColorPresentationHandler(DocumentColorRegistrationOptions registrationOpt public static class ColorPresentationExtensions { - public static IDisposable OnColorPresentation( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnColorPresentation(this ILanguageServerRegistry registry, Func>> handler, DocumentColorRegistrationOptions registrationOptions) @@ -40,8 +39,7 @@ public static IDisposable OnColorPresentation( DocumentColorRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnColorPresentation( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnColorPresentation(this ILanguageServerRegistry registry, Func>> handler, DocumentColorRegistrationOptions registrationOptions) { @@ -51,8 +49,7 @@ public static IDisposable OnColorPresentation( DocumentColorRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnColorPresentation( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnColorPresentation(this ILanguageServerRegistry registry, Func>> handler, DocumentColorRegistrationOptions registrationOptions) { diff --git a/src/Protocol/Document/ICompletionHandler.cs b/src/Protocol/Document/ICompletionHandler.cs index 39266c992..0131434b7 100644 --- a/src/Protocol/Document/ICompletionHandler.cs +++ b/src/Protocol/Document/ICompletionHandler.cs @@ -14,14 +14,19 @@ namespace OmniSharp.Extensions.LanguageServer.Protocol.Document { [Parallel, Method(TextDocumentNames.Completion, Direction.ClientToServer)] - public interface ICompletionHandler : IJsonRpcRequestHandler, IRegistration, ICapability { } + public interface ICompletionHandler : IJsonRpcRequestHandler, IRegistration, ICapability + { + } [Parallel, Method(TextDocumentNames.CompletionResolve, Direction.ClientToServer)] - public interface ICompletionResolveHandler : ICanBeResolvedHandler { } + public interface ICompletionResolveHandler : ICanBeResolvedHandler + { + } public abstract class CompletionHandler : ICompletionHandler, ICompletionResolveHandler { private readonly CompletionRegistrationOptions _options; + public CompletionHandler(CompletionRegistrationOptions registrationOptions) { _options = registrationOptions; @@ -37,7 +42,7 @@ public CompletionHandler(CompletionRegistrationOptions registrationOptions) public static class CompletionExtensions { - public static IDisposable OnCompletion( + public static ILanguageServerRegistry OnCompletion( this ILanguageServerRegistry registry, Func> handler, CompletionRegistrationOptions registrationOptions) @@ -51,7 +56,7 @@ public static IDisposable OnCompletion( registrationOptions)); } - public static IDisposable OnCompletion( + public static ILanguageServerRegistry OnCompletion( this ILanguageServerRegistry registry, Func> handler, Func canResolve, @@ -63,21 +68,21 @@ public static IDisposable OnCompletion( canResolve ??= item => registrationOptions.ResolveProvider; resolveHandler ??= (link, cap, token) => Task.FromException(new NotImplementedException()); - return new CompositeDisposable( - registry.AddHandler(TextDocumentNames.Completion, + return registry + .AddHandler(TextDocumentNames.Completion, new LanguageProtocolDelegatingHandlers.Request( handler, - registrationOptions)), - registry.AddHandler(TextDocumentNames.CompletionResolve, + registrationOptions) + ) + .AddHandler(TextDocumentNames.CompletionResolve, new LanguageProtocolDelegatingHandlers.CanBeResolved( resolveHandler, canResolve, - registrationOptions)) - ); + registrationOptions)); } - public static IDisposable OnCompletion( - this ILanguageServerRegistry registry, + + public static ILanguageServerRegistry OnCompletion(this ILanguageServerRegistry registry, Func> handler, CompletionRegistrationOptions registrationOptions) { @@ -90,8 +95,7 @@ public static IDisposable OnCompletion( registrationOptions)); } - public static IDisposable OnCompletion( - this ILanguageServerRegistry registry, + public static ILanguageServerRegistry OnCompletion(this ILanguageServerRegistry registry, Func> handler, Func canResolve, Func> resolveHandler, @@ -102,21 +106,20 @@ public static IDisposable OnCompletion( canResolve ??= item => registrationOptions.ResolveProvider; resolveHandler ??= (link, token) => Task.FromException(new NotImplementedException()); - return new CompositeDisposable( - registry.AddHandler(TextDocumentNames.Completion, + return registry + .AddHandler(TextDocumentNames.Completion, new LanguageProtocolDelegatingHandlers.RequestRegistration( handler, - registrationOptions)), - registry.AddHandler(TextDocumentNames.CompletionResolve, + registrationOptions)) + .AddHandler(TextDocumentNames.CompletionResolve, new LanguageProtocolDelegatingHandlers.CanBeResolved( resolveHandler, canResolve, - registrationOptions)) - ); + registrationOptions)); } - public static IDisposable OnCompletion( - this ILanguageServerRegistry registry, + + public static ILanguageServerRegistry OnCompletion(this ILanguageServerRegistry registry, Func> handler, CompletionRegistrationOptions registrationOptions) { @@ -129,8 +132,7 @@ public static IDisposable OnCompletion( registrationOptions)); } - public static IDisposable OnCompletion( - this ILanguageServerRegistry registry, + public static ILanguageServerRegistry OnCompletion(this ILanguageServerRegistry registry, Func> handler, Func canResolve, Func> resolveHandler, @@ -141,22 +143,21 @@ public static IDisposable OnCompletion( canResolve ??= item => registrationOptions.ResolveProvider; resolveHandler ??= (link) => Task.FromException(new NotImplementedException()); - return new CompositeDisposable( - registry.AddHandler(TextDocumentNames.Completion, + return registry + .AddHandler(TextDocumentNames.Completion, new LanguageProtocolDelegatingHandlers.RequestRegistration( handler, - registrationOptions)), - registry.AddHandler(TextDocumentNames.CompletionResolve, + registrationOptions) + ) + .AddHandler(TextDocumentNames.CompletionResolve, new LanguageProtocolDelegatingHandlers.CanBeResolved( resolveHandler, canResolve, - registrationOptions)) - ); + registrationOptions)); } - public static IDisposable OnCompletion( - this ILanguageServerRegistry registry, + public static ILanguageServerRegistry OnCompletion(this ILanguageServerRegistry registry, Action>, CompletionCapability, CancellationToken> handler, CompletionRegistrationOptions registrationOptions) { @@ -171,8 +172,7 @@ public static IDisposable OnCompletion( x => new CompletionList(x))); } - public static IDisposable OnCompletion( - this ILanguageServerRegistry registry, + public static ILanguageServerRegistry OnCompletion(this ILanguageServerRegistry registry, Action>, CompletionCapability, CancellationToken> handler, Func canResolve, Func> resolveHandler, @@ -183,30 +183,29 @@ public static IDisposable OnCompletion( canResolve ??= item => registrationOptions.ResolveProvider; resolveHandler ??= (link, cap, token) => Task.FromException(new NotImplementedException()); - return new CompositeDisposable( - registry.AddHandler(TextDocumentNames.Completion, + return registry.AddHandler(TextDocumentNames.Completion, _ => new LanguageProtocolDelegatingHandlers.PartialResults( handler, registrationOptions, _.GetRequiredService(), - x => new CompletionList(x))), - registry.AddHandler(TextDocumentNames.CompletionResolve, + x => new CompletionList(x)) + ) + .AddHandler(TextDocumentNames.CompletionResolve, new LanguageProtocolDelegatingHandlers.CanBeResolved( resolveHandler, canResolve, - registrationOptions)) - ); + registrationOptions)); } - public static IDisposable OnCompletion( - this ILanguageServerRegistry registry, + + public static ILanguageServerRegistry OnCompletion(this ILanguageServerRegistry registry, Action>, CancellationToken> handler, CompletionRegistrationOptions registrationOptions) { registrationOptions ??= new CompletionRegistrationOptions(); return registry.AddHandler(TextDocumentNames.Completion, - _=> new LanguageProtocolDelegatingHandlers.PartialResults new LanguageProtocolDelegatingHandlers.PartialResults( handler, registrationOptions, @@ -214,8 +213,7 @@ public static IDisposable OnCompletion( x => new CompletionList(x))); } - public static IDisposable OnCompletion( - this ILanguageServerRegistry registry, + public static ILanguageServerRegistry OnCompletion(this ILanguageServerRegistry registry, Action>, CancellationToken> handler, Func canResolve, Func> resolveHandler, @@ -226,30 +224,28 @@ public static IDisposable OnCompletion( canResolve ??= item => registrationOptions.ResolveProvider; resolveHandler ??= (link, token) => Task.FromException(new NotImplementedException()); - return new CompositeDisposable( - registry.AddHandler(TextDocumentNames.Completion, - _=> new LanguageProtocolDelegatingHandlers.PartialResults new LanguageProtocolDelegatingHandlers.PartialResults( handler, registrationOptions, _.GetRequiredService(), - x => new CompletionList(x))), - registry.AddHandler(TextDocumentNames.CompletionResolve, + x => new CompletionList(x))) + .AddHandler(TextDocumentNames.CompletionResolve, new LanguageProtocolDelegatingHandlers.CanBeResolved( resolveHandler, canResolve, - registrationOptions)) - ); + registrationOptions)); } - public static IDisposable OnCompletion( - this ILanguageServerRegistry registry, + + public static ILanguageServerRegistry OnCompletion(this ILanguageServerRegistry registry, Action>> handler, CompletionRegistrationOptions registrationOptions) { registrationOptions ??= new CompletionRegistrationOptions(); return registry.AddHandler(TextDocumentNames.Completion, - _=> new LanguageProtocolDelegatingHandlers.PartialResults new LanguageProtocolDelegatingHandlers.PartialResults( handler, registrationOptions, @@ -257,8 +253,7 @@ public static IDisposable OnCompletion( x => new CompletionList(x))); } - public static IDisposable OnCompletion( - this ILanguageServerRegistry registry, + public static ILanguageServerRegistry OnCompletion(this ILanguageServerRegistry registry, Action>> handler, Func canResolve, Func> resolveHandler, @@ -269,23 +264,22 @@ public static IDisposable OnCompletion( canResolve ??= item => registrationOptions.ResolveProvider; resolveHandler ??= (link) => Task.FromException(new NotImplementedException()); - return new CompositeDisposable( - registry.AddHandler(TextDocumentNames.Completion, - _=> new LanguageProtocolDelegatingHandlers.PartialResults new LanguageProtocolDelegatingHandlers.PartialResults( handler, registrationOptions, _.GetRequiredService(), - x => new CompletionList(x))), - registry.AddHandler(TextDocumentNames.CompletionResolve, + x => new CompletionList(x))) + .AddHandler(TextDocumentNames.CompletionResolve, new LanguageProtocolDelegatingHandlers.CanBeResolved( resolveHandler, canResolve, - registrationOptions)) - ); + registrationOptions)); } - public static IRequestProgressObservable, CompletionList> RequestCompletion(this ITextDocumentLanguageClient mediator, CompletionParams @params, CancellationToken cancellationToken = default) + public static IRequestProgressObservable, CompletionList> RequestCompletion(this ITextDocumentLanguageClient mediator, CompletionParams @params, + CancellationToken cancellationToken = default) { return mediator.ProgressManager.MonitorUntil(@params, x => new CompletionList(x), cancellationToken); } diff --git a/src/Protocol/Document/IDeclarationHandler.cs b/src/Protocol/Document/IDeclarationHandler.cs index 320ccfed5..a62ece19b 100644 --- a/src/Protocol/Document/IDeclarationHandler.cs +++ b/src/Protocol/Document/IDeclarationHandler.cs @@ -31,8 +31,7 @@ public DeclarationHandler(DeclarationRegistrationOptions registrationOptions) public static class DeclarationExtensions { - public static IDisposable OnDeclaration( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDeclaration(this ILanguageServerRegistry registry, Func> handler, DeclarationRegistrationOptions registrationOptions) @@ -43,8 +42,7 @@ public static IDisposable OnDeclaration( DeclarationRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDeclaration( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDeclaration(this ILanguageServerRegistry registry, Func> handler, DeclarationRegistrationOptions registrationOptions) { @@ -54,8 +52,7 @@ public static IDisposable OnDeclaration( DeclarationRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDeclaration( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDeclaration(this ILanguageServerRegistry registry, Func> handler, DeclarationRegistrationOptions registrationOptions) { @@ -65,8 +62,7 @@ public static IDisposable OnDeclaration( DeclarationRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDeclaration( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDeclaration(this ILanguageServerRegistry registry, Func> handler, DeclarationRegistrationOptions registrationOptions) { @@ -76,8 +72,7 @@ public static IDisposable OnDeclaration( DeclarationRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDeclaration( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDeclaration(this ILanguageServerRegistry registry, Action>, DeclarationCapability, CancellationToken> handler, DeclarationRegistrationOptions registrationOptions) @@ -91,8 +86,7 @@ public static IDisposable OnDeclaration( x => new LocationOrLocationLinks(x))); } - public static IDisposable OnDeclaration( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDeclaration(this ILanguageServerRegistry registry, Action>, DeclarationCapability> handler, DeclarationRegistrationOptions registrationOptions) @@ -107,8 +101,7 @@ public static IDisposable OnDeclaration( )); } - public static IDisposable OnDeclaration( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDeclaration(this ILanguageServerRegistry registry, Action>, CancellationToken> handler, DeclarationRegistrationOptions registrationOptions) { @@ -121,8 +114,7 @@ public static IDisposable OnDeclaration( x => new LocationOrLocationLinks(x))); } - public static IDisposable OnDeclaration( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDeclaration(this ILanguageServerRegistry registry, Action>> handler, DeclarationRegistrationOptions registrationOptions) { diff --git a/src/Protocol/Document/IDefinitionHandler.cs b/src/Protocol/Document/IDefinitionHandler.cs index fb2c4fbc1..c2210a35a 100644 --- a/src/Protocol/Document/IDefinitionHandler.cs +++ b/src/Protocol/Document/IDefinitionHandler.cs @@ -31,8 +31,7 @@ public DefinitionHandler(DefinitionRegistrationOptions registrationOptions) public static class DefinitionExtensions { - public static IDisposable OnDefinition( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDefinition(this ILanguageServerRegistry registry, Func> handler, DefinitionRegistrationOptions registrationOptions) @@ -43,8 +42,7 @@ public static IDisposable OnDefinition( DefinitionRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDefinition( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDefinition(this ILanguageServerRegistry registry, Func> handler, DefinitionRegistrationOptions registrationOptions) { @@ -54,8 +52,7 @@ public static IDisposable OnDefinition( DefinitionRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDefinition( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDefinition(this ILanguageServerRegistry registry, Func> handler, DefinitionRegistrationOptions registrationOptions) { @@ -65,8 +62,7 @@ public static IDisposable OnDefinition( DefinitionRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDefinition( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDefinition(this ILanguageServerRegistry registry, Action>, DefinitionCapability, CancellationToken> handler, DefinitionRegistrationOptions registrationOptions) @@ -80,8 +76,7 @@ public static IDisposable OnDefinition( x => new LocationOrLocationLinks(x))); } - public static IDisposable OnDefinition( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDefinition(this ILanguageServerRegistry registry, Action>, DefinitionCapability> handler, DefinitionRegistrationOptions registrationOptions) @@ -95,8 +90,7 @@ public static IDisposable OnDefinition( x => new LocationOrLocationLinks(x))); } - public static IDisposable OnDefinition( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDefinition(this ILanguageServerRegistry registry, Action>, CancellationToken> handler, DefinitionRegistrationOptions registrationOptions) { @@ -109,8 +103,7 @@ public static IDisposable OnDefinition( x => new LocationOrLocationLinks(x))); } - public static IDisposable OnDefinition( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDefinition(this ILanguageServerRegistry registry, Action>> handler, DefinitionRegistrationOptions registrationOptions) { diff --git a/src/Protocol/Document/IDidChangeTextDocumentHandler.cs b/src/Protocol/Document/IDidChangeTextDocumentHandler.cs index 58dd5ff06..da7ce9398 100644 --- a/src/Protocol/Document/IDidChangeTextDocumentHandler.cs +++ b/src/Protocol/Document/IDidChangeTextDocumentHandler.cs @@ -31,8 +31,7 @@ public DidChangeTextDocumentHandler(TextDocumentChangeRegistrationOptions regist public static class DidChangeTextDocumentExtensions { - public static IDisposable OnDidChangeTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeTextDocument(this ILanguageServerRegistry registry, Action handler, TextDocumentChangeRegistrationOptions registrationOptions) { @@ -42,8 +41,7 @@ public static IDisposable OnDidChangeTextDocument( TextDocumentChangeRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidChangeTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeTextDocument(this ILanguageServerRegistry registry, Action handler, TextDocumentChangeRegistrationOptions registrationOptions) { @@ -53,8 +51,7 @@ public static IDisposable OnDidChangeTextDocument( TextDocumentChangeRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidChangeTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeTextDocument(this ILanguageServerRegistry registry, Action handler, TextDocumentChangeRegistrationOptions registrationOptions) { @@ -64,8 +61,7 @@ public static IDisposable OnDidChangeTextDocument( TextDocumentChangeRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidChangeTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeTextDocument(this ILanguageServerRegistry registry, Action handler, TextDocumentChangeRegistrationOptions registrationOptions) { @@ -75,8 +71,7 @@ public static IDisposable OnDidChangeTextDocument( TextDocumentChangeRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidChangeTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeTextDocument(this ILanguageServerRegistry registry, Func handler, TextDocumentChangeRegistrationOptions registrationOptions) { @@ -86,8 +81,7 @@ public static IDisposable OnDidChangeTextDocument( TextDocumentChangeRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidChangeTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeTextDocument(this ILanguageServerRegistry registry, Func handler, TextDocumentChangeRegistrationOptions registrationOptions) { @@ -97,8 +91,7 @@ public static IDisposable OnDidChangeTextDocument( TextDocumentChangeRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidChangeTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeTextDocument(this ILanguageServerRegistry registry, Func handler, TextDocumentChangeRegistrationOptions registrationOptions) { @@ -108,8 +101,7 @@ public static IDisposable OnDidChangeTextDocument( TextDocumentChangeRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidChangeTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeTextDocument(this ILanguageServerRegistry registry, Func handler, TextDocumentChangeRegistrationOptions registrationOptions) { diff --git a/src/Protocol/Document/IDidCloseTextDocumentHandler.cs b/src/Protocol/Document/IDidCloseTextDocumentHandler.cs index f896a8d8d..456248552 100644 --- a/src/Protocol/Document/IDidCloseTextDocumentHandler.cs +++ b/src/Protocol/Document/IDidCloseTextDocumentHandler.cs @@ -29,8 +29,7 @@ public DidCloseTextDocumentHandler(TextDocumentRegistrationOptions registrationO public static class DidCloseTextDocumentExtensions { - public static IDisposable OnDidCloseTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidCloseTextDocument(this ILanguageServerRegistry registry, Action handler, TextDocumentRegistrationOptions registrationOptions) { @@ -40,8 +39,7 @@ public static IDisposable OnDidCloseTextDocument( TextDocumentRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidCloseTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidCloseTextDocument(this ILanguageServerRegistry registry, Action handler, TextDocumentRegistrationOptions registrationOptions) { @@ -51,8 +49,7 @@ public static IDisposable OnDidCloseTextDocument( TextDocumentRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidCloseTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidCloseTextDocument(this ILanguageServerRegistry registry, Action handler, TextDocumentRegistrationOptions registrationOptions) { @@ -62,8 +59,7 @@ public static IDisposable OnDidCloseTextDocument( TextDocumentRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidCloseTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidCloseTextDocument(this ILanguageServerRegistry registry, Action handler, TextDocumentRegistrationOptions registrationOptions) { @@ -73,8 +69,7 @@ public static IDisposable OnDidCloseTextDocument( TextDocumentRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidCloseTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidCloseTextDocument(this ILanguageServerRegistry registry, Func handler, TextDocumentRegistrationOptions registrationOptions) { @@ -84,8 +79,7 @@ public static IDisposable OnDidCloseTextDocument( TextDocumentRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidCloseTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidCloseTextDocument(this ILanguageServerRegistry registry, Func handler, TextDocumentRegistrationOptions registrationOptions) { @@ -95,8 +89,7 @@ public static IDisposable OnDidCloseTextDocument( TextDocumentRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidCloseTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidCloseTextDocument(this ILanguageServerRegistry registry, Func handler, TextDocumentRegistrationOptions registrationOptions) { @@ -106,8 +99,7 @@ public static IDisposable OnDidCloseTextDocument( TextDocumentRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidCloseTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidCloseTextDocument(this ILanguageServerRegistry registry, Func handler, TextDocumentRegistrationOptions registrationOptions) { diff --git a/src/Protocol/Document/IDidOpenTextDocumentHandler.cs b/src/Protocol/Document/IDidOpenTextDocumentHandler.cs index 9da19f99d..d492243c0 100644 --- a/src/Protocol/Document/IDidOpenTextDocumentHandler.cs +++ b/src/Protocol/Document/IDidOpenTextDocumentHandler.cs @@ -29,8 +29,7 @@ public DidOpenTextDocumentHandler(TextDocumentRegistrationOptions registrationOp public static class DidOpenTextDocumentExtensions { - public static IDisposable OnDidOpenTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidOpenTextDocument(this ILanguageServerRegistry registry, Action handler, TextDocumentRegistrationOptions registrationOptions) { @@ -40,8 +39,7 @@ public static IDisposable OnDidOpenTextDocument( TextDocumentRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidOpenTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidOpenTextDocument(this ILanguageServerRegistry registry, Action handler, TextDocumentRegistrationOptions registrationOptions) { @@ -51,8 +49,7 @@ public static IDisposable OnDidOpenTextDocument( TextDocumentRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidOpenTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidOpenTextDocument(this ILanguageServerRegistry registry, Action handler, TextDocumentRegistrationOptions registrationOptions) { @@ -62,8 +59,7 @@ public static IDisposable OnDidOpenTextDocument( TextDocumentRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidOpenTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidOpenTextDocument(this ILanguageServerRegistry registry, Action handler, TextDocumentRegistrationOptions registrationOptions) { @@ -73,8 +69,7 @@ public static IDisposable OnDidOpenTextDocument( TextDocumentRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidOpenTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidOpenTextDocument(this ILanguageServerRegistry registry, Func handler, TextDocumentRegistrationOptions registrationOptions) { @@ -84,8 +79,7 @@ public static IDisposable OnDidOpenTextDocument( TextDocumentRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidOpenTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidOpenTextDocument(this ILanguageServerRegistry registry, Func handler, TextDocumentRegistrationOptions registrationOptions) { @@ -95,8 +89,7 @@ public static IDisposable OnDidOpenTextDocument( TextDocumentRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidOpenTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidOpenTextDocument(this ILanguageServerRegistry registry, Func handler, TextDocumentRegistrationOptions registrationOptions) { @@ -106,8 +99,7 @@ public static IDisposable OnDidOpenTextDocument( TextDocumentRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidOpenTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidOpenTextDocument(this ILanguageServerRegistry registry, Func handler, TextDocumentRegistrationOptions registrationOptions) { diff --git a/src/Protocol/Document/IDidSaveTextDocumentHandler.cs b/src/Protocol/Document/IDidSaveTextDocumentHandler.cs index f38b4842e..495978087 100644 --- a/src/Protocol/Document/IDidSaveTextDocumentHandler.cs +++ b/src/Protocol/Document/IDidSaveTextDocumentHandler.cs @@ -29,8 +29,7 @@ public DidSaveTextDocumentHandler(TextDocumentSaveRegistrationOptions registrati public static class DidSaveTextDocumentExtensions { - public static IDisposable OnDidSaveTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidSaveTextDocument(this ILanguageServerRegistry registry, Action handler, TextDocumentSaveRegistrationOptions registrationOptions) { @@ -40,8 +39,7 @@ public static IDisposable OnDidSaveTextDocument( TextDocumentSaveRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidSaveTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidSaveTextDocument(this ILanguageServerRegistry registry, Action handler, TextDocumentSaveRegistrationOptions registrationOptions) { @@ -51,8 +49,7 @@ public static IDisposable OnDidSaveTextDocument( TextDocumentSaveRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidSaveTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidSaveTextDocument(this ILanguageServerRegistry registry, Action handler, TextDocumentSaveRegistrationOptions registrationOptions) { @@ -62,8 +59,7 @@ public static IDisposable OnDidSaveTextDocument( TextDocumentSaveRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidSaveTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidSaveTextDocument(this ILanguageServerRegistry registry, Action handler, TextDocumentSaveRegistrationOptions registrationOptions) { @@ -73,8 +69,7 @@ public static IDisposable OnDidSaveTextDocument( TextDocumentSaveRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidSaveTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidSaveTextDocument(this ILanguageServerRegistry registry, Func handler, TextDocumentSaveRegistrationOptions registrationOptions) { @@ -84,8 +79,7 @@ public static IDisposable OnDidSaveTextDocument( TextDocumentSaveRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidSaveTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidSaveTextDocument(this ILanguageServerRegistry registry, Func handler, TextDocumentSaveRegistrationOptions registrationOptions) { @@ -95,8 +89,7 @@ public static IDisposable OnDidSaveTextDocument( TextDocumentSaveRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidSaveTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidSaveTextDocument(this ILanguageServerRegistry registry, Func handler, TextDocumentSaveRegistrationOptions registrationOptions) { @@ -106,8 +99,7 @@ public static IDisposable OnDidSaveTextDocument( TextDocumentSaveRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidSaveTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidSaveTextDocument(this ILanguageServerRegistry registry, Func handler, TextDocumentSaveRegistrationOptions registrationOptions) { diff --git a/src/Protocol/Document/IDocumentColorHandler.cs b/src/Protocol/Document/IDocumentColorHandler.cs index 3be18ce5f..c2c5c0d80 100644 --- a/src/Protocol/Document/IDocumentColorHandler.cs +++ b/src/Protocol/Document/IDocumentColorHandler.cs @@ -31,8 +31,7 @@ public DocumentColorHandler(DocumentColorRegistrationOptions registrationOptions public static class DocumentColorExtensions { - public static IDisposable OnDocumentColor( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentColor(this ILanguageServerRegistry registry, Func>> handler, DocumentColorRegistrationOptions registrationOptions) @@ -43,8 +42,7 @@ public static IDisposable OnDocumentColor( DocumentColorRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDocumentColor( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentColor(this ILanguageServerRegistry registry, Func>> handler, DocumentColorRegistrationOptions registrationOptions) { @@ -54,8 +52,7 @@ public static IDisposable OnDocumentColor( DocumentColorRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDocumentColor( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentColor(this ILanguageServerRegistry registry, Func>> handler, DocumentColorRegistrationOptions registrationOptions) { @@ -65,8 +62,7 @@ public static IDisposable OnDocumentColor( DocumentColorRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDocumentColor( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentColor(this ILanguageServerRegistry registry, Action>, ColorProviderCapability, CancellationToken> handler, DocumentColorRegistrationOptions registrationOptions) @@ -80,8 +76,7 @@ public static IDisposable OnDocumentColor( x => new Container(x))); } - public static IDisposable OnDocumentColor( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentColor(this ILanguageServerRegistry registry, Action>, ColorProviderCapability> handler, DocumentColorRegistrationOptions registrationOptions) @@ -95,8 +90,7 @@ public static IDisposable OnDocumentColor( x => new Container(x))); } - public static IDisposable OnDocumentColor( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentColor(this ILanguageServerRegistry registry, Action>, CancellationToken> handler, DocumentColorRegistrationOptions registrationOptions) { @@ -109,8 +103,7 @@ public static IDisposable OnDocumentColor( x => new Container(x))); } - public static IDisposable OnDocumentColor( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentColor(this ILanguageServerRegistry registry, Action>> handler, DocumentColorRegistrationOptions registrationOptions) { diff --git a/src/Protocol/Document/IDocumentFormattingHandler.cs b/src/Protocol/Document/IDocumentFormattingHandler.cs index da4339d9f..40db9cf4d 100644 --- a/src/Protocol/Document/IDocumentFormattingHandler.cs +++ b/src/Protocol/Document/IDocumentFormattingHandler.cs @@ -28,8 +28,7 @@ public DocumentFormattingHandler(DocumentFormattingRegistrationOptions registrat public static class DocumentFormattingExtensions { - public static IDisposable OnDocumentFormatting( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentFormatting(this ILanguageServerRegistry registry, Func> handler, DocumentFormattingRegistrationOptions registrationOptions) @@ -40,8 +39,7 @@ public static IDisposable OnDocumentFormatting( DocumentFormattingRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDocumentFormatting( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentFormatting(this ILanguageServerRegistry registry, Func> handler, DocumentFormattingRegistrationOptions registrationOptions) { @@ -51,8 +49,7 @@ public static IDisposable OnDocumentFormatting( DocumentFormattingRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDocumentFormatting( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentFormatting(this ILanguageServerRegistry registry, Func> handler, DocumentFormattingRegistrationOptions registrationOptions) { diff --git a/src/Protocol/Document/IDocumentHighlightHandler.cs b/src/Protocol/Document/IDocumentHighlightHandler.cs index 04ac1cb72..e38dad3f4 100644 --- a/src/Protocol/Document/IDocumentHighlightHandler.cs +++ b/src/Protocol/Document/IDocumentHighlightHandler.cs @@ -31,8 +31,7 @@ public DocumentHighlightHandler(DocumentHighlightRegistrationOptions registratio public static class DocumentHighlightExtensions { - public static IDisposable OnDocumentHighlight( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentHighlight(this ILanguageServerRegistry registry, Func> handler, DocumentHighlightRegistrationOptions registrationOptions) @@ -43,8 +42,7 @@ public static IDisposable OnDocumentHighlight( DocumentHighlightRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDocumentHighlight( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentHighlight(this ILanguageServerRegistry registry, Func> handler, DocumentHighlightRegistrationOptions registrationOptions) { @@ -54,8 +52,7 @@ public static IDisposable OnDocumentHighlight( DocumentHighlightRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDocumentHighlight( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentHighlight(this ILanguageServerRegistry registry, Func> handler, DocumentHighlightRegistrationOptions registrationOptions) { @@ -65,8 +62,7 @@ public static IDisposable OnDocumentHighlight( DocumentHighlightRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDocumentHighlight( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentHighlight(this ILanguageServerRegistry registry, Action>, DocumentHighlightCapability, CancellationToken> handler, DocumentHighlightRegistrationOptions registrationOptions) @@ -80,8 +76,7 @@ public static IDisposable OnDocumentHighlight( x => new DocumentHighlightContainer(x))); } - public static IDisposable OnDocumentHighlight( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentHighlight(this ILanguageServerRegistry registry, Action>, DocumentHighlightCapability> handler, DocumentHighlightRegistrationOptions registrationOptions) @@ -95,8 +90,7 @@ public static IDisposable OnDocumentHighlight( x => new DocumentHighlightContainer(x))); } - public static IDisposable OnDocumentHighlight( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentHighlight(this ILanguageServerRegistry registry, Action>, CancellationToken> handler, DocumentHighlightRegistrationOptions registrationOptions) { @@ -109,8 +103,7 @@ public static IDisposable OnDocumentHighlight( x => new DocumentHighlightContainer(x))); } - public static IDisposable OnDocumentHighlight( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentHighlight(this ILanguageServerRegistry registry, Action>> handler, DocumentHighlightRegistrationOptions registrationOptions) { diff --git a/src/Protocol/Document/IDocumentLinkHandler.cs b/src/Protocol/Document/IDocumentLinkHandler.cs index 1c05ef41d..dbf84fdce 100644 --- a/src/Protocol/Document/IDocumentLinkHandler.cs +++ b/src/Protocol/Document/IDocumentLinkHandler.cs @@ -45,8 +45,7 @@ public DocumentLinkHandler(DocumentLinkRegistrationOptions registrationOptions) public static class DocumentLinkExtensions { - public static IDisposable OnDocumentLink( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentLink(this ILanguageServerRegistry registry, Func> handler, DocumentLinkRegistrationOptions registrationOptions) { @@ -59,8 +58,7 @@ public static IDisposable OnDocumentLink( registrationOptions)); } - public static IDisposable OnDocumentLink( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentLink(this ILanguageServerRegistry registry, Func> handler, Func canResolve, Func> resolveHandler, @@ -71,22 +69,20 @@ public static IDisposable OnDocumentLink( canResolve ??= item => registrationOptions.ResolveProvider; resolveHandler ??= (link, cap, token) => Task.FromException(new NotImplementedException()); - return new CompositeDisposable( - registry.AddHandler(TextDocumentNames.DocumentLink, - new LanguageProtocolDelegatingHandlers.Request( - handler, - registrationOptions)), - registry.AddHandler(TextDocumentNames.DocumentLinkResolve, - new LanguageProtocolDelegatingHandlers.CanBeResolved( - resolveHandler, - canResolve, - registrationOptions)) - ); + return registry.AddHandler(TextDocumentNames.DocumentLink, + new LanguageProtocolDelegatingHandlers.Request( + handler, + registrationOptions)) + .AddHandler(TextDocumentNames.DocumentLinkResolve, + new LanguageProtocolDelegatingHandlers.CanBeResolved( + resolveHandler, + canResolve, + registrationOptions)) + ; } - public static IDisposable OnDocumentLink( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentLink(this ILanguageServerRegistry registry, Func> handler, DocumentLinkRegistrationOptions registrationOptions) { @@ -99,8 +95,7 @@ public static IDisposable OnDocumentLink( registrationOptions)); } - public static IDisposable OnDocumentLink( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentLink(this ILanguageServerRegistry registry, Func> handler, Func canResolve, Func> resolveHandler, @@ -111,22 +106,19 @@ public static IDisposable OnDocumentLink( canResolve ??= item => registrationOptions.ResolveProvider; resolveHandler ??= (link, token) => Task.FromException(new NotImplementedException()); - return new CompositeDisposable( - registry.AddHandler(TextDocumentNames.DocumentLink, + return registry.AddHandler(TextDocumentNames.DocumentLink, new LanguageProtocolDelegatingHandlers.RequestRegistration( handler, - registrationOptions)), - registry.AddHandler(TextDocumentNames.DocumentLinkResolve, + registrationOptions)).AddHandler(TextDocumentNames.DocumentLinkResolve, new LanguageProtocolDelegatingHandlers.CanBeResolved( resolveHandler, canResolve, registrationOptions)) - ); + ; } - public static IDisposable OnDocumentLink( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentLink(this ILanguageServerRegistry registry, Func> handler, DocumentLinkRegistrationOptions registrationOptions) { @@ -139,8 +131,7 @@ public static IDisposable OnDocumentLink( registrationOptions)); } - public static IDisposable OnDocumentLink( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentLink(this ILanguageServerRegistry registry, Func> handler, Func canResolve, Func> resolveHandler, @@ -151,22 +142,20 @@ public static IDisposable OnDocumentLink( canResolve ??= item => registrationOptions.ResolveProvider; resolveHandler ??= (link) => Task.FromException(new NotImplementedException()); - return new CompositeDisposable( - registry.AddHandler(TextDocumentNames.DocumentLink, - new LanguageProtocolDelegatingHandlers.RequestRegistration( - handler, - registrationOptions)), - registry.AddHandler(TextDocumentNames.DocumentLinkResolve, - new LanguageProtocolDelegatingHandlers.CanBeResolved( - resolveHandler, - canResolve, - registrationOptions)) - ); + return registry.AddHandler(TextDocumentNames.DocumentLink, + new LanguageProtocolDelegatingHandlers.RequestRegistration( + handler, + registrationOptions)) + .AddHandler(TextDocumentNames.DocumentLinkResolve, + new LanguageProtocolDelegatingHandlers.CanBeResolved( + resolveHandler, + canResolve, + registrationOptions)) + ; } - public static IDisposable OnDocumentLink( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentLink(this ILanguageServerRegistry registry, Action>, DocumentLinkCapability, CancellationToken> handler, DocumentLinkRegistrationOptions registrationOptions) { @@ -181,8 +170,7 @@ public static IDisposable OnDocumentLink( x => new DocumentLinkContainer(x))); } - public static IDisposable OnDocumentLink( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentLink(this ILanguageServerRegistry registry, Action>, DocumentLinkCapability, CancellationToken> handler, Func canResolve, Func> resolveHandler, @@ -193,24 +181,22 @@ public static IDisposable OnDocumentLink( canResolve ??= item => registrationOptions.ResolveProvider; resolveHandler ??= (link, cap, token) => Task.FromException(new NotImplementedException()); - return new CompositeDisposable( - registry.AddHandler(TextDocumentNames.DocumentLink, - _ => new LanguageProtocolDelegatingHandlers.PartialResults( - handler, - registrationOptions, - _.GetRequiredService(), - x => new DocumentLinkContainer(x))), - registry.AddHandler(TextDocumentNames.DocumentLinkResolve, - new LanguageProtocolDelegatingHandlers.CanBeResolved( - resolveHandler, - canResolve, - registrationOptions)) - ); + return registry.AddHandler(TextDocumentNames.DocumentLink, + _ => new LanguageProtocolDelegatingHandlers.PartialResults( + handler, + registrationOptions, + _.GetRequiredService(), + x => new DocumentLinkContainer(x))) + .AddHandler(TextDocumentNames.DocumentLinkResolve, + new LanguageProtocolDelegatingHandlers.CanBeResolved( + resolveHandler, + canResolve, + registrationOptions)) + ; } - public static IDisposable OnDocumentLink( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentLink(this ILanguageServerRegistry registry, Action>, CancellationToken> handler, DocumentLinkRegistrationOptions registrationOptions) { @@ -225,8 +211,7 @@ public static IDisposable OnDocumentLink( x => new DocumentLinkContainer(x))); } - public static IDisposable OnDocumentLink( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentLink(this ILanguageServerRegistry registry, Action>, CancellationToken> handler, Func canResolve, Func> resolveHandler, @@ -237,24 +222,22 @@ public static IDisposable OnDocumentLink( canResolve ??= item => registrationOptions.ResolveProvider; resolveHandler ??= (link, token) => Task.FromException(new NotImplementedException()); - return new CompositeDisposable( - registry.AddHandler(TextDocumentNames.DocumentLink, - _ => new LanguageProtocolDelegatingHandlers.PartialResults( - handler, - registrationOptions, - _.GetRequiredService(), - x => new DocumentLinkContainer(x))), - registry.AddHandler(TextDocumentNames.DocumentLinkResolve, - new LanguageProtocolDelegatingHandlers.CanBeResolved( - resolveHandler, - canResolve, - registrationOptions)) - ); + return registry.AddHandler(TextDocumentNames.DocumentLink, + _ => new LanguageProtocolDelegatingHandlers.PartialResults( + handler, + registrationOptions, + _.GetRequiredService(), + x => new DocumentLinkContainer(x))) + .AddHandler(TextDocumentNames.DocumentLinkResolve, + new LanguageProtocolDelegatingHandlers.CanBeResolved( + resolveHandler, + canResolve, + registrationOptions)) + ; } - public static IDisposable OnDocumentLink( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentLink(this ILanguageServerRegistry registry, Action>> handler, DocumentLinkRegistrationOptions registrationOptions) { @@ -269,8 +252,7 @@ public static IDisposable OnDocumentLink( x => new DocumentLinkContainer(x))); } - public static IDisposable OnDocumentLink( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentLink(this ILanguageServerRegistry registry, Action>> handler, Func canResolve, Func> resolveHandler, @@ -281,23 +263,23 @@ public static IDisposable OnDocumentLink( canResolve ??= item => registrationOptions.ResolveProvider; resolveHandler ??= (link) => Task.FromException(new NotImplementedException()); - return new CompositeDisposable( - registry.AddHandler(TextDocumentNames.DocumentLink, - _ => new LanguageProtocolDelegatingHandlers.PartialResults( - handler, - registrationOptions, - _.GetRequiredService(), - x => new DocumentLinkContainer(x))), - registry.AddHandler(TextDocumentNames.DocumentLinkResolve, - new LanguageProtocolDelegatingHandlers.CanBeResolved( - resolveHandler, - canResolve, - registrationOptions)) - ); + return registry.AddHandler(TextDocumentNames.DocumentLink, + _ => new LanguageProtocolDelegatingHandlers.PartialResults( + handler, + registrationOptions, + _.GetRequiredService(), + x => new DocumentLinkContainer(x))) + .AddHandler(TextDocumentNames.DocumentLinkResolve, + new LanguageProtocolDelegatingHandlers.CanBeResolved( + resolveHandler, + canResolve, + registrationOptions)) + ; } - public static IRequestProgressObservable, DocumentLinkContainer> RequestDocumentLink(this ITextDocumentLanguageClient mediator, DocumentLinkParams @params, + public static IRequestProgressObservable, DocumentLinkContainer> RequestDocumentLink(this ITextDocumentLanguageClient mediator, + DocumentLinkParams @params, CancellationToken cancellationToken = default) { return mediator.ProgressManager.MonitorUntil(@params, x => new DocumentLinkContainer(x), cancellationToken); diff --git a/src/Protocol/Document/IDocumentOnTypeFormattingHandler.cs b/src/Protocol/Document/IDocumentOnTypeFormattingHandler.cs index 12cab72c3..602e2bc88 100644 --- a/src/Protocol/Document/IDocumentOnTypeFormattingHandler.cs +++ b/src/Protocol/Document/IDocumentOnTypeFormattingHandler.cs @@ -28,8 +28,7 @@ public DocumentOnTypeFormattingHandler(DocumentOnTypeFormattingRegistrationOptio public static class DocumentOnTypeFormattingExtensions { - public static IDisposable OnDocumentOnTypeFormatting( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentOnTypeFormatting(this ILanguageServerRegistry registry, Func> handler, DocumentOnTypeFormattingRegistrationOptions registrationOptions) @@ -40,8 +39,7 @@ public static IDisposable OnDocumentOnTypeFormatting( DocumentOnTypeFormattingRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDocumentOnTypeFormatting( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentOnTypeFormatting(this ILanguageServerRegistry registry, Func> handler, DocumentOnTypeFormattingRegistrationOptions registrationOptions) { @@ -51,8 +49,7 @@ public static IDisposable OnDocumentOnTypeFormatting( DocumentOnTypeFormattingRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDocumentOnTypeFormatting( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentOnTypeFormatting(this ILanguageServerRegistry registry, Func> handler, DocumentOnTypeFormattingRegistrationOptions registrationOptions) { diff --git a/src/Protocol/Document/IDocumentRangeFormattingHandler.cs b/src/Protocol/Document/IDocumentRangeFormattingHandler.cs index de3eb649d..5f07426b1 100644 --- a/src/Protocol/Document/IDocumentRangeFormattingHandler.cs +++ b/src/Protocol/Document/IDocumentRangeFormattingHandler.cs @@ -28,8 +28,7 @@ public DocumentRangeFormattingHandler(DocumentRangeFormattingRegistrationOptions public static class DocumentRangeFormattingExtensions { - public static IDisposable OnDocumentRangeFormatting( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentRangeFormatting(this ILanguageServerRegistry registry, Func> handler, DocumentRangeFormattingRegistrationOptions registrationOptions) @@ -40,8 +39,7 @@ public static IDisposable OnDocumentRangeFormatting( DocumentRangeFormattingRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDocumentRangeFormatting( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentRangeFormatting(this ILanguageServerRegistry registry, Func> handler, DocumentRangeFormattingRegistrationOptions registrationOptions) { @@ -51,8 +49,7 @@ public static IDisposable OnDocumentRangeFormatting( DocumentRangeFormattingRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDocumentRangeFormatting( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentRangeFormatting(this ILanguageServerRegistry registry, Func> handler, DocumentRangeFormattingRegistrationOptions registrationOptions) { diff --git a/src/Protocol/Document/IDocumentSymbolHandler.cs b/src/Protocol/Document/IDocumentSymbolHandler.cs index e751f6e0a..52fb25792 100644 --- a/src/Protocol/Document/IDocumentSymbolHandler.cs +++ b/src/Protocol/Document/IDocumentSymbolHandler.cs @@ -31,8 +31,7 @@ public DocumentSymbolHandler(DocumentSymbolRegistrationOptions registrationOptio public static class DocumentSymbolExtensions { - public static IDisposable OnDocumentSymbol( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentSymbol(this ILanguageServerRegistry registry, Func> handler, DocumentSymbolRegistrationOptions registrationOptions) @@ -43,8 +42,7 @@ public static IDisposable OnDocumentSymbol( DocumentSymbolRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDocumentSymbol( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentSymbol(this ILanguageServerRegistry registry, Func> handler, DocumentSymbolRegistrationOptions registrationOptions) { @@ -54,8 +52,7 @@ public static IDisposable OnDocumentSymbol( DocumentSymbolRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDocumentSymbol( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentSymbol(this ILanguageServerRegistry registry, Func> handler, DocumentSymbolRegistrationOptions registrationOptions) { @@ -65,8 +62,7 @@ public static IDisposable OnDocumentSymbol( DocumentSymbolRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDocumentSymbol( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentSymbol(this ILanguageServerRegistry registry, Action>, DocumentSymbolCapability, CancellationToken> handler, DocumentSymbolRegistrationOptions registrationOptions) @@ -79,8 +75,7 @@ public static IDisposable OnDocumentSymbol( registrationOptions, _.GetService(), x => new SymbolInformationOrDocumentSymbolContainer(x))); } - public static IDisposable OnDocumentSymbol( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentSymbol(this ILanguageServerRegistry registry, Action>, DocumentSymbolCapability> handler, DocumentSymbolRegistrationOptions registrationOptions) @@ -93,8 +88,7 @@ public static IDisposable OnDocumentSymbol( registrationOptions, _.GetService(), x => new SymbolInformationOrDocumentSymbolContainer(x))); } - public static IDisposable OnDocumentSymbol( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentSymbol(this ILanguageServerRegistry registry, Action>, CancellationToken> handler, DocumentSymbolRegistrationOptions registrationOptions) { @@ -106,8 +100,7 @@ public static IDisposable OnDocumentSymbol( _.GetService(), x => new SymbolInformationOrDocumentSymbolContainer(x))); } - public static IDisposable OnDocumentSymbol( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDocumentSymbol(this ILanguageServerRegistry registry, Action>> handler, DocumentSymbolRegistrationOptions registrationOptions) { diff --git a/src/Protocol/Document/IFoldingRangeHandler.cs b/src/Protocol/Document/IFoldingRangeHandler.cs index 58ccd617a..5395aabe7 100644 --- a/src/Protocol/Document/IFoldingRangeHandler.cs +++ b/src/Protocol/Document/IFoldingRangeHandler.cs @@ -41,8 +41,7 @@ public abstract Task> Handle(FoldingRangeRequestParam re public static class FoldingRangeExtensions { - public static IDisposable OnFoldingRange( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnFoldingRange(this ILanguageServerRegistry registry, Func>> handler, FoldingRangeRegistrationOptions registrationOptions) @@ -54,8 +53,7 @@ public static IDisposable OnFoldingRange( FoldingRangeRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnFoldingRange( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnFoldingRange(this ILanguageServerRegistry registry, Func>> handler, FoldingRangeRegistrationOptions registrationOptions) { @@ -65,8 +63,7 @@ public static IDisposable OnFoldingRange( FoldingRangeRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnFoldingRange( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnFoldingRange(this ILanguageServerRegistry registry, Func>> handler, FoldingRangeRegistrationOptions registrationOptions) { @@ -76,8 +73,7 @@ public static IDisposable OnFoldingRange( FoldingRangeRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnFoldingRange( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnFoldingRange(this ILanguageServerRegistry registry, Action>, FoldingRangeCapability, CancellationToken> handler, FoldingRangeRegistrationOptions registrationOptions) { @@ -89,8 +85,7 @@ public static IDisposable OnFoldingRange( registrationOptions, _.GetService(), x => new Container(x))); } - public static IDisposable OnFoldingRange( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnFoldingRange(this ILanguageServerRegistry registry, Action>, FoldingRangeCapability> handler, FoldingRangeRegistrationOptions registrationOptions) @@ -103,8 +98,7 @@ public static IDisposable OnFoldingRange( registrationOptions, _.GetService(), x => new Container(x))); } - public static IDisposable OnFoldingRange( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnFoldingRange(this ILanguageServerRegistry registry, Action>, CancellationToken> handler, FoldingRangeRegistrationOptions registrationOptions) { @@ -116,8 +110,7 @@ public static IDisposable OnFoldingRange( _.GetService(), x => new Container(x))); } - public static IDisposable OnFoldingRange( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnFoldingRange(this ILanguageServerRegistry registry, Action>> handler, FoldingRangeRegistrationOptions registrationOptions) { diff --git a/src/Protocol/Document/IHoverHandler.cs b/src/Protocol/Document/IHoverHandler.cs index f7e9a4b9e..0958ced50 100644 --- a/src/Protocol/Document/IHoverHandler.cs +++ b/src/Protocol/Document/IHoverHandler.cs @@ -28,8 +28,7 @@ public HoverHandler(HoverRegistrationOptions registrationOptions) public static class HoverExtensions { - public static IDisposable OnHover( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnHover(this ILanguageServerRegistry registry, Func> handler, HoverRegistrationOptions registrationOptions) @@ -40,8 +39,7 @@ public static IDisposable OnHover( HoverRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnHover( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnHover(this ILanguageServerRegistry registry, Func> handler, HoverRegistrationOptions registrationOptions) { @@ -51,8 +49,7 @@ public static IDisposable OnHover( HoverRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnHover( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnHover(this ILanguageServerRegistry registry, Func> handler, HoverRegistrationOptions registrationOptions) { diff --git a/src/Protocol/Document/IImplementationHandler.cs b/src/Protocol/Document/IImplementationHandler.cs index c215a609e..776c1bcf5 100644 --- a/src/Protocol/Document/IImplementationHandler.cs +++ b/src/Protocol/Document/IImplementationHandler.cs @@ -30,8 +30,7 @@ protected ImplementationHandler(ImplementationRegistrationOptions registrationOp public static class ImplementationExtensions { - public static IDisposable OnImplementation( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnImplementation(this ILanguageServerRegistry registry, Func> handler, ImplementationRegistrationOptions registrationOptions) @@ -42,8 +41,7 @@ public static IDisposable OnImplementation( ImplementationRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnImplementation( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnImplementation(this ILanguageServerRegistry registry, Func> handler, ImplementationRegistrationOptions registrationOptions) { @@ -53,8 +51,7 @@ public static IDisposable OnImplementation( ImplementationRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnImplementation( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnImplementation(this ILanguageServerRegistry registry, Func> handler, ImplementationRegistrationOptions registrationOptions) { @@ -64,8 +61,7 @@ public static IDisposable OnImplementation( ImplementationRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnImplementation( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnImplementation(this ILanguageServerRegistry registry, Action>, ImplementationCapability, CancellationToken> handler, ImplementationRegistrationOptions registrationOptions) @@ -78,8 +74,7 @@ public static IDisposable OnImplementation( registrationOptions, _.GetService(), x => new LocationOrLocationLinks(x))); } - public static IDisposable OnImplementation( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnImplementation(this ILanguageServerRegistry registry, Action>, ImplementationCapability> handler, ImplementationRegistrationOptions registrationOptions) @@ -92,8 +87,7 @@ public static IDisposable OnImplementation( registrationOptions, _.GetService(), x => new LocationOrLocationLinks(x))); } - public static IDisposable OnImplementation( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnImplementation(this ILanguageServerRegistry registry, Action>, CancellationToken> handler, ImplementationRegistrationOptions registrationOptions) { @@ -105,8 +99,7 @@ public static IDisposable OnImplementation( _.GetService(), x => new LocationOrLocationLinks(x))); } - public static IDisposable OnImplementation( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnImplementation(this ILanguageServerRegistry registry, Action>> handler, ImplementationRegistrationOptions registrationOptions) { diff --git a/src/Protocol/Document/IPrepareRenameHandler.cs b/src/Protocol/Document/IPrepareRenameHandler.cs index 3ba6b698f..efaed1be7 100644 --- a/src/Protocol/Document/IPrepareRenameHandler.cs +++ b/src/Protocol/Document/IPrepareRenameHandler.cs @@ -28,8 +28,7 @@ public PrepareRenameHandler(TextDocumentRegistrationOptions registrationOptions) public static class PrepareRenameExtensions { - public static IDisposable OnPrepareRename( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnPrepareRename(this ILanguageServerRegistry registry, Func> handler) { @@ -37,15 +36,13 @@ public static IDisposable OnPrepareRename( new LanguageProtocolDelegatingHandlers.RequestCapability(handler)); } - public static IDisposable OnPrepareRename( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnPrepareRename(this ILanguageServerRegistry registry, Func> handler) { return registry.AddHandler(TextDocumentNames.PrepareRename, RequestHandler.For(handler)); } - public static IDisposable OnPrepareRename( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnPrepareRename(this ILanguageServerRegistry registry, Func> handler) { return registry.AddHandler(TextDocumentNames.PrepareRename, RequestHandler.For(handler)); diff --git a/src/Protocol/Document/IPublishDiagnosticsHandler.cs b/src/Protocol/Document/IPublishDiagnosticsHandler.cs index c0ca2d0d4..e7218c3fc 100644 --- a/src/Protocol/Document/IPublishDiagnosticsHandler.cs +++ b/src/Protocol/Document/IPublishDiagnosticsHandler.cs @@ -20,25 +20,25 @@ public abstract class PublishDiagnosticsHandler : IPublishDiagnosticsHandler public static class PublishDiagnosticsExtensions { - public static IDisposable OnPublishDiagnostics(this ILanguageClientRegistry registry, + public static ILanguageClientRegistry OnPublishDiagnostics(this ILanguageClientRegistry registry, Func handler) { return registry.AddHandler(TextDocumentNames.PublishDiagnostics, NotificationHandler.For(handler)); } - public static IDisposable OnPublishDiagnostics(this ILanguageClientRegistry registry, + public static ILanguageClientRegistry OnPublishDiagnostics(this ILanguageClientRegistry registry, Func handler) { return registry.AddHandler(TextDocumentNames.PublishDiagnostics, NotificationHandler.For(handler)); } - public static IDisposable OnPublishDiagnostics(this ILanguageClientRegistry registry, + public static ILanguageClientRegistry OnPublishDiagnostics(this ILanguageClientRegistry registry, Action handler) { return registry.AddHandler(TextDocumentNames.PublishDiagnostics, NotificationHandler.For(handler)); } - public static IDisposable OnPublishDiagnostics(this ILanguageClientRegistry registry, + public static ILanguageClientRegistry OnPublishDiagnostics(this ILanguageClientRegistry registry, Action handler) { return registry.AddHandler(TextDocumentNames.PublishDiagnostics, NotificationHandler.For(handler)); diff --git a/src/Protocol/Document/IReferencesHandler.cs b/src/Protocol/Document/IReferencesHandler.cs index 2b0a8be88..ce447a0cc 100644 --- a/src/Protocol/Document/IReferencesHandler.cs +++ b/src/Protocol/Document/IReferencesHandler.cs @@ -31,8 +31,7 @@ public ReferencesHandler(ReferenceRegistrationOptions registrationOptions) public static class ReferencesExtensions { - public static IDisposable OnReferences( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnReferences(this ILanguageServerRegistry registry, Func> handler, ReferenceRegistrationOptions registrationOptions) @@ -43,8 +42,7 @@ public static IDisposable OnReferences( ReferenceRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnReferences( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnReferences(this ILanguageServerRegistry registry, Func> handler, ReferenceRegistrationOptions registrationOptions) { @@ -54,8 +52,7 @@ public static IDisposable OnReferences( ReferenceRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnReferences( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnReferences(this ILanguageServerRegistry registry, Func> handler, ReferenceRegistrationOptions registrationOptions) { @@ -65,8 +62,7 @@ public static IDisposable OnReferences( ReferenceRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnReferences( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnReferences(this ILanguageServerRegistry registry, Action>, ReferenceCapability, CancellationToken> handler, ReferenceRegistrationOptions registrationOptions) @@ -79,8 +75,7 @@ public static IDisposable OnReferences( registrationOptions, _.GetService(), x => new LocationContainer(x))); } - public static IDisposable OnReferences( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnReferences(this ILanguageServerRegistry registry, Action>, ReferenceCapability> handler, ReferenceRegistrationOptions registrationOptions) @@ -93,8 +88,7 @@ public static IDisposable OnReferences( registrationOptions, _.GetService(), x => new LocationContainer(x))); } - public static IDisposable OnReferences( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnReferences(this ILanguageServerRegistry registry, Action>, CancellationToken> handler, ReferenceRegistrationOptions registrationOptions) { @@ -106,8 +100,7 @@ public static IDisposable OnReferences( _.GetService(), x => new LocationContainer(x))); } - public static IDisposable OnReferences( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnReferences(this ILanguageServerRegistry registry, Action>> handler, ReferenceRegistrationOptions registrationOptions) { diff --git a/src/Protocol/Document/IRenameHandler.cs b/src/Protocol/Document/IRenameHandler.cs index fd8dc07f7..406db33c1 100644 --- a/src/Protocol/Document/IRenameHandler.cs +++ b/src/Protocol/Document/IRenameHandler.cs @@ -28,8 +28,7 @@ public RenameHandler(RenameRegistrationOptions registrationOptions) public static class RenameExtensions { - public static IDisposable OnRename( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnRename(this ILanguageServerRegistry registry, Func> handler, RenameRegistrationOptions registrationOptions) @@ -40,8 +39,7 @@ public static IDisposable OnRename( RenameRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnRename( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnRename(this ILanguageServerRegistry registry, Func> handler, RenameRegistrationOptions registrationOptions) { @@ -51,8 +49,7 @@ public static IDisposable OnRename( RenameRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnRename( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnRename(this ILanguageServerRegistry registry, Func> handler, RenameRegistrationOptions registrationOptions) { diff --git a/src/Protocol/Document/ISelectionRangeHandler.cs b/src/Protocol/Document/ISelectionRangeHandler.cs index d820de63c..27d7e74b7 100644 --- a/src/Protocol/Document/ISelectionRangeHandler.cs +++ b/src/Protocol/Document/ISelectionRangeHandler.cs @@ -41,8 +41,7 @@ public abstract Task> Handle(SelectionRangeParams requ public static class SelectionRangeExtensions { - public static IDisposable OnSelectionRange( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnSelectionRange(this ILanguageServerRegistry registry, Func>> handler, SelectionRangeRegistrationOptions registrationOptions) @@ -54,8 +53,7 @@ public static IDisposable OnSelectionRange( SelectionRangeRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnSelectionRange( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnSelectionRange(this ILanguageServerRegistry registry, Func>> handler, SelectionRangeRegistrationOptions registrationOptions) { @@ -65,8 +63,7 @@ public static IDisposable OnSelectionRange( SelectionRangeRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnSelectionRange( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnSelectionRange(this ILanguageServerRegistry registry, Func>> handler, SelectionRangeRegistrationOptions registrationOptions) { @@ -76,8 +73,7 @@ public static IDisposable OnSelectionRange( SelectionRangeRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnSelectionRange( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnSelectionRange(this ILanguageServerRegistry registry, Action>, SelectionRangeCapability, CancellationToken> handler, SelectionRangeRegistrationOptions registrationOptions) @@ -90,8 +86,7 @@ public static IDisposable OnSelectionRange( registrationOptions, _.GetService(), x => new Container(x))); } - public static IDisposable OnSelectionRange( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnSelectionRange(this ILanguageServerRegistry registry, Action>, SelectionRangeCapability> handler, SelectionRangeRegistrationOptions registrationOptions) @@ -104,8 +99,7 @@ public static IDisposable OnSelectionRange( registrationOptions, _.GetService(), x => new Container(x))); } - public static IDisposable OnSelectionRange( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnSelectionRange(this ILanguageServerRegistry registry, Action>, CancellationToken> handler, SelectionRangeRegistrationOptions registrationOptions) { @@ -117,8 +111,7 @@ public static IDisposable OnSelectionRange( _.GetService(), x => new Container(x))); } - public static IDisposable OnSelectionRange( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnSelectionRange(this ILanguageServerRegistry registry, Action>> handler, SelectionRangeRegistrationOptions registrationOptions) { diff --git a/src/Protocol/Document/ISignatureHelpHandler.cs b/src/Protocol/Document/ISignatureHelpHandler.cs index ab0b94ec9..777914b74 100644 --- a/src/Protocol/Document/ISignatureHelpHandler.cs +++ b/src/Protocol/Document/ISignatureHelpHandler.cs @@ -28,8 +28,7 @@ public SignatureHelpHandler(SignatureHelpRegistrationOptions registrationOptions public static class SignatureHelpExtensions { - public static IDisposable OnSignatureHelp( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnSignatureHelp(this ILanguageServerRegistry registry, Func> handler, SignatureHelpRegistrationOptions registrationOptions) @@ -40,8 +39,7 @@ public static IDisposable OnSignatureHelp( SignatureHelpRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnSignatureHelp( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnSignatureHelp(this ILanguageServerRegistry registry, Func> handler, SignatureHelpRegistrationOptions registrationOptions) { @@ -51,8 +49,7 @@ public static IDisposable OnSignatureHelp( SignatureHelpRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnSignatureHelp( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnSignatureHelp(this ILanguageServerRegistry registry, Func> handler, SignatureHelpRegistrationOptions registrationOptions) { diff --git a/src/Protocol/Document/ITextDocumentSyncHandler.cs b/src/Protocol/Document/ITextDocumentSyncHandler.cs index d297a9a40..bed599868 100644 --- a/src/Protocol/Document/ITextDocumentSyncHandler.cs +++ b/src/Protocol/Document/ITextDocumentSyncHandler.cs @@ -49,8 +49,7 @@ TextDocumentChangeRegistrationOptions IRegistration getTextDocumentAttributes, Func onOpenHandler, @@ -64,8 +63,7 @@ public static IDisposable OnTextDocumentSync( onSaveHandler, getTextDocumentAttributes, registrationOptions, kind)); } - public static IDisposable OnTextDocumentSync( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnTextDocumentSync(this ILanguageServerRegistry registry, TextDocumentSyncKind kind, Func getTextDocumentAttributes, Action onOpenHandler, @@ -79,8 +77,7 @@ public static IDisposable OnTextDocumentSync( onSaveHandler, getTextDocumentAttributes, registrationOptions, kind)); } - public static IDisposable OnTextDocumentSync( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnTextDocumentSync(this ILanguageServerRegistry registry, TextDocumentSyncKind kind, Func getTextDocumentAttributes, Action onOpenHandler, @@ -98,8 +95,7 @@ public static IDisposable OnTextDocumentSync( getTextDocumentAttributes, registrationOptions, kind)); } - public static IDisposable OnTextDocumentSync( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnTextDocumentSync(this ILanguageServerRegistry registry, TextDocumentSyncKind kind, Func getTextDocumentAttributes, Func onOpenHandler, @@ -117,8 +113,7 @@ public static IDisposable OnTextDocumentSync( getTextDocumentAttributes, registrationOptions, kind)); } - public static IDisposable OnTextDocumentSync( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnTextDocumentSync(this ILanguageServerRegistry registry, TextDocumentSyncKind kind, Func getTextDocumentAttributes, Action onOpenHandler, @@ -136,8 +131,7 @@ public static IDisposable OnTextDocumentSync( getTextDocumentAttributes, registrationOptions, kind)); } - public static IDisposable OnTextDocumentSync( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnTextDocumentSync(this ILanguageServerRegistry registry, TextDocumentSyncKind kind, Func getTextDocumentAttributes, Func onOpenHandler, @@ -155,8 +149,7 @@ public static IDisposable OnTextDocumentSync( getTextDocumentAttributes, registrationOptions, kind)); } - public static IDisposable OnTextDocumentSync( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnTextDocumentSync(this ILanguageServerRegistry registry, TextDocumentSyncKind kind, Func getTextDocumentAttributes, Action onOpenHandler, diff --git a/src/Protocol/Document/ITypeDefinitionHandler.cs b/src/Protocol/Document/ITypeDefinitionHandler.cs index 5f5d70e36..10d7d3021 100644 --- a/src/Protocol/Document/ITypeDefinitionHandler.cs +++ b/src/Protocol/Document/ITypeDefinitionHandler.cs @@ -31,8 +31,7 @@ public TypeDefinitionHandler(TypeDefinitionRegistrationOptions registrationOptio public static class TypeDefinitionExtensions { - public static IDisposable OnTypeDefinition( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnTypeDefinition(this ILanguageServerRegistry registry, Func> handler, TypeDefinitionRegistrationOptions registrationOptions) @@ -43,8 +42,7 @@ public static IDisposable OnTypeDefinition( TypeDefinitionRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnTypeDefinition( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnTypeDefinition(this ILanguageServerRegistry registry, Func> handler, TypeDefinitionRegistrationOptions registrationOptions) { @@ -54,8 +52,7 @@ public static IDisposable OnTypeDefinition( TypeDefinitionRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnTypeDefinition( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnTypeDefinition(this ILanguageServerRegistry registry, Func> handler, TypeDefinitionRegistrationOptions registrationOptions) { @@ -65,8 +62,7 @@ public static IDisposable OnTypeDefinition( TypeDefinitionRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnTypeDefinition( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnTypeDefinition(this ILanguageServerRegistry registry, Action>, TypeDefinitionCapability, CancellationToken> handler, TypeDefinitionRegistrationOptions registrationOptions) @@ -79,8 +75,7 @@ public static IDisposable OnTypeDefinition( registrationOptions, _.GetService(), x => new LocationOrLocationLinks(x))); } - public static IDisposable OnTypeDefinition( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnTypeDefinition(this ILanguageServerRegistry registry, Action>, TypeDefinitionCapability> handler, TypeDefinitionRegistrationOptions registrationOptions) @@ -93,8 +88,7 @@ public static IDisposable OnTypeDefinition( registrationOptions, _.GetService(), x => new LocationOrLocationLinks(x))); } - public static IDisposable OnTypeDefinition( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnTypeDefinition(this ILanguageServerRegistry registry, Action>, CancellationToken> handler, TypeDefinitionRegistrationOptions registrationOptions) { @@ -106,8 +100,7 @@ public static IDisposable OnTypeDefinition( _.GetService(), x => new LocationOrLocationLinks(x))); } - public static IDisposable OnTypeDefinition( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnTypeDefinition(this ILanguageServerRegistry registry, Action>> handler, TypeDefinitionRegistrationOptions registrationOptions) { diff --git a/src/Protocol/Document/IWillSaveTextDocumentHandler.cs b/src/Protocol/Document/IWillSaveTextDocumentHandler.cs index 921ad0e10..0f965cf12 100644 --- a/src/Protocol/Document/IWillSaveTextDocumentHandler.cs +++ b/src/Protocol/Document/IWillSaveTextDocumentHandler.cs @@ -29,8 +29,7 @@ public WillSaveTextDocumentHandler(TextDocumentRegistrationOptions registrationO public static class WillSaveTextDocumentExtensions { - public static IDisposable OnWillSaveTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnWillSaveTextDocument(this ILanguageServerRegistry registry, Func handler, TextDocumentRegistrationOptions registrationOptions) { @@ -40,8 +39,7 @@ public static IDisposable OnWillSaveTextDocument( TextDocumentRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnWillSaveTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnWillSaveTextDocument(this ILanguageServerRegistry registry, Func handler, TextDocumentRegistrationOptions registrationOptions) { @@ -51,8 +49,7 @@ public static IDisposable OnWillSaveTextDocument( TextDocumentRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnWillSaveTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnWillSaveTextDocument(this ILanguageServerRegistry registry, Func handler, TextDocumentRegistrationOptions registrationOptions) { @@ -62,8 +59,7 @@ public static IDisposable OnWillSaveTextDocument( TextDocumentRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnWillSaveTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnWillSaveTextDocument(this ILanguageServerRegistry registry, Action handler, TextDocumentRegistrationOptions registrationOptions) { @@ -76,8 +72,7 @@ public static IDisposable OnWillSaveTextDocument( }, registrationOptions)); } - public static IDisposable OnWillSaveTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnWillSaveTextDocument(this ILanguageServerRegistry registry, Action handler, TextDocumentRegistrationOptions registrationOptions) { @@ -90,8 +85,7 @@ public static IDisposable OnWillSaveTextDocument( }, registrationOptions)); } - public static IDisposable OnWillSaveTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnWillSaveTextDocument(this ILanguageServerRegistry registry, Action handler, TextDocumentRegistrationOptions registrationOptions) { diff --git a/src/Protocol/Document/IWillSaveWaitUntilTextDocumentHandler.cs b/src/Protocol/Document/IWillSaveWaitUntilTextDocumentHandler.cs index e1c6de730..acf62375d 100644 --- a/src/Protocol/Document/IWillSaveWaitUntilTextDocumentHandler.cs +++ b/src/Protocol/Document/IWillSaveWaitUntilTextDocumentHandler.cs @@ -28,8 +28,7 @@ public WillSaveWaitUntilTextDocumentHandler(TextDocumentRegistrationOptions regi public static class WillSaveWaitUntilTextDocumentExtensions { - public static IDisposable OnWillSaveWaitUntilTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnWillSaveWaitUntilTextDocument(this ILanguageServerRegistry registry, Func> handler, TextDocumentRegistrationOptions registrationOptions) @@ -40,8 +39,7 @@ public static IDisposable OnWillSaveWaitUntilTextDocument( TextDocumentRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnWillSaveWaitUntilTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnWillSaveWaitUntilTextDocument(this ILanguageServerRegistry registry, Func> handler, TextDocumentRegistrationOptions registrationOptions) { @@ -51,8 +49,7 @@ public static IDisposable OnWillSaveWaitUntilTextDocument( TextDocumentRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnWillSaveWaitUntilTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnWillSaveWaitUntilTextDocument(this ILanguageServerRegistry registry, Func> handler, TextDocumentRegistrationOptions registrationOptions) { @@ -62,8 +59,7 @@ public static IDisposable OnWillSaveWaitUntilTextDocument( TextDocumentRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnWillSaveWaitUntilTextDocument( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnWillSaveWaitUntilTextDocument(this ILanguageServerRegistry registry, Func> handler, TextDocumentRegistrationOptions registrationOptions) { diff --git a/src/Protocol/Document/Proposals/ICallHierarchyHandler.cs b/src/Protocol/Document/Proposals/ICallHierarchyHandler.cs index 16f96d4ea..c8963e6db 100644 --- a/src/Protocol/Document/Proposals/ICallHierarchyHandler.cs +++ b/src/Protocol/Document/Proposals/ICallHierarchyHandler.cs @@ -67,7 +67,7 @@ public abstract Task> Handle(CallHierarchyO [Obsolete(Constants.Proposal)] public static class CallHierarchyExtensions { - public static IDisposable OnCallHierarchy( + public static ILanguageServerRegistry OnCallHierarchy( this ILanguageServerRegistry registry, Func>> handler, Func>> incomingHandler, @@ -75,208 +75,199 @@ public static IDisposable OnCallHierarchy( CallHierarchyRegistrationOptions registrationOptions) { registrationOptions ??= new CallHierarchyRegistrationOptions(); - return new CompositeDisposable( - registry.AddHandler(TextDocumentNames.PrepareCallHierarchy, - new LanguageProtocolDelegatingHandlers.Request, - CallHierarchyCapability, - CallHierarchyRegistrationOptions>(handler, registrationOptions)), - registry.AddHandler(TextDocumentNames.CallHierarchyIncoming, - new LanguageProtocolDelegatingHandlers.Request, - CallHierarchyCapability, - CallHierarchyRegistrationOptions>(incomingHandler, registrationOptions)), - registry.AddHandler(TextDocumentNames.CallHierarchyOutgoing, - new LanguageProtocolDelegatingHandlers.Request, - CallHierarchyCapability, - CallHierarchyRegistrationOptions>(outgoingHandler, registrationOptions)) - ); + return registry.AddHandler(TextDocumentNames.PrepareCallHierarchy, + new LanguageProtocolDelegatingHandlers.Request, + CallHierarchyCapability, + CallHierarchyRegistrationOptions>(handler, registrationOptions)) + .AddHandler(TextDocumentNames.CallHierarchyIncoming, + new LanguageProtocolDelegatingHandlers.Request, + CallHierarchyCapability, + CallHierarchyRegistrationOptions>(incomingHandler, registrationOptions)) + .AddHandler(TextDocumentNames.CallHierarchyOutgoing, + new LanguageProtocolDelegatingHandlers.Request, + CallHierarchyCapability, + CallHierarchyRegistrationOptions>(outgoingHandler, registrationOptions)) + ; } - public static IDisposable OnCallHierarchy( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnCallHierarchy(this ILanguageServerRegistry registry, Func>> handler, Func>> incomingHandler, Func>> outgoingHandler, CallHierarchyRegistrationOptions registrationOptions) { registrationOptions ??= new CallHierarchyRegistrationOptions(); - return new CompositeDisposable( - registry.AddHandler(TextDocumentNames.PrepareCallHierarchy, - new LanguageProtocolDelegatingHandlers.Request, - CallHierarchyCapability, - CallHierarchyRegistrationOptions>(handler, registrationOptions)), - registry.AddHandler(TextDocumentNames.CallHierarchyIncoming, - new LanguageProtocolDelegatingHandlers.Request, - CallHierarchyCapability, - CallHierarchyRegistrationOptions>(incomingHandler, registrationOptions)), - registry.AddHandler(TextDocumentNames.CallHierarchyOutgoing, - new LanguageProtocolDelegatingHandlers.Request, - CallHierarchyCapability, - CallHierarchyRegistrationOptions>(outgoingHandler, registrationOptions)) - ); + return registry.AddHandler(TextDocumentNames.PrepareCallHierarchy, + new LanguageProtocolDelegatingHandlers.Request, + CallHierarchyCapability, + CallHierarchyRegistrationOptions>(handler, registrationOptions)) + .AddHandler(TextDocumentNames.CallHierarchyIncoming, + new LanguageProtocolDelegatingHandlers.Request, + CallHierarchyCapability, + CallHierarchyRegistrationOptions>(incomingHandler, registrationOptions)) + .AddHandler(TextDocumentNames.CallHierarchyOutgoing, + new LanguageProtocolDelegatingHandlers.Request, + CallHierarchyCapability, + CallHierarchyRegistrationOptions>(outgoingHandler, registrationOptions)) + ; } - public static IDisposable OnCallHierarchy( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnCallHierarchy(this ILanguageServerRegistry registry, Func>> handler, Func>> incomingHandler, Func>> outgoingHandler, CallHierarchyRegistrationOptions registrationOptions) { registrationOptions ??= new CallHierarchyRegistrationOptions(); - return new CompositeDisposable( + return registry.AddHandler(TextDocumentNames.PrepareCallHierarchy, - new LanguageProtocolDelegatingHandlers.RequestRegistration, - CallHierarchyRegistrationOptions>(handler, registrationOptions)), - registry.AddHandler(TextDocumentNames.CallHierarchyIncoming, - new LanguageProtocolDelegatingHandlers.RequestRegistration, - CallHierarchyRegistrationOptions>(incomingHandler, registrationOptions)), - registry.AddHandler(TextDocumentNames.CallHierarchyOutgoing, - new LanguageProtocolDelegatingHandlers.RequestRegistration, - CallHierarchyRegistrationOptions>(outgoingHandler, registrationOptions)) - ); + new LanguageProtocolDelegatingHandlers.RequestRegistration, + CallHierarchyRegistrationOptions>(handler, registrationOptions)) + .AddHandler(TextDocumentNames.CallHierarchyIncoming, + new LanguageProtocolDelegatingHandlers.RequestRegistration, + CallHierarchyRegistrationOptions>(incomingHandler, registrationOptions)) + .AddHandler(TextDocumentNames.CallHierarchyOutgoing, + new LanguageProtocolDelegatingHandlers.RequestRegistration, + CallHierarchyRegistrationOptions>(outgoingHandler, registrationOptions)) + ; } - public static IDisposable OnCallHierarchy( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnCallHierarchy(this ILanguageServerRegistry registry, Func>> handler, Func>> incomingHandler, Func>> outgoingHandler, CallHierarchyRegistrationOptions registrationOptions) { registrationOptions ??= new CallHierarchyRegistrationOptions(); - return new CompositeDisposable( + return registry.AddHandler(TextDocumentNames.PrepareCallHierarchy, - new LanguageProtocolDelegatingHandlers.RequestRegistration, - CallHierarchyRegistrationOptions>(handler, registrationOptions)), - registry.AddHandler(TextDocumentNames.CallHierarchyIncoming, - new LanguageProtocolDelegatingHandlers.RequestRegistration, - CallHierarchyRegistrationOptions>(incomingHandler, registrationOptions)), - registry.AddHandler(TextDocumentNames.CallHierarchyOutgoing, - new LanguageProtocolDelegatingHandlers.RequestRegistration, - CallHierarchyRegistrationOptions>(outgoingHandler, registrationOptions)) - ); + new LanguageProtocolDelegatingHandlers.RequestRegistration, + CallHierarchyRegistrationOptions>(handler, registrationOptions)) + .AddHandler(TextDocumentNames.CallHierarchyIncoming, + new LanguageProtocolDelegatingHandlers.RequestRegistration, + CallHierarchyRegistrationOptions>(incomingHandler, registrationOptions)) + .AddHandler(TextDocumentNames.CallHierarchyOutgoing, + new LanguageProtocolDelegatingHandlers.RequestRegistration, + CallHierarchyRegistrationOptions>(outgoingHandler, registrationOptions)) + ; } - public static IDisposable OnCallHierarchy( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnCallHierarchy(this ILanguageServerRegistry registry, Func>> handler, Action>, CallHierarchyCapability, CancellationToken> incomingHandler, Action>, CallHierarchyCapability, CancellationToken> outgoingHandler, CallHierarchyRegistrationOptions registrationOptions) { registrationOptions ??= new CallHierarchyRegistrationOptions(); - return new CompositeDisposable( + return registry.AddHandler(TextDocumentNames.PrepareCallHierarchy, - new LanguageProtocolDelegatingHandlers.Request, - CallHierarchyCapability, - CallHierarchyRegistrationOptions>(handler, registrationOptions)), - registry.AddHandler(TextDocumentNames.CallHierarchyIncoming, - _ => new LanguageProtocolDelegatingHandlers.PartialResults, CallHierarchyIncomingCall, - CallHierarchyCapability, - CallHierarchyRegistrationOptions>(incomingHandler, registrationOptions, - _.GetRequiredService(), x => new Container(x))), - registry.AddHandler(TextDocumentNames.CallHierarchyOutgoing, - _ => new LanguageProtocolDelegatingHandlers.PartialResults, CallHierarchyOutgoingCall, - CallHierarchyCapability, - CallHierarchyRegistrationOptions>(outgoingHandler, registrationOptions, - _.GetRequiredService(), x => new Container(x))) - ); + new LanguageProtocolDelegatingHandlers.Request, + CallHierarchyCapability, + CallHierarchyRegistrationOptions>(handler, registrationOptions)) + .AddHandler(TextDocumentNames.CallHierarchyIncoming, + _ => new LanguageProtocolDelegatingHandlers.PartialResults, CallHierarchyIncomingCall, + CallHierarchyCapability, + CallHierarchyRegistrationOptions>(incomingHandler, registrationOptions, + _.GetRequiredService(), x => new Container(x))) + .AddHandler(TextDocumentNames.CallHierarchyOutgoing, + _ => new LanguageProtocolDelegatingHandlers.PartialResults, CallHierarchyOutgoingCall, + CallHierarchyCapability, + CallHierarchyRegistrationOptions>(outgoingHandler, registrationOptions, + _.GetRequiredService(), x => new Container(x))) + ; } - public static IDisposable OnCallHierarchy( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnCallHierarchy(this ILanguageServerRegistry registry, Func>> handler, Action>, CallHierarchyCapability> incomingHandler, Action>, CallHierarchyCapability> outgoingHandler, CallHierarchyRegistrationOptions registrationOptions) { registrationOptions ??= new CallHierarchyRegistrationOptions(); - return new CompositeDisposable( + return registry.AddHandler(TextDocumentNames.PrepareCallHierarchy, - new LanguageProtocolDelegatingHandlers.Request, - CallHierarchyCapability, - CallHierarchyRegistrationOptions>(handler, registrationOptions)), - registry.AddHandler(TextDocumentNames.CallHierarchyIncoming, - _ => new LanguageProtocolDelegatingHandlers.PartialResults,CallHierarchyIncomingCall, - CallHierarchyCapability, - CallHierarchyRegistrationOptions>(incomingHandler, registrationOptions, - _.GetRequiredService(), x => new Container(x))), - registry.AddHandler(TextDocumentNames.CallHierarchyOutgoing, - _ => new LanguageProtocolDelegatingHandlers.PartialResults,CallHierarchyOutgoingCall, - CallHierarchyCapability, - CallHierarchyRegistrationOptions>(outgoingHandler, registrationOptions, - _.GetRequiredService(), x => new Container(x))) - ); + new LanguageProtocolDelegatingHandlers.Request, + CallHierarchyCapability, + CallHierarchyRegistrationOptions>(handler, registrationOptions)) + .AddHandler(TextDocumentNames.CallHierarchyIncoming, + _ => new LanguageProtocolDelegatingHandlers.PartialResults, CallHierarchyIncomingCall, + CallHierarchyCapability, + CallHierarchyRegistrationOptions>(incomingHandler, registrationOptions, + _.GetRequiredService(), x => new Container(x))) + .AddHandler(TextDocumentNames.CallHierarchyOutgoing, + _ => new LanguageProtocolDelegatingHandlers.PartialResults, CallHierarchyOutgoingCall, + CallHierarchyCapability, + CallHierarchyRegistrationOptions>(outgoingHandler, registrationOptions, + _.GetRequiredService(), x => new Container(x))) + ; } - public static IDisposable OnCallHierarchy( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnCallHierarchy(this ILanguageServerRegistry registry, Func>> handler, Action>, CancellationToken> incomingHandler, Action>, CancellationToken> outgoingHandler, CallHierarchyRegistrationOptions registrationOptions) { registrationOptions ??= new CallHierarchyRegistrationOptions(); - return new CompositeDisposable( + return registry.AddHandler(TextDocumentNames.PrepareCallHierarchy, - new LanguageProtocolDelegatingHandlers.RequestRegistration, - CallHierarchyRegistrationOptions>(handler, registrationOptions)), - registry.AddHandler(TextDocumentNames.CallHierarchyIncoming, - _ => new LanguageProtocolDelegatingHandlers.PartialResults,CallHierarchyIncomingCall, - CallHierarchyRegistrationOptions>(incomingHandler, registrationOptions, - _.GetRequiredService(), x => new Container(x))), - registry.AddHandler(TextDocumentNames.CallHierarchyOutgoing, - _ => new LanguageProtocolDelegatingHandlers.PartialResults,CallHierarchyOutgoingCall, - CallHierarchyRegistrationOptions>(outgoingHandler, registrationOptions, - _.GetRequiredService(), x => new Container(x))) - ); + new LanguageProtocolDelegatingHandlers.RequestRegistration, + CallHierarchyRegistrationOptions>(handler, registrationOptions)) + .AddHandler(TextDocumentNames.CallHierarchyIncoming, + _ => new LanguageProtocolDelegatingHandlers.PartialResults, CallHierarchyIncomingCall, + CallHierarchyRegistrationOptions>(incomingHandler, registrationOptions, + _.GetRequiredService(), x => new Container(x))) + .AddHandler(TextDocumentNames.CallHierarchyOutgoing, + _ => new LanguageProtocolDelegatingHandlers.PartialResults, CallHierarchyOutgoingCall, + CallHierarchyRegistrationOptions>(outgoingHandler, registrationOptions, + _.GetRequiredService(), x => new Container(x))) + ; } - public static IDisposable OnCallHierarchy( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnCallHierarchy(this ILanguageServerRegistry registry, Func>> handler, Action>> incomingHandler, Action>> outgoingHandler, CallHierarchyRegistrationOptions registrationOptions) { registrationOptions ??= new CallHierarchyRegistrationOptions(); - return new CompositeDisposable( + return registry.AddHandler(TextDocumentNames.PrepareCallHierarchy, - new LanguageProtocolDelegatingHandlers.RequestRegistration, - CallHierarchyRegistrationOptions>(handler, registrationOptions)), - registry.AddHandler(TextDocumentNames.CallHierarchyIncoming, - _ => new LanguageProtocolDelegatingHandlers.PartialResults,CallHierarchyIncomingCall, - CallHierarchyRegistrationOptions>(incomingHandler, registrationOptions, - _.GetRequiredService(), x => new Container(x))), - registry.AddHandler(TextDocumentNames.CallHierarchyOutgoing, - _ => new LanguageProtocolDelegatingHandlers.PartialResults,CallHierarchyOutgoingCall, - CallHierarchyRegistrationOptions>(outgoingHandler, registrationOptions, - _.GetRequiredService(), x => new Container(x))) - ); + new LanguageProtocolDelegatingHandlers.RequestRegistration, + CallHierarchyRegistrationOptions>(handler, registrationOptions)) + .AddHandler(TextDocumentNames.CallHierarchyIncoming, + _ => new LanguageProtocolDelegatingHandlers.PartialResults, CallHierarchyIncomingCall, + CallHierarchyRegistrationOptions>(incomingHandler, registrationOptions, + _.GetRequiredService(), x => new Container(x))) + .AddHandler(TextDocumentNames.CallHierarchyOutgoing, + _ => new LanguageProtocolDelegatingHandlers.PartialResults, CallHierarchyOutgoingCall, + CallHierarchyRegistrationOptions>(outgoingHandler, registrationOptions, + _.GetRequiredService(), x => new Container(x))) + ; } public static Task> RequestCallHierarchy(this ITextDocumentLanguageClient mediator, @@ -287,21 +278,21 @@ public static Task> RequestCallHierarchy(this IText public static IRequestProgressObservable RequestCallHierarchyIncoming( this ITextDocumentLanguageClient mediator, CallHierarchyIncomingCallsParams @params, - CancellationToken cancellationToken= default) + CancellationToken cancellationToken = default) { return mediator.ProgressManager.MonitorUntil(@params, cancellationToken); } public static Task> RequestCallHierarchyOutgoing( this ITextDocumentLanguageClient mediator, CallHierarchyOutgoingCallsParams @params, - CancellationToken cancellationToken= default) + CancellationToken cancellationToken = default) { return mediator.SendRequest(@params, cancellationToken); } public static IRequestProgressObservable RequestCallHierarchyIncoming( this ITextDocumentLanguageClient mediator, CallHierarchyOutgoingCallsParams @params, - CancellationToken cancellationToken= default) + CancellationToken cancellationToken = default) { return mediator.ProgressManager.MonitorUntil(@params, cancellationToken); } diff --git a/src/Protocol/Document/Proposals/ISemanticTokensEditsHandler.cs b/src/Protocol/Document/Proposals/ISemanticTokensEditsHandler.cs index f7498fac7..ec3157637 100644 --- a/src/Protocol/Document/Proposals/ISemanticTokensEditsHandler.cs +++ b/src/Protocol/Document/Proposals/ISemanticTokensEditsHandler.cs @@ -81,8 +81,7 @@ public virtual async Task Handle(SemanticTokensRangeParams reque [Obsolete(Constants.Proposal)] public static class SemanticTokensExtensions { - public static IDisposable OnSemanticTokens( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnSemanticTokens(this ILanguageServerRegistry registry, Func tokenize, Func> getSemanticTokensDocument, SemanticTokensRegistrationOptions registrationOptions) @@ -102,8 +101,7 @@ public static IDisposable OnSemanticTokens( new DelegatingHandler(tokenize, getSemanticTokensDocument, registrationOptions)); } - public static IDisposable OnSemanticTokens( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnSemanticTokens(this ILanguageServerRegistry registry, Func tokenize, Func> getSemanticTokensDocument, SemanticTokensRegistrationOptions registrationOptions) @@ -125,8 +123,7 @@ public static IDisposable OnSemanticTokens( registrationOptions)); } - public static IDisposable OnSemanticTokens( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnSemanticTokens(this ILanguageServerRegistry registry, Func tokenize, Func> getSemanticTokensDocument, SemanticTokensRegistrationOptions registrationOptions) diff --git a/src/Protocol/DocumentUri.cs b/src/Protocol/DocumentUri.cs index ff41d5ef6..f9ff6a183 100644 --- a/src/Protocol/DocumentUri.cs +++ b/src/Protocol/DocumentUri.cs @@ -246,6 +246,10 @@ public static DocumentUri From(Uri uri) /// public static DocumentUri From(string url) { + if (string.IsNullOrWhiteSpace (url)) + { + throw new UriFormatException("Given uri is null or empty"); + } if (url.StartsWith(@"\\") || (url.StartsWith("/")) || WindowsPath.IsMatch(url)) { return File(url); @@ -307,7 +311,7 @@ public static string GetFileSystemPath(DocumentUri documentUri) /// /// The LSP document URI. /// - public static DocumentUri FromFileSystemPath(string fileSystemPath) => From(fileSystemPath); + public static DocumentUri FromFileSystemPath(string fileSystemPath) => File(fileSystemPath); private sealed class DocumentUriEqualityComparer : IEqualityComparer { @@ -364,6 +368,10 @@ public DocumentUri(string scheme, string authority, string path, string query, s /// public static DocumentUri Parse(string value, bool strict = false) { + if (string.IsNullOrWhiteSpace (value)) + { + throw new UriFormatException("Given uri is null or empty"); + } var match = Regexp.Match(value); if (!match.Success) { @@ -393,6 +401,10 @@ public static DocumentUri Parse(string value, bool strict = false) /// public static DocumentUri File(string path) { + if (string.IsNullOrWhiteSpace (path)) + { + throw new UriFormatException("Given path is null or empty"); + } var authority = Empty; // normalize to fwd-slashes on windows, diff --git a/src/Protocol/General/IExitHandler.cs b/src/Protocol/General/IExitHandler.cs index a68dba971..01525e9d5 100644 --- a/src/Protocol/General/IExitHandler.cs +++ b/src/Protocol/General/IExitHandler.cs @@ -27,25 +27,25 @@ public virtual async Task Handle(ExitParams request, CancellationToken can public static class ExitExtensions { - public static IDisposable OnExit(this ILanguageServerRegistry registry, Action handler) + public static ILanguageServerRegistry OnExit(this ILanguageServerRegistry registry, Action handler) { return registry.AddHandler(GeneralNames.Exit, NotificationHandler.For(handler)); } - public static IDisposable OnExit(this ILanguageServerRegistry registry, Func handler) + public static ILanguageServerRegistry OnExit(this ILanguageServerRegistry registry, Func handler) { return registry.AddHandler(GeneralNames.Exit, NotificationHandler.For(handler)); } - public static IDisposable OnExit(this ILanguageServerRegistry registry, Action handler) + public static ILanguageServerRegistry OnExit(this ILanguageServerRegistry registry, Action handler) { return registry.AddHandler(GeneralNames.Exit, NotificationHandler.For(handler)); } - public static IDisposable OnExit(this ILanguageServerRegistry registry, Func handler) + public static ILanguageServerRegistry OnExit(this ILanguageServerRegistry registry, Func handler) { return registry.AddHandler(GeneralNames.Exit, NotificationHandler.For(handler)); diff --git a/src/Protocol/General/IInitializeHandler.cs b/src/Protocol/General/ILanguageProtocolInitializeHandler.cs similarity index 61% rename from src/Protocol/General/IInitializeHandler.cs rename to src/Protocol/General/ILanguageProtocolInitializeHandler.cs index 9bf8877a4..14f081489 100644 --- a/src/Protocol/General/IInitializeHandler.cs +++ b/src/Protocol/General/ILanguageProtocolInitializeHandler.cs @@ -12,31 +12,32 @@ namespace OmniSharp.Extensions.LanguageServer.Protocol.General /// InitializeError /// [Serial, Method(GeneralNames.Initialize, Direction.ClientToServer)] - public interface IInitializeHandler : IJsonRpcRequestHandler { } + public interface ILanguageProtocolInitializeHandler : IJsonRpcRequestHandler + { + } - public abstract class InitializeHandler : IInitializeHandler + public abstract class LanguageProtocolInitializeHandler : ILanguageProtocolInitializeHandler { public abstract Task Handle(InitializeParams request, CancellationToken cancellationToken); } - public static class InitializeExtensions + public static class LanguageProtocolInitializeExtensions { - public static IDisposable OnInitialize( - this ILanguageServerRegistry registry, + public static ILanguageServerRegistry OnLanguageProtocolInitialize(this ILanguageServerRegistry registry, Func> handler) { return registry.AddHandler(GeneralNames.Initialize, RequestHandler.For(handler)); } - public static IDisposable OnInitialize( - this ILanguageServerRegistry registry, + public static ILanguageServerRegistry OnLanguageProtocolInitialize(this ILanguageServerRegistry registry, Func> handler) { return registry.AddHandler(GeneralNames.Initialize, RequestHandler.For(handler)); } - public static Task RequestInitialize(this ILanguageClient mediator, InitializeParams @params, CancellationToken cancellationToken = default) + public static Task RequestLanguageProtocolInitialize(this ILanguageClient mediator, InitializeParams @params, + CancellationToken cancellationToken = default) { return mediator.SendRequest(@params, cancellationToken); } diff --git a/src/Protocol/General/IInitializedHandler.cs b/src/Protocol/General/ILanguageProtocolInitializedHandler.cs similarity index 52% rename from src/Protocol/General/IInitializedHandler.cs rename to src/Protocol/General/ILanguageProtocolInitializedHandler.cs index 77ec6ae01..81bb5eeb7 100644 --- a/src/Protocol/General/IInitializedHandler.cs +++ b/src/Protocol/General/ILanguageProtocolInitializedHandler.cs @@ -10,36 +10,36 @@ namespace OmniSharp.Extensions.LanguageServer.Protocol.General { [Serial, Method(GeneralNames.Initialized, Direction.ClientToServer)] - public interface IInitializedHandler : IJsonRpcNotificationHandler { } + public interface ILanguageProtocolInitializedHandler : IJsonRpcNotificationHandler { } - public abstract class InitializedHandler : IInitializedHandler + public abstract class LanguageProtocolInitializedHandler : ILanguageProtocolInitializedHandler { public abstract Task Handle(InitializedParams request, CancellationToken cancellationToken); } - public static class InitializedExtensions + public static class LanguageProtocolInitializedExtensions { - public static IDisposable OnInitialized(this ILanguageServerRegistry registry, Action handler) + public static ILanguageServerRegistry OnLanguageProtocolInitialized(this ILanguageServerRegistry registry, Action handler) { return registry.AddHandler(GeneralNames.Initialized, NotificationHandler.For(handler)); } - public static IDisposable OnInitialized(this ILanguageServerRegistry registry, Action handler) + public static ILanguageServerRegistry OnLanguageProtocolInitialized(this ILanguageServerRegistry registry, Action handler) { return registry.AddHandler(GeneralNames.Initialized, NotificationHandler.For(handler)); } - public static IDisposable OnInitialized(this ILanguageServerRegistry registry, Func handler) + public static ILanguageServerRegistry OnLanguageProtocolInitialized(this ILanguageServerRegistry registry, Func handler) { return registry.AddHandler(GeneralNames.Initialized, NotificationHandler.For(handler)); } - public static IDisposable OnInitialized(this ILanguageServerRegistry registry, Func handler) + public static ILanguageServerRegistry OnLanguageProtocolInitialized(this ILanguageServerRegistry registry, Func handler) { return registry.AddHandler(GeneralNames.Initialized, NotificationHandler.For(handler)); } - public static void SendInitialized(this ILanguageClient mediator, InitializedParams @params) + public static void SendLanguageProtocolInitialized(this ILanguageClient mediator, InitializedParams @params) { mediator.SendNotification(@params); } diff --git a/src/Protocol/General/IShutdownHandler.cs b/src/Protocol/General/IShutdownHandler.cs index 408dc92d2..b3837006d 100644 --- a/src/Protocol/General/IShutdownHandler.cs +++ b/src/Protocol/General/IShutdownHandler.cs @@ -27,8 +27,7 @@ public virtual async Task Handle(ShutdownParams request, CancellationToken public static class ShutdownExtensions { - public static IDisposable OnShutdown( - this ILanguageServerRegistry registry, + public static ILanguageServerRegistry OnShutdown(this ILanguageServerRegistry registry, Func handler) { @@ -39,10 +38,7 @@ public static IDisposable OnShutdown( })); } - public static IDisposable OnShutdown( - this ILanguageServerRegistry registry, - Func - handler) + public static ILanguageServerRegistry OnShutdown(this ILanguageServerRegistry registry, Func handler) { return registry.AddHandler(GeneralNames.Shutdown, RequestHandler.For(async (_, ct) => { diff --git a/src/Protocol/ILanguageProtocolRpcOptions.cs b/src/Protocol/ILanguageProtocolRpcOptions.cs new file mode 100644 index 000000000..209e258c8 --- /dev/null +++ b/src/Protocol/ILanguageProtocolRpcOptions.cs @@ -0,0 +1,12 @@ +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.LanguageServer.Protocol.Document; + +namespace OmniSharp.Extensions.LanguageServer.Protocol +{ + public interface ILanguageProtocolRpcOptions : IJsonRpcHandlerRegistry, IJsonRpcServerOptions + where T : ILanguageProtocolRpcOptions + { + T AddTextDocumentIdentifier(params ITextDocumentIdentifier[] handlers); + T AddTextDocumentIdentifier() where TTextDocumentIdentifier : ITextDocumentIdentifier; + } +} diff --git a/src/Protocol/IProgressHandler.cs b/src/Protocol/IProgressHandler.cs index 15fe36764..469a820cc 100644 --- a/src/Protocol/IProgressHandler.cs +++ b/src/Protocol/IProgressHandler.cs @@ -23,57 +23,49 @@ public abstract class ProgressHandler : IProgressHandler public static class ProgressExtensions { - public static IDisposable OnProgress( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnProgress(this ILanguageServerRegistry registry, Action handler) { return registry.AddHandler(GeneralNames.Progress, NotificationHandler.For(handler)); } - public static IDisposable OnProgress( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnProgress(this ILanguageServerRegistry registry, Func handler) { return registry.AddHandler(GeneralNames.Progress, NotificationHandler.For(handler)); } - public static IDisposable OnProgress( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnProgress(this ILanguageServerRegistry registry, Action handler) { return registry.AddHandler(GeneralNames.Progress, NotificationHandler.For(handler)); } - public static IDisposable OnProgress( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnProgress(this ILanguageServerRegistry registry, Func handler) { return registry.AddHandler(GeneralNames.Progress, NotificationHandler.For(handler)); } - public static IDisposable OnProgress( - this ILanguageClientRegistry registry, +public static ILanguageClientRegistry OnProgress(this ILanguageClientRegistry registry, Action handler) { return registry.AddHandler(GeneralNames.Progress, NotificationHandler.For(handler)); } - public static IDisposable OnProgress( - this ILanguageClientRegistry registry, +public static ILanguageClientRegistry OnProgress(this ILanguageClientRegistry registry, Func handler) { return registry.AddHandler(GeneralNames.Progress, NotificationHandler.For(handler)); } - public static IDisposable OnProgress( - this ILanguageClientRegistry registry, +public static ILanguageClientRegistry OnProgress(this ILanguageClientRegistry registry, Action handler) { return registry.AddHandler(GeneralNames.Progress, NotificationHandler.For(handler)); } - public static IDisposable OnProgress( - this ILanguageClientRegistry registry, +public static ILanguageClientRegistry OnProgress(this ILanguageClientRegistry registry, Func handler) { return registry.AddHandler(GeneralNames.Progress, NotificationHandler.For(handler)); diff --git a/src/Protocol/IRegistrationManager.cs b/src/Protocol/IRegistrationManager.cs index ac8c195b5..000802dfc 100644 --- a/src/Protocol/IRegistrationManager.cs +++ b/src/Protocol/IRegistrationManager.cs @@ -1,4 +1,5 @@ -using DynamicData; +using System; +using System.Collections.Generic; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Protocol.Server.Capabilities; @@ -6,9 +7,10 @@ namespace OmniSharp.Extensions.LanguageServer.Protocol { public interface IRegistrationManager { - IObservableList Registrations { get; } - IObservableList GetRegistrationsForMethod(string method); - IObservableList GetRegistrationsMatchingSelector(DocumentSelector documentSelector); + IObservable> Registrations { get; } + IEnumerable CurrentRegistrations { get; } + IEnumerable GetRegistrationsForMethod(string method); + IEnumerable GetRegistrationsMatchingSelector(DocumentSelector documentSelector); void RegisterCapabilities(ServerCapabilities serverCapabilities); } } diff --git a/src/Protocol/IWorkspaceFoldersManager.cs b/src/Protocol/IWorkspaceFoldersManager.cs index eabf71b58..01f243ef2 100644 --- a/src/Protocol/IWorkspaceFoldersManager.cs +++ b/src/Protocol/IWorkspaceFoldersManager.cs @@ -1,4 +1,5 @@ -using DynamicData; +using System; +using System.Collections.Generic; using OmniSharp.Extensions.LanguageServer.Protocol.Models; namespace OmniSharp.Extensions.LanguageServer.Protocol @@ -10,6 +11,7 @@ public interface IWorkspaceFoldersManager void Remove(DocumentUri uri); void Remove(string name); void Remove(WorkspaceFolder workspaceFolder); - IObservableList WorkspaceFolders { get; } + IObservable> WorkspaceFolders { get; } + IEnumerable CurrentWorkspaceFolders { get; } } } diff --git a/src/Protocol/LanguageProtocolRpcOptionsBase.cs b/src/Protocol/LanguageProtocolRpcOptionsBase.cs new file mode 100644 index 000000000..b9afc3648 --- /dev/null +++ b/src/Protocol/LanguageProtocolRpcOptionsBase.cs @@ -0,0 +1,32 @@ +using System; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.LanguageServer.Protocol.Document; + +namespace OmniSharp.Extensions.LanguageServer.Protocol +{ + public abstract class LanguageProtocolRpcOptionsBase : JsonRpcServerOptionsBase where T : IJsonRpcHandlerRegistry + { + public T AddTextDocumentIdentifier(params ITextDocumentIdentifier[] handlers) + { + foreach (var item in handlers) + { + Services.AddSingleton(typeof(ITextDocumentIdentifier), handlers); + } + + return (T) (object) this; + } + + public T AddTextDocumentIdentifier() where TTextDocumentIdentifier : ITextDocumentIdentifier + { + Services.AddSingleton(typeof(ITextDocumentIdentifier), typeof(TTextDocumentIdentifier)); + return (T) (object) this; + } + + internal bool AddDefaultLoggingProvider { get; set; } + internal Action LoggingBuilderAction { get; set; } = _ => { }; + internal Action ConfigurationBuilderAction { get; set; } = _ => { }; + } +} diff --git a/src/Protocol/Progress/ProgressManager.cs b/src/Protocol/Progress/ProgressManager.cs index 9ed98c855..afbca58b3 100644 --- a/src/Protocol/Progress/ProgressManager.cs +++ b/src/Protocol/Progress/ProgressManager.cs @@ -7,6 +7,7 @@ using MediatR; using Newtonsoft.Json.Linq; using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.JsonRpc.Server; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using ISerializer = OmniSharp.Extensions.LanguageServer.Protocol.Serialization.ISerializer; @@ -65,7 +66,7 @@ public IRequestProgressObservable MonitorUntil(I observable = new RequestProgressObservable( _serializer, request.PartialResultToken, - Observable.FromAsync(ct => _router.SendRequest(request, ct)), + MakeRequest(request), (x, f) => factory(x), cancellationToken, () => _activeObservables.TryRemove(request.PartialResultToken, out var disposable)); _activeObservables.TryAdd(request.PartialResultToken, observable); @@ -81,7 +82,7 @@ public IRequestProgressObservable MonitorUntil(I return observable; } - observable = new RequestProgressObservable(_serializer, request.PartialResultToken, Observable.FromAsync(ct => _router.SendRequest(request, ct)), factory, + observable = new RequestProgressObservable(_serializer, request.PartialResultToken, MakeRequest(request), factory, cancellationToken, () => _activeObservables.TryRemove(request.PartialResultToken, out var disposable)); _activeObservables.TryAdd(request.PartialResultToken, observable); return observable; @@ -96,7 +97,7 @@ public IRequestProgressObservable, IEnumerable> Monito return observable; } - observable = new PartialItemsRequestProgressObservable>(_serializer,request.PartialResultToken, Observable.FromAsync(ct => _router.SendRequest(request, ct)), + observable = new PartialItemsRequestProgressObservable>(_serializer,request.PartialResultToken, MakeRequest(request), x => x, cancellationToken, () => _activeObservables.TryRemove(request.PartialResultToken, out var disposable)); _activeObservables.TryAdd(request.PartialResultToken, observable); return observable; @@ -112,7 +113,7 @@ public IRequestProgressObservable, TResponse> MonitorUntil(_serializer,request.PartialResultToken, Observable.FromAsync(ct => _router.SendRequest(request, ct)), factory, cancellationToken, + observable = new PartialItemsRequestProgressObservable(_serializer,request.PartialResultToken, MakeRequest(request), factory, cancellationToken, () => _activeObservables.TryRemove(request.PartialResultToken, out var disposable)); _activeObservables.TryAdd(request.PartialResultToken, observable); return observable; @@ -126,7 +127,7 @@ public IRequestProgressObservable MonitorUntil(IPartialItemsReques return observable; } - observable = new PartialItemsRequestProgressObservable(_serializer,request.PartialResultToken, Observable.FromAsync(ct => _router.SendRequest(request, ct)), x => new Container(x), cancellationToken, + observable = new PartialItemsRequestProgressObservable(_serializer,request.PartialResultToken, MakeRequest(request), x => new Container(x), cancellationToken, () => _activeObservables.TryRemove(request.PartialResultToken, out var disposable)); _activeObservables.TryAdd(request.PartialResultToken, observable); return observable; @@ -170,5 +171,26 @@ public IProgressObserver> For(IPartialItems _activeObservers.TryAdd(request.PartialResultToken, observer); return observer; } + + private IObservable MakeRequest(IRequest request) + { + // Has problems with wanting custom exceptions around cancellation. + // Observable.FromAsync(ct => _router.SendRequest(request, ct)) + return Observable.Create(async (observer, ct) => { + try + { + observer.OnNext(await _router.SendRequest(request, ct)); + observer.OnCompleted(); + } + catch (OperationCanceledException e) + { + observer.OnError(e); + } + catch (Exception e) + { + observer.OnError(e); + } + }); + } } } diff --git a/src/Protocol/Protocol.csproj b/src/Protocol/Protocol.csproj index 8c6a30556..996e1cb95 100644 --- a/src/Protocol/Protocol.csproj +++ b/src/Protocol/Protocol.csproj @@ -7,11 +7,13 @@ Language Server Protocol models, classes, interfaces and helper methods - <_Parameter1>OmniSharp.Extensions.LanguageServer, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f + + <_Parameter1>OmniSharp.Extensions.LanguageServer.Shared, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f + <_Parameter1>OmniSharp.Extensions.LanguageClient, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f diff --git a/src/Protocol/Server/ILanguageServer.cs b/src/Protocol/Server/ILanguageServer.cs index 5cab4830c..cc8856ebc 100644 --- a/src/Protocol/Server/ILanguageServer.cs +++ b/src/Protocol/Server/ILanguageServer.cs @@ -1,11 +1,12 @@ using System; using System.Threading; using System.Threading.Tasks; +using OmniSharp.Extensions.JsonRpc; using OmniSharp.Extensions.LanguageServer.Protocol.Models; namespace OmniSharp.Extensions.LanguageServer.Protocol.Server { - public interface ILanguageServer : ILanguageServerRegistry, IServerProxy, IDisposable + public interface ILanguageServer : IServerProxy, IJsonRpcHandlerInstance, IDisposable { ITextDocumentLanguageServer TextDocument { get; } IClientLanguageServer Client { get; } diff --git a/src/Protocol/Server/ILanguageServerRegistry.cs b/src/Protocol/Server/ILanguageServerRegistry.cs index 7dfe83916..639345811 100644 --- a/src/Protocol/Server/ILanguageServerRegistry.cs +++ b/src/Protocol/Server/ILanguageServerRegistry.cs @@ -4,10 +4,7 @@ namespace OmniSharp.Extensions.LanguageServer.Protocol.Server { - public interface ILanguageServerRegistry : IJsonRpcHandlerRegistry + public interface ILanguageServerRegistry : IJsonRpcHandlerRegistry { - IDisposable AddTextDocumentIdentifier(params ITextDocumentIdentifier[] handlers); - IDisposable AddTextDocumentIdentifier() where T : ITextDocumentIdentifier; - IDisposable AddHandler(Func handlerFunc) where T : IJsonRpcHandler; } } diff --git a/src/Protocol/Server/ServerProxyBase.cs b/src/Protocol/Server/ServerProxyBase.cs index 55a63a3ab..c68954ebd 100644 --- a/src/Protocol/Server/ServerProxyBase.cs +++ b/src/Protocol/Server/ServerProxyBase.cs @@ -33,7 +33,7 @@ public ServerProxyBase(IServerProxy proxy, IServiceProvider serviceProvider) public Task SendRequest(IRequest request, CancellationToken cancellationToken) => _proxy.SendRequest(request, cancellationToken); - public TaskCompletionSource GetRequest(long id) => _proxy.GetRequest(id); + (string method, TaskCompletionSource pendingTask) IResponseRouter.GetRequest(long id) => _proxy.GetRequest(id); object IServiceProvider.GetService(Type serviceType) => _serviceProvider.GetService(serviceType); public IProgressManager ProgressManager => _proxy.ProgressManager; public IServerWorkDoneManager WorkDoneManager => _proxy.WorkDoneManager; diff --git a/src/Protocol/Shared/ILspHandlerTypeDescriptor.cs b/src/Protocol/Shared/ILspHandlerTypeDescriptor.cs new file mode 100644 index 000000000..d32abb706 --- /dev/null +++ b/src/Protocol/Shared/ILspHandlerTypeDescriptor.cs @@ -0,0 +1,18 @@ +using System; +using OmniSharp.Extensions.JsonRpc; + +namespace OmniSharp.Extensions.LanguageServer.Protocol.Shared +{ + public interface ILspHandlerTypeDescriptor : IHandlerTypeDescriptor + { + bool HasRegistration { get; } + Type RegistrationType { get; } + bool HasCapability { get; } + Type CapabilityType { get; } + bool IsDynamicCapability { get; } + Type PartialItemsType { get; } + Type PartialItemType { get; } + bool HasPartialItems { get; } + bool HasPartialItem { get; } + } +} diff --git a/src/Protocol/Shared/LspHandlerDescriptor.cs b/src/Protocol/Shared/LspHandlerDescriptor.cs index 37684b1f4..ef6f6a0f4 100644 --- a/src/Protocol/Shared/LspHandlerDescriptor.cs +++ b/src/Protocol/Shared/LspHandlerDescriptor.cs @@ -14,6 +14,7 @@ namespace OmniSharp.Extensions.LanguageServer.Protocol.Shared class LspHandlerDescriptor : ILspHandlerDescriptor, IDisposable, IEquatable { private readonly Action _disposeAction; + private readonly Func _allowsDynamicRegistration; public LspHandlerDescriptor( string method, @@ -23,8 +24,9 @@ public LspHandlerDescriptor( Type @params, Type registrationType, object registrationOptions, - bool allowsDynamicRegistration, + Func allowsDynamicRegistration, Type capabilityType, + RequestProcessType? requestProcessType, Action disposeAction) { _disposeAction = disposeAction; @@ -37,7 +39,7 @@ public LspHandlerDescriptor( Params = @params; RegistrationType = registrationType; RegistrationOptions = registrationOptions; - AllowsDynamicRegistration = allowsDynamicRegistration; + _allowsDynamicRegistration = allowsDynamicRegistration; CapabilityType = capabilityType; var requestInterface = @params?.GetInterfaces() @@ -73,6 +75,7 @@ public LspHandlerDescriptor( .GetInterfaces().Any(z => z.IsGenericType && typeof(IJsonRpcNotificationHandler<>).IsAssignableFrom(z.GetGenericTypeDefinition())); IsRequest = !IsNotification; + RequestProcessType = requestProcessType; } public Type ImplementationType { get; } @@ -82,7 +85,7 @@ public LspHandlerDescriptor( public bool HasRegistration => RegistrationType != null; public Type RegistrationType { get; } public object RegistrationOptions { get; } - public bool AllowsDynamicRegistration { get; } + public bool AllowsDynamicRegistration => _allowsDynamicRegistration(); public bool HasCapability => CapabilityType != null; public Type CapabilityType { get; } @@ -102,6 +105,7 @@ public LspHandlerDescriptor( public IJsonRpcHandler Handler { get; } public bool IsNotification { get; } public bool IsRequest { get; } + public RequestProcessType? RequestProcessType { get; } public void Dispose() { diff --git a/src/Protocol/Shared/LspHandlerTypeDescriptor.cs b/src/Protocol/Shared/LspHandlerTypeDescriptor.cs new file mode 100644 index 000000000..60886d7f4 --- /dev/null +++ b/src/Protocol/Shared/LspHandlerTypeDescriptor.cs @@ -0,0 +1,43 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; + +namespace OmniSharp.Extensions.LanguageServer.Protocol.Shared +{ + [DebuggerDisplay("{" + nameof(Method) + "}")] + class LspHandlerTypeDescriptor : HandlerTypeDescriptor, ILspHandlerTypeDescriptor + { + public LspHandlerTypeDescriptor(Type handlerType) : base(handlerType) + { + PartialItemsType = ParamsType.GetInterfaces().FirstOrDefault(z => z.IsGenericType && typeof(IPartialItems<>).IsAssignableFrom(z.GetGenericTypeDefinition())) + ?.GetGenericArguments()[0]; + HasPartialItems = PartialItemsType != null; + PartialItemType = ParamsType.GetInterfaces().FirstOrDefault(z => z.IsGenericType && typeof(IPartialItem<>).IsAssignableFrom(z.GetGenericTypeDefinition())) + ?.GetGenericArguments()[0]; + HasPartialItem = PartialItemType != null; + RegistrationType = HandlerTypeDescriptorHelper.UnwrapGenericType(typeof(IRegistration<>), handlerType); + HasRegistration = RegistrationType != null && RegistrationType != typeof(object); + if (!HasRegistration) RegistrationType = null; + CapabilityType = HandlerTypeDescriptorHelper.UnwrapGenericType(typeof(ICapability<>), handlerType); + HasCapability = CapabilityType != null; + if (!HasCapability) CapabilityType = null; + if (HasCapability) + IsDynamicCapability = typeof(IDynamicCapability).GetTypeInfo().IsAssignableFrom(CapabilityType); + } + + public Type PartialItemsType { get; } + public Type PartialItemType { get; } + public bool HasPartialItems { get; } + public bool HasPartialItem { get; } + public bool HasRegistration { get; } + public Type RegistrationType { get; } + public bool HasCapability { get; } + public Type CapabilityType { get; } + public bool IsDynamicCapability { get; } + public override string ToString() => $"{Method}"; + } +} diff --git a/src/Protocol/Shared/LspHandlerTypeDescriptorHelper.cs b/src/Protocol/Shared/LspHandlerTypeDescriptorHelper.cs new file mode 100644 index 000000000..173e8a686 --- /dev/null +++ b/src/Protocol/Shared/LspHandlerTypeDescriptorHelper.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using OmniSharp.Extensions.JsonRpc; + +namespace OmniSharp.Extensions.LanguageServer.Protocol.Shared +{ + public static class LspHandlerTypeDescriptorHelper + { + private static readonly ConcurrentDictionary MethodNames = + new ConcurrentDictionary(); + + private static readonly ImmutableSortedDictionary KnownHandlers; + + static LspHandlerTypeDescriptorHelper() + { + try + { + KnownHandlers = HandlerTypeDescriptorHelper.KnownHandlers.Values + .Select(x => new LspHandlerTypeDescriptor(x.HandlerType) as ILspHandlerTypeDescriptor) + .ToImmutableSortedDictionary(x => x.Method, x => x, StringComparer.Ordinal); + } + catch (Exception e) + { + throw new AggregateException($"Failed", e); + } + } + + public static ILspHandlerTypeDescriptor GetHandlerTypeForRegistrationOptions(object registrationOptions) + { + var registrationType = registrationOptions.GetType(); + var interfaces = new HashSet(registrationOptions.GetType().GetInterfaces() + .Except(registrationType.BaseType?.GetInterfaces() ?? Enumerable.Empty())); + return interfaces.SelectMany(x => + KnownHandlers.Values + .Where(z => z.HasRegistration) + .Where(z => x.IsAssignableFrom(z.RegistrationType))) + .FirstOrDefault(); + } + + public static ILspHandlerTypeDescriptor GetHandlerTypeDescriptor(string method) + { + return KnownHandlers.TryGetValue(method, out var descriptor) ? descriptor : null; + } + + public static ILspHandlerTypeDescriptor GetHandlerTypeDescriptor() + { + return KnownHandlers.Values.FirstOrDefault(x => x.InterfaceType == typeof(T)) ?? + GetHandlerTypeDescriptor(HandlerTypeDescriptorHelper.GetMethodName(typeof(T))); + } + + public static ILspHandlerTypeDescriptor GetHandlerTypeDescriptor(Type type) + { + var @default = KnownHandlers.Values.FirstOrDefault(x => x.InterfaceType == type); + if (@default != null) + { + return @default; + } + + var methodName = HandlerTypeDescriptorHelper.GetMethodName(type); + if (string.IsNullOrWhiteSpace(methodName)) return null; + return GetHandlerTypeDescriptor(methodName); + } + } +} diff --git a/src/Protocol/Window/ILogMessageHandler.cs b/src/Protocol/Window/ILogMessageHandler.cs index b840bf402..b1fc064e8 100644 --- a/src/Protocol/Window/ILogMessageHandler.cs +++ b/src/Protocol/Window/ILogMessageHandler.cs @@ -19,29 +19,25 @@ public abstract class LogMessageHandler : ILogMessageHandler public static class LogMessageExtensions { - public static IDisposable OnLogMessage( - this ILanguageClientRegistry registry, +public static ILanguageClientRegistry OnLogMessage(this ILanguageClientRegistry registry, Action handler) { return registry.AddHandler(WindowNames.LogMessage, NotificationHandler.For(handler)); } - public static IDisposable OnLogMessage( - this ILanguageClientRegistry registry, +public static ILanguageClientRegistry OnLogMessage(this ILanguageClientRegistry registry, Action handler) { return registry.AddHandler(WindowNames.LogMessage, NotificationHandler.For(handler)); } - public static IDisposable OnLogMessage( - this ILanguageClientRegistry registry, +public static ILanguageClientRegistry OnLogMessage(this ILanguageClientRegistry registry, Func handler) { return registry.AddHandler(WindowNames.LogMessage, NotificationHandler.For(handler)); } - public static IDisposable OnLogMessage( - this ILanguageClientRegistry registry, +public static ILanguageClientRegistry OnLogMessage(this ILanguageClientRegistry registry, Func handler) { return registry.AddHandler(WindowNames.LogMessage, NotificationHandler.For(handler)); diff --git a/src/Protocol/Window/IShowMessageHandler.cs b/src/Protocol/Window/IShowMessageHandler.cs index 1bebeb5ed..8972b17e7 100644 --- a/src/Protocol/Window/IShowMessageHandler.cs +++ b/src/Protocol/Window/IShowMessageHandler.cs @@ -19,29 +19,25 @@ public abstract class ShowMessageHandler : IShowMessageHandler public static class ShowMessageExtensions { - public static IDisposable OnShowMessage( - this ILanguageClientRegistry registry, +public static ILanguageClientRegistry OnShowMessage(this ILanguageClientRegistry registry, Action handler) { return registry.AddHandler(WindowNames.ShowMessage, NotificationHandler.For(handler)); } - public static IDisposable OnShowMessage( - this ILanguageClientRegistry registry, +public static ILanguageClientRegistry OnShowMessage(this ILanguageClientRegistry registry, Action handler) { return registry.AddHandler(WindowNames.ShowMessage, NotificationHandler.For(handler)); } - public static IDisposable OnShowMessage( - this ILanguageClientRegistry registry, +public static ILanguageClientRegistry OnShowMessage(this ILanguageClientRegistry registry, Func handler) { return registry.AddHandler(WindowNames.ShowMessage, NotificationHandler.For(handler)); } - public static IDisposable OnShowMessage( - this ILanguageClientRegistry registry, +public static ILanguageClientRegistry OnShowMessage(this ILanguageClientRegistry registry, Func handler) { return registry.AddHandler(WindowNames.ShowMessage, NotificationHandler.For(handler)); diff --git a/src/Protocol/Window/IShowMessageRequestHandler.cs b/src/Protocol/Window/IShowMessageRequestHandler.cs index dda9eeaab..314d860de 100644 --- a/src/Protocol/Window/IShowMessageRequestHandler.cs +++ b/src/Protocol/Window/IShowMessageRequestHandler.cs @@ -18,16 +18,14 @@ public abstract class ShowMessageRequestHandler : IShowMessageRequestHandler public static class ShowMessageRequestExtensions { - public static IDisposable OnShowMessageRequest( - this ILanguageClientRegistry registry, +public static ILanguageClientRegistry OnShowMessageRequest(this ILanguageClientRegistry registry, Func> handler) { return registry.AddHandler(WindowNames.ShowMessageRequest, RequestHandler.For(handler)); } - public static IDisposable OnShowMessageRequest( - this ILanguageClientRegistry registry, +public static ILanguageClientRegistry OnShowMessageRequest(this ILanguageClientRegistry registry, Func> handler) { return registry.AddHandler(WindowNames.ShowMessageRequest, RequestHandler.For(handler)); diff --git a/src/Protocol/Window/ITelemetryEventHandler.cs b/src/Protocol/Window/ITelemetryEventHandler.cs index cd89c8c1e..945e07661 100644 --- a/src/Protocol/Window/ITelemetryEventHandler.cs +++ b/src/Protocol/Window/ITelemetryEventHandler.cs @@ -19,29 +19,25 @@ public abstract class TelemetryEventHandler : ITelemetryEventHandler public static class TelemetryEventExtensions { - public static IDisposable OnTelemetryEvent( - this ILanguageClientRegistry registry, +public static ILanguageClientRegistry OnTelemetryEvent(this ILanguageClientRegistry registry, Action handler) { return registry.AddHandler(WindowNames.TelemetryEvent, NotificationHandler.For(handler)); } - public static IDisposable OnTelemetryEvent( - this ILanguageClientRegistry registry, +public static ILanguageClientRegistry OnTelemetryEvent(this ILanguageClientRegistry registry, Action handler) { return registry.AddHandler(WindowNames.TelemetryEvent, NotificationHandler.For(handler)); } - public static IDisposable OnTelemetryEvent( - this ILanguageClientRegistry registry, +public static ILanguageClientRegistry OnTelemetryEvent(this ILanguageClientRegistry registry, Func handler) { return registry.AddHandler(WindowNames.TelemetryEvent, NotificationHandler.For(handler)); } - public static IDisposable OnTelemetryEvent( - this ILanguageClientRegistry registry, +public static ILanguageClientRegistry OnTelemetryEvent(this ILanguageClientRegistry registry, Func handler) { return registry.AddHandler(WindowNames.TelemetryEvent, NotificationHandler.For(handler)); diff --git a/src/Protocol/Window/IWorkDoneProgressCancelHandler.cs b/src/Protocol/Window/IWorkDoneProgressCancelHandler.cs index ac64d6fa3..2f7aaf261 100644 --- a/src/Protocol/Window/IWorkDoneProgressCancelHandler.cs +++ b/src/Protocol/Window/IWorkDoneProgressCancelHandler.cs @@ -19,29 +19,25 @@ public abstract class WorkDoneProgressCancelHandler : IWorkDoneProgressCancelHan public static class WorkDoneProgressCancelExtensions { - public static IDisposable OnWorkDoneProgressCancel( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnWorkDoneProgressCancel(this ILanguageServerRegistry registry, Action handler) { return registry.AddHandler(WindowNames.WorkDoneProgressCancel, NotificationHandler.For(handler)); } - public static IDisposable OnWorkDoneProgressCancel( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnWorkDoneProgressCancel(this ILanguageServerRegistry registry, Action handler) { return registry.AddHandler(WindowNames.WorkDoneProgressCancel, NotificationHandler.For(handler)); } - public static IDisposable OnWorkDoneProgressCancel( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnWorkDoneProgressCancel(this ILanguageServerRegistry registry, Func handler) { return registry.AddHandler(WindowNames.WorkDoneProgressCancel, NotificationHandler.For(handler)); } - public static IDisposable OnWorkDoneProgressCancel( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnWorkDoneProgressCancel(this ILanguageServerRegistry registry, Func handler) { return registry.AddHandler(WindowNames.WorkDoneProgressCancel, NotificationHandler.For(handler)); diff --git a/src/Protocol/Window/IWorkDoneProgressCreateHandler.cs b/src/Protocol/Window/IWorkDoneProgressCreateHandler.cs index 93d291b5d..604bbaa03 100644 --- a/src/Protocol/Window/IWorkDoneProgressCreateHandler.cs +++ b/src/Protocol/Window/IWorkDoneProgressCreateHandler.cs @@ -19,16 +19,14 @@ public abstract class WorkDoneProgressCreateHandler : IWorkDoneProgressCreateHan public static class WorkDoneProgressCreateExtensions { - public static IDisposable OnWorkDoneProgressCreate( - this ILanguageClientRegistry registry, +public static ILanguageClientRegistry OnWorkDoneProgressCreate(this ILanguageClientRegistry registry, Func handler) { return registry.AddHandler(WindowNames.WorkDoneProgressCreate, RequestHandler.For(handler)); } - public static IDisposable OnWorkDoneProgressCreate( - this ILanguageClientRegistry registry, +public static ILanguageClientRegistry OnWorkDoneProgressCreate(this ILanguageClientRegistry registry, Func handler) { diff --git a/src/Protocol/Workspace/IApplyEditHandler.cs b/src/Protocol/Workspace/IApplyEditHandler.cs index 0c4d76189..aa71891db 100644 --- a/src/Protocol/Workspace/IApplyEditHandler.cs +++ b/src/Protocol/Workspace/IApplyEditHandler.cs @@ -18,16 +18,14 @@ public abstract class ApplyWorkspaceEditHandler : IApplyWorkspaceEditHandler public static class ApplyWorkspaceEditExtensions { - public static IDisposable OnApplyWorkspaceEdit( - this ILanguageClientRegistry registry, +public static ILanguageClientRegistry OnApplyWorkspaceEdit(this ILanguageClientRegistry registry, Func> handler) { return registry.AddHandler(WorkspaceNames.ApplyEdit, RequestHandler.For(handler)); } - public static IDisposable OnApplyWorkspaceEdit( - this ILanguageClientRegistry registry, +public static ILanguageClientRegistry OnApplyWorkspaceEdit(this ILanguageClientRegistry registry, Func> handler) { return registry.AddHandler(WorkspaceNames.ApplyEdit, RequestHandler.For(handler)); diff --git a/src/Protocol/Workspace/IConfigurationHandler.cs b/src/Protocol/Workspace/IConfigurationHandler.cs index ed7f497a2..8785049bb 100644 --- a/src/Protocol/Workspace/IConfigurationHandler.cs +++ b/src/Protocol/Workspace/IConfigurationHandler.cs @@ -19,16 +19,14 @@ public abstract class ConfigurationHandler : IConfigurationHandler public static class ConfigurationExtensions { - public static IDisposable OnConfiguration( - this ILanguageClientRegistry registry, +public static ILanguageClientRegistry OnConfiguration(this ILanguageClientRegistry registry, Func>> handler) { return registry.AddHandler(WorkspaceNames.Configuration, RequestHandler.For(handler)); } - public static IDisposable OnConfiguration( - this ILanguageClientRegistry registry, +public static ILanguageClientRegistry OnConfiguration(this ILanguageClientRegistry registry, Func>> handler) { return registry.AddHandler(WorkspaceNames.Configuration, RequestHandler.For(handler)); diff --git a/src/Protocol/Workspace/IDidChangeConfigurationHandler.cs b/src/Protocol/Workspace/IDidChangeConfigurationHandler.cs index 58a6d9f0f..57d854a59 100644 --- a/src/Protocol/Workspace/IDidChangeConfigurationHandler.cs +++ b/src/Protocol/Workspace/IDidChangeConfigurationHandler.cs @@ -26,8 +26,7 @@ public abstract class DidChangeConfigurationHandler : IDidChangeConfigurationHan public static class DidChangeConfigurationExtensions { - public static IDisposable OnDidChangeConfiguration( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeConfiguration(this ILanguageServerRegistry registry, Action handler) { return registry.AddHandler(WorkspaceNames.DidChangeConfiguration, @@ -38,8 +37,7 @@ public static IDisposable OnDidChangeConfiguration( })); } - public static IDisposable OnDidChangeConfiguration( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeConfiguration(this ILanguageServerRegistry registry, Action handler) { return registry.AddHandler(WorkspaceNames.DidChangeConfiguration, @@ -47,22 +45,19 @@ public static IDisposable OnDidChangeConfiguration( DidChangeConfigurationCapability>(handler)); } - public static IDisposable OnDidChangeConfiguration( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeConfiguration(this ILanguageServerRegistry registry, Action handler) { return registry.AddHandler(WorkspaceNames.DidChangeConfiguration, NotificationHandler.For(handler)); } - public static IDisposable OnDidChangeConfiguration( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeConfiguration(this ILanguageServerRegistry registry, Action handler) { return registry.AddHandler(WorkspaceNames.DidChangeConfiguration, NotificationHandler.For(handler)); } - public static IDisposable OnDidChangeConfiguration( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeConfiguration(this ILanguageServerRegistry registry, Func handler) { return registry.AddHandler(WorkspaceNames.DidChangeConfiguration, @@ -70,8 +65,7 @@ public static IDisposable OnDidChangeConfiguration( DidChangeConfigurationCapability>(handler)); } - public static IDisposable OnDidChangeConfiguration( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeConfiguration(this ILanguageServerRegistry registry, Func handler) { return registry.AddHandler(WorkspaceNames.DidChangeConfiguration, @@ -79,15 +73,13 @@ public static IDisposable OnDidChangeConfiguration( DidChangeConfigurationCapability>(handler)); } - public static IDisposable OnDidChangeConfiguration( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeConfiguration(this ILanguageServerRegistry registry, Func handler) { return registry.AddHandler(WorkspaceNames.DidChangeConfiguration, NotificationHandler.For(handler)); } - public static IDisposable OnDidChangeConfiguration( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeConfiguration(this ILanguageServerRegistry registry, Func handler) { return registry.AddHandler(WorkspaceNames.DidChangeConfiguration, NotificationHandler.For(handler)); diff --git a/src/Protocol/Workspace/IDidChangeWatchedFilesHandler.cs b/src/Protocol/Workspace/IDidChangeWatchedFilesHandler.cs index 973c6ef40..f8aa8bdc7 100644 --- a/src/Protocol/Workspace/IDidChangeWatchedFilesHandler.cs +++ b/src/Protocol/Workspace/IDidChangeWatchedFilesHandler.cs @@ -29,8 +29,7 @@ public DidChangeWatchedFilesHandler(DidChangeWatchedFilesRegistrationOptions reg public static class DidChangeWatchedFilesExtensions { - public static IDisposable OnDidChangeWatchedFiles( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeWatchedFiles(this ILanguageServerRegistry registry, Action handler, DidChangeWatchedFilesRegistrationOptions registrationOptions) { @@ -40,8 +39,7 @@ public static IDisposable OnDidChangeWatchedFiles( DidChangeWatchedFilesRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidChangeWatchedFiles( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeWatchedFiles(this ILanguageServerRegistry registry, Action handler, DidChangeWatchedFilesRegistrationOptions registrationOptions) { @@ -51,8 +49,7 @@ public static IDisposable OnDidChangeWatchedFiles( DidChangeWatchedFilesRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidChangeWatchedFiles( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeWatchedFiles(this ILanguageServerRegistry registry, Action handler, DidChangeWatchedFilesRegistrationOptions registrationOptions) { @@ -62,8 +59,7 @@ public static IDisposable OnDidChangeWatchedFiles( DidChangeWatchedFilesRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidChangeWatchedFiles( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeWatchedFiles(this ILanguageServerRegistry registry, Action handler, DidChangeWatchedFilesRegistrationOptions registrationOptions) { @@ -73,8 +69,7 @@ public static IDisposable OnDidChangeWatchedFiles( DidChangeWatchedFilesRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidChangeWatchedFiles( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeWatchedFiles(this ILanguageServerRegistry registry, Func handler, DidChangeWatchedFilesRegistrationOptions registrationOptions) { @@ -84,8 +79,7 @@ public static IDisposable OnDidChangeWatchedFiles( DidChangeWatchedFilesRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidChangeWatchedFiles( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeWatchedFiles(this ILanguageServerRegistry registry, Func handler, DidChangeWatchedFilesRegistrationOptions registrationOptions) { @@ -95,8 +89,7 @@ public static IDisposable OnDidChangeWatchedFiles( DidChangeWatchedFilesRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidChangeWatchedFiles( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeWatchedFiles(this ILanguageServerRegistry registry, Func handler, DidChangeWatchedFilesRegistrationOptions registrationOptions) { @@ -106,8 +99,7 @@ public static IDisposable OnDidChangeWatchedFiles( DidChangeWatchedFilesRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnDidChangeWatchedFiles( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeWatchedFiles(this ILanguageServerRegistry registry, Func handler, DidChangeWatchedFilesRegistrationOptions registrationOptions) { diff --git a/src/Protocol/Workspace/IDidChangeWorkspaceFoldersHandler.cs b/src/Protocol/Workspace/IDidChangeWorkspaceFoldersHandler.cs index 8154b68c4..b68b2ba14 100644 --- a/src/Protocol/Workspace/IDidChangeWorkspaceFoldersHandler.cs +++ b/src/Protocol/Workspace/IDidChangeWorkspaceFoldersHandler.cs @@ -26,8 +26,7 @@ public abstract class DidChangeWorkspaceFoldersHandler : IDidChangeWorkspaceFold public static class DidChangeWorkspaceFoldersExtensions { - public static IDisposable OnDidChangeWorkspaceFolders( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeWorkspaceFolders(this ILanguageServerRegistry registry, Action handler) { return registry.AddHandler(WorkspaceNames.DidChangeWorkspaceFolders, @@ -38,8 +37,7 @@ public static IDisposable OnDidChangeWorkspaceFolders( })); } - public static IDisposable OnDidChangeWorkspaceFolders( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeWorkspaceFolders(this ILanguageServerRegistry registry, Action handler) { return registry.AddHandler(WorkspaceNames.DidChangeWorkspaceFolders, @@ -47,24 +45,21 @@ public static IDisposable OnDidChangeWorkspaceFolders( DidChangeWorkspaceFolderCapability>(handler)); } - public static IDisposable OnDidChangeWorkspaceFolders( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeWorkspaceFolders(this ILanguageServerRegistry registry, Action handler) { return registry.AddHandler(WorkspaceNames.DidChangeWorkspaceFolders, NotificationHandler.For(handler)); } - public static IDisposable OnDidChangeWorkspaceFolders( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeWorkspaceFolders(this ILanguageServerRegistry registry, Action handler) { return registry.AddHandler(WorkspaceNames.DidChangeWorkspaceFolders, NotificationHandler.For(handler)); } - public static IDisposable OnDidChangeWorkspaceFolders( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeWorkspaceFolders(this ILanguageServerRegistry registry, Func handler) { return registry.AddHandler(WorkspaceNames.DidChangeWorkspaceFolders, @@ -72,8 +67,7 @@ public static IDisposable OnDidChangeWorkspaceFolders( DidChangeWorkspaceFolderCapability>(handler)); } - public static IDisposable OnDidChangeWorkspaceFolders( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeWorkspaceFolders(this ILanguageServerRegistry registry, Func handler) { return registry.AddHandler(WorkspaceNames.DidChangeWorkspaceFolders, @@ -81,16 +75,14 @@ public static IDisposable OnDidChangeWorkspaceFolders( DidChangeWorkspaceFolderCapability>(handler)); } - public static IDisposable OnDidChangeWorkspaceFolders( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeWorkspaceFolders(this ILanguageServerRegistry registry, Func handler) { return registry.AddHandler(WorkspaceNames.DidChangeWorkspaceFolders, NotificationHandler.For(handler)); } - public static IDisposable OnDidChangeWorkspaceFolders( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnDidChangeWorkspaceFolders(this ILanguageServerRegistry registry, Func handler) { return registry.AddHandler(WorkspaceNames.DidChangeWorkspaceFolders, diff --git a/src/Protocol/Workspace/IExecuteCommandHandler.cs b/src/Protocol/Workspace/IExecuteCommandHandler.cs index 5ce27c531..72787787b 100644 --- a/src/Protocol/Workspace/IExecuteCommandHandler.cs +++ b/src/Protocol/Workspace/IExecuteCommandHandler.cs @@ -30,8 +30,7 @@ public ExecuteCommandHandler(ExecuteCommandRegistrationOptions registrationOptio public static class ExecuteCommandExtensions { - public static IDisposable OnExecuteCommand( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnExecuteCommand(this ILanguageServerRegistry registry, Func handler, ExecuteCommandRegistrationOptions registrationOptions) @@ -42,8 +41,7 @@ public static IDisposable OnExecuteCommand( ExecuteCommandRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnExecuteCommand( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnExecuteCommand(this ILanguageServerRegistry registry, Func handler, ExecuteCommandRegistrationOptions registrationOptions) { @@ -53,8 +51,7 @@ public static IDisposable OnExecuteCommand( ExecuteCommandRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnExecuteCommand( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnExecuteCommand(this ILanguageServerRegistry registry, Func handler, ExecuteCommandRegistrationOptions registrationOptions) { @@ -64,8 +61,7 @@ public static IDisposable OnExecuteCommand( ExecuteCommandRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnExecuteCommand( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnExecuteCommand(this ILanguageServerRegistry registry, Func handler, ExecuteCommandRegistrationOptions registrationOptions) { diff --git a/src/Protocol/Workspace/IWorkspaceFoldersHandler.cs b/src/Protocol/Workspace/IWorkspaceFoldersHandler.cs index 1a5f38e35..f35828d39 100644 --- a/src/Protocol/Workspace/IWorkspaceFoldersHandler.cs +++ b/src/Protocol/Workspace/IWorkspaceFoldersHandler.cs @@ -18,16 +18,14 @@ public abstract class WorkspaceFoldersHandler : IWorkspaceFoldersHandler public static class WorkspaceFoldersExtensions { - public static IDisposable OnWorkspaceFolders( - this ILanguageClientRegistry registry, +public static ILanguageClientRegistry OnWorkspaceFolders(this ILanguageClientRegistry registry, Func>> handler) { return registry.AddHandler(WorkspaceNames.WorkspaceFolders, RequestHandler.For(handler)); } - public static IDisposable OnWorkspaceFolders( - this ILanguageClientRegistry registry, +public static ILanguageClientRegistry OnWorkspaceFolders(this ILanguageClientRegistry registry, Func>> handler) { return registry.AddHandler(WorkspaceNames.WorkspaceFolders, RequestHandler.For(handler)); diff --git a/src/Protocol/Workspace/IWorkspaceSymbolsHandler.cs b/src/Protocol/Workspace/IWorkspaceSymbolsHandler.cs index c2e0a2ccd..7303e0034 100644 --- a/src/Protocol/Workspace/IWorkspaceSymbolsHandler.cs +++ b/src/Protocol/Workspace/IWorkspaceSymbolsHandler.cs @@ -32,8 +32,7 @@ public WorkspaceSymbolsHandler(WorkspaceSymbolRegistrationOptions registrationOp public static class WorkspaceSymbolsExtensions { - public static IDisposable OnWorkspaceSymbols( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnWorkspaceSymbols(this ILanguageServerRegistry registry, Func>> handler, WorkspaceSymbolRegistrationOptions registrationOptions) @@ -44,8 +43,7 @@ public static IDisposable OnWorkspaceSymbols( WorkspaceSymbolRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnWorkspaceSymbols( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnWorkspaceSymbols(this ILanguageServerRegistry registry, Func>> handler, WorkspaceSymbolRegistrationOptions registrationOptions) { @@ -55,8 +53,7 @@ public static IDisposable OnWorkspaceSymbols( WorkspaceSymbolRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnWorkspaceSymbols( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnWorkspaceSymbols(this ILanguageServerRegistry registry, Func>> handler, WorkspaceSymbolRegistrationOptions registrationOptions) { @@ -66,8 +63,7 @@ public static IDisposable OnWorkspaceSymbols( WorkspaceSymbolRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnWorkspaceSymbols( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnWorkspaceSymbols(this ILanguageServerRegistry registry, Func>> handler, WorkspaceSymbolRegistrationOptions registrationOptions) { @@ -77,8 +73,7 @@ public static IDisposable OnWorkspaceSymbols( WorkspaceSymbolRegistrationOptions>(handler, registrationOptions)); } - public static IDisposable OnWorkspaceSymbols( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnWorkspaceSymbols(this ILanguageServerRegistry registry, Action>, WorkspaceSymbolCapability, CancellationToken> handler, WorkspaceSymbolRegistrationOptions registrationOptions) { registrationOptions ??= new WorkspaceSymbolRegistrationOptions(); @@ -90,8 +85,7 @@ public static IDisposable OnWorkspaceSymbols( x => new Container(x))); } - public static IDisposable OnWorkspaceSymbols( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnWorkspaceSymbols(this ILanguageServerRegistry registry, Action>, WorkspaceSymbolCapability> handler, WorkspaceSymbolRegistrationOptions registrationOptions) @@ -105,8 +99,7 @@ public static IDisposable OnWorkspaceSymbols( x => new Container(x))); } - public static IDisposable OnWorkspaceSymbols( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnWorkspaceSymbols(this ILanguageServerRegistry registry, Action>, CancellationToken> handler, WorkspaceSymbolRegistrationOptions registrationOptions) { @@ -119,8 +112,7 @@ public static IDisposable OnWorkspaceSymbols( x => new Container(x))); } - public static IDisposable OnWorkspaceSymbols( - this ILanguageServerRegistry registry, +public static ILanguageServerRegistry OnWorkspaceSymbols(this ILanguageServerRegistry registry, Action>> handler, WorkspaceSymbolRegistrationOptions registrationOptions) { diff --git a/src/Server/Abstractions/ILspHandlerDescriptor.cs b/src/Server/Abstractions/ILspHandlerDescriptor.cs deleted file mode 100644 index be5a3ce48..000000000 --- a/src/Server/Abstractions/ILspHandlerDescriptor.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using OmniSharp.Extensions.JsonRpc; - -namespace OmniSharp.Extensions.LanguageServer.Server.Abstractions -{ - public interface ILspHandlerDescriptor : IHandlerDescriptor - { - Guid Id { get; } - bool HasRegistration { get; } - Type RegistrationType { get; } - object RegistrationOptions { get; } - bool AllowsDynamicRegistration { get; } - - bool HasCapability { get; } - Type CapabilityType { get; } - bool IsDynamicCapability { get; } - Type CanBeResolvedHandlerType { get; } - StartedDelegate StartedDelegate { get; } - } -} diff --git a/src/Server/ClientCapabilityProvider.cs b/src/Server/ClientCapabilityProvider.cs index 9a1178a01..74d20befe 100644 --- a/src/Server/ClientCapabilityProvider.cs +++ b/src/Server/ClientCapabilityProvider.cs @@ -6,7 +6,7 @@ using OmniSharp.Extensions.LanguageServer.Protocol; using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; using OmniSharp.Extensions.LanguageServer.Protocol.Models; -using OmniSharp.Extensions.LanguageServer.Server.Abstractions; +using OmniSharp.Extensions.LanguageServer.Shared; namespace OmniSharp.Extensions.LanguageServer.Server { @@ -22,7 +22,7 @@ public ClientCapabilityProvider(IHandlerCollection collection, bool supportsProg } public bool HasStaticHandler(Supports capability) - where T : DynamicCapability, ConnectedCapability + where T : ConnectedCapability, IDynamicCapability { // Dynamic registration will cause us to double register things if we report our capabilities staticly. // However if the client does not tell us it's capabilities we should just assume that they do not support diff --git a/src/Server/CompositeDisposable.cs b/src/Server/CompositeDisposable.cs deleted file mode 100644 index d9a428886..000000000 --- a/src/Server/CompositeDisposable.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace OmniSharp.Extensions.LanguageServer.Server -{ - class CompositeDisposable : IDisposable - { - private readonly List _instances; - - public CompositeDisposable(IEnumerable instances) - { - _instances = instances.ToList(); - } - - public CompositeDisposable(params IDisposable[] instances) - { - _instances = instances.ToList(); - } - - public void Add(params IDisposable[] disposable) - { - _instances.AddRange(disposable); - } - - public void Remove(IDisposable disposable) - { - _instances.Remove(disposable); - } - - public void Dispose() - { - foreach (var instance in _instances) - { - instance.Dispose(); - } - } - } -} diff --git a/src/Server/Configuration/DidChangeConfigurationProvider.cs b/src/Server/Configuration/DidChangeConfigurationProvider.cs index 6cabc808f..1bf3d14b6 100644 --- a/src/Server/Configuration/DidChangeConfigurationProvider.cs +++ b/src/Server/Configuration/DidChangeConfigurationProvider.cs @@ -14,12 +14,13 @@ using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Protocol.Server; +using OmniSharp.Extensions.LanguageServer.Protocol.Shared; using OmniSharp.Extensions.LanguageServer.Protocol.Workspace; namespace OmniSharp.Extensions.LanguageServer.Server.Configuration { class DidChangeConfigurationProvider : BaseWorkspaceConfigurationProvider, IDidChangeConfigurationHandler, - IOnStarted, ILanguageServerConfiguration + IOnServerStarted, ILanguageServerConfiguration { private readonly ILanguageServer _server; private DidChangeConfigurationCapability _capability; @@ -56,7 +57,7 @@ public async Task Handle(DidChangeConfigurationParams request, Cancellatio public void SetCapability(DidChangeConfigurationCapability capability) => _capability = capability; - Task IOnStarted.OnStarted(ILanguageServer server, InitializeResult result, CancellationToken cancellationToken) => GetWorkspaceConfiguration(); + Task IOnServerStarted.OnStarted(ILanguageServer server, InitializeResult result, CancellationToken cancellationToken) => GetWorkspaceConfiguration(); private async Task GetWorkspaceConfiguration() { diff --git a/src/Server/HandlerDescriptor.cs b/src/Server/HandlerDescriptor.cs deleted file mode 100644 index 8eb28cd4f..000000000 --- a/src/Server/HandlerDescriptor.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using MediatR; -using OmniSharp.Extensions.JsonRpc; -using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; -using OmniSharp.Extensions.LanguageServer.Protocol.Models; -using OmniSharp.Extensions.LanguageServer.Server.Abstractions; - -namespace OmniSharp.Extensions.LanguageServer.Server -{ - class HandlerDescriptor : ILspHandlerDescriptor, IDisposable, IEquatable - { - private readonly Action _disposeAction; - - public HandlerDescriptor( - string method, - string key, - IJsonRpcHandler handler, - Type handlerType, - Type @params, - Type registrationType, - object registrationOptions, - bool allowsDynamicRegistration, - Type capabilityType, - Action disposeAction) - { - _disposeAction = disposeAction; - Id = Guid.NewGuid(); - Method = method; - Key = key; - ImplementationType = handler.GetType(); - Handler = handler; - HandlerType = handlerType; - Params = @params; - Response = Response; - RegistrationType = registrationType; - RegistrationOptions = registrationOptions; - AllowsDynamicRegistration = allowsDynamicRegistration; - CapabilityType = capabilityType; - - var requestInterface = @params?.GetInterfaces() - .FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IRequest<>)); - if (requestInterface != null) - Response = requestInterface.GetGenericArguments()[0]; - - // If multiple are implemented this behavior is unknown - CanBeResolvedHandlerType = handler.GetType().GetTypeInfo() - .ImplementedInterfaces - .FirstOrDefault(x => x.GetTypeInfo().IsGenericType && x.GetTypeInfo().GetGenericTypeDefinition() == typeof(ICanBeResolvedHandler<>)); - - HasReturnType = HandlerType.GetInterfaces().Any(@interface => - @interface.IsGenericType && - typeof(IRequestHandler<,>).IsAssignableFrom(@interface.GetGenericTypeDefinition()) - ); - - IsDelegatingHandler = @params?.IsGenericType == true && - ( - typeof(DelegatingRequest<>).IsAssignableFrom(@params.GetGenericTypeDefinition()) || - typeof(DelegatingNotification<>).IsAssignableFrom(@params.GetGenericTypeDefinition()) - ); - if (handler is IOnStarted started) - { - StartedDelegate = started.OnStarted; - } - } - - public Type ImplementationType { get; } - public Type HandlerType { get; } - - public Guid Id { get; } - public bool HasRegistration => RegistrationType != null; - public Type RegistrationType { get; } - public object RegistrationOptions { get; } - public bool AllowsDynamicRegistration { get; } - - public bool HasCapability => CapabilityType != null; - public Type CapabilityType { get; } - public StartedDelegate StartedDelegate { get; } - - public string Method { get; } - public string Key { get; } - public Type Params { get; } - public Type Response { get; } - public bool IsDelegatingHandler { get; } - - public bool IsDynamicCapability => typeof(DynamicCapability).GetTypeInfo().IsAssignableFrom(CapabilityType); - public Type CanBeResolvedHandlerType { get; } - public bool HasReturnType { get; } - - public IJsonRpcHandler Handler { get; } - - public void Dispose() - { - _disposeAction(); - } - - public override bool Equals(object obj) - { - return Equals(obj as HandlerDescriptor); - } - - public bool Equals(HandlerDescriptor other) - { - return other != null && - EqualityComparer.Default.Equals(HandlerType, other.HandlerType) && - Method == other.Method && - Key == other.Key; - } - - public override int GetHashCode() - { - var hashCode = -45133801; - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(HandlerType); - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Method); - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Key); - return hashCode; - } - - public static bool operator ==(HandlerDescriptor descriptor1, HandlerDescriptor descriptor2) - { - return EqualityComparer.Default.Equals(descriptor1, descriptor2); - } - - public static bool operator !=(HandlerDescriptor descriptor1, HandlerDescriptor descriptor2) - { - return !(descriptor1 == descriptor2); - } - } -} diff --git a/src/Server/HandlerTypeHelpers.cs b/src/Server/HandlerTypeHelpers.cs deleted file mode 100644 index 445ab1d75..000000000 --- a/src/Server/HandlerTypeHelpers.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; -using OmniSharp.Extensions.JsonRpc; - -namespace OmniSharp.Extensions.LanguageServer.Server -{ - public static class HandlerTypeHelpers - { - private static readonly Type[] HandlerTypes = { typeof(IJsonRpcNotificationHandler), typeof(IJsonRpcNotificationHandler<>), typeof(IJsonRpcRequestHandler<>), typeof(IJsonRpcRequestHandler<,>), }; - - private static bool IsValidInterface(Type type) - { - if (type.GetTypeInfo().IsGenericType) - { - return HandlerTypes.Contains(type.GetGenericTypeDefinition()); - } - return HandlerTypes.Contains(type); - } - - public static Type GetHandlerInterface(Type type) - { - return type?.GetTypeInfo() - .ImplementedInterfaces - .First(IsValidInterface); - } - } -} diff --git a/src/Server/Handlers/ExitHandler.cs b/src/Server/Handlers/ExitHandler.cs deleted file mode 100644 index ce7ccb18b..000000000 --- a/src/Server/Handlers/ExitHandler.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Reactive.Subjects; -using System.Reactive.Threading.Tasks; -using System.Threading; -using System.Threading.Tasks; -using MediatR; -using OmniSharp.Extensions.LanguageServer.Protocol.General; -using OmniSharp.Extensions.LanguageServer.Protocol.Models; - -namespace OmniSharp.Extensions.LanguageServer.Server.Handlers -{ - public class ServerExitHandler : IExitHandler - { - private readonly ISubject _exitSubject; - private readonly ServerShutdownHandler _shutdownHandler; - - public ServerExitHandler(ServerShutdownHandler shutdownHandler) - { - _shutdownHandler = shutdownHandler; - Exit = _exitSubject = new AsyncSubject(); - } - - public Task WaitForExit => Exit.ToTask(); - public IObservable Exit { get; } - - - public async Task Handle(ExitParams request, CancellationToken token) - { - await Task.Yield(); - - var result = _shutdownHandler.ShutdownRequested ? 0 : 1; - _exitSubject.OnNext(result); - _exitSubject.OnCompleted(); - return Unit.Value; - } - } -} diff --git a/src/Server/Handlers/ShutdownHandler.cs b/src/Server/Handlers/ShutdownHandler.cs deleted file mode 100644 index 31bb83042..000000000 --- a/src/Server/Handlers/ShutdownHandler.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Reactive.Subjects; -using System.Reactive.Threading.Tasks; -using System.Threading; -using System.Threading.Tasks; -using MediatR; -using OmniSharp.Extensions.LanguageServer.Protocol.General; -using OmniSharp.Extensions.LanguageServer.Protocol.Models; - -namespace OmniSharp.Extensions.LanguageServer.Server.Handlers -{ - public class ServerShutdownHandler : IShutdownHandler - { - private readonly ISubject _shutdownSubject; - - public ServerShutdownHandler() - { - Shutdown = _shutdownSubject = new AsyncSubject(); - } - - public IObservable Shutdown { get; } - public bool ShutdownRequested { get; private set; } - public Task WasShutDown => Shutdown.ToTask(); - - public async Task Handle(ShutdownParams request, CancellationToken token) - { - await Task.Yield(); // Ensure shutdown handler runs asynchronously. - - ShutdownRequested = true; - try - { - _shutdownSubject.OnNext(ShutdownRequested); - } - finally - { - _shutdownSubject.OnCompleted(); - } - return Unit.Value; - } - } -} diff --git a/src/Server/IConfigureLanguageServer.cs b/src/Server/IConfigureLanguageServer.cs new file mode 100644 index 000000000..30811b3b9 --- /dev/null +++ b/src/Server/IConfigureLanguageServer.cs @@ -0,0 +1,7 @@ +namespace OmniSharp.Extensions.LanguageServer.Server +{ + public interface IConfigureLanguageServer + { + void Configure(LanguageServerOptions options); + } +} diff --git a/src/Server/ILanguageServer.cs b/src/Server/ILanguageServer.cs deleted file mode 100644 index ca3246b44..000000000 --- a/src/Server/ILanguageServer.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Threading.Tasks; -using OmniSharp.Extensions.LanguageServer.Protocol.Models; -using OmniSharp.Extensions.LanguageServer.Protocol.Server; - -namespace OmniSharp.Extensions.LanguageServer.Server -{ - public interface ILanguageServer : OmniSharp.Extensions.LanguageServer.Protocol.Server.ILanguageServer - { - InitializeParams ClientSettings { get; } - InitializeResult ServerSettings { get; } - IServiceProvider Services { get; } - ILanguageServerConfiguration Configuration { get; } - - IObservable Start { get; } - IObservable Shutdown { get; } - IObservable Exit { get; } - Task WasStarted { get; } - Task WasShutDown { get; } - Task WaitForExit { get; } - } -} diff --git a/src/Server/ILspReciever.cs b/src/Server/ILspServerReceiver.cs similarity index 56% rename from src/Server/ILspReciever.cs rename to src/Server/ILspServerReceiver.cs index 3177bb7b1..477c872b1 100644 --- a/src/Server/ILspReciever.cs +++ b/src/Server/ILspServerReceiver.cs @@ -2,8 +2,9 @@ namespace OmniSharp.Extensions.LanguageServer.Server { - public interface ILspReceiver : IReceiver + public interface ILspServerReceiver : IReceiver { void Initialized(); + bool ShouldFilterOutput(object value); } -} \ No newline at end of file +} diff --git a/src/Server/ISupportedCapabilities.cs b/src/Server/ISupportedCapabilities.cs deleted file mode 100644 index 3dbb57ae4..000000000 --- a/src/Server/ISupportedCapabilities.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using OmniSharp.Extensions.JsonRpc; -using OmniSharp.Extensions.LanguageServer.Server.Abstractions; - -namespace OmniSharp.Extensions.LanguageServer.Server -{ - public interface ISupportedCapabilities - { - bool AllowsDynamicRegistration(Type capabilityType); - void SetCapability(ILspHandlerDescriptor descriptor, IJsonRpcHandler handler); - } - -} diff --git a/src/Server/IWorkspaceFolders.cs b/src/Server/IWorkspaceFolders.cs new file mode 100644 index 000000000..56c384d87 --- /dev/null +++ b/src/Server/IWorkspaceFolders.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; + +namespace OmniSharp.Extensions.LanguageServer.Server +{ + public interface IWorkspaceFolders + { + IObservable Refresh(); + IObservable> WorkspaceFolders { get; } + } +} \ No newline at end of file diff --git a/src/Server/ImmutableDisposable.cs b/src/Server/ImmutableDisposable.cs deleted file mode 100644 index 2bb269a79..000000000 --- a/src/Server/ImmutableDisposable.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace OmniSharp.Extensions.LanguageServer.Server -{ - class ImmutableDisposable : IDisposable - { - private readonly IEnumerable _instances; - - public ImmutableDisposable(IEnumerable instances) - { - _instances = instances; - } - - public ImmutableDisposable(params IDisposable[] instances) - { - _instances = instances; - } - - public void Dispose() - { - foreach (var instance in _instances) - instance.Dispose(); - } - } -} diff --git a/src/Server/InitializeDelegate.cs b/src/Server/InitializeDelegate.cs index 86942b5c2..00203cd8b 100644 --- a/src/Server/InitializeDelegate.cs +++ b/src/Server/InitializeDelegate.cs @@ -1,14 +1,9 @@ using System.Threading; using System.Threading.Tasks; using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; namespace OmniSharp.Extensions.LanguageServer.Server { public delegate Task InitializeDelegate(ILanguageServer server, InitializeParams request, CancellationToken cancellationToken); - public delegate Task StartedDelegate(ILanguageServer server, InitializeResult result, CancellationToken cancellationToken); - - public interface IOnStarted - { - Task OnStarted(ILanguageServer server, InitializeResult result, CancellationToken cancellationToken); - } } diff --git a/src/Server/InitializedDelegate.cs b/src/Server/InitializedDelegate.cs index 220a82435..af049b86f 100644 --- a/src/Server/InitializedDelegate.cs +++ b/src/Server/InitializedDelegate.cs @@ -1,6 +1,7 @@ using System.Threading; using System.Threading.Tasks; using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; namespace OmniSharp.Extensions.LanguageServer.Server { diff --git a/src/Server/LanguageServer.Shutdown.cs b/src/Server/LanguageServer.Shutdown.cs new file mode 100644 index 000000000..be76e9b21 --- /dev/null +++ b/src/Server/LanguageServer.Shutdown.cs @@ -0,0 +1,58 @@ +using System; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Reactive.Threading.Tasks; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using OmniSharp.Extensions.LanguageServer.Protocol.General; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; + +namespace OmniSharp.Extensions.LanguageServer.Server +{ + public partial class LanguageServer : IExitHandler, IShutdownHandler + { + private readonly ISubject _exitSubject = new AsyncSubject(); + private readonly ISubject _shutdownSubject = new AsyncSubject(); + private bool _shutdownRequested; + + public IObservable Shutdown => _shutdownSubject.AsObservable(); + public IObservable Exit => _exitSubject.AsObservable(); + public Task WasShutDown => _shutdownSubject.ToTask(); + public Task WaitForExit => _exitSubject.ToTask(); + + public void ForcefulShutdown() + { + ((IShutdownHandler) this).Handle(ShutdownParams.Instance, CancellationToken.None); + ((IExitHandler) this).Handle(ExitParams.Instance, CancellationToken.None); + } + + async Task IRequestHandler.Handle(ExitParams request, CancellationToken token) + { + await Task.Yield(); + + var result = _shutdownRequested ? 0 : 1; + _exitSubject.OnNext(result); + _exitSubject.OnCompleted(); + await _connection.StopAsync(); + return Unit.Value; + } + + async Task IRequestHandler.Handle(ShutdownParams request, CancellationToken token) + { + await Task.Yield(); // Ensure shutdown handler runs asynchronously. + + _shutdownRequested = true; + + try + { + _shutdownSubject.OnNext(_shutdownRequested); + } + finally + { + _shutdownSubject.OnCompleted(); + } + return Unit.Value; + } + } +} diff --git a/src/Server/LanguageServer.cs b/src/Server/LanguageServer.cs index cfcd964d2..ac3f45af4 100644 --- a/src/Server/LanguageServer.cs +++ b/src/Server/LanguageServer.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Reactive.Linq; using System.Reactive.Subjects; @@ -14,12 +13,8 @@ using Newtonsoft.Json.Linq; using OmniSharp.Extensions.JsonRpc; using OmniSharp.Extensions.LanguageServer.Protocol; -using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; using OmniSharp.Extensions.LanguageServer.Protocol.Models; -using OmniSharp.Extensions.LanguageServer.Protocol.Server; -using OmniSharp.Extensions.LanguageServer.Protocol.Server.Capabilities; using OmniSharp.Extensions.LanguageServer.Server.Abstractions; -using OmniSharp.Extensions.LanguageServer.Server.Handlers; using OmniSharp.Extensions.LanguageServer.Server.Matchers; using OmniSharp.Extensions.LanguageServer.Server.Pipelines; using ISerializer = OmniSharp.Extensions.LanguageServer.Protocol.Serialization.ISerializer; @@ -27,66 +22,68 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; using OmniSharp.Extensions.LanguageServer.Protocol.Client; +using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; using OmniSharp.Extensions.LanguageServer.Protocol.Document; using OmniSharp.Extensions.LanguageServer.Protocol.General; using OmniSharp.Extensions.LanguageServer.Protocol.Models.Proposals; using OmniSharp.Extensions.LanguageServer.Protocol.Progress; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; +using OmniSharp.Extensions.LanguageServer.Protocol.Server.Capabilities; using OmniSharp.Extensions.LanguageServer.Protocol.Server.WorkDone; +using OmniSharp.Extensions.LanguageServer.Protocol.Shared; using OmniSharp.Extensions.LanguageServer.Protocol.Workspace; using OmniSharp.Extensions.LanguageServer.Server.Configuration; using OmniSharp.Extensions.LanguageServer.Server.Logging; +using OmniSharp.Extensions.LanguageServer.Shared; namespace OmniSharp.Extensions.LanguageServer.Server { - public class LanguageServer : ILanguageServer, IInitializeHandler, IInitializedHandler, IAwaitableTermination + public partial class LanguageServer : JsonRpcServerBase, ILanguageServer, ILanguageProtocolInitializeHandler, ILanguageProtocolInitializedHandler, IAwaitableTermination, + IDisposable { private readonly Connection _connection; - private readonly ServerShutdownHandler _shutdownHandler = new ServerShutdownHandler(); - private readonly ServerExitHandler _exitHandler; private ClientVersion? _clientVersion; private readonly ServerInfo _serverInfo; - private readonly ILspReceiver _receiver; + private readonly IServerWorkDoneManager _serverWorkDoneManager; + private readonly ILspServerReceiver _serverReceiver; private readonly ISerializer _serializer; private readonly TextDocumentIdentifiers _textDocumentIdentifiers; private readonly IHandlerCollection _collection; private readonly IEnumerable _initializeDelegates; private readonly IEnumerable _initializedDelegates; - private readonly IEnumerable _startedDelegates; + private readonly IEnumerable _startedDelegates; private readonly IResponseRouter _responseRouter; private readonly ISubject _initializeComplete = new AsyncSubject(); private readonly CompositeDisposable _disposable = new CompositeDisposable(); private readonly IServiceProvider _serviceProvider; private readonly SupportedCapabilities _supportedCapabilities; private Task _initializingTask; - private readonly int? _concurrency; - private readonly IServerWorkDoneManager _workDoneManager; - private Protocol.Server.ILanguageServerConfiguration _configuration1; - public static Task From(Action optionsAction) + public static Task From(Action optionsAction) { return From(optionsAction, CancellationToken.None); } - public static Task From(LanguageServerOptions options) + public static Task From(LanguageServerOptions options) { return From(options, CancellationToken.None); } - public static Task From(Action optionsAction, CancellationToken token) + public static Task From(Action optionsAction, CancellationToken token) { var options = new LanguageServerOptions(); optionsAction(options); return From(options, token); } - public static ILanguageServer PreInit(Action optionsAction) + public static LanguageServer PreInit(Action optionsAction) { var options = new LanguageServerOptions(); optionsAction(options); return PreInit(options); } - public static async Task From(LanguageServerOptions options, CancellationToken token) + public static async Task From(LanguageServerOptions options, CancellationToken token) { var server = (LanguageServer)PreInit(options); await server.Initialize(token); @@ -101,111 +98,54 @@ public static async Task From(LanguageServerOptions options, Ca /// /// /// - public static ILanguageServer PreInit(LanguageServerOptions options) + public static LanguageServer PreInit(LanguageServerOptions options) { - return new LanguageServer( - options.Input, - options.Output, - options.Receiver, - options.RequestProcessIdentifier, - options.Serializer, - options.Services, - options.HandlerTypes.Select(x => x.Assembly) - .Distinct().Concat(options.HandlerAssemblies), - options.Handlers, - options.HandlerTypes, - options.NamedHandlers, - options.NamedServiceHandlers, - options.TextDocumentIdentifiers, - options.TextDocumentIdentifierTypes, - options.InitializeDelegates, - options.InitializedDelegates, - options.StartedDelegates, - options.LoggingBuilderAction, - options.AddDefaultLoggingProvider, - options.ServerInfo, - options.ConfigurationBuilderAction, - options.Concurrency - ); + return new LanguageServer(options); } - internal LanguageServer( - Stream input, - Stream output, - ILspReceiver receiver, - IRequestProcessIdentifier requestProcessIdentifier, - ISerializer serializer, - IServiceCollection services, - IEnumerable assemblies, - IEnumerable handlers, - IEnumerable handlerTypes, - IEnumerable<(string name, IJsonRpcHandler handler)> namedHandlers, - IEnumerable<(string name, Func handlerFunc)> namedServiceHandlers, - IEnumerable textDocumentIdentifiers, - IEnumerable textDocumentIdentifierTypes, - IEnumerable initializeDelegates, - IEnumerable initializedDelegates, - IEnumerable startedDelegates, - Action loggingBuilderAction, - bool addDefaultLoggingProvider, - ServerInfo serverInfo, - Action configurationBuilderAction, - int? concurrency) + internal LanguageServer(LanguageServerOptions options) : base(options) { - var configurationProvider = new DidChangeConfigurationProvider(this, configurationBuilderAction); + var services = options.Services; + var configurationProvider = new DidChangeConfigurationProvider(this, options.ConfigurationBuilderAction); services.AddSingleton(configurationProvider); + options.RequestProcessIdentifier ??= (options.SupportsContentModified + ? new RequestProcessIdentifier(RequestProcessType.Parallel) + : new RequestProcessIdentifier(RequestProcessType.Serial)); services.AddSingleton(configurationProvider); services.AddSingleton(Configuration = configurationProvider); - services.AddLogging(builder => loggingBuilderAction(builder)); + services.AddLogging(builder => options.LoggingBuilderAction(builder)); services.AddSingleton, LanguageServerLoggerFilterOptions>(); - _serverInfo = serverInfo; - _receiver = receiver; - _serializer = serializer; + _serverInfo = options.ServerInfo; + _serverReceiver = options.Receiver; + _serializer = options.Serializer; _supportedCapabilities = new SupportedCapabilities(); _textDocumentIdentifiers = new TextDocumentIdentifiers(); - var collection = new HandlerCollection(_supportedCapabilities, _textDocumentIdentifiers); + var collection = new SharedHandlerCollection(_supportedCapabilities, _textDocumentIdentifiers); + services.AddSingleton(collection); _collection = collection; - _initializeDelegates = initializeDelegates; - _initializedDelegates = initializedDelegates; - _startedDelegates = startedDelegates; + _initializeDelegates = options.InitializeDelegates; + _initializedDelegates = options.InitializedDelegates; + _startedDelegates = options.StartedDelegates; - services.AddSingleton(_ => ActivatorUtilities.CreateInstance(_, output)); + services.AddSingleton(_ => new OutputHandler( + options.Output, + options.Serializer, + options.Receiver.ShouldFilterOutput, + _.GetService>()) + ); services.AddSingleton(_collection); services.AddSingleton(_textDocumentIdentifiers); services.AddSingleton(_serializer); services.AddSingleton(_serializer); - services.AddSingleton(requestProcessIdentifier); - services.AddSingleton(receiver); - services.AddSingleton(receiver); + services.AddSingleton(options.RequestProcessIdentifier); + services.AddSingleton(options.Receiver); + services.AddSingleton(options.Receiver); - foreach (var item in handlers) - { - services.AddSingleton(item); - } - - foreach (var item in textDocumentIdentifiers) - { - services.AddSingleton(item); - } - - foreach (var item in handlerTypes) - { - services.AddSingleton(typeof(IJsonRpcHandler), item); - } - - foreach (var item in textDocumentIdentifierTypes) - { - services.AddSingleton(typeof(ITextDocumentIdentifier), item); - } - - services.AddJsonRpcMediatR(assemblies); services.AddTransient(); - services.AddSingleton(this); services.AddSingleton(this); - services.AddSingleton(this); services.AddTransient(); services.AddTransient(); services.AddSingleton(); @@ -214,149 +154,69 @@ internal LanguageServer( services.AddSingleton(); services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ResolveCommandPipeline<,>)); - var foundHandlers = services - .Where(x => typeof(IJsonRpcHandler).IsAssignableFrom(x.ServiceType) && - x.ServiceType != typeof(IJsonRpcHandler)) - .ToArray(); - - // Handlers are created at the start and maintained as a singleton - foreach (var handler in foundHandlers) - { - services.Remove(handler); - - if (handler.ImplementationFactory != null) - services.Add(ServiceDescriptor.Singleton(typeof(IJsonRpcHandler), handler.ImplementationFactory)); - else if (handler.ImplementationInstance != null) - services.Add(ServiceDescriptor.Singleton(typeof(IJsonRpcHandler), handler.ImplementationInstance)); - else - services.Add(ServiceDescriptor.Singleton(typeof(IJsonRpcHandler), handler.ImplementationType)); - } - services.AddSingleton(); services.AddSingleton(_ => _.GetRequiredService() as IJsonRpcHandler); services.AddSingleton(); services.AddSingleton(_ => _.GetRequiredService() as IJsonRpcHandler); - _serviceProvider = services.BuildServiceProvider(); + EnsureAllHandlersAreRegistered(); + + var serviceProvider = services.BuildServiceProvider(); + _disposable.Add(serviceProvider); + _serviceProvider = serviceProvider; collection.SetServiceProvider(_serviceProvider); var requestRouter = _serviceProvider.GetRequiredService>(); _responseRouter = _serviceProvider.GetRequiredService(); ProgressManager = _serviceProvider.GetRequiredService(); - _workDoneManager = _serviceProvider.GetRequiredService(); + _serverWorkDoneManager = _serviceProvider.GetRequiredService(); _connection = new Connection( - input, + options.Input, _serviceProvider.GetRequiredService(), - receiver, - requestProcessIdentifier, + options.Receiver, + options.RequestProcessIdentifier, _serviceProvider.GetRequiredService>(), _responseRouter, _serviceProvider.GetRequiredService(), - serializer, - concurrency + options.OnUnhandledException ?? (e => { ForcefulShutdown(); }), + options.CreateResponseException, + options.MaximumRequestTimeout, + options.SupportsContentModified, + options.Concurrency ); - _exitHandler = new ServerExitHandler(_shutdownHandler); - // We need to at least create Window here in case any handler does loggin in their constructor TextDocument = new TextDocumentLanguageServer(this, _serviceProvider); Client = new ClientLanguageServer(this, _serviceProvider); + General = new GeneralLanguageServer(this, _serviceProvider); Window = new WindowLanguageServer(this, _serviceProvider); Workspace = new WorkspaceLanguageServer(this, _serviceProvider); - _disposable.Add( - AddHandlers(this, _shutdownHandler, _exitHandler, - new CancelRequestHandler(requestRouter)) - ); + _disposable.Add(_collection.Add(this)); - var serviceHandlers = _serviceProvider.GetServices().ToArray(); - var serviceIdentifiers = _serviceProvider.GetServices().ToArray(); - _disposable.Add(_textDocumentIdentifiers.Add(serviceIdentifiers)); - _disposable.Add(_collection.Add(serviceHandlers)); - - foreach (var (name, handler) in namedHandlers) { - _disposable.Add(_collection.Add(name, handler)); - } - - foreach (var (name, handlerFunc) in namedServiceHandlers) - { - _disposable.Add(_collection.Add(name, handlerFunc(_serviceProvider))); + var serviceHandlers = _serviceProvider.GetServices().ToArray(); + var serviceIdentifiers = _serviceProvider.GetServices().ToArray(); + _disposable.Add(_textDocumentIdentifiers.Add(serviceIdentifiers)); + _disposable.Add(_collection.Add(serviceHandlers)); } } + public ITextDocumentLanguageServer TextDocument { get; } public IClientLanguageServer Client { get; } public IGeneralLanguageServer General { get; } public IWindowLanguageServer Window { get; } public IWorkspaceLanguageServer Workspace { get; } - public IProgressManager ProgressManager { get; } - - public IServerWorkDoneManager WorkDoneManager => _workDoneManager; public InitializeParams ClientSettings { get; private set; } public InitializeResult ServerSettings { get; private set; } public IServiceProvider Services => _serviceProvider; - public ILanguageServerConfiguration Configuration { get; } - - public IDisposable AddHandler(string method, IJsonRpcHandler handler) - { - var handlerDisposable = _collection.Add(method, handler); - return RegisterHandlers(handlerDisposable, CancellationToken.None); - } - - public IDisposable AddHandler(string method, Func handlerFunc) - { - var handlerDisposable = _collection.Add(method, handlerFunc); - return RegisterHandlers(handlerDisposable, CancellationToken.None); - } - - public IDisposable AddHandlers(params IJsonRpcHandler[] handlers) - { - var handlerDisposable = _collection.Add(handlers); - return RegisterHandlers(handlerDisposable, CancellationToken.None); - } - - public IDisposable AddHandler(string method, Type handlerType) - { - var handlerDisposable = _collection.Add(method, handlerType); - return RegisterHandlers(handlerDisposable, CancellationToken.None); - } - - public IDisposable AddHandler() - where T : IJsonRpcHandler - { - return AddHandlers(typeof(T)); - } - - public IDisposable AddHandler(Func factory) - where T : IJsonRpcHandler - { - return AddHandlers(factory(_serviceProvider)); - } - - public IDisposable AddHandlers(params Type[] handlerTypes) - { - var handlerDisposable = _collection.Add(_serviceProvider, handlerTypes); - return RegisterHandlers(handlerDisposable, CancellationToken.None); - } - - public IDisposable AddTextDocumentIdentifier(params ITextDocumentIdentifier[] handlers) - { - var cd = new CompositeDisposable(); - foreach (var textDocumentIdentifier in handlers) - { - cd.Add(_textDocumentIdentifiers.Add(textDocumentIdentifier)); - } - - return cd; - } + public IProgressManager ProgressManager { get; } - public IDisposable AddTextDocumentIdentifier() where T : ITextDocumentIdentifier - { - return _textDocumentIdentifiers.Add(ActivatorUtilities.CreateInstance(_serviceProvider)); - } + public IServerWorkDoneManager WorkDoneManager => _serverWorkDoneManager; + public ILanguageServerConfiguration Configuration { get; } public async Task Initialize(CancellationToken token) { @@ -377,7 +237,7 @@ public async Task Initialize(CancellationToken token) _connection.Open(); try { - await _initializeComplete + _initializingTask = _initializeComplete .Select(result => _startedDelegates.Select(@delegate => Observable.FromAsync(() => @delegate(this, result, token)) ) @@ -386,60 +246,22 @@ await _initializeComplete .Select(z => result) ) .Merge() - .LastAsync() + .LastOrDefaultAsync() .ToTask(token); + await _initializingTask; } catch (TaskCanceledException e) { _initializeComplete.OnError(e); + throw; } catch (Exception e) { _initializeComplete.OnError(e); + throw; } } - private IDisposable RegisterHandlers(LspHandlerDescriptorDisposable handlerDisposable, CancellationToken token) - { - var registrations = new List(); - foreach (var descriptor in handlerDisposable.Descriptors) - { - if (descriptor.AllowsDynamicRegistration) - { - if (descriptor.RegistrationOptions is IWorkDoneProgressOptions wdpo) - { - wdpo.WorkDoneProgress = _workDoneManager.IsSupported; - } - registrations.Add(new Registration() { - Id = descriptor.Id.ToString(), - Method = descriptor.Method, - RegisterOptions = descriptor.RegistrationOptions - }); - } - - if (descriptor.StartedDelegate != null) - { - // Fire and forget to initialize the handler - _initializeComplete - .Select(result => - Observable.FromAsync(() => descriptor.StartedDelegate(this, result, token))) - .Merge() - .Subscribe(); - } - } - - // Fire and forget - DynamicallyRegisterHandlers(registrations.ToArray()).ToObservable().Subscribe(); - - return new ImmutableDisposable( - handlerDisposable, - Disposable.Create(() => { - Client.UnregisterCapability(new UnregistrationParams() { - Unregisterations = registrations.ToArray() - }).ToObservable().Subscribe(); - })); - } - async Task IRequestHandler.Handle( InitializeParams request, CancellationToken token) { @@ -493,9 +315,11 @@ async Task IRequestHandler ClientSettings.Capabilities.TextDocument ??= new TextDocumentClientCapabilities(); var workspaceCapabilities = ClientSettings.Capabilities.Workspace ??= new WorkspaceClientCapabilities(); var windowCapabilities = ClientSettings.Capabilities.Window ??= new WindowClientCapabilities(); - _workDoneManager.Initialized(windowCapabilities); + _serverWorkDoneManager.Initialized(windowCapabilities); - AddHandlers(_serviceProvider.GetServices().ToArray()); + { + RegisterHandlers(_collection); + } await Task.WhenAll(_initializeDelegates.Select(c => c(this, request, token))); @@ -517,9 +341,11 @@ async Task IRequestHandler DocumentLinkProvider = ccp.GetStaticOptions(textDocumentCapabilities.DocumentLink) .Get(DocumentLinkOptions.Of), DocumentOnTypeFormattingProvider = ccp.GetStaticOptions(textDocumentCapabilities.OnTypeFormatting) - .Get(DocumentOnTypeFormattingOptions.Of), + .Get( + DocumentOnTypeFormattingOptions.Of), DocumentRangeFormattingProvider = ccp.GetStaticOptions(textDocumentCapabilities.RangeFormatting) - .Get(DocumentRangeFormattingOptions.Of), + .Get(DocumentRangeFormattingOptions + .Of), DocumentSymbolProvider = ccp.GetStaticOptions(textDocumentCapabilities.DocumentSymbol) .Get(DocumentSymbolOptions.Of), ExecuteCommandProvider = ccp.GetStaticOptions(workspaceCapabilities.ExecuteCommand) @@ -605,7 +431,7 @@ async Task IRequestHandler // TODO: Need a call back here // serverCapabilities.Experimental; - _receiver.Initialized(); + _serverReceiver.Initialized(); var result = ServerSettings = new InitializeResult() { Capabilities = serverCapabilities, @@ -627,9 +453,6 @@ async Task IRequestHandler // TODO: if (_clientVersion == ClientVersion.Lsp2) { - // Small delay to let client respond - await Task.Delay(100, token); - _initializeComplete.OnNext(result); _initializeComplete.OnCompleted(); } @@ -641,9 +464,6 @@ async Task IRequestHandler { if (_clientVersion == ClientVersion.Lsp3) { - // Small delay to let client respond - await Task.Delay(100, token); - _initializeComplete.OnNext(ServerSettings); _initializeComplete.OnCompleted(); } @@ -662,57 +482,85 @@ private async Task DynamicallyRegisterHandlers(Registration[] registrations) await Client.RegisterCapability(@params); } - - public IObservable Shutdown => _shutdownHandler.Shutdown; - public IObservable Exit => _exitHandler.Exit; public IObservable Start => _initializeComplete.AsObservable(); - public void SendNotification(string method) - { - _responseRouter.SendNotification(method); - } + public Task WasStarted => _initializeComplete.ToTask(); - public void SendNotification(string method, T @params) + public void Dispose() { - _responseRouter.SendNotification(method, @params); + _disposable?.Dispose(); + _connection?.Dispose(); } - public void SendNotification(IRequest @params) - { - _responseRouter.SendNotification(@params); - } + public IDictionary Experimental { get; } = new Dictionary(); + object IServiceProvider.GetService(Type serviceType) => _serviceProvider.GetService(serviceType); - public IResponseRouterReturns SendRequest(string method) + protected override IResponseRouter ResponseRouter => _responseRouter; + protected override IHandlersManager HandlersManager => _collection; + public IDisposable Register(Action registryAction) { - return _responseRouter.SendRequest(method); + var manager = new CompositeHandlersManager(_collection); + registryAction(new LangaugeServerRegistry(_serviceProvider, manager, _textDocumentIdentifiers)); + return RegisterHandlers(manager.GetDisposable()); } - public IResponseRouterReturns SendRequest(string method, T @params) + private IDisposable RegisterHandlers(IEnumerable collection) { - return _responseRouter.SendRequest(method, @params); - } + var registrations = new List(); + foreach (var descriptor in collection) + { + if (descriptor.HasCapability && _supportedCapabilities.AllowsDynamicRegistration(descriptor.CapabilityType)) + { + if (descriptor.RegistrationOptions is IWorkDoneProgressOptions wdpo) + { + wdpo.WorkDoneProgress = _serverWorkDoneManager.IsSupported; + } - public Task SendRequest(IRequest @params, CancellationToken cancellationToken) - { - return _responseRouter.SendRequest(@params, cancellationToken); + registrations.Add(new Registration() { + Id = descriptor.Id.ToString(), + Method = descriptor.Method, + RegisterOptions = descriptor.RegistrationOptions + }); + } + + if (descriptor.OnServerStartedDelegate != null) + { + // Fire and forget to initialize the handler + _initializeComplete + .Select(result => + Observable.FromAsync((ct) => descriptor.OnServerStartedDelegate(this, result, ct))) + .Merge() + .Subscribe(); + } + } + + // Fire and forget + DynamicallyRegisterHandlers(registrations.ToArray()).ToObservable().Subscribe(); + + return Disposable.Create(() => { + Client.UnregisterCapability(new UnregistrationParams() { + Unregisterations = registrations.ToArray() + }).ToObservable().Subscribe(); + }); } - public TaskCompletionSource GetRequest(long id) + private IDisposable RegisterHandlers(IDisposable handlerDisposable) { - return _responseRouter.GetRequest(id); + if (handlerDisposable is LspHandlerDescriptorDisposable lsp) + { + return new CompositeDisposable(lsp, RegisterHandlers(lsp.Descriptors)); + } + if (!(handlerDisposable is CompositeDisposable cd)) return Disposable.Empty; + cd.Add(RegisterHandlers(cd.OfType().SelectMany(z => z.Descriptors))); + return cd; } - public Task WasShutDown => _shutdownHandler.WasShutDown; - public Task WaitForExit => _exitHandler.WaitForExit; - public Task WasStarted => _initializeComplete.ToTask(); + } - public void Dispose() + class LangaugeServerRegistry : InterimLanguageProtocolRegistry, ILanguageServerRegistry + { + public LangaugeServerRegistry(IServiceProvider serviceProvider, CompositeHandlersManager handlersManager, TextDocumentIdentifiers textDocumentIdentifiers) : base(serviceProvider, handlersManager, textDocumentIdentifiers) { - _connection?.Dispose(); - _disposable?.Dispose(); } - - public IDictionary Experimental { get; } = new Dictionary(); - public object GetService(Type serviceType) => _serviceProvider.GetService(serviceType); } } diff --git a/src/Server/LanguageServerOptions.cs b/src/Server/LanguageServerOptions.cs index 2158e94aa..e5c62a1ee 100644 --- a/src/Server/LanguageServerOptions.cs +++ b/src/Server/LanguageServerOptions.cs @@ -1,84 +1,100 @@ using System; using System.Collections.Generic; -using System.IO; +using System.IO.Pipelines; using System.Reactive.Disposables; using System.Reflection; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.JsonRpc.Server; +using OmniSharp.Extensions.LanguageServer.Protocol; +using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; using OmniSharp.Extensions.LanguageServer.Protocol.Document; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Protocol.Server; +using OmniSharp.Extensions.LanguageServer.Protocol.Shared; +using OmniSharp.Extensions.LanguageServer.Shared; using ISerializer = OmniSharp.Extensions.LanguageServer.Protocol.Serialization.ISerializer; namespace OmniSharp.Extensions.LanguageServer.Server { - public class LanguageServerOptions : ILanguageServerRegistry + public class LanguageServerOptions : LanguageProtocolRpcOptionsBase, ILanguageServerRegistry { - public LanguageServerOptions() - { - } - - public Stream Input { get; set; } - public Stream Output { get; set; } public ServerInfo ServerInfo { get; set; } - public ISerializer Serializer { get; set; } = Protocol.Serialization.Serializer.Instance; - public IRequestProcessIdentifier RequestProcessIdentifier { get; set; } = new RequestProcessIdentifier(); - public ILspReceiver Receiver { get; set; } = new LspReceiver(); - public IServiceCollection Services { get; set; } = new ServiceCollection(); - internal List Handlers { get; set; } = new List(); - internal List TextDocumentIdentifiers { get; set; } = new List(); - internal List<(string name, IJsonRpcHandler handler)> NamedHandlers { get; set; } = new List<(string name, IJsonRpcHandler handler)>(); - internal List<(string name, Func handlerFunc)> NamedServiceHandlers { get; set; } = new List<(string name, Func handlerFunc)>(); - internal List HandlerTypes { get; set; } = new List(); - internal List TextDocumentIdentifierTypes { get; set; } = new List(); - internal List HandlerAssemblies { get; set; } = new List(); - internal Action LoggingBuilderAction { get; set; } = new Action(_ => { }); - internal Action ConfigurationBuilderAction { get; set; } = new Action(_ => { }); - internal bool AddDefaultLoggingProvider { get; set; } - public int? Concurrency { get; set; } + public ISerializer Serializer { get; set; } = new Protocol.Serialization.Serializer(ClientVersion.Lsp3); + public ILspServerReceiver Receiver { get; set; } = new LspServerReceiver(); internal readonly List InitializeDelegates = new List(); internal readonly List InitializedDelegates = new List(); - internal readonly List StartedDelegates = new List(); + internal readonly List StartedDelegates = new List(); - public IDisposable AddHandler(string method, IJsonRpcHandler handler) + public LanguageServerOptions WithReceiver(ILspServerReceiver receiver) { - NamedHandlers.Add((method, handler)); - return Disposable.Empty; + Receiver = receiver; + return this; } - public IDisposable AddHandler(string method, Func handlerFunc) - { - NamedServiceHandlers.Add((method, handlerFunc)); - return Disposable.Empty; - } + ILanguageServerRegistry IJsonRpcHandlerRegistry.AddHandler(string method, IJsonRpcHandler handler, JsonRpcHandlerOptions options) => this.AddHandler(method, handler, options); - public IDisposable AddHandlers(params IJsonRpcHandler[] handlers) - { - Handlers.AddRange(handlers); - return Disposable.Empty; - } + ILanguageServerRegistry IJsonRpcHandlerRegistry.AddHandler(string method, Func handlerFunc, JsonRpcHandlerOptions options) => this.AddHandler(method, handlerFunc, options); - public IDisposable AddHandler() where T : IJsonRpcHandler - { - HandlerTypes.Add(typeof(T)); - return Disposable.Empty; - } + ILanguageServerRegistry IJsonRpcHandlerRegistry.AddHandlers(params IJsonRpcHandler[] handlers) => this.AddHandlers(handlers); - public IDisposable AddTextDocumentIdentifier(params ITextDocumentIdentifier[] handlers) - { - TextDocumentIdentifiers.AddRange(handlers); - return Disposable.Empty; - } + ILanguageServerRegistry IJsonRpcHandlerRegistry.AddHandler(Func handlerFunc, JsonRpcHandlerOptions options) => this.AddHandler(handlerFunc, options); - public IDisposable AddTextDocumentIdentifier() where T : ITextDocumentIdentifier - { - TextDocumentIdentifierTypes.Add(typeof(T)); - return Disposable.Empty; - } + ILanguageServerRegistry IJsonRpcHandlerRegistry.AddHandler(THandler handler, JsonRpcHandlerOptions options) => this.AddHandler(handler, options); + + ILanguageServerRegistry IJsonRpcHandlerRegistry.AddHandler(JsonRpcHandlerOptions options) => this.AddHandler(options); + + ILanguageServerRegistry IJsonRpcHandlerRegistry.AddHandler(string method, JsonRpcHandlerOptions options) => this.AddHandler(method, options); + + ILanguageServerRegistry IJsonRpcHandlerRegistry.AddHandler(Type type, JsonRpcHandlerOptions options) => this.AddHandler(type, options); + + ILanguageServerRegistry IJsonRpcHandlerRegistry.AddHandler(string method, Type type, JsonRpcHandlerOptions options) => this.AddHandler(method, type, options); + + ILanguageServerRegistry IJsonRpcHandlerRegistry.OnJsonRequest(string method, Func> handler, JsonRpcHandlerOptions options) => OnJsonRequest(method, handler, options); + + ILanguageServerRegistry IJsonRpcHandlerRegistry.OnJsonRequest(string method, Func> handler, JsonRpcHandlerOptions options) => OnJsonRequest(method, handler, options); + ILanguageServerRegistry IJsonRpcHandlerRegistry.OnRequest(string method, Func> handler, JsonRpcHandlerOptions options) => OnRequest(method, handler, options); + + ILanguageServerRegistry IJsonRpcHandlerRegistry.OnRequest(string method, Func> handler, JsonRpcHandlerOptions options) => OnRequest(method, handler, options); + + ILanguageServerRegistry IJsonRpcHandlerRegistry.OnRequest(string method, Func> handler, JsonRpcHandlerOptions options) => OnRequest(method, handler, options); + + ILanguageServerRegistry IJsonRpcHandlerRegistry.OnRequest(string method, Func> handler, JsonRpcHandlerOptions options) => OnRequest(method, handler, options); + + ILanguageServerRegistry IJsonRpcHandlerRegistry.OnRequest(string method, Func handler, JsonRpcHandlerOptions options) => OnRequest(method, handler, options); + + ILanguageServerRegistry IJsonRpcHandlerRegistry.OnRequest(string method, Func handler, JsonRpcHandlerOptions options) => OnRequest(method, handler, options); + + ILanguageServerRegistry IJsonRpcHandlerRegistry.OnRequest(string method, Func handler, JsonRpcHandlerOptions options) => OnRequest(method, handler, options); + + ILanguageServerRegistry IJsonRpcHandlerRegistry.OnNotification(string method, Action handler, JsonRpcHandlerOptions options) => OnNotification(method, handler, options); + + ILanguageServerRegistry IJsonRpcHandlerRegistry.OnJsonNotification(string method, Action handler, JsonRpcHandlerOptions options) => OnJsonNotification(method, handler, options); + + ILanguageServerRegistry IJsonRpcHandlerRegistry.OnJsonNotification(string method, Func handler, JsonRpcHandlerOptions options) => OnJsonNotification(method, handler, options); + + ILanguageServerRegistry IJsonRpcHandlerRegistry.OnJsonNotification(string method, Func handler, JsonRpcHandlerOptions options) => OnJsonNotification(method, handler, options); + + ILanguageServerRegistry IJsonRpcHandlerRegistry.OnJsonNotification(string method, Action handler, JsonRpcHandlerOptions options) => OnJsonNotification(method, handler, options); + + ILanguageServerRegistry IJsonRpcHandlerRegistry.OnNotification(string method, Action handler, JsonRpcHandlerOptions options) => OnNotification(method, handler, options); + + ILanguageServerRegistry IJsonRpcHandlerRegistry.OnNotification(string method, Func handler, JsonRpcHandlerOptions options) => OnNotification(method, handler, options); + + ILanguageServerRegistry IJsonRpcHandlerRegistry.OnNotification(string method, Func handler, JsonRpcHandlerOptions options) => OnNotification(method, handler, options); + + ILanguageServerRegistry IJsonRpcHandlerRegistry.OnNotification(string method, Action handler, JsonRpcHandlerOptions options) => OnNotification(method, handler, options); + + ILanguageServerRegistry IJsonRpcHandlerRegistry.OnNotification(string method, Func handler, JsonRpcHandlerOptions options) => OnNotification(method, handler, options); + + ILanguageServerRegistry IJsonRpcHandlerRegistry.OnNotification(string method, Func handler, JsonRpcHandlerOptions options) => OnNotification(method, handler, options); - public IDisposable AddHandler(Func handlerFunc) where T : IJsonRpcHandler => throw new NotImplementedException(); + public override IRequestProcessIdentifier RequestProcessIdentifier { get; set; } } } diff --git a/src/Server/LanguageServerOptionsExtensions.cs b/src/Server/LanguageServerOptionsExtensions.cs index 674ec3b74..a3c6d6f11 100644 --- a/src/Server/LanguageServerOptionsExtensions.cs +++ b/src/Server/LanguageServerOptionsExtensions.cs @@ -1,29 +1,22 @@ using System; +using System.ComponentModel.DataAnnotations; using System.IO; +using System.IO.Pipelines; using System.Reflection; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Nerdbank.Streams; using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.JsonRpc.Server; using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Shared; using ISerializer = OmniSharp.Extensions.LanguageServer.Protocol.Serialization.ISerializer; namespace OmniSharp.Extensions.LanguageServer.Server { public static class LanguageServerOptionsExtensions { - public static LanguageServerOptions WithInput(this LanguageServerOptions options, Stream input) - { - options.Input = input; - return options; - } - - public static LanguageServerOptions WithOutput(this LanguageServerOptions options, Stream output) - { - options.Output = output; - return options; - } - public static LanguageServerOptions WithRequestProcessIdentifier(this LanguageServerOptions options, IRequestProcessIdentifier requestProcessIdentifier) { options.RequestProcessIdentifier = requestProcessIdentifier; @@ -36,68 +29,12 @@ public static LanguageServerOptions WithSerializer(this LanguageServerOptions op return options; } - public static LanguageServerOptions WithReciever(this LanguageServerOptions options, ILspReceiver receiver) - { - options.Receiver = receiver; - return options; - } - - public static LanguageServerOptions WithHandler(this LanguageServerOptions options) - where T : class, IJsonRpcHandler - { - options.Services.AddSingleton(); - return options; - } - - public static LanguageServerOptions WithHandler(this LanguageServerOptions options, T handler) - where T : IJsonRpcHandler - { - options.Services.AddSingleton(handler); - return options; - } - - public static LanguageServerOptions WithHandlersFrom(this LanguageServerOptions options, Type type) - { - options.HandlerTypes.Add(type); - return options; - } - - public static LanguageServerOptions WithHandlersFrom(this LanguageServerOptions options, TypeInfo typeInfo) - { - options.HandlerTypes.Add(typeInfo.AsType()); - return options; - } - - public static LanguageServerOptions WithHandlersFrom(this LanguageServerOptions options, Assembly assembly) - { - options.HandlerAssemblies.Add(assembly); - return options; - } - - public static LanguageServerOptions WithServices(this LanguageServerOptions options, Action servicesAction) - { - servicesAction(options.Services); - return options; - } - public static LanguageServerOptions WithServerInfo(this LanguageServerOptions options, ServerInfo serverInfo) { options.ServerInfo = serverInfo; return options; } - /// - /// Set maximum number of allowed parallel actions - /// - /// - /// - /// - public static LanguageServerOptions WithConcurrency(this LanguageServerOptions options, int? concurrency) - { - options.Concurrency = concurrency; - return options; - } - public static LanguageServerOptions OnInitialize(this LanguageServerOptions options, InitializeDelegate @delegate) { options.InitializeDelegates.Add(@delegate); @@ -111,7 +48,7 @@ public static LanguageServerOptions OnInitialized(this LanguageServerOptions opt return options; } - public static LanguageServerOptions OnStarted(this LanguageServerOptions options, StartedDelegate @delegate) + public static LanguageServerOptions OnStarted(this LanguageServerOptions options, OnServerStartedDelegate @delegate) { options.StartedDelegates.Add(@delegate); return options; diff --git a/src/Server/Logging/LanguageServerLogger.cs b/src/Server/Logging/LanguageServerLogger.cs index 4a6efcf3a..9e0900900 100644 --- a/src/Server/Logging/LanguageServerLogger.cs +++ b/src/Server/Logging/LanguageServerLogger.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reactive.Disposables; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; using OmniSharp.Extensions.LanguageServer.Protocol.Window; namespace OmniSharp.Extensions.LanguageServer.Server @@ -23,7 +25,7 @@ public LanguageServerLogger(ILanguageServer responseRouter, string categoryName, public IDisposable BeginScope(TState state) { - return new ImmutableDisposable(); + return new CompositeDisposable(); } public bool IsEnabled(LogLevel logLevel) => logLevel >= _logLevelGetter(); diff --git a/src/Server/Logging/LanguageServerLoggerExtensions.cs b/src/Server/Logging/LanguageServerLoggerExtensions.cs index ebeb2ecb6..f87c1188b 100644 --- a/src/Server/Logging/LanguageServerLoggerExtensions.cs +++ b/src/Server/Logging/LanguageServerLoggerExtensions.cs @@ -6,7 +6,7 @@ namespace OmniSharp.Extensions.LanguageServer.Server { public static class LanguageServerLoggerExtensions { - public static ILoggingBuilder AddLanguageServer(this ILoggingBuilder builder) + public static ILoggingBuilder AddLanguageProtocolLogging(this ILoggingBuilder builder) { builder.Services.AddSingleton(services => { var filterOptions = services.GetService>(); @@ -19,7 +19,7 @@ public static ILoggingBuilder AddLanguageServer(this ILoggingBuilder builder) return builder; } - public static ILoggingBuilder AddLanguageServer(this ILoggingBuilder builder, LogLevel minLevel) + public static ILoggingBuilder AddLanguageProtocolLogging(this ILoggingBuilder builder, LogLevel minLevel) { builder.Services.AddSingleton(_ => new LanguageServerLoggerSettings { MinimumLogLevel = minLevel }); builder.Services.AddSingleton(); diff --git a/src/Server/Logging/LanguageServerLoggerProvider.cs b/src/Server/Logging/LanguageServerLoggerProvider.cs index 2b89d2225..8fc9479a1 100644 --- a/src/Server/Logging/LanguageServerLoggerProvider.cs +++ b/src/Server/Logging/LanguageServerLoggerProvider.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Logging; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; namespace OmniSharp.Extensions.LanguageServer.Server { diff --git a/src/Server/LspHelper.cs b/src/Server/LspHelper.cs deleted file mode 100644 index 821a146bd..000000000 --- a/src/Server/LspHelper.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Linq; -using System.Reflection; -using OmniSharp.Extensions.JsonRpc; - -namespace OmniSharp.Extensions.LanguageServer.Server -{ - public static class LspHelper - { - private static readonly ConcurrentDictionary MethodNames = new ConcurrentDictionary(); - - public static string GetMethodName() - where T : IJsonRpcHandler - { - return GetMethodName(typeof(T)); - } - - public static string GetMethodName(Type type) - { - if (MethodNames.TryGetValue(type, out var method)) return method; - - // Custom method - var attribute = type.GetTypeInfo().GetCustomAttribute(); - if (attribute is null) - { - attribute = type.GetTypeInfo() - .ImplementedInterfaces - .Select(t => t.GetTypeInfo().GetCustomAttribute()) - .FirstOrDefault(x => x != null); - } - - // TODO: Log unknown method name - if (attribute is null) - { - return null; - } - - MethodNames.TryAdd(type, attribute.Method); - return attribute.Method; - } - } -} \ No newline at end of file diff --git a/src/Server/LspReciever.cs b/src/Server/LspServerReceiver.cs similarity index 63% rename from src/Server/LspReciever.cs rename to src/Server/LspServerReceiver.cs index 182874fab..c6fbdba5a 100644 --- a/src/Server/LspReciever.cs +++ b/src/Server/LspServerReceiver.cs @@ -1,13 +1,15 @@ using System.Collections.Generic; using Newtonsoft.Json.Linq; using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.JsonRpc.Client; using OmniSharp.Extensions.JsonRpc.Server; using OmniSharp.Extensions.LanguageServer.Protocol.General; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Server.Messages; namespace OmniSharp.Extensions.LanguageServer.Server { - public class LspReceiver : Receiver, ILspReceiver + public class LspServerReceiver : Receiver, ILspServerReceiver { private bool _initialized; @@ -21,7 +23,7 @@ public override (IEnumerable results, bool hasResponse) GetRequests(JToke var (results, hasResponse) = base.GetRequests(container); foreach (var item in results) { - if (item.IsRequest && item.Request.Method == LspHelper.GetMethodName()) + if (item.IsRequest && HandlerTypeDescriptorHelper.IsMethodName(item.Request.Method, typeof(ILanguageProtocolInitializeHandler))) { newResults.Add(item); } @@ -35,7 +37,8 @@ public override (IEnumerable results, bool hasResponse) GetRequests(JToke } else if (item.IsNotification) { - newResults.Add(item); + // drop notifications + // newResults.Add(item); } } @@ -46,5 +49,13 @@ public void Initialized() { _initialized = true; } + + public bool ShouldFilterOutput(object value) + { + if (_initialized) return true; + return value is OutgoingResponse || + (value is OutgoingNotification n && (n.Params is LogMessageParams || n.Params is ShowMessageParams || n.Params is TelemetryEventParams)) || + (value is OutgoingRequest r && r.Params is ShowMessageRequestParams); + } } } diff --git a/src/Server/Matchers/ExecuteCommandMatcher.cs b/src/Server/Matchers/ExecuteCommandMatcher.cs index 3d6171b1b..958e58731 100644 --- a/src/Server/Matchers/ExecuteCommandMatcher.cs +++ b/src/Server/Matchers/ExecuteCommandMatcher.cs @@ -2,7 +2,8 @@ using System.Linq; using Microsoft.Extensions.Logging; using OmniSharp.Extensions.LanguageServer.Protocol.Models; -using OmniSharp.Extensions.LanguageServer.Server.Abstractions; +using OmniSharp.Extensions.LanguageServer.Protocol.Shared; +using OmniSharp.Extensions.LanguageServer.Shared; namespace OmniSharp.Extensions.LanguageServer.Server.Matchers { diff --git a/src/Server/Matchers/ResolveCommandMatcher.cs b/src/Server/Matchers/ResolveCommandMatcher.cs index fbba34a67..62a6121f9 100644 --- a/src/Server/Matchers/ResolveCommandMatcher.cs +++ b/src/Server/Matchers/ResolveCommandMatcher.cs @@ -5,7 +5,8 @@ using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; using OmniSharp.Extensions.LanguageServer.Protocol.Models; -using OmniSharp.Extensions.LanguageServer.Server.Abstractions; +using OmniSharp.Extensions.LanguageServer.Protocol.Shared; +using OmniSharp.Extensions.LanguageServer.Shared; namespace OmniSharp.Extensions.LanguageServer.Server.Matchers { @@ -72,7 +73,7 @@ public IEnumerable FindHandler(object parameters, IEnumer descriptor.Method, descriptor.ImplementationType.FullName); if ((descriptor.ImplementationType.FullName == handlerType || descriptor.HandlerType.FullName == handlerType) && - ((descriptor is HandlerDescriptor handlerDescriptor) && handlerDescriptor.Key == handlerKey)) + ((descriptor is LspHandlerDescriptor handlerDescriptor) && handlerDescriptor.Key == handlerKey)) { yield return descriptor; } diff --git a/src/Server/Matchers/TextDocumentMatcher.cs b/src/Server/Matchers/TextDocumentMatcher.cs index c5718e94b..d1c636dc3 100644 --- a/src/Server/Matchers/TextDocumentMatcher.cs +++ b/src/Server/Matchers/TextDocumentMatcher.cs @@ -4,7 +4,8 @@ using OmniSharp.Extensions.LanguageServer.Protocol; using OmniSharp.Extensions.LanguageServer.Protocol.Document; using OmniSharp.Extensions.LanguageServer.Protocol.Models; -using OmniSharp.Extensions.LanguageServer.Server.Abstractions; +using OmniSharp.Extensions.LanguageServer.Protocol.Shared; +using OmniSharp.Extensions.LanguageServer.Shared; namespace OmniSharp.Extensions.LanguageServer.Server.Matchers { diff --git a/src/Server/Messages/ServerErrorEnd.cs b/src/Server/Messages/ServerErrorEnd.cs deleted file mode 100644 index ab282d197..000000000 --- a/src/Server/Messages/ServerErrorEnd.cs +++ /dev/null @@ -1,10 +0,0 @@ -using OmniSharp.Extensions.JsonRpc; -using OmniSharp.Extensions.JsonRpc.Server.Messages; - -namespace OmniSharp.Extensions.LanguageServer.Server.Messages -{ - public class ServerErrorEnd : RpcError - { - internal ServerErrorEnd() : base(null, new ErrorMessage(-32000, "Server Error End")) { } - } -} diff --git a/src/Server/Messages/ServerErrorStart.cs b/src/Server/Messages/ServerErrorStart.cs deleted file mode 100644 index 3c5bbf264..000000000 --- a/src/Server/Messages/ServerErrorStart.cs +++ /dev/null @@ -1,10 +0,0 @@ -using OmniSharp.Extensions.JsonRpc; -using OmniSharp.Extensions.JsonRpc.Server.Messages; - -namespace OmniSharp.Extensions.LanguageServer.Server.Messages -{ - public class ServerErrorStart : RpcError - { - internal ServerErrorStart(object data) : base(null, new ErrorMessage(-32099, "Server Error Start", data)) { } - } -} diff --git a/src/Server/Pipelines/ResolveCommandPipeline.cs b/src/Server/Pipelines/ResolveCommandPipeline.cs index 94ac85058..ab1c0bc91 100644 --- a/src/Server/Pipelines/ResolveCommandPipeline.cs +++ b/src/Server/Pipelines/ResolveCommandPipeline.cs @@ -7,7 +7,7 @@ using Newtonsoft.Json.Linq; using OmniSharp.Extensions.JsonRpc; using OmniSharp.Extensions.LanguageServer.Protocol.Models; -using OmniSharp.Extensions.LanguageServer.Server.Abstractions; +using OmniSharp.Extensions.LanguageServer.Protocol.Shared; namespace OmniSharp.Extensions.LanguageServer.Server.Pipelines { @@ -39,9 +39,10 @@ public async Task Handle(TRequest request, CancellationToken cancella } var response = await next(); + cancellationToken.ThrowIfCancellationRequested(); // Only pin the handler type, if we know the source handler (codelens) is also the resolver. - if (_descriptor is HandlerDescriptor handlerDescriptor && + if (_descriptor is LspHandlerDescriptor handlerDescriptor && response is IEnumerable canBeResolveds && _descriptor?.CanBeResolvedHandlerType?.GetTypeInfo().IsAssignableFrom(_descriptor.ImplementationType) == true) { diff --git a/src/Server/RequestProcessIdentifier.cs b/src/Server/RequestProcessIdentifier.cs deleted file mode 100644 index bc361c4c3..000000000 --- a/src/Server/RequestProcessIdentifier.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Linq; -using System.Reflection; -using OmniSharp.Extensions.JsonRpc; - -namespace OmniSharp.Extensions.LanguageServer.Server -{ - public class RequestProcessIdentifier : IRequestProcessIdentifier - { - private readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); - private readonly RequestProcessType _defaultRequestProcessType; - - public RequestProcessIdentifier(RequestProcessType defaultRequestProcessType = RequestProcessType.Serial) - { - _defaultRequestProcessType = defaultRequestProcessType; - } - - public RequestProcessType Identify(IHandlerDescriptor descriptor) - { - if (_cache.TryGetValue(descriptor.HandlerType, out var type)) return type; - - type = _defaultRequestProcessType; - var handlerType = descriptor.ImplementationType; - var processAttribute = descriptor.ImplementationType - .GetCustomAttributes(true) - .Concat(descriptor.HandlerType.GetCustomAttributes(true)) - .Concat(descriptor.ImplementationType.GetInterfaces().SelectMany(x => x.GetCustomAttributes())) - .Concat(descriptor.HandlerType.GetInterfaces().SelectMany(x => x.GetCustomAttributes())) - .OfType() - .FirstOrDefault(); - if (processAttribute != null) - { - type = processAttribute.Type; - } - - _cache.TryAdd(descriptor.HandlerType, type); - - return type; - } - } -} diff --git a/src/Server/Server.csproj b/src/Server/Server.csproj index d3bc8166d..239553952 100644 --- a/src/Server/Server.csproj +++ b/src/Server/Server.csproj @@ -10,6 +10,7 @@ + diff --git a/src/Server/TextDocumentIdentifiers.cs b/src/Server/TextDocumentIdentifiers.cs deleted file mode 100644 index 1ecd9b158..000000000 --- a/src/Server/TextDocumentIdentifiers.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Reactive.Disposables; -using OmniSharp.Extensions.LanguageServer.Protocol.Document; - -namespace OmniSharp.Extensions.LanguageServer.Server -{ - internal class TextDocumentIdentifiers : IEnumerable - { - private readonly HashSet _textDocumentIdentifiers = new HashSet(); - public IEnumerator GetEnumerator() => _textDocumentIdentifiers.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - public IDisposable Add(params ITextDocumentIdentifier[] identifiers) - { - foreach (var item in identifiers) - _textDocumentIdentifiers.Add(item); - return Disposable.Create(() => { - foreach (var textDocumentIdentifier in identifiers) - { - _textDocumentIdentifiers.Remove(textDocumentIdentifier); - } - }); - } - } -} diff --git a/src/Server/HandlerCollectionExtensions.cs b/src/Shared/HandlerCollectionExtensions.cs similarity index 64% rename from src/Server/HandlerCollectionExtensions.cs rename to src/Shared/HandlerCollectionExtensions.cs index bbc91f9f4..0c25e591a 100644 --- a/src/Server/HandlerCollectionExtensions.cs +++ b/src/Shared/HandlerCollectionExtensions.cs @@ -2,9 +2,10 @@ using System.Collections.Generic; using System.Linq; using OmniSharp.Extensions.JsonRpc; -using OmniSharp.Extensions.LanguageServer.Server.Abstractions; +using OmniSharp.Extensions.LanguageServer.Protocol.Client; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; -namespace OmniSharp.Extensions.LanguageServer.Server +namespace OmniSharp.Extensions.LanguageServer.Shared { static class HandlerCollectionExtensions { @@ -27,5 +28,15 @@ public static LspHandlerDescriptorDisposable Add(this ILanguageServer collection { return collection.Add(handlerTypes.ToArray()); } + + public static LspHandlerDescriptorDisposable Add(this ILanguageClient collection, IEnumerable handlers) + { + return collection.Add(handlers.ToArray()); + } + + public static LspHandlerDescriptorDisposable Add(this ILanguageClient collection, IEnumerable handlerTypes) + { + return collection.Add(handlerTypes.ToArray()); + } } } diff --git a/src/Server/Abstractions/IHandlerCollection.cs b/src/Shared/IHandlerCollection.cs similarity index 66% rename from src/Server/Abstractions/IHandlerCollection.cs rename to src/Shared/IHandlerCollection.cs index 48f37e64f..d116a4684 100644 --- a/src/Server/Abstractions/IHandlerCollection.cs +++ b/src/Shared/IHandlerCollection.cs @@ -2,16 +2,17 @@ using System.Collections.Generic; using System.Reflection; using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.LanguageServer.Protocol.Shared; -namespace OmniSharp.Extensions.LanguageServer.Server.Abstractions +namespace OmniSharp.Extensions.LanguageServer.Shared { - internal interface IHandlerCollection : IEnumerable + internal interface IHandlerCollection : IEnumerable, IHandlersManager { LspHandlerDescriptorDisposable Add(params IJsonRpcHandler[] handlers); LspHandlerDescriptorDisposable Add(params Type[] handlerTypes); - LspHandlerDescriptorDisposable Add(string method, IJsonRpcHandler handler); - LspHandlerDescriptorDisposable Add(string method, Func handlerFunc); - LspHandlerDescriptorDisposable Add(string method, Type handlerType); + LspHandlerDescriptorDisposable Add(string method, IJsonRpcHandler handler, JsonRpcHandlerOptions options); + LspHandlerDescriptorDisposable Add(string method, Func handlerFunc, JsonRpcHandlerOptions options); + LspHandlerDescriptorDisposable Add(string method, Type handlerType, JsonRpcHandlerOptions options); bool ContainsHandler(Type type); bool ContainsHandler(TypeInfo typeInfo); } diff --git a/src/Server/Abstractions/IHandlerMatcher.cs b/src/Shared/IHandlerMatcher.cs similarity index 80% rename from src/Server/Abstractions/IHandlerMatcher.cs rename to src/Shared/IHandlerMatcher.cs index ac2907dc9..739335d79 100644 --- a/src/Server/Abstractions/IHandlerMatcher.cs +++ b/src/Shared/IHandlerMatcher.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; +using OmniSharp.Extensions.LanguageServer.Protocol.Shared; -namespace OmniSharp.Extensions.LanguageServer.Server.Abstractions +namespace OmniSharp.Extensions.LanguageServer.Shared { public interface IHandlerMatcher { diff --git a/src/Shared/InterimLanguageProtocolRegistry.cs b/src/Shared/InterimLanguageProtocolRegistry.cs new file mode 100644 index 000000000..2714706cc --- /dev/null +++ b/src/Shared/InterimLanguageProtocolRegistry.cs @@ -0,0 +1,27 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.LanguageServer.Protocol.Document; + +namespace OmniSharp.Extensions.LanguageServer.Shared +{ + class InterimLanguageProtocolRegistry : InterimJsonRpcServerRegistry where T : IJsonRpcHandlerRegistry + { + private readonly TextDocumentIdentifiers _textDocumentIdentifiers; + + public InterimLanguageProtocolRegistry(IServiceProvider serviceProvider, CompositeHandlersManager handlersManager, TextDocumentIdentifiers textDocumentIdentifiers) : base(serviceProvider, handlersManager) + { + _textDocumentIdentifiers = textDocumentIdentifiers; + } + + public IDisposable AddTextDocumentIdentifier(params ITextDocumentIdentifier[] handlers) + { + return _textDocumentIdentifiers.Add(handlers); + } + + public IDisposable AddTextDocumentIdentifier() where TTextDocumentIdentifier : ITextDocumentIdentifier + { + return _textDocumentIdentifiers.Add(ActivatorUtilities.CreateInstance(_serviceProvider)); + } + } +} \ No newline at end of file diff --git a/src/Server/LspHandlerDescriptorDisposable.cs b/src/Shared/LspHandlerDescriptorDisposable.cs similarity index 86% rename from src/Server/LspHandlerDescriptorDisposable.cs rename to src/Shared/LspHandlerDescriptorDisposable.cs index 1c803856d..f284b835a 100644 --- a/src/Server/LspHandlerDescriptorDisposable.cs +++ b/src/Shared/LspHandlerDescriptorDisposable.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; -using OmniSharp.Extensions.LanguageServer.Server.Abstractions; +using OmniSharp.Extensions.LanguageServer.Protocol.Shared; -namespace OmniSharp.Extensions.LanguageServer.Server +namespace OmniSharp.Extensions.LanguageServer.Shared { class LspHandlerDescriptorDisposable : IDisposable { diff --git a/src/Server/LspHandlerDescriptorHelpers.cs b/src/Shared/LspHandlerDescriptorHelpers.cs similarity index 56% rename from src/Server/LspHandlerDescriptorHelpers.cs rename to src/Shared/LspHandlerDescriptorHelpers.cs index 09e40e0e6..17166b641 100644 --- a/src/Server/LspHandlerDescriptorHelpers.cs +++ b/src/Shared/LspHandlerDescriptorHelpers.cs @@ -3,9 +3,10 @@ using System.Reflection; using OmniSharp.Extensions.JsonRpc; using OmniSharp.Extensions.LanguageServer.Protocol; -using OmniSharp.Extensions.LanguageServer.Server.Abstractions; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Shared; -namespace OmniSharp.Extensions.LanguageServer.Server +namespace OmniSharp.Extensions.LanguageServer.Shared { static class LspHandlerDescriptorHelpers { @@ -25,5 +26,17 @@ public static IEnumerable GetSupportedCapabilities(object capabilitie .Select(x => x.GetValue(capabilities)) .OfType(); } + + public static IEnumerable GetStaticRegistrationOptions(object capabilities) + { + return capabilities + .GetType() + .GetTypeInfo() + .DeclaredProperties + .Where(x => x.CanRead) + .Select(x => x.GetValue(capabilities)) + .Select(z => z is ISupports supports ? supports.IsSupported ? supports.Value : z : z) + .OfType(); + } } } diff --git a/src/Server/LspRequestRouter.cs b/src/Shared/LspRequestRouter.cs similarity index 87% rename from src/Server/LspRequestRouter.cs rename to src/Shared/LspRequestRouter.cs index dff1bb94e..80d794ec1 100644 --- a/src/Server/LspRequestRouter.cs +++ b/src/Shared/LspRequestRouter.cs @@ -8,10 +8,10 @@ using Newtonsoft.Json.Linq; using OmniSharp.Extensions.JsonRpc; using OmniSharp.Extensions.JsonRpc.Server; -using OmniSharp.Extensions.LanguageServer.Server.Abstractions; +using OmniSharp.Extensions.LanguageServer.Protocol.Shared; using ISerializer = OmniSharp.Extensions.LanguageServer.Protocol.Serialization.ISerializer; -namespace OmniSharp.Extensions.LanguageServer.Server +namespace OmniSharp.Extensions.LanguageServer.Shared { internal class LspRequestRouter : RequestRouterBase, IRequestRouter { @@ -24,7 +24,8 @@ public LspRequestRouter( IEnumerable handlerMatchers, ISerializer serializer, IServiceProvider serviceProvider, - IServiceScopeFactory serviceScopeFactory) : base(serializer, serviceProvider, serviceScopeFactory, loggerFactory.CreateLogger()) + IServiceScopeFactory serviceScopeFactory) : + base(serializer, serviceProvider, serviceScopeFactory, loggerFactory.CreateLogger()) { _collection = collection; _handlerMatchers = handlerMatchers; @@ -51,26 +52,29 @@ private ILspHandlerDescriptor FindDescriptor(string method, JToken @params) var descriptor = _collection.FirstOrDefault(x => x.Method == method); if (descriptor is null) { - _logger.LogDebug("Unable to find {Method}, methods found include {Methods}", method, string.Join(", ", _collection.Select(x => x.Method + ":" + x.Handler?.GetType()?.FullName))); + _logger.LogDebug("Unable to find {Method}, methods found include {Methods}", method, + string.Join(", ", _collection.Select(x => x.Method + ":" + x.Handler?.GetType()?.FullName))); return null; } if (@params == null || descriptor.Params == null) return descriptor; - var paramsValue = @params.ToObject(descriptor.Params, _serializer.JsonSerializer); - var lspHandlerDescriptors = _collection.Where(handler => handler.Method == method).ToList(); + if (lspHandlerDescriptors.Count == 1) return descriptor; + var paramsValue = @params.ToObject(descriptor.Params, _serializer.JsonSerializer); return _handlerMatchers.SelectMany(strat => strat.FindHandler(paramsValue, lspHandlerDescriptors)).FirstOrDefault() ?? descriptor; } IHandlerDescriptor IRequestRouter.GetDescriptor(Notification notification) => GetDescriptor(notification); IHandlerDescriptor IRequestRouter.GetDescriptor(Request request) => GetDescriptor(request); + Task IRequestRouter.RouteNotification(IHandlerDescriptor descriptor, Notification notification, CancellationToken token) => RouteNotification( descriptor is ILspHandlerDescriptor d ? d : throw new Exception("This should really never happen, seriously, only hand this correct descriptors"), notification, token); + Task IRequestRouter.RouteRequest(IHandlerDescriptor descriptor, Request request, CancellationToken token) => RouteRequest( descriptor is ILspHandlerDescriptor d ? d : throw new Exception("This should really never happen, seriously, only hand this correct descriptors"), diff --git a/src/Shared/RequestProcessIdentifier.cs b/src/Shared/RequestProcessIdentifier.cs new file mode 100644 index 000000000..e4bdbb50d --- /dev/null +++ b/src/Shared/RequestProcessIdentifier.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Reflection; +using OmniSharp.Extensions.JsonRpc; + +namespace OmniSharp.Extensions.LanguageServer.Shared +{ + public class RequestProcessIdentifier : IRequestProcessIdentifier + { + private readonly ConcurrentDictionary _cache = + new ConcurrentDictionary(); + + private readonly RequestProcessType _defaultRequestProcessType; + + public RequestProcessIdentifier(RequestProcessType defaultRequestProcessType = RequestProcessType.Serial) + { + _defaultRequestProcessType = defaultRequestProcessType; + } + + public RequestProcessType Identify(IHandlerDescriptor descriptor) + { + if (descriptor.RequestProcessType.HasValue) + { + return descriptor.RequestProcessType.Value; + } + + if (_cache.TryGetValue(descriptor.HandlerType, out var type)) return type; + + type = _defaultRequestProcessType; + var typeDescriptor = HandlerTypeDescriptorHelper.GetHandlerTypeDescriptor(descriptor.HandlerType); + if (typeDescriptor?.RequestProcessType.HasValue == true) + { + type = typeDescriptor.RequestProcessType.Value; + } + else + { + var processAttribute = descriptor.ImplementationType + .GetCustomAttributes(true) + .Concat(descriptor.HandlerType.GetCustomAttributes(true)) + .OfType() + .FirstOrDefault(); + if (processAttribute != null) + { + type = processAttribute.Type; + } + } + + _cache.TryAdd(descriptor.HandlerType, type); + + return type; + } + } +} diff --git a/src/Shared/Shared.csproj b/src/Shared/Shared.csproj new file mode 100644 index 000000000..5b358ae79 --- /dev/null +++ b/src/Shared/Shared.csproj @@ -0,0 +1,18 @@ + + + netstandard2.1;netstandard2.0 + AnyCPU + OmniSharp.Extensions.LanguageServer.Shared + OmniSharp.Extensions.LanguageServer.Shared + Shared classes for language server protocol + + + + + <_Parameter1>OmniSharp.Extensions.LanguageServer, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f + + + <_Parameter1>OmniSharp.Extensions.LanguageClient, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f + + + diff --git a/src/Server/HandlerCollection.cs b/src/Shared/SharedHandlerCollection.cs similarity index 62% rename from src/Server/HandlerCollection.cs rename to src/Shared/SharedHandlerCollection.cs index 680239585..a583010b5 100644 --- a/src/Server/HandlerCollection.cs +++ b/src/Shared/SharedHandlerCollection.cs @@ -2,28 +2,28 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Reactive.Disposables; using System.Reflection; using Microsoft.Extensions.DependencyInjection; using OmniSharp.Extensions.JsonRpc; using OmniSharp.Extensions.LanguageServer.Protocol; using OmniSharp.Extensions.LanguageServer.Protocol.Document; using OmniSharp.Extensions.LanguageServer.Protocol.Models; -using OmniSharp.Extensions.LanguageServer.Server.Abstractions; +using OmniSharp.Extensions.LanguageServer.Protocol.Shared; -namespace OmniSharp.Extensions.LanguageServer.Server +namespace OmniSharp.Extensions.LanguageServer.Shared { - class HandlerCollection : IHandlerCollection + class SharedHandlerCollection : IHandlerCollection { - private static readonly MethodInfo GetRegistrationMethod = typeof(HandlerCollection) + private static readonly MethodInfo GetRegistrationMethod = typeof(SharedHandlerCollection) .GetTypeInfo() .GetMethod(nameof(GetRegistration), BindingFlags.NonPublic | BindingFlags.Static); private readonly ISupportedCapabilities _supportedCapabilities; private readonly TextDocumentIdentifiers _textDocumentIdentifiers; - internal readonly HashSet _handlers = new HashSet(); + internal readonly HashSet _handlers = new HashSet(); private IServiceProvider _serviceProvider; - - public HandlerCollection(ISupportedCapabilities supportedCapabilities, + public SharedHandlerCollection(ISupportedCapabilities supportedCapabilities, TextDocumentIdentifiers textDocumentIdentifiers) { _supportedCapabilities = supportedCapabilities; @@ -45,9 +45,13 @@ IEnumerator IEnumerable.GetEnumerator() return GetEnumerator(); } - public LspHandlerDescriptorDisposable Add(string method, IJsonRpcHandler handler) + IDisposable IHandlersManager.Add(IJsonRpcHandler handler, JsonRpcHandlerOptions options) => Add( new [] { handler }, options); + + IDisposable IHandlersManager.Add(string method, IJsonRpcHandler handler, JsonRpcHandlerOptions options) => Add(method, handler, options); + + public LspHandlerDescriptorDisposable Add(string method, IJsonRpcHandler handler, JsonRpcHandlerOptions options) { - var descriptor = GetDescriptor(method, handler.GetType(), handler); + var descriptor = GetDescriptor(method, handler.GetType(), handler, options); _handlers.Add(descriptor); var cd = new CompositeDisposable(); if (descriptor.Handler is ITextDocumentIdentifier textDocumentIdentifier) @@ -57,22 +61,22 @@ public LspHandlerDescriptorDisposable Add(string method, IJsonRpcHandler handler return new LspHandlerDescriptorDisposable(new[] { descriptor }, cd); } - public LspHandlerDescriptorDisposable Add(string method, Func handlerFunc) + public LspHandlerDescriptorDisposable Add(string method, Func handlerFunc, JsonRpcHandlerOptions options) { var handler = handlerFunc(_serviceProvider); - var descriptor = GetDescriptor(method, handler.GetType(), handler); + var descriptor = GetDescriptor(method, handler.GetType(), handler, options); _handlers.Add(descriptor); var cd = new CompositeDisposable(); if (descriptor.Handler is ITextDocumentIdentifier textDocumentIdentifier) { - _textDocumentIdentifiers.Add(textDocumentIdentifier); + cd.Add(_textDocumentIdentifiers.Add(textDocumentIdentifier)); } return new LspHandlerDescriptorDisposable(new[] { descriptor }, cd); } - public LspHandlerDescriptorDisposable Add(string method, Type handlerType) + public LspHandlerDescriptorDisposable Add(string method, Type handlerType, JsonRpcHandlerOptions options) { - var descriptor = GetDescriptor(method, handlerType, _serviceProvider); + var descriptor = GetDescriptor(method, handlerType, _serviceProvider, options); _handlers.Add(descriptor); var cd = new CompositeDisposable(); if (descriptor.Handler is ITextDocumentIdentifier textDocumentIdentifier) @@ -84,16 +88,16 @@ public LspHandlerDescriptorDisposable Add(string method, Type handlerType) public LspHandlerDescriptorDisposable Add(params Type[] handlerTypes) { - var descriptors = new HashSet(); + var descriptors = new HashSet(); var cd = new CompositeDisposable(); foreach (var handlerType in handlerTypes) { foreach (var (method, implementedInterface) in handlerType.GetTypeInfo() .ImplementedInterfaces - .Select(x => (method: LspHelper.GetMethodName(x), implementedInterface: x)) + .Select(x => (method: HandlerTypeDescriptorHelper.GetMethodName(x), implementedInterface: x)) .Where(x => !string.IsNullOrWhiteSpace(x.method))) { - var descriptor = GetDescriptor(method, implementedInterface, _serviceProvider); + var descriptor = GetDescriptor(method, implementedInterface, _serviceProvider, null); descriptors.Add(descriptor); _handlers.Add(descriptor); if (descriptor.Handler is ITextDocumentIdentifier textDocumentIdentifier) @@ -108,16 +112,44 @@ public LspHandlerDescriptorDisposable Add(params Type[] handlerTypes) public LspHandlerDescriptorDisposable Add(params IJsonRpcHandler[] handlers) { - var descriptors = new HashSet(); + var descriptors = new HashSet(); + var cd = new CompositeDisposable(); + foreach (var handler in handlers) + { + if (descriptors.Any(z => z.Handler == handler)) continue; + + foreach (var (method, implementedInterface) in handler.GetType().GetTypeInfo() + .ImplementedInterfaces + .Select(x => (method: HandlerTypeDescriptorHelper.GetMethodName(x), implementedInterface: x)) + .Where(x => !string.IsNullOrWhiteSpace(x.method))) + { + var descriptor = GetDescriptor(method, implementedInterface, handler, null); + descriptors.Add(descriptor); + _handlers.Add(descriptor); + if (descriptor.Handler is ITextDocumentIdentifier textDocumentIdentifier) + { + cd.Add(_textDocumentIdentifiers.Add(textDocumentIdentifier)); + } + } + } + + return new LspHandlerDescriptorDisposable(descriptors, cd); + } + + private LspHandlerDescriptorDisposable Add(IJsonRpcHandler[] handlers, JsonRpcHandlerOptions options) + { + var descriptors = new HashSet(); var cd = new CompositeDisposable(); foreach (var handler in handlers) { + if (descriptors.Any(z => z.Handler == handler)) continue; + foreach (var (method, implementedInterface) in handler.GetType().GetTypeInfo() .ImplementedInterfaces - .Select(x => (method: LspHelper.GetMethodName(x), implementedInterface: x)) + .Select(x => (method: HandlerTypeDescriptorHelper.GetMethodName(x), implementedInterface: x)) .Where(x => !string.IsNullOrWhiteSpace(x.method))) { - var descriptor = GetDescriptor(method, implementedInterface, handler); + var descriptor = GetDescriptor(method, implementedInterface, handler, options); descriptors.Add(descriptor); _handlers.Add(descriptor); if (descriptor.Handler is ITextDocumentIdentifier textDocumentIdentifier) @@ -130,19 +162,21 @@ public LspHandlerDescriptorDisposable Add(params IJsonRpcHandler[] handlers) return new LspHandlerDescriptorDisposable(descriptors, cd); } - private HandlerDescriptor GetDescriptor(string method, Type handlerType, IServiceProvider serviceProvider) + private LspHandlerDescriptor GetDescriptor(string method, Type handlerType, IServiceProvider serviceProvider, JsonRpcHandlerOptions options) { return GetDescriptor( method, handlerType, - ActivatorUtilities.CreateInstance(serviceProvider, handlerType) as IJsonRpcHandler); + ActivatorUtilities.CreateInstance(serviceProvider, handlerType) as IJsonRpcHandler, + options); } - private HandlerDescriptor GetDescriptor(string method, Type handlerType, IJsonRpcHandler handler) + private LspHandlerDescriptor GetDescriptor(string method, Type handlerType, IJsonRpcHandler handler, JsonRpcHandlerOptions options) { - var @interface = HandlerTypeHelpers.GetHandlerInterface(handlerType); - var registrationType = UnwrapGenericType(typeof(IRegistration<>), handlerType); - var capabilityType = UnwrapGenericType(typeof(ICapability<>), handlerType); + var typeDescriptor = LspHandlerTypeDescriptorHelper.GetHandlerTypeDescriptor(method); + var @interface = HandlerTypeDescriptorHelper.GetHandlerInterface(handlerType); + var registrationType = typeDescriptor?.RegistrationType ?? HandlerTypeDescriptorHelper.UnwrapGenericType(typeof(IRegistration<>), handlerType); + var capabilityType = typeDescriptor?.CapabilityType ?? HandlerTypeDescriptorHelper.UnwrapGenericType(typeof(ICapability<>), handlerType); Type @params = null; object registrationOptions = null; @@ -169,7 +203,7 @@ private HandlerDescriptor GetDescriptor(string method, Type handlerType, IJsonRp // In some scenarios, users will implement both the main handler and the resolve handler to the same class // This allows us to get a key for those interfaces so we can register many resolve handlers // and then route those resolve requests to the correct handler - if (handlerType.GetTypeInfo().ImplementedInterfaces.Any(x => x.GetTypeInfo().IsGenericType && x.GetTypeInfo().GetGenericTypeDefinition() == typeof(ICanBeResolvedHandler<>))) + if (handler.GetType().GetTypeInfo().ImplementedInterfaces.Any(x => x.GetTypeInfo().IsGenericType && x.GetTypeInfo().GetGenericTypeDefinition() == typeof(ICanBeResolvedHandler<>))) { key = handlerRegistration?.GetRegistrationOptions()?.DocumentSelector ?? key; } @@ -177,7 +211,15 @@ private HandlerDescriptor GetDescriptor(string method, Type handlerType, IJsonRp if (string.IsNullOrWhiteSpace(key)) key = "default"; - var descriptor = new HandlerDescriptor( + var requestProcessType = + options?.RequestProcessType ?? + typeDescriptor?.RequestProcessType ?? + handlerType.GetCustomAttributes(true) + .Concat(@interface.GetCustomAttributes(true)) + .OfType() + .FirstOrDefault()?.Type; + + var descriptor = new LspHandlerDescriptor( method, key, handler, @@ -185,8 +227,9 @@ private HandlerDescriptor GetDescriptor(string method, Type handlerType, IJsonRp @params, registrationType, registrationOptions, - registrationType != null && _supportedCapabilities.AllowsDynamicRegistration(capabilityType), + (registrationType == null ? (Func) (() => false) : (() => _supportedCapabilities.AllowsDynamicRegistration(capabilityType))), capabilityType, + requestProcessType, () => { _handlers.RemoveWhere(d => d.Handler == handler); }); @@ -194,15 +237,6 @@ private HandlerDescriptor GetDescriptor(string method, Type handlerType, IJsonRp return descriptor; } - private Type UnwrapGenericType(Type genericType, Type type) - { - return type?.GetTypeInfo() - .ImplementedInterfaces - .FirstOrDefault(x => x.GetTypeInfo().IsGenericType && x.GetTypeInfo().GetGenericTypeDefinition() == genericType) - ?.GetTypeInfo() - ?.GetGenericArguments()[0]; - } - public bool ContainsHandler(Type type) { return ContainsHandler(type.GetTypeInfo()); diff --git a/src/Server/SupportedCapabilities.cs b/src/Shared/SupportedCapabilities.cs similarity index 77% rename from src/Server/SupportedCapabilities.cs rename to src/Shared/SupportedCapabilities.cs index 92db52746..bbd05579f 100644 --- a/src/Server/SupportedCapabilities.cs +++ b/src/Shared/SupportedCapabilities.cs @@ -1,12 +1,12 @@ -using System; +using System; using System.Collections.Generic; using System.Reflection; using OmniSharp.Extensions.JsonRpc; using OmniSharp.Extensions.LanguageServer.Protocol; using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; -using OmniSharp.Extensions.LanguageServer.Server.Abstractions; +using OmniSharp.Extensions.LanguageServer.Protocol.Shared; -namespace OmniSharp.Extensions.LanguageServer.Server +namespace OmniSharp.Extensions.LanguageServer.Shared { class SupportedCapabilities : ISupportedCapabilities { @@ -23,8 +23,9 @@ public void Add(IEnumerable supports) { foreach (var item in supports) { - if (!_supports.TryGetValue(item.ValueType, out _)) - _supports.Add(item.ValueType, item.Value); + if (_supports.TryGetValue(item.ValueType, out _)) + _supports.Remove(item.ValueType); + _supports.Add(item.ValueType, item.Value); } } @@ -32,7 +33,7 @@ public bool AllowsDynamicRegistration(Type capabilityType) { if (_supports.TryGetValue(capabilityType, out var capability)) { - if (capability is DynamicCapability dc) + if (capability is IDynamicCapability dc) return dc.DynamicRegistration; } return false; @@ -41,6 +42,7 @@ public bool AllowsDynamicRegistration(Type capabilityType) public void SetCapability(ILspHandlerDescriptor descriptor, IJsonRpcHandler handler) { if (!descriptor.HasCapability) return; + if (descriptor.CapabilityType == null ||!handler.GetType().IsInstanceOfType(descriptor.CapabilityType)) return; if (_supports.TryGetValue(descriptor.CapabilityType, out var capability)) { diff --git a/src/Testing/ClientCapabilityExtensions.cs b/src/Testing/ClientCapabilityExtensions.cs new file mode 100644 index 000000000..a2450ffc0 --- /dev/null +++ b/src/Testing/ClientCapabilityExtensions.cs @@ -0,0 +1,27 @@ +using System; +using System.Linq; +using OmniSharp.Extensions.LanguageServer.Client; +using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; + +namespace OmniSharp.Extensions.LanguageProtocol.Testing +{ + public static class ClientCapabilityExtensions + { + public static LanguageClientOptions EnableAllCapabilities( this LanguageClientOptions options) + { + var capabilities = typeof(ICapability).Assembly.GetExportedTypes() + .Where(z => typeof(ICapability).IsAssignableFrom(z)) + .Where(z => z.IsClass && !z.IsAbstract); + foreach (var item in capabilities) + { + options.WithCapability(Activator.CreateInstance(item, Array.Empty()) as ICapability); + } + return options; + } + public static LanguageClientOptions DisableAllCapabilities( this LanguageClientOptions options) + { + options.SupportedCapabilities.Clear(); + return options; + } + } +} diff --git a/src/Testing/LanguageProtocolTestBase.cs b/src/Testing/LanguageProtocolTestBase.cs new file mode 100644 index 000000000..b29355c9f --- /dev/null +++ b/src/Testing/LanguageProtocolTestBase.cs @@ -0,0 +1,86 @@ +using System; +using System.IO.Pipelines; +using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; +using System.Threading.Tasks; +using MediatR; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; +using OmniSharp.Extensions.JsonRpc.Testing; +using OmniSharp.Extensions.LanguageServer.Client; +using OmniSharp.Extensions.LanguageServer.Protocol.Client; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; +using OmniSharp.Extensions.LanguageServer.Server; +using RealLanguageServer = OmniSharp.Extensions.LanguageServer.Server.LanguageServer; + +namespace OmniSharp.Extensions.LanguageProtocol.Testing +{ + /// + /// This is a test class that is designed to allow you configure an in memory lsp client and server to do testing of handlers or behaviors. + /// + public abstract class LanguageProtocolTestBase : JsonRpcTestBase + { + private ILanguageClient _client; + private ILanguageServer _server; + + public LanguageProtocolTestBase(JsonRpcTestOptions testOptions) : base(testOptions) + { + } + + protected virtual void ConfigureClientInputOutput(PipeReader serverOutput, PipeWriter clientInput, LanguageClientOptions options) + { + options.WithInput(serverOutput).WithOutput(clientInput); + } + + protected virtual void ConfigureServerInputOutput(PipeReader clientOutput, PipeWriter serverInput, LanguageServerOptions options) + { + options.WithInput(clientOutput).WithOutput(serverInput); + } + + protected virtual async Task<(ILanguageClient client, ILanguageServer server)> Initialize( + Action clientOptionsAction, + Action serverOptionsAction) + { + var clientPipe = new Pipe(new PipeOptions(readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false)); + var serverPipe = new Pipe(new PipeOptions(readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false)); + + _client = LanguageClient.PreInit(options => { + options + .WithLoggerFactory(TestOptions.ClientLoggerFactory) + .ConfigureLogging(x => { + x.Services.RemoveAll(typeof(ILoggerFactory)); + x.Services.AddSingleton(TestOptions.ClientLoggerFactory); + }) + .Services + .AddTransient(typeof(IPipelineBehavior<,>), typeof(SettlePipeline<,>)) + .AddSingleton(ServerEvents as IRequestSettler); + ConfigureClientInputOutput(serverPipe.Reader, clientPipe.Writer, options); + clientOptionsAction(options); + }); + + _server = RealLanguageServer.PreInit(options => { + options + .WithLoggerFactory(TestOptions.ServerLoggerFactory) + .ConfigureLogging(x => { + x.Services.RemoveAll(typeof(ILoggerFactory)); + x.Services.AddSingleton(TestOptions.ServerLoggerFactory); + }) + .Services + .AddTransient(typeof(IPipelineBehavior<,>), typeof(SettlePipeline<,>)) + .AddSingleton(ServerEvents as IRequestSettler); + ConfigureServerInputOutput(clientPipe.Reader, serverPipe.Writer, options); + serverOptionsAction(options); + }); + + Disposable.Add(_client); + Disposable.Add(_server); + + return await ObservableEx.ForkJoin( + Observable.FromAsync(_client.Initialize), + Observable.FromAsync(_server.Initialize), + (a, b) => (_client, _server) + ).ToTask(CancellationToken); + } + } +} diff --git a/src/Testing/LanguageServerTestBase.cs b/src/Testing/LanguageServerTestBase.cs new file mode 100644 index 000000000..56bea7e7c --- /dev/null +++ b/src/Testing/LanguageServerTestBase.cs @@ -0,0 +1,55 @@ +using System; +using System.IO; +using System.IO.Pipelines; +using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; +using System.Threading.Tasks; +using MediatR; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using OmniSharp.Extensions.JsonRpc.Testing; +using OmniSharp.Extensions.LanguageServer.Client; +using OmniSharp.Extensions.LanguageServer.Protocol.Client; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; +using OmniSharp.Extensions.LanguageServer.Server; + +namespace OmniSharp.Extensions.LanguageProtocol.Testing +{ + /// + /// This is a test class that is designed to allow you configure an in memory lsp client and and your server configuration to do integration tests against a server + /// + public abstract class LanguageServerTestBase : JsonRpcTestBase + { + private ILanguageClient _client; + + public LanguageServerTestBase(JsonRpcTestOptions jsonRpcTestOptions) : base(jsonRpcTestOptions) + { + } + + protected abstract (Stream clientOutput, Stream serverInput) SetupServer(); + + protected virtual async Task InitializeClient(Action clientOptionsAction = null) + { + _client = LanguageClient.PreInit(options => { + var (reader, writer) = SetupServer(); + options + .WithInput(reader) + .WithOutput(writer) + .ConfigureLogging(x => { + x.SetMinimumLevel(LogLevel.Trace); + x.Services.AddSingleton(TestOptions.ClientLoggerFactory); + }) + .Services + .AddTransient(typeof(IPipelineBehavior<,>), typeof(SettlePipeline<,>)) + .AddSingleton(ServerEvents as IRequestSettler); + clientOptionsAction?.Invoke(options); + }); + + Disposable.Add(_client); + + await _client.Initialize(CancellationToken); + + return _client; + } + } +} diff --git a/src/Testing/Testing.csproj b/src/Testing/Testing.csproj new file mode 100644 index 000000000..9d4212633 --- /dev/null +++ b/src/Testing/Testing.csproj @@ -0,0 +1,17 @@ + + + + netstandard2.1;netstandard2.0 + AnyCPU + OmniSharp.Extensions.LanguageProtocol.Testing + OmniSharp.Extensions.LanguageProtocol.Testing + You can use this package to test a language server protocol client or server + + + + + + + + + diff --git a/test/Client.Tests/Client.Tests.csproj b/test/Client.Tests/Client.Tests.csproj index 50dd308e3..41869fa0f 100644 --- a/test/Client.Tests/Client.Tests.csproj +++ b/test/Client.Tests/Client.Tests.csproj @@ -3,7 +3,7 @@ netcoreapp3.1 OmniSharp.Extensions.LanguageClient.Tests - OmniSharp.Extensions.LanguageServerProtocol.Client.Tests + OmniSharp.Extensions.LanguageServer.Client.Tests @@ -16,9 +16,7 @@ - - - + diff --git a/test/Client.Tests/ClientTests.cs b/test/Client.Tests/ClientTests.cs index c6be531b5..4777e53b6 100644 --- a/test/Client.Tests/ClientTests.cs +++ b/test/Client.Tests/ClientTests.cs @@ -1,27 +1,23 @@ -using Microsoft.Extensions.Logging; using System.Linq; using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; using System.Collections.Generic; using System; -using OmniSharp.Extensions.LanguageServer.Client; -using OmniSharp.Extensions.LanguageServer.Client.Dispatcher; -using OmniSharp.Extensions.LanguageServer.Client.Protocol; +using MediatR; using OmniSharp.Extensions.LanguageServer.Protocol.Models; -using OmniSharp.Extensions.LanguageServer.Protocol.Server.Capabilities; using OmniSharp.Extensions.LanguageServer.Protocol; -using OmniSharp.Extensions.LanguageServer.Protocol.Serialization; using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; +using OmniSharp.Extensions.LanguageServer.Protocol.Document; +using OmniSharp.Extensions.LanguageServer.Server; using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range; -namespace OmniSharp.Extensions.LanguageServerProtocol.Client.Tests +namespace OmniSharp.Extensions.LanguageServer.Client.Tests { /// /// Tests for . /// - public class ClientTests - : PipeServerTestBase + public class ClientTests : PipeServerTestBase { /// /// Create a new test suite. @@ -39,54 +35,49 @@ public ClientTests(ITestOutputHelper testOutput) /// string AbsoluteDocumentPath => IsWindows ? @"c:\Foo.txt" : "/Foo.txt"; - /// - /// The under test. - /// - LanguageClient LanguageClient { get; set; } - - /// - /// The server-side dispatcher. - /// - LspDispatcher ServerDispatcher { get; } = new LspDispatcher(new Serializer(ClientVersion.Lsp3)); - - /// - /// The server-side connection. - /// - LspConnection ServerConnection { get; set; } - /// /// Ensure that the language client can successfully request Hover information. /// [Fact(DisplayName = "Language client can successfully request hover info")] public async Task Hover_Success() { - await Connect(); - const int line = 5; const int column = 5; var expectedHoverContent = new MarkedStringsOrMarkupContent("123", "456", "789"); - ServerDispatcher.HandleRequest(TextDocumentNames.Hover, (request, cancellationToken) => { - Assert.NotNull(request.TextDocument); - - Assert.Equal(AbsoluteDocumentPath, - DocumentUri.GetFileSystemPath(request.TextDocument.Uri) - ); - - Assert.Equal(line, request.Position.Line); - Assert.Equal(column, request.Position.Character); + var (client, server) = await Initialize( + client => { + client.WithCapability(new HoverCapability() { + ContentFormat = new Container(MarkupKind.Markdown, MarkupKind.PlainText), + }); + }, + server => { + server.OnHover((request, token) => { + Assert.NotNull(request.TextDocument); + + Assert.Equal(AbsoluteDocumentPath, + DocumentUri.GetFileSystemPath(request.TextDocument.Uri) + ); + + Assert.Equal(line, request.Position.Line); + Assert.Equal(column, request.Position.Character); + + return Task.FromResult(new Hover { + Contents = expectedHoverContent, + Range = new Range { + Start = request.Position, + End = request.Position + } + }); + }, new HoverRegistrationOptions()); + } + ); - return Task.FromResult(new Hover { - Contents = expectedHoverContent, - Range = new Range { - Start = request.Position, - End = request.Position - } - }); + var hover = await client.TextDocument.RequestHover(new HoverParams() { + TextDocument = AbsoluteDocumentPath, + Position = (line, column) }); - var hover = await LanguageClient.TextDocument.Hover(AbsoluteDocumentPath, line, column); - Assert.NotNull(hover.Range); Assert.NotNull(hover.Range.Start); Assert.NotNull(hover.Range.End); @@ -100,7 +91,7 @@ public async Task Hover_Success() Assert.NotNull(hover.Contents); Assert.True(expectedHoverContent.HasMarkedStrings); Assert.Equal(expectedHoverContent.MarkedStrings - .Select(markedString => markedString.Value), + .Select(markedString => markedString.Value), hover.Contents.MarkedStrings.Select( markedString => markedString.Value ) @@ -113,54 +104,56 @@ public async Task Hover_Success() [Fact(DisplayName = "Language client can successfully request completions")] public async Task Completions_Success() { - await Connect(); - const int line = 5; const int column = 5; var expectedDocumentPath = AbsoluteDocumentPath; var expectedDocumentUri = DocumentUri.FromFileSystemPath(expectedDocumentPath); - - var expectedCompletionItems = new CompletionItem[] - { - new CompletionItem - { + var expectedCompletionItems = new CompletionItem[] { + new CompletionItem { Kind = CompletionItemKind.Class, Label = "Class1", - TextEdit = new TextEdit - { - Range = new Range - { - Start = new Position - { - Line = line, - Character = column - }, - End = new Position - { - Line = line, - Character = column - } - }, + TextEdit = new TextEdit { + Range = ((line, column), (line, column)), NewText = "Class1", } } }; - ServerDispatcher.HandleRequest(TextDocumentNames.Completion, (request, cancellationToken) => { - Assert.NotNull(request.TextDocument); - - Assert.Equal(expectedDocumentUri, request.TextDocument.Uri); - - Assert.Equal(line, request.Position.Line); - Assert.Equal(column, request.Position.Character); - - return Task.FromResult(new CompletionList( - expectedCompletionItems, - isIncomplete: true - )); - }); + var (client, server) = await Initialize( + client => { + client.WithCapability(new CompletionCapability() { + CompletionItem = new CompletionItemCapability() { + DeprecatedSupport = true, + DocumentationFormat = new Container(MarkupKind.Markdown, MarkupKind.PlainText), + PreselectSupport = true, + SnippetSupport = true, + TagSupport = new CompletionItemTagSupportCapability() { + ValueSet = new[] {CompletionItemTag.Deprecated} + }, + CommitCharactersSupport = true + } + }); + }, + server => { + server.OnCompletion((request, cancellationToken) => { + Assert.NotNull(request.TextDocument); + + Assert.Equal(expectedDocumentUri, request.TextDocument.Uri); + + Assert.Equal(line, request.Position.Line); + Assert.Equal(column, request.Position.Character); + + return Task.FromResult(new CompletionList( + expectedCompletionItems, + isIncomplete: true + )); + }, new CompletionRegistrationOptions()); + }); - var actualCompletions = await LanguageClient.TextDocument.Completions(AbsoluteDocumentPath, line, column); + var actualCompletions = await client.TextDocument.RequestCompletion(new CompletionParams() { + TextDocument = AbsoluteDocumentPath, + Position = (line, column), + }, CancellationToken); Assert.True(actualCompletions.IsIncomplete, "completions.IsIncomplete"); Assert.NotNull(actualCompletions.Items); @@ -178,10 +171,14 @@ public async Task Completions_Success() Assert.NotNull(actualCompletionItem.TextEdit.Range); Assert.NotNull(actualCompletionItem.TextEdit.Range.Start); Assert.NotNull(actualCompletionItem.TextEdit.Range.End); - Assert.Equal(expectedCompletionItem.TextEdit.Range.Start.Line, actualCompletionItem.TextEdit.Range.Start.Line); - Assert.Equal(expectedCompletionItem.TextEdit.Range.Start.Character, actualCompletionItem.TextEdit.Range.Start.Character); - Assert.Equal(expectedCompletionItem.TextEdit.Range.End.Line, actualCompletionItem.TextEdit.Range.End.Line); - Assert.Equal(expectedCompletionItem.TextEdit.Range.End.Character, actualCompletionItem.TextEdit.Range.End.Character); + Assert.Equal(expectedCompletionItem.TextEdit.Range.Start.Line, + actualCompletionItem.TextEdit.Range.Start.Line); + Assert.Equal(expectedCompletionItem.TextEdit.Range.Start.Character, + actualCompletionItem.TextEdit.Range.Start.Character); + Assert.Equal(expectedCompletionItem.TextEdit.Range.End.Line, + actualCompletionItem.TextEdit.Range.End.Line); + Assert.Equal(expectedCompletionItem.TextEdit.Range.End.Character, + actualCompletionItem.TextEdit.Range.End.Character); }); } @@ -191,8 +188,6 @@ public async Task Completions_Success() [Fact(DisplayName = "Language client can successfully request signature help")] public async Task SignatureHelp_Success() { - await Connect(); - const int line = 5; const int column = 5; var expectedDocumentPath = AbsoluteDocumentPath; @@ -215,18 +210,37 @@ public async Task SignatureHelp_Success() } }; - ServerDispatcher.HandleRequest(TextDocumentNames.SignatureHelp, (request, cancellationToken) => { - Assert.NotNull(request.TextDocument); + var (client, server) = await Initialize( + client => { + client.WithCapability( + new SignatureHelpCapability() { + ContextSupport = true, + SignatureInformation = new SignatureInformationCapability() { + DocumentationFormat = new Container(MarkupKind.Markdown), + ParameterInformation = new SignatureParameterInformationCapability() { + LabelOffsetSupport = true + } + } + }); + }, + server => { + server.OnSignatureHelp( + (request, cancellationToken) => { + Assert.NotNull(request.TextDocument); - Assert.Equal(expectedDocumentUri, request.TextDocument.Uri); + Assert.Equal(expectedDocumentUri, request.TextDocument.Uri); - Assert.Equal(line, request.Position.Line); - Assert.Equal(column, request.Position.Character); + Assert.Equal(line, request.Position.Line); + Assert.Equal(column, request.Position.Character); - return Task.FromResult(expectedSignatureHelp); - }); + return Task.FromResult(expectedSignatureHelp); + }, new SignatureHelpRegistrationOptions()); + }); - var actualSignatureHelp = await LanguageClient.TextDocument.SignatureHelp(AbsoluteDocumentPath, line, column); + var actualSignatureHelp = await client.TextDocument.RequestSignatureHelp(new SignatureHelpParams() { + TextDocument = AbsoluteDocumentPath, + Position = (line, column), + }, CancellationToken); Assert.Equal(expectedSignatureHelp.ActiveParameter, actualSignatureHelp.ActiveParameter); Assert.Equal(expectedSignatureHelp.ActiveSignature, actualSignatureHelp.ActiveSignature); @@ -258,8 +272,6 @@ public async Task SignatureHelp_Success() [Fact(DisplayName = "Language client can successfully request definition")] public async Task Definition_Success() { - await Connect(); - const int line = 5; const int column = 5; var expectedDocumentPath = AbsoluteDocumentPath; @@ -268,30 +280,34 @@ public async Task Definition_Success() var expectedDefinitions = new LocationOrLocationLinks( new LocationOrLocationLink(new Location { Uri = expectedDocumentUri, - Range = new Range { - Start = new Position { - Line = line, - Character = column - }, - End = new Position { - Line = line, - Character = column - } - }, + Range = ((line, column), (line, column)), })); - ServerDispatcher.HandleRequest(TextDocumentNames.Definition, (request, cancellationToken) => { - Assert.NotNull(request.TextDocument); + var (client, server) = await Initialize( + client => { + client.WithCapability( + new DefinitionCapability() { + LinkSupport = true + } + ); + }, + server => { + server.OnDefinition((request, cancellationToken) => { + Assert.NotNull(request.TextDocument); - Assert.Equal(expectedDocumentUri, request.TextDocument.Uri); + Assert.Equal(expectedDocumentUri, request.TextDocument.Uri); - Assert.Equal(line, request.Position.Line); - Assert.Equal(column, request.Position.Character); + Assert.Equal(line, request.Position.Line); + Assert.Equal(column, request.Position.Character); - return Task.FromResult(expectedDefinitions); - }); + return Task.FromResult(expectedDefinitions); + }, new DefinitionRegistrationOptions()); + }); - var definitions = await LanguageClient.TextDocument.Definition(AbsoluteDocumentPath, line, column); + var definitions = await client.TextDocument.RequestDefinition(new DefinitionParams() { + TextDocument = AbsoluteDocumentPath, + Position = (line, column), + }, CancellationToken); var actualDefinitions = definitions.ToArray(); Assert.Collection(actualDefinitions, actualDefinition => { @@ -304,9 +320,11 @@ public async Task Definition_Success() Assert.NotNull(actualDefinition.Location.Range.Start); Assert.NotNull(actualDefinition.Location.Range.End); Assert.Equal(expectedDefinition.Location.Range.Start.Line, actualDefinition.Location.Range.Start.Line); - Assert.Equal(expectedDefinition.Location.Range.Start.Character, actualDefinition.Location.Range.Start.Character); + Assert.Equal(expectedDefinition.Location.Range.Start.Character, + actualDefinition.Location.Range.Start.Character); Assert.Equal(expectedDefinition.Location.Range.End.Line, actualDefinition.Location.Range.End.Line); - Assert.Equal(expectedDefinition.Location.Range.End.Character, actualDefinition.Location.Range.End.Character); + Assert.Equal(expectedDefinition.Location.Range.End.Character, + actualDefinition.Location.Range.End.Character); }); } @@ -316,8 +334,6 @@ public async Task Definition_Success() [Fact(DisplayName = "Language client can successfully request document highlights")] public async Task DocumentHighlights_Success() { - await Connect(); - const int line = 5; const int column = 5; var expectedDocumentPath = AbsoluteDocumentPath; @@ -326,30 +342,32 @@ public async Task DocumentHighlights_Success() var expectedHighlights = new DocumentHighlightContainer( new DocumentHighlight { Kind = DocumentHighlightKind.Write, - Range = new Range { - Start = new Position { - Line = line, - Character = column - }, - End = new Position { - Line = line, - Character = column - } - }, + Range = ((line, column), (line, column)), }); - ServerDispatcher.HandleRequest(TextDocumentNames.DocumentHighlight, (request, cancellationToken) => { - Assert.NotNull(request.TextDocument); + var (client, server) = await Initialize( + client => { + client.WithCapability( + new DocumentHighlightCapability() { } + ); + }, + server => { + server.OnDocumentHighlight((request, cancellationToken) => { + Assert.NotNull(request.TextDocument); - Assert.Equal(expectedDocumentUri, request.TextDocument.Uri); + Assert.Equal(expectedDocumentUri, request.TextDocument.Uri); - Assert.Equal(line, request.Position.Line); - Assert.Equal(column, request.Position.Character); + Assert.Equal(line, request.Position.Line); + Assert.Equal(column, request.Position.Character); - return Task.FromResult(expectedHighlights); - }); + return Task.FromResult(expectedHighlights); + }, new DocumentHighlightRegistrationOptions()); + }); - var definitions = await LanguageClient.TextDocument.DocumentHighlights(AbsoluteDocumentPath, line, column); + var definitions = await client.TextDocument.RequestDocumentHighlight(new DocumentHighlightParams() { + TextDocument = AbsoluteDocumentPath, + Position = (line, column), + }, CancellationToken); var actualDefinitions = definitions.ToArray(); Assert.Collection(actualDefinitions, actualHighlight => { @@ -373,8 +391,6 @@ public async Task DocumentHighlights_Success() [Fact(DisplayName = "Language client can successfully request document symbols")] public async Task DocumentSymbols_DocumentSymbol_Success() { - await Connect(); - const int line = 5; const int character = 6; var expectedDocumentPath = AbsoluteDocumentPath; @@ -389,17 +405,33 @@ public async Task DocumentSymbols_DocumentSymbol_Success() var expectedSymbols = new SymbolInformationOrDocumentSymbolContainer( new SymbolInformationOrDocumentSymbol(documentSymbol)); - ServerDispatcher.HandleRequest(TextDocumentNames.DocumentSymbol, (request, cancellationToken) => { - Assert.NotNull(request.TextDocument); + var (client, server) = await Initialize( + client => { + client.WithCapability(new DocumentSymbolCapability() { + DynamicRegistration = true, + SymbolKind = new SymbolKindCapability() { + ValueSet = new Container(Enum.GetValues(typeof(SymbolKind)).Cast() + .ToArray()) + }, + TagSupport = new TagSupportCapability() { + ValueSet = new[] {SymbolTag.Deprecated} + }, + HierarchicalDocumentSymbolSupport = true + }); + }, + server => { + server.OnDocumentSymbol((request, cancellationToken) => { + Assert.NotNull(request.TextDocument); - Assert.Equal(expectedDocumentUri, request.TextDocument.Uri); + Assert.Equal(expectedDocumentUri, request.TextDocument.Uri); - return Task.FromResult(expectedSymbols); - }); - var documentSymbolParams = new DocumentSymbolParams { - TextDocument = new TextDocumentIdentifier(expectedDocumentUri) - }; - var symbols = await LanguageClient.SendRequest(TextDocumentNames.DocumentSymbol, documentSymbolParams); + return Task.FromResult(expectedSymbols); + }, new DocumentSymbolRegistrationOptions()); + }); + + var symbols = await client.TextDocument.RequestDocumentSymbol(new DocumentSymbolParams { + TextDocument = expectedDocumentUri + }, CancellationToken); var actualSymbols = symbols.ToArray(); Assert.Collection(actualSymbols, actualSymbol => { @@ -410,10 +442,13 @@ public async Task DocumentSymbols_DocumentSymbol_Success() Assert.NotNull(actualSymbol.DocumentSymbol); Assert.Equal(expectedSymbol.DocumentSymbol.Detail, actualSymbol.DocumentSymbol.Detail); Assert.Equal(expectedSymbol.DocumentSymbol.Kind, actualSymbol.DocumentSymbol.Kind); - Assert.Equal(expectedSymbol.DocumentSymbol.Range.Start.Line, actualSymbol.DocumentSymbol.Range.Start.Line); - Assert.Equal(expectedSymbol.DocumentSymbol.Range.Start.Character, actualSymbol.DocumentSymbol.Range.Start.Character); + Assert.Equal(expectedSymbol.DocumentSymbol.Range.Start.Line, + actualSymbol.DocumentSymbol.Range.Start.Line); + Assert.Equal(expectedSymbol.DocumentSymbol.Range.Start.Character, + actualSymbol.DocumentSymbol.Range.Start.Character); Assert.Equal(expectedSymbol.DocumentSymbol.Range.End.Line, actualSymbol.DocumentSymbol.Range.End.Line); - Assert.Equal(expectedSymbol.DocumentSymbol.Range.End.Character, actualSymbol.DocumentSymbol.Range.End.Character); + Assert.Equal(expectedSymbol.DocumentSymbol.Range.End.Character, + actualSymbol.DocumentSymbol.Range.End.Character); }); } @@ -423,8 +458,6 @@ public async Task DocumentSymbols_DocumentSymbol_Success() [Fact(DisplayName = "Language client can successfully request document folding ranges")] public async Task FoldingRanges_Success() { - await Connect(); - var expectedDocumentPath = AbsoluteDocumentPath; var expectedDocumentUri = DocumentUri.FromFileSystemPath(expectedDocumentPath); @@ -437,13 +470,24 @@ public async Task FoldingRanges_Success() EndCharacter = 2, }); - ServerDispatcher.HandleRequest>(TextDocumentNames.FoldingRange, (request, cancellationToken) => { - Assert.NotNull(request.TextDocument); - Assert.Equal(expectedDocumentUri, request.TextDocument.Uri); - return Task.FromResult(expectedFoldingRanges); - }); + var (client, server) = await Initialize( + client => { + client.WithCapability(new FoldingRangeCapability() { + RangeLimit = 100, + LineFoldingOnly = true + }); + }, + server => { + server.OnFoldingRange((request, cancellationToken) => { + Assert.NotNull(request.TextDocument); + Assert.Equal(expectedDocumentUri, request.TextDocument.Uri); + return Task.FromResult(expectedFoldingRanges); + }, new FoldingRangeRegistrationOptions()); + }); - var foldingRanges = await LanguageClient.TextDocument.FoldingRanges(AbsoluteDocumentPath); + var foldingRanges = await client.TextDocument.RequestFoldingRange(new FoldingRangeRequestParam() { + TextDocument = AbsoluteDocumentPath + }, CancellationToken); var actualFoldingRanges = foldingRanges.ToArray(); Assert.Collection(actualFoldingRanges, actualFoldingRange => { @@ -464,26 +508,19 @@ public async Task FoldingRanges_Success() [Fact(DisplayName = "Language client can successfully receive diagnostics")] public async Task Diagnostics_Success() { - await Connect(); - var documentPath = AbsoluteDocumentPath; var expectedDocumentUri = DocumentUri.FromFileSystemPath(documentPath); - var expectedDiagnostics = new List - { - new Diagnostic - { + var expectedDiagnostics = new List { + new Diagnostic { Source = "Test", Code = new DiagnosticCode(1234), Message = "This is a diagnostic message.", - Range = new Range - { - Start = new Position - { + Range = new Range { + Start = new Position { Line = 2, Character = 5 }, - End = new Position - { + End = new Position { Line = 3, Character = 7 } @@ -496,23 +533,33 @@ public async Task Diagnostics_Success() DocumentUri actualDocumentUri = null; List actualDiagnostics = null; - LanguageClient.TextDocument.OnPublishDiagnostics((documentUri, diagnostics) => { - actualDocumentUri = documentUri; - actualDiagnostics = diagnostics; - receivedDiagnosticsNotification.SetResult(null); - }); + var (client, server) = await Initialize( + client => { + client.OnPublishDiagnostics((request) => { + actualDocumentUri = request.Uri; + actualDiagnostics = request.Diagnostics.ToList(); + + receivedDiagnosticsNotification.SetResult(null); + return Unit.Task; + }); + }, + server => { + }); - ServerConnection.SendNotification(TextDocumentNames.PublishDiagnostics, new PublishDiagnosticsParams { + server.TextDocument.PublishDiagnostics(new PublishDiagnosticsParams { Uri = DocumentUri.FromFileSystemPath(documentPath), Diagnostics = expectedDiagnostics }); + CancellationToken.Register(() => receivedDiagnosticsNotification.SetCanceled()); + // Timeout. var winner = await Task.WhenAny( receivedDiagnosticsNotification.Task, Task.Delay( - TimeSpan.FromSeconds(2) + TimeSpan.FromSeconds(2), + CancellationToken ) ); Assert.Same(receivedDiagnosticsNotification.Task, winner); @@ -535,39 +582,5 @@ public async Task Diagnostics_Success() Assert.Equal(expectedDiagnostic.Severity, actualDiagnostic.Severity); Assert.Equal(expectedDiagnostic.Source, actualDiagnostic.Source); } - - /// - /// Connect the client and server. - /// - /// - /// Add standard handlers for server initialisation? - /// - async Task Connect(bool handleServerInitialize = true) - { - ServerConnection = await CreateServerConnection(); - ServerConnection.Connect(ServerDispatcher); - - if (handleServerInitialize) - HandleServerInitialize(); - - LanguageClient = await CreateClient(initialize: true); - } - - /// - /// Add standard handlers for sever initialisation. - /// - void HandleServerInitialize() - { - ServerDispatcher.HandleRequest("initialize", (request, cancellationToken) => { - return Task.FromResult(new InitializeResult { - Capabilities = new ServerCapabilities { - HoverProvider = true - } - }); - }); - ServerDispatcher.HandleEmptyNotification("initialized", () => { - Log.LogInformation("Server initialized."); - }); - } } } diff --git a/test/Client.Tests/ConnectionTests.cs b/test/Client.Tests/ConnectionTests.cs deleted file mode 100644 index bd9c9b923..000000000 --- a/test/Client.Tests/ConnectionTests.cs +++ /dev/null @@ -1,237 +0,0 @@ -using Microsoft.Extensions.Logging; -using System.Threading.Tasks; -using OmniSharp.Extensions.LanguageServer.Client.Dispatcher; -using OmniSharp.Extensions.LanguageServer.Client.Protocol; -using Xunit; -using Xunit.Abstractions; -using OmniSharp.Extensions.LanguageServer.Protocol.Serialization; -using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; - -namespace OmniSharp.Extensions.LanguageServerProtocol.Client.Tests -{ - /// - /// Tests for . - /// - public class ConnectionTests - : PipeServerTestBase - { - /// - /// Create a new test suite. - /// - /// - /// Output for the current test. - /// - public ConnectionTests(ITestOutputHelper testOutput) - : base(testOutput) - { - } - - /// - /// Verify that a server can handle an empty notification from a client . - /// - [Fact(DisplayName = "Server connection can handle empty notification from client")] - public async Task Client_HandleEmptyNotification_Success() - { - var testCompletion = new TaskCompletionSource(); - - LspConnection clientConnection = await CreateClientConnection(); - LspConnection serverConnection = await CreateServerConnection(); - - var serverDispatcher = CreateDispatcher(); - serverDispatcher.HandleEmptyNotification("test", () => - { - Log.LogInformation("Got notification."); - - testCompletion.SetResult(null); - }); - serverConnection.Connect(serverDispatcher); - - clientConnection.Connect(CreateDispatcher()); - clientConnection.SendEmptyNotification("test"); - - await testCompletion.Task; - - clientConnection.Disconnect(flushOutgoing: true); - serverConnection.Disconnect(); - - await Task.WhenAll(clientConnection.HasHasDisconnected, serverConnection.HasHasDisconnected); - } - - /// - /// Verify that a client can handle an empty notification from a server . - /// - [Fact(DisplayName = "Client connection can handle empty notification from server")] - public async Task Server_HandleEmptyNotification_Success() - { - var testCompletion = new TaskCompletionSource(); - - LspConnection clientConnection = await CreateClientConnection(); - LspConnection serverConnection = await CreateServerConnection(); - - var clientDispatcher = CreateDispatcher(); - clientDispatcher.HandleEmptyNotification("test", () => - { - Log.LogInformation("Got notification."); - - testCompletion.SetResult(null); - }); - clientConnection.Connect(clientDispatcher); - - serverConnection.Connect(CreateDispatcher()); - serverConnection.SendEmptyNotification("test"); - - await testCompletion.Task; - - serverConnection.Disconnect(flushOutgoing: true); - clientConnection.Disconnect(); - - await Task.WhenAll(clientConnection.HasHasDisconnected, serverConnection.HasHasDisconnected); - } - - /// - /// Verify that a client can handle a request from a server . - /// - [Fact(DisplayName = "Client connection can handle request from server")] - public async Task Server_HandleRequest_Success() - { - LspConnection clientConnection = await CreateClientConnection(); - LspConnection serverConnection = await CreateServerConnection(); - - var clientDispatcher = CreateDispatcher(); - clientDispatcher.HandleRequest("test", (request, cancellationToken) => - { - Log.LogInformation("Got request: {@Request}", request); - - return Task.FromResult(new TestResponse - { - Value = request.Value.ToString() - }); - }); - clientConnection.Connect(clientDispatcher); - - serverConnection.Connect(CreateDispatcher()); - TestResponse response = await serverConnection.SendRequest("test", new TestRequest - { - Value = 1234 - }); - - Assert.Equal("1234", response.Value); - - Log.LogInformation("Got response: {@Response}", response); - - serverConnection.Disconnect(flushOutgoing: true); - clientConnection.Disconnect(); - - await Task.WhenAll(clientConnection.HasHasDisconnected, serverConnection.HasHasDisconnected); - } - - /// - /// Verify that a server can handle a request from a client . - /// - [Fact(DisplayName = "Server connection can handle request from client")] - public async Task Client_HandleRequest_Success() - { - LspConnection clientConnection = await CreateClientConnection(); - LspConnection serverConnection = await CreateServerConnection(); - - var serverDispatcher = CreateDispatcher(); - serverDispatcher.HandleRequest("test", (request, cancellationToken) => - { - Log.LogInformation("Got request: {@Request}", request); - - return Task.FromResult(new TestResponse - { - Value = request.Value.ToString() - }); - }); - serverConnection.Connect(serverDispatcher); - - clientConnection.Connect(CreateDispatcher()); - TestResponse response = await clientConnection.SendRequest("test", new TestRequest - { - Value = 1234 - }); - - Assert.Equal("1234", response.Value); - - Log.LogInformation("Got response: {@Response}", response); - - clientConnection.Disconnect(flushOutgoing: true); - serverConnection.Disconnect(); - - await Task.WhenAll(clientConnection.HasHasDisconnected, serverConnection.HasHasDisconnected); - } - - /// - /// Verify that a client can handle a command-style request (i.e. no response body) from a server . - /// - [Fact(DisplayName = "Client connection can handle command request from server")] - public async Task Server_HandleCommandRequest_Success() - { - LspConnection clientConnection = await CreateClientConnection(); - LspConnection serverConnection = await CreateServerConnection(); - - var clientDispatcher = CreateDispatcher(); - clientDispatcher.HandleRequest("test", (request, cancellationToken) => - { - Log.LogInformation("Got request: {@Request}", request); - - Assert.Equal(1234, request.Value); - - return Task.CompletedTask; - }); - clientConnection.Connect(clientDispatcher); - - serverConnection.Connect(CreateDispatcher()); - await serverConnection.SendRequest("test", new TestRequest - { - Value = 1234 - }); - - serverConnection.Disconnect(flushOutgoing: true); - clientConnection.Disconnect(); - - await Task.WhenAll(clientConnection.HasHasDisconnected, serverConnection.HasHasDisconnected); - } - - /// - /// Verify that a server can handle a command-style request (i.e. no response body) from a client . - /// - [Fact(DisplayName = "Server connection can handle command request from client")] - public async Task Client_HandleCommandRequest_Success() - { - LspConnection clientConnection = await CreateClientConnection(); - LspConnection serverConnection = await CreateServerConnection(); - - var serverDispatcher = CreateDispatcher(); - serverDispatcher.HandleRequest("test", (request, cancellationToken) => - { - Log.LogInformation("Got request: {@Request}", request); - - Assert.Equal(1234, request.Value); - - return Task.CompletedTask; - }); - serverConnection.Connect(serverDispatcher); - - clientConnection.Connect(CreateDispatcher()); - await clientConnection.SendRequest("test", new TestRequest - { - Value = 1234 - }); - - clientConnection.Disconnect(flushOutgoing: true); - serverConnection.Disconnect(); - - await Task.WhenAll(clientConnection.HasHasDisconnected, serverConnection.HasHasDisconnected); - } - - /// - /// Create an for use in tests. - /// - /// - /// The . - /// - LspDispatcher CreateDispatcher() => new LspDispatcher(new Serializer(ClientVersion.Lsp3)); - } -} diff --git a/test/Client.Tests/HandlerTests.cs b/test/Client.Tests/HandlerTests.cs deleted file mode 100644 index f93c7a102..000000000 --- a/test/Client.Tests/HandlerTests.cs +++ /dev/null @@ -1,97 +0,0 @@ -using OmniSharp.Extensions.LanguageServer.Client.Handlers; -using System.Threading.Tasks; -using Xunit; -using Xunit.Abstractions; - -namespace OmniSharp.Extensions.LanguageServerProtocol.Client.Tests -{ - /// - /// Tests for and friends. - /// - public class HandlerTests - : TestBase - { - /// - /// Create a new test suite. - /// - /// - /// Output for the current test. - /// - public HandlerTests(ITestOutputHelper testOutput) - : base(testOutput) - { - } - - /// - /// Verify that specifies the correct payload type. - /// - [Fact(DisplayName = "DelegateEmptyNotificationHandler specifies correct payload type")] - public void DelegateEmptyNotificationHandler_PayloadType() - { - IHandler handler = new DelegateEmptyNotificationHandler( - method: "test", - handler: () => - { - // Nothing to do. - } - ); - - Assert.Null(handler.PayloadType); - } - - /// - /// Verify that specifies the correct payload type. - /// - [Fact(DisplayName = "DelegateNotificationHandler specifies correct payload type")] - public void DelegateNotificationHandler_PayloadType() - { - IHandler handler = new DelegateNotificationHandler( - method: "test", - handler: notification => - { - // Nothing to do. - } - ); - - Assert.Equal(typeof(string), handler.PayloadType); - } - - /// - /// Verify that specifies the correct payload type (null). - /// - [Fact(DisplayName = "DelegateRequestHandler specifies correct payload type")] - public void DelegateRequestHandler_PayloadType() - { - IHandler handler = new DelegateRequestHandler( - method: "test", - handler: (request, cancellationToken) => - { - // Nothing to do. - - return Task.CompletedTask; - } - ); - - Assert.Equal(typeof(string), handler.PayloadType); - } - - /// - /// Verify that specifies the correct payload type (null). - /// - [Fact(DisplayName = "DelegateRequestResponseHandler specifies correct payload type")] - public void DelegateRequestResponseHandler_PayloadType() - { - IHandler handler = new DelegateRequestResponseHandler( - method: "test", - handler: (request, cancellationToken) => - { - // Nothing to do. - - return Task.FromResult("hello"); - } - ); - - Assert.Equal(typeof(string), handler.PayloadType); - } - } -} diff --git a/test/Client.Tests/Logging/TestOutputLogScope.cs b/test/Client.Tests/Logging/TestOutputLogScope.cs index 450d78ef9..026837306 100644 --- a/test/Client.Tests/Logging/TestOutputLogScope.cs +++ b/test/Client.Tests/Logging/TestOutputLogScope.cs @@ -1,7 +1,7 @@ using System; using System.Threading; -namespace OmniSharp.Extensions.LanguageServerProtocol.Client.Tests.Logging +namespace OmniSharp.Extensions.LanguageServer.Client.Tests.Logging { /// /// Log scope for . diff --git a/test/Client.Tests/Logging/TestOutputLogger.cs b/test/Client.Tests/Logging/TestOutputLogger.cs index aeb2f6400..c6e494805 100644 --- a/test/Client.Tests/Logging/TestOutputLogger.cs +++ b/test/Client.Tests/Logging/TestOutputLogger.cs @@ -2,7 +2,7 @@ using System; using Xunit.Abstractions; -namespace OmniSharp.Extensions.LanguageServerProtocol.Client.Tests.Logging +namespace OmniSharp.Extensions.LanguageServer.Client.Tests.Logging { /// /// A logger that writes to Xunit test output. diff --git a/test/Client.Tests/Logging/TestOutputLoggingExtensions.cs b/test/Client.Tests/Logging/TestOutputLoggingExtensions.cs index bf3ff51cf..6fe2a699b 100644 --- a/test/Client.Tests/Logging/TestOutputLoggingExtensions.cs +++ b/test/Client.Tests/Logging/TestOutputLoggingExtensions.cs @@ -2,7 +2,7 @@ using System; using Xunit.Abstractions; -namespace OmniSharp.Extensions.LanguageServerProtocol.Client.Tests.Logging +namespace OmniSharp.Extensions.LanguageServer.Client.Tests.Logging { /// /// Extension methods for configuring logging to Xunit test output. diff --git a/test/Client.Tests/Logging/TestOutputLoggingProvider.cs b/test/Client.Tests/Logging/TestOutputLoggingProvider.cs index dc242181e..f4e41939a 100644 --- a/test/Client.Tests/Logging/TestOutputLoggingProvider.cs +++ b/test/Client.Tests/Logging/TestOutputLoggingProvider.cs @@ -3,7 +3,7 @@ using Xunit.Abstractions; using System.Collections.Concurrent; -namespace OmniSharp.Extensions.LanguageServerProtocol.Client.Tests.Logging +namespace OmniSharp.Extensions.LanguageServer.Client.Tests.Logging { /// /// A provider for loggers that send log events to Xunit test output. diff --git a/test/Client.Tests/PipeServerTestBase.cs b/test/Client.Tests/PipeServerTestBase.cs index 79c4e5698..47a82c5c3 100644 --- a/test/Client.Tests/PipeServerTestBase.cs +++ b/test/Client.Tests/PipeServerTestBase.cs @@ -1,23 +1,32 @@ using System; +using System.Diagnostics; using System.IO; +using System.IO.Pipelines; +using System.Threading; using System.Threading.Tasks; -using OmniSharp.Extensions.LanguageServer.Client; -using OmniSharp.Extensions.LanguageServer.Client.Processes; -using OmniSharp.Extensions.LanguageServer.Client.Protocol; +using Microsoft.Extensions.DependencyInjection; +using OmniSharp.Extensions.LanguageServer.Protocol.Client; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; +using OmniSharp.Extensions.LanguageServer.Server; +using Xunit; using Xunit.Abstractions; -namespace OmniSharp.Extensions.LanguageServerProtocol.Client.Tests +namespace OmniSharp.Extensions.LanguageServer.Client.Tests { /// /// The base class for test suites that use a . /// - public abstract class PipeServerTestBase - : TestBase + public abstract class PipeServerTestBase : TestBase, IAsyncLifetime { /// /// The used to connect client and server streams. /// - readonly NamedPipeServerProcess _serverProcess; + private readonly CancellationTokenSource _cancellationTokenSource; + + protected CancellationToken CancellationToken => _cancellationTokenSource.Token; + + private ILanguageClient _client; + private ILanguageServer _server; /// /// Create a new . @@ -28,114 +37,54 @@ public abstract class PipeServerTestBase protected PipeServerTestBase(ITestOutputHelper testOutput) : base(testOutput) { - _serverProcess = new NamedPipeServerProcess(Guid.NewGuid().ToString("N"), LoggerFactory); - Disposal.Add(_serverProcess); + _cancellationTokenSource = new CancellationTokenSource(); + if (!Debugger.IsAttached) + { + _cancellationTokenSource.CancelAfter(TimeSpan.FromMinutes(1)); + } } /// /// The workspace root path. /// - protected virtual string WorkspaceRoot => Path.GetDirectoryName(GetType().Assembly.Location); - - /// - /// The client's output stream (server reads from this). - /// - protected Stream ClientOutputStream => _serverProcess.ClientOutputStream; - - /// - /// The client's input stream (server writes to this). - /// - protected Stream ClientInputStream => _serverProcess.ClientInputStream; - - /// - /// The server's output stream (client reads from this). - /// - protected Stream ServerOutputStream => _serverProcess.ServerOutputStream; - - /// - /// The server's input stream (client writes to this). - /// - protected Stream ServerInputStream => _serverProcess.ServerInputStream; + protected string WorkspaceRoot => Path.GetDirectoryName(GetType().Assembly.Location); /// /// Create a connected to the test's . /// - /// - /// Automatically initialise the client? - /// - /// Default is true. - /// /// /// The . /// - protected async Task CreateClient(bool initialize = true) - { - if (!_serverProcess.IsRunning) - await StartServer(); - - await _serverProcess.HasStarted; - - var client = new LanguageClient(LoggerFactory, _serverProcess); - Disposal.Add(client); - - if (initialize) - await client.Initialize(WorkspaceRoot); - - return client; - } - - /// - /// Create a that uses the client ends of the the test's streams. - /// - /// - /// The . - /// - protected async Task CreateClientConnection() - { - if (!_serverProcess.IsRunning) - await StartServer(); - - await _serverProcess.HasStarted; - - var connection = new LspConnection(LoggerFactory, input: ServerOutputStream, output: ServerInputStream); - Disposal.Add(connection); - - return connection; - } - - /// - /// Create a that uses the server ends of the the test's streams. - /// - /// - /// The . - /// - protected async Task CreateServerConnection() + protected async Task<(ILanguageClient client, ILanguageServer server)> Initialize( + Action clientOptionsAction, + Action serverOptionsAction) { - if (!_serverProcess.IsRunning) - await StartServer(); - - await _serverProcess.HasStarted; - - var connection = new LspConnection(LoggerFactory, input: ClientOutputStream, output: ClientInputStream); - Disposal.Add(connection); - - return connection; + var clientPipe = new Pipe(); + var serverPipe = new Pipe(); + _client = LanguageClient.PreInit(options => { + options.Services.AddSingleton(LoggerFactory); + options.WithInput(serverPipe.Reader).WithOutput(clientPipe.Writer); + options.WithRootPath(WorkspaceRoot); + clientOptionsAction(options); + }); + Disposal.Add(_client); + + _server = OmniSharp.Extensions.LanguageServer.Server.LanguageServer.PreInit(options => { + options.Services.AddSingleton(LoggerFactory); + options.WithInput(clientPipe.Reader).WithOutput(serverPipe.Writer); + serverOptionsAction(options); + }); + Disposal.Add(_server); + + await Task.WhenAll( + _client.Initialize(CancellationToken), + _server.Initialize(CancellationToken) + ); + + return (_client, _server); } - /// - /// Called to start the server process. - /// - /// - /// A representing the operation. - /// - protected virtual Task StartServer() => _serverProcess.Start(); - - /// - /// Called to stop the server process. - /// - /// - /// A representing the operation. - /// - protected virtual Task StopServer() => _serverProcess.Stop(); + public virtual Task InitializeAsync() => Task.CompletedTask; + public Task DisposeAsync() => _client.Shutdown(); } } diff --git a/test/Client.Tests/PipeTests.cs b/test/Client.Tests/PipeTests.cs index 0f7dd64b7..f468e8f4d 100644 --- a/test/Client.Tests/PipeTests.cs +++ b/test/Client.Tests/PipeTests.cs @@ -1,6 +1,6 @@ using Xunit.Abstractions; -namespace OmniSharp.Extensions.LanguageServerProtocol.Client.Tests +namespace OmniSharp.Extensions.LanguageServer.Client.Tests { public class PipeTests : TestBase diff --git a/test/Client.Tests/TestBase.cs b/test/Client.Tests/TestBase.cs index 136432db3..ba6629e1f 100644 --- a/test/Client.Tests/TestBase.cs +++ b/test/Client.Tests/TestBase.cs @@ -7,7 +7,7 @@ using Xunit; using Xunit.Abstractions; -namespace OmniSharp.Extensions.LanguageServerProtocol.Client.Tests +namespace OmniSharp.Extensions.LanguageServer.Client.Tests { using Logging; diff --git a/test/Client.Tests/TestRequest.cs b/test/Client.Tests/TestRequest.cs index e3cb35834..b6e9cfc20 100644 --- a/test/Client.Tests/TestRequest.cs +++ b/test/Client.Tests/TestRequest.cs @@ -1,4 +1,4 @@ -namespace OmniSharp.Extensions.LanguageServerProtocol.Client.Tests +namespace OmniSharp.Extensions.LanguageServer.Client.Tests { /// /// A test request. @@ -10,4 +10,4 @@ class TestRequest /// public int Value { get; set; } } -} \ No newline at end of file +} diff --git a/test/Client.Tests/TestResponse.cs b/test/Client.Tests/TestResponse.cs index a7e89dc1d..bbbab1d39 100644 --- a/test/Client.Tests/TestResponse.cs +++ b/test/Client.Tests/TestResponse.cs @@ -1,4 +1,4 @@ -namespace OmniSharp.Extensions.LanguageServerProtocol.Client.Tests +namespace OmniSharp.Extensions.LanguageServer.Client.Tests { /// /// A test response. @@ -10,4 +10,4 @@ class TestResponse /// public string Value { get; set; } } -} \ No newline at end of file +} diff --git a/test/Dap.Tests/DapOutputHandlerTests.cs b/test/Dap.Tests/DapOutputHandlerTests.cs new file mode 100644 index 000000000..dbc83a51e --- /dev/null +++ b/test/Dap.Tests/DapOutputHandlerTests.cs @@ -0,0 +1,107 @@ +using System.IO; +using System.IO.Pipelines; +using System.Reactive.Concurrency; +using System.Threading.Tasks; +using FluentAssertions; +using Microsoft.Extensions.Logging.Abstractions; +using Newtonsoft.Json.Linq; +using OmniSharp.Extensions.DebugAdapter.Protocol; +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.JsonRpc.Client; +using OmniSharp.Extensions.JsonRpc.Server.Messages; +using Xunit; + +namespace Dap.Tests +{ + public class DapOutputHandlerTests + { + private static OutputHandler NewHandler(PipeWriter writer) + { + return new OutputHandler(writer, new DapSerializer(), _ => true, Scheduler.Immediate, NullLogger.Instance); + } + + [Fact] + public async Task ShouldSerializeResponses() + { + var pipe = new Pipe(new PipeOptions()); + using var handler = NewHandler(pipe.Writer); + + var value = new OutgoingResponse(1, new object(), + new OmniSharp.Extensions.JsonRpc.Server.Request(1, "command", new JObject())); + + handler.Send(value); + await handler.WriteAndFlush(); + + using var reader = new StreamReader(pipe.Reader.AsStream()); + var received = await reader.ReadToEndAsync(); + + const string send = + "Content-Length: 88\r\n\r\n{\"seq\":1,\"type\":\"response\",\"request_seq\":1,\"success\":true,\"command\":\"command\",\"body\":{}}"; + received.Should().Be(send); + } + + [Fact] + public async Task ShouldSerializeNotifications() + { + var pipe = new Pipe(new PipeOptions()); + using var handler = NewHandler(pipe.Writer); + + var value = new OmniSharp.Extensions.JsonRpc.Client.OutgoingNotification() { + Method = "method", + Params = new object() + }; + + handler.Send(value); + await handler.WriteAndFlush(); + + using var reader = new StreamReader(pipe.Reader.AsStream()); + var received = await reader.ReadToEndAsync(); + + const string send = + "Content-Length: 51\r\n\r\n{\"seq\":1,\"type\":\"event\",\"event\":\"method\",\"body\":{}}"; + received.Should().Be(send); + } + + [Fact] + public async Task ShouldSerializeRequests() + { + var pipe = new Pipe(new PipeOptions()); + using var handler = NewHandler(pipe.Writer); + + var value = new OmniSharp.Extensions.JsonRpc.Client.OutgoingRequest() { + Method = "method", + Id = 1, + Params = new object(), + }; + + handler.Send(value); + await handler.WriteAndFlush(); + + using var reader = new StreamReader(pipe.Reader.AsStream()); + var received = await reader.ReadToEndAsync(); + + const string send = + "Content-Length: 60\r\n\r\n{\"seq\":1,\"type\":\"request\",\"command\":\"method\",\"arguments\":{}}"; + received.Should().Be(send); + } + + [Fact] + public async Task ShouldSerializeErrors() + { + var pipe = new Pipe(new PipeOptions()); + using var handler = NewHandler(pipe.Writer); + + var value = new RpcError(1, new ErrorMessage(1, "something", "data")); + + handler.Send(value); + await handler.WriteAndFlush(); + + using var reader = new StreamReader(pipe.Reader.AsStream()); + var received = await reader.ReadToEndAsync(); + + const string send = + "Content-Length: 76\r\n\r\n{\"seq\":1,\"type\":\"response\",\"request_seq\":1,\"success\":false,\"message\":\"data\"}"; + received.Should().Be(send); + } + } +} diff --git a/test/Dap.Tests/FoundationTests.cs b/test/Dap.Tests/FoundationTests.cs new file mode 100644 index 000000000..7ea1bc34b --- /dev/null +++ b/test/Dap.Tests/FoundationTests.cs @@ -0,0 +1,423 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using FluentAssertions; +using MediatR; +using Microsoft.Extensions.Logging; +using NSubstitute; +using OmniSharp.Extensions.DebugAdapter.Protocol.Requests; +using OmniSharp.Extensions.JsonRpc; +using Xunit; +using Xunit.Abstractions; + +namespace Dap.Tests +{ + public class FoundationTests + { + private readonly ILogger _logger; + + public FoundationTests(ITestOutputHelper outputHelper) + { + this._logger = new TestLoggerFactory(outputHelper).CreateLogger(typeof(FoundationTests)); + } + + [Theory(DisplayName = "Params types should have a method attribute")] + [ClassData(typeof(ParamsShouldHaveMethodAttributeData))] + public void ParamsShouldHaveMethodAttribute(Type type) + { + type.GetCustomAttributes().Any(z => z.Direction != Direction.Unspecified).Should() + .Be(true, $"{type.Name} is missing a method attribute or the direction is not specified"); + } + + [Theory(DisplayName = "Handler interfaces should have a method attribute")] + [ClassData(typeof(HandlersShouldHaveMethodAttributeData))] + public void HandlersShouldHaveMethodAttribute(Type type) + { + type.GetCustomAttributes().Any(z => z.Direction != Direction.Unspecified).Should() + .Be(true, $"{type.Name} is missing a method attribute or the direction is not specified"); + } + + [Theory(DisplayName = "Handler method should match params method")] + [ClassData(typeof(HandlersShouldHaveMethodAttributeData))] + public void HandlersShouldMatchParamsMethodAttribute(Type type) + { + if (typeof(IJsonRpcNotificationHandler).IsAssignableFrom(type)) return; + var paramsType = HandlerTypeDescriptorHelper.GetHandlerInterface(type).GetGenericArguments()[0]; + + var lhs = type.GetCustomAttribute(true); + var rhs = paramsType.GetCustomAttribute(true); + lhs.Method.Should().Be(rhs.Method, $"{type.FullName} method does not match {paramsType.FullName}"); + lhs.Direction.Should().Be(rhs.Direction, $"{type.FullName} direction does not match {paramsType.FullName}"); + } + + [Theory(DisplayName = "Handler interfaces should have a abstract class")] + [ClassData(typeof(TypeHandlerData))] + public void HandlersShouldAbstractClass(IHandlerTypeDescriptor descriptor) + { + _logger.LogInformation("Handler: {Type}", descriptor.HandlerType); + // This test requires a refactor, the delegating handlers have been removed and replaced by shared implementations + // TODO: + // * Check for extension methods + // * Check for IPartialItem(s)<> extension methods + // * Check that the extension method calls `AddHandler` using the correct eventname + // * check extension method name + // * Also update events to have a nicer fire and forget abstract class + // * Ensure all notifications have an action and task returning function + var abstractHandler = descriptor.HandlerType.Assembly.ExportedTypes.FirstOrDefault(z => z.IsAbstract && z.IsClass && descriptor.HandlerType.IsAssignableFrom(z)); + abstractHandler.Should().NotBeNull($"{descriptor.HandlerType.FullName} is missing abstract base class"); + + var delegatingHandler = descriptor.HandlerType.Assembly.DefinedTypes.FirstOrDefault(z => abstractHandler.IsAssignableFrom(z) && abstractHandler != z); + if (delegatingHandler != null) + { + _logger.LogInformation("Delegating Handler: {Type}", delegatingHandler); + delegatingHandler.DeclaringType.Should().NotBeNull(); + delegatingHandler.DeclaringType.GetMethods(BindingFlags.Public | BindingFlags.Static).Any(z => z.Name.StartsWith("On")).Should() + .BeTrue($"{descriptor.HandlerType.FullName} is missing delegating extension method"); + } + } + + + [Theory(DisplayName = "Handler extension method classes with appropriately named methods")] + [ClassData(typeof(TypeHandlerExtensionData))] + public void HandlersShouldExtensionMethodClassWithMethods(IHandlerTypeDescriptor descriptor, string onMethodName, string sendMethodName, + Type extensionClass, string extensionClassName) + { + // This test requires a refactor, the delegating handlers have been removed and replaced by shared implementations + // TODO: + // * Check for IPartialItem(s)<> extension methods + // * Also update events to have a nicer fire and forget abstract class + + _logger.LogInformation("Handler: {Type} {Extension} {ExtensionName} {OnMethod} {SendMethod}", descriptor.HandlerType, + extensionClass, extensionClassName, onMethodName, sendMethodName); + + extensionClass.Should().NotBeNull($"{descriptor.HandlerType.FullName} is missing extension method class"); + extensionClass.GetMethods().Any(z => z.Name == onMethodName && typeof(IJsonRpcHandlerRegistry).IsAssignableFrom(z.GetParameters()[0].ParameterType)).Should() + .BeTrue($"{descriptor.HandlerType.FullName} is missing event extension methods named {onMethodName}"); + extensionClass.GetMethods().Any(z => z.Name == sendMethodName && typeof(IResponseRouter).IsAssignableFrom(z.GetParameters()[0].ParameterType)).Should() + .BeTrue($"{descriptor.HandlerType.FullName} is missing execute extension methods named {sendMethodName}"); + + var registries = extensionClass.GetMethods(BindingFlags.Static | BindingFlags.Public) + .Where(z => z.Name == onMethodName || z.Name == sendMethodName) + .Select(z => z.GetParameters()[0].ParameterType) + .Distinct() + .ToHashSet(); + + registries.Should().HaveCount(descriptor.Direction == Direction.Bidirectional ? 4 : 2, + $"{descriptor.HandlerType.FullName} there should be methods for both handing the event and sending the event"); + } + + [Theory(DisplayName = "Handler all expected extensions methods based on method direction")] + [ClassData(typeof(TypeHandlerExtensionData))] + public void HandlersShouldHaveExpectedExtensionMethodsBasedOnDirection(IHandlerTypeDescriptor descriptor, string onMethodName, string sendMethodName, + Type extensionClass, string extensionClassName) + { + _logger.LogInformation("Handler: {Type} {Extension} {ExtensionName} {OnMethod} {SendMethod}", descriptor.HandlerType, + extensionClass, extensionClassName, onMethodName, sendMethodName); + + var onMethodRegistries = extensionClass.GetMethods(BindingFlags.Static | BindingFlags.Public) + .Where(z => z.Name == onMethodName) + .Select(z => z.GetParameters()[0].ParameterType) + .Distinct() + .ToHashSet(); + + var sendMethodRegistries = extensionClass.GetMethods(BindingFlags.Static | BindingFlags.Public) + .Where(z => z.Name == sendMethodName) + .Select(z => z.GetParameters()[0].ParameterType) + .Distinct() + .ToHashSet(); + + var expectedEventRegistries = descriptor.Direction switch { + Direction.ClientToServer => new (string type, Func matcher)[] {("Server", info => info.ParameterType.Name.EndsWith("ServerRegistry"))}, + Direction.ServerToClient => new (string type, Func matcher)[] {("Client", info => info.ParameterType.Name.EndsWith("ClientRegistry"))}, + Direction.Bidirectional => new (string type, Func matcher)[] + {("Server", info => info.ParameterType.Name.EndsWith("ServerRegistry")), ("Client", info => info.ParameterType.Name.EndsWith("ClientRegistry"))}, + _ => throw new NotImplementedException(descriptor.HandlerType.FullName) + }; + + var expectedRequestHandlers = descriptor.Direction switch { + Direction.ClientToServer => new (string type, Func matcher)[] {("Server", info => info.ParameterType.Name.EndsWith("Client"))}, + Direction.ServerToClient => new (string type, Func matcher)[] {("Client", info => info.ParameterType.Name.EndsWith("Server"))}, + Direction.Bidirectional => new (string type, Func matcher)[] + {("Server", info => info.ParameterType.Name.EndsWith("Client")), ("Client", info => info.ParameterType.Name.EndsWith("Server"))}, + _ => throw new NotImplementedException(descriptor.HandlerType.FullName) + }; + + foreach (var item in expectedEventRegistries) + { + extensionClass.GetMethods(BindingFlags.Static | BindingFlags.Public) + .Where(z => z.Name == onMethodName) + .Where(z => item.matcher(z.GetParameters()[0])) + .Should().HaveCountGreaterOrEqualTo(1, $"{descriptor.HandlerType.FullName} is missing a registry implementation for {item.type}"); + } + + foreach (var item in expectedRequestHandlers) + { + extensionClass.GetMethods(BindingFlags.Static | BindingFlags.Public) + .Where(z => z.Name == sendMethodName) + .Where(z => item.matcher(z.GetParameters()[0])) + .Should().HaveCountGreaterOrEqualTo(1, $"{descriptor.HandlerType.FullName} is missing a request implementation for {item.type}"); + } + + { + var matcher = new MethodMatcher(onMethodRegistries, descriptor, extensionClass, onMethodName); + + Func ForParameter(int index, Func m) + { + return (info) => m(info.GetParameters()[index]); + } + + var containsCancellationToken = ForParameter(1, info => info.ParameterType.GetGenericArguments().Reverse().Take(2).Any(x => x == typeof(CancellationToken))); + var returnType = descriptor.HasResponseType ? typeof(Task<>).MakeGenericType(descriptor.ResponseType) : typeof(Task); + var returns = ForParameter(1, info => info.ParameterType.GetGenericArguments().LastOrDefault() == returnType); + var isAction = ForParameter(1, info => info.ParameterType.Name.StartsWith(nameof(Action))); + var isFunc = ForParameter(1, info => info.ParameterType.Name.StartsWith("Func")); + var takesParameter = ForParameter(1, info => info.ParameterType.GetGenericArguments().FirstOrDefault() == descriptor.ParamsType); + + if (descriptor.IsRequest) + { + matcher.Match($"Func<{descriptor.ParamsType.Name}, {returnType.Name}>", isFunc, takesParameter, returns); + matcher.Match($"Func<{descriptor.ParamsType.Name}, CancellationToken, {returnType.Name}>", isFunc, takesParameter, containsCancellationToken, returns); + } + + if (descriptor.IsNotification) + { + matcher.Match($"Func<{descriptor.ParamsType.Name}, {returnType.Name}>", isFunc, takesParameter, returns); + matcher.Match($"Func<{descriptor.ParamsType.Name}, CancellationToken, {returnType.Name}>", isFunc, takesParameter, containsCancellationToken, returns); + matcher.Match($"Action<{descriptor.ParamsType.Name}>", isAction, takesParameter); + matcher.Match($"Action<{descriptor.ParamsType.Name}, CancellationToken>", isAction, takesParameter, containsCancellationToken); + } + } + { + var matcher = new MethodMatcher(sendMethodRegistries, descriptor, extensionClass, sendMethodName); + Func containsCancellationToken = info => info.GetParameters().Reverse().Take(2).Any(x => x.ParameterType == typeof(CancellationToken)); + var returnType = descriptor.HasResponseType ? typeof(Task<>).MakeGenericType(descriptor.ResponseType) : typeof(Task); + Func returns = info => info.ReturnType == returnType; + Func isAction = info => info.ReturnType.Name == "Void"; + var isFunc = returns; + Func takesParameter = info => info.GetParameters().Skip(1).Any(z => z.ParameterType == descriptor.ParamsType); + + if (descriptor.IsRequest) + { + matcher.Match($"Func<{descriptor.ParamsType.Name}, CancellationToken, {returnType.Name}>", isFunc, takesParameter, containsCancellationToken, returns); + } + + if (descriptor.IsNotification) + { + matcher.Match($"Action<{descriptor.ParamsType.Name}>", isAction, takesParameter); + } + } + } + + class MethodMatcher + { + private readonly IEnumerable _registries; + private readonly IHandlerTypeDescriptor _descriptor; + private readonly Type _extensionClass; + private readonly string _methodName; + + public MethodMatcher(IEnumerable registries, + IHandlerTypeDescriptor descriptor, Type extensionClass, string methodName) + { + _registries = registries; + _descriptor = descriptor; + _extensionClass = extensionClass; + _methodName = methodName; + } + + public void Match(string description, params Func[] matchers) + { + foreach (var registry in _registries) + { + var methods = _extensionClass.GetMethods(BindingFlags.Static | BindingFlags.Public) + .Where(z => z.Name == _methodName) + .Where(z => matchers.All(matcher => matcher(z))) + .ToHashSet(); + methods.Count.Should().BeGreaterThan(0, + $"{_descriptor.HandlerType.FullName} missing extension with parameter type {description} method for {registry.FullName}"); + + foreach (var method in methods) + { + if (method.Name == GetOnMethodName(_descriptor)) + { + var registrySub = Substitute.For(new Type[] {method.GetParameters()[0].ParameterType}, + Array.Empty()); + + method.Invoke(null, + new[] { + registrySub, Substitute.For(new Type[] {method.GetParameters()[1].ParameterType}, Array.Empty()), + }.Concat(method.GetParameters().Skip(2).Select(z => + !z.ParameterType.IsGenericType ? Activator.CreateInstance(z.ParameterType) : Substitute.For(new Type[] {z.ParameterType}, Array.Empty())) + ) + .ToArray()); + + registrySub.Received().ReceivedCalls() + .Any(z => z.GetMethodInfo().Name == nameof(IJsonRpcHandlerRegistry.AddHandler) && z.GetArguments().Length == 3 && + z.GetArguments()[0].Equals(_descriptor.Method)).Should() + .BeTrue($"{_descriptor.HandlerType.Name} {description} should have the correct method."); + } + + if (_descriptor.IsRequest && method.Name == GetSendMethodName(_descriptor)) + { + method.GetParameters().Last().ParameterType.Should().Be(typeof(CancellationToken), + $"{_descriptor.HandlerType.Name} {description} send method must have optional cancellation token"); + method.GetParameters().Last().IsOptional.Should().BeTrue( + $"{_descriptor.HandlerType.Name} {description} send method must have optional cancellation token"); + } + } + } + } + } + + public class ParamsShouldHaveMethodAttributeData : TheoryData + { + public ParamsShouldHaveMethodAttributeData() + { + foreach (var type in typeof(IDataBreakpointInfoHandler).Assembly.ExportedTypes + .Where(z => z.IsClass && !z.IsAbstract && z.GetInterfaces().Any(z => + z.IsGenericType && + typeof(IRequest<>).IsAssignableFrom(z.GetGenericTypeDefinition())))) + { + Add(type); + } + } + } + + public class HandlersShouldHaveMethodAttributeData : TheoryData + { + public HandlersShouldHaveMethodAttributeData() + { + foreach (var type in typeof(IDataBreakpointInfoHandler).Assembly.ExportedTypes + .Where(z => z.IsInterface && typeof(IJsonRpcHandler).IsAssignableFrom(z) && !z.IsGenericType)) + { + Add(type); + } + } + } + + public class HandlersShouldAbstractClassData : TheoryData + { + public HandlersShouldAbstractClassData() + { + foreach (var type in typeof(IDataBreakpointInfoHandler).Assembly.ExportedTypes + .Where(z => z.IsInterface && typeof(IJsonRpcHandler).IsAssignableFrom(z) && !z.IsGenericType)) + { + Add(type); + } + } + } + + + private static readonly Type[] HandlerTypes = { + typeof(IJsonRpcNotificationHandler), typeof(IJsonRpcNotificationHandler<>), + typeof(IJsonRpcRequestHandler<>), typeof(IJsonRpcRequestHandler<,>), + }; + + private static bool IsValidInterface(Type type) + { + if (type.GetTypeInfo().IsGenericType) + { + return HandlerTypes.Contains(type.GetGenericTypeDefinition()); + } + + return HandlerTypes.Contains(type); + } + + public static Type GetHandlerInterface(Type type) + { + if (IsValidInterface(type)) return type; + return type?.GetTypeInfo() + .ImplementedInterfaces + .First(IsValidInterface); + } + + + + public class TypeHandlerData : TheoryData + { + public TypeHandlerData() + { + foreach (var type in typeof(CompletionsArguments).Assembly.ExportedTypes.Where(z => z.IsInterface && typeof(IJsonRpcHandler).IsAssignableFrom(z) && !z.IsGenericType)) + { + Add(HandlerTypeDescriptorHelper.GetHandlerTypeDescriptor(type)); + } + } + } + + public class TypeHandlerExtensionData : TheoryData + { + public TypeHandlerExtensionData() + { + foreach (var type in typeof(CompletionsArguments).Assembly.ExportedTypes + .Where(z => z.IsInterface && typeof(IJsonRpcHandler).IsAssignableFrom(z) && !z.IsGenericType)) + { + var descriptor = HandlerTypeDescriptorHelper.GetHandlerTypeDescriptor(type); + + Add( + descriptor, + GetOnMethodName(descriptor), + GetSendMethodName(descriptor), + GetExtensionClass(descriptor), + GetExtensionClassName(descriptor).Substring(GetExtensionClassName(descriptor).LastIndexOf('.') + 1) + ); + } + } + } + + private static string GetExtensionClassName(IHandlerTypeDescriptor descriptor) + { + return SpecialCasedHandlerFullName(descriptor) + "Extensions"; + ; + } + + private static string SpecialCasedHandlerFullName(IHandlerTypeDescriptor descriptor) + { + return new Regex(@"(\w+)$") + .Replace(descriptor.HandlerType.FullName ?? string.Empty, + descriptor.HandlerType.Name.Substring(1, descriptor.HandlerType.Name.IndexOf("Handler", StringComparison.Ordinal) - 1)) + ; + } + + private static string HandlerName(IHandlerTypeDescriptor descriptor) + { + var name = HandlerFullName(descriptor); + return name.Substring(name.LastIndexOf('.') + 1); + } + + private static string HandlerFullName(IHandlerTypeDescriptor descriptor) + { + return new Regex(@"(\w+)$") + .Replace(descriptor.HandlerType.FullName ?? string.Empty, + descriptor.HandlerType.Name.Substring(1, descriptor.HandlerType.Name.IndexOf("Handler", StringComparison.Ordinal) - 1)); + } + + private static string SpecialCasedHandlerName(IHandlerTypeDescriptor descriptor) + { + var name = SpecialCasedHandlerFullName(descriptor); + return name.Substring(name.LastIndexOf('.') + 1); + } + + private static Type GetExtensionClass(IHandlerTypeDescriptor descriptor) + { + var name = GetExtensionClassName(descriptor); + return descriptor.HandlerType.Assembly.GetExportedTypes() + .FirstOrDefault(z => z.IsClass && z.FullName == name); + } + + private static string GetOnMethodName(IHandlerTypeDescriptor descriptor) + { + return "On" + SpecialCasedHandlerName(descriptor); + } + + private static string GetSendMethodName(IHandlerTypeDescriptor descriptor) + { + var name = HandlerName(descriptor); + if (name.StartsWith("Run")) return name; + + return descriptor.IsNotification ? "Send" + name : "Request" + name; + } + } +} diff --git a/test/JsonRpc.Tests/AutoNSubstitute/TestExtensions.cs b/test/JsonRpc.Tests/AutoNSubstitute/TestExtensions.cs new file mode 100644 index 000000000..e308b43a7 --- /dev/null +++ b/test/JsonRpc.Tests/AutoNSubstitute/TestExtensions.cs @@ -0,0 +1,22 @@ +using System.Threading; +using OmniSharp.Extensions.JsonRpc.Testing; +using Xunit.Abstractions; + +// ReSharper disable once CheckNamespace +namespace NSubstitute +{ + public static class TestExtensions + { + public static void Wait(this CancellationTokenSource cancellationTokenSource) + { + cancellationTokenSource.Token.WaitHandle.WaitOne(); + } + + public static JsonRpcTestOptions ConfigureForXUnit(this JsonRpcTestOptions jsonRpcTestOptions, ITestOutputHelper outputHelper) + { + return jsonRpcTestOptions + .WithClientLoggerFactory(new TestLoggerFactory(outputHelper, "{Timestamp:yyyy-MM-dd HH:mm:ss} [Client] [{Level}] {Message}{NewLine}{Exception}")) + .WithServerLoggerFactory(new TestLoggerFactory(outputHelper, "{Timestamp:yyyy-MM-dd HH:mm:ss} [Server] [{Level}] {Message}{NewLine}{Exception}")); + } + } +} diff --git a/test/JsonRpc.Tests/AutoNSubstitute/TestLoggerFactory.cs b/test/JsonRpc.Tests/AutoNSubstitute/TestLoggerFactory.cs index 0a977730b..8823edf80 100644 --- a/test/JsonRpc.Tests/AutoNSubstitute/TestLoggerFactory.cs +++ b/test/JsonRpc.Tests/AutoNSubstitute/TestLoggerFactory.cs @@ -1,6 +1,7 @@ using System; using Microsoft.Extensions.Logging; using Serilog; +using Serilog.Events; using Serilog.Extensions.Logging; using Xunit.Abstractions; using ILogger = Microsoft.Extensions.Logging.ILogger; @@ -12,10 +13,11 @@ public class TestLoggerFactory : ILoggerFactory { private readonly SerilogLoggerProvider _loggerProvider; - public TestLoggerFactory(ITestOutputHelper testOutputHelper) + public TestLoggerFactory(ITestOutputHelper testOutputHelper, string outputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level}] {Message}{NewLine}{Exception}", LogEventLevel logEventLevel = LogEventLevel.Debug) { _loggerProvider = new SerilogLoggerProvider( new LoggerConfiguration() + .MinimumLevel.Is(logEventLevel) .WriteTo.TestOutput(testOutputHelper) .CreateLogger() ); diff --git a/test/JsonRpc.Tests/ConnectionTests.cs b/test/JsonRpc.Tests/ConnectionTests.cs index c14b25182..3170cc9bf 100644 --- a/test/JsonRpc.Tests/ConnectionTests.cs +++ b/test/JsonRpc.Tests/ConnectionTests.cs @@ -1,20 +1,132 @@ -using System.IO; +using System; +using System.IO; +using System.IO.Pipes; +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; +using Nerdbank.Streams; using NSubstitute; +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.JsonRpc.Testing; +using Xunit; +using Xunit.Abstractions; namespace JsonRpc.Tests { - public class ConnectionTests + public class JsonRpcServerTests : JsonRpcServerTestBase { - public void Test() + public JsonRpcServerTests(ITestOutputHelper testOutputHelper) : base(new JsonRpcTestOptions().ConfigureForXUnit(testOutputHelper)) { - var streamIn = Substitute.For(); - var streamOut = Substitute.For(); - - //var connection = new Connection( - // streamIn, - // streamOut, - // new SerialRequestProcessIdentifier() - //); + } + + private readonly string _pipeName = Guid.NewGuid().ToString(); + + [Fact] + public async Task Can_Connect_To_Stdio() + { + var (client, server) = await Initialize(clientOptions => { + clientOptions + .WithInput(Console.OpenStandardInput().UsePipeReader()) + .WithOutput(Console.OpenStandardError().UsePipeWriter()); + }, serverOptions => { + serverOptions + .WithInput(Console.OpenStandardInput().UsePipeReader()) + .WithOutput(Console.OpenStandardError().UsePipeWriter()); + }); + } + + [Fact] + public async Task Can_Connect_To_A_Named_Pipe() + { + var serverPipe = new NamedPipeServerStream( + pipeName: _pipeName, + direction: PipeDirection.InOut, + maxNumberOfServerInstances: 1, + transmissionMode: PipeTransmissionMode.Byte, + options: PipeOptions.CurrentUserOnly | PipeOptions.Asynchronous); + var clientPipe = new NamedPipeClientStream( + ".", + _pipeName, + PipeDirection.InOut, + PipeOptions.CurrentUserOnly | PipeOptions.Asynchronous + ); + + var (client, server) = await Initialize(clientOptions => { + clientOptions + .WithInput(clientPipe) + .WithOutput(clientPipe); + }, serverOptions => { + serverOptions + .WithInput(serverPipe) + .WithOutput(serverPipe); + }); + + await Task.WhenAll(clientPipe.ConnectAsync(CancellationToken), serverPipe.WaitForConnectionAsync(CancellationToken)); + } + + [Fact] + public async Task Can_Reconnect_To_A_Named_Pipe() + { + { + var serverPipe = new NamedPipeServerStream( + pipeName: _pipeName, + direction: PipeDirection.InOut, + maxNumberOfServerInstances: 1, + transmissionMode: PipeTransmissionMode.Byte, + options: PipeOptions.CurrentUserOnly | PipeOptions.Asynchronous); + var clientPipe = new NamedPipeClientStream( + ".", + _pipeName, + PipeDirection.InOut, + PipeOptions.CurrentUserOnly | PipeOptions.Asynchronous + ); + + var (client, server) = await Initialize(clientOptions => { + clientOptions + .WithInput(clientPipe) + .WithOutput(clientPipe); + }, serverOptions => { + serverOptions + .WithInput(serverPipe) + .WithOutput(serverPipe); + }); + + await Task.WhenAll(clientPipe.ConnectAsync(CancellationToken), serverPipe.WaitForConnectionAsync(CancellationToken)); + + client.Dispose(); + server.Dispose(); + // + // serverPipe.Dispose(); + // clientPipe.Dispose(); + } + + { + var serverPipe = new NamedPipeServerStream( + pipeName: _pipeName, + direction: PipeDirection.InOut, + maxNumberOfServerInstances: 1, + transmissionMode: PipeTransmissionMode.Byte, + options: PipeOptions.CurrentUserOnly | PipeOptions.Asynchronous); + var clientPipe = new NamedPipeClientStream( + ".", + _pipeName, + PipeDirection.InOut, + PipeOptions.CurrentUserOnly | PipeOptions.Asynchronous + ); + + var (client, server) = await Initialize(clientOptions => { + clientOptions + .WithInput(clientPipe) + .WithOutput(clientPipe); + // clientOptions.RegisterForDisposal(clientPipe); + }, serverOptions => { + serverOptions + .WithInput(serverPipe) + .WithOutput(serverPipe); + // serverOptions.RegisterForDisposal(serverPipe); + }); + + await Task.WhenAll(clientPipe.ConnectAsync(CancellationToken), serverPipe.WaitForConnectionAsync(CancellationToken)); + } } } } diff --git a/test/JsonRpc.Tests/HandlerResolverTests.cs b/test/JsonRpc.Tests/HandlerResolverTests.cs index 16d101df7..67459117a 100644 --- a/test/JsonRpc.Tests/HandlerResolverTests.cs +++ b/test/JsonRpc.Tests/HandlerResolverTests.cs @@ -34,7 +34,7 @@ public interface IInlineJsonRpcNotificationHandler : IJsonRpcNotificationHandler [InlineData(typeof(IInlineJsonRpcNotificationHandler), "notification")] public void Should_Contain_AllDefinedMethods(Type requestHandler, string key) { - var handler = new HandlerCollection(); + var handler = new HandlerCollection(Enumerable.Empty()); handler.Add((IJsonRpcHandler)Substitute.For(new Type[] { requestHandler }, new object[0])); handler._handlers.Should().Contain(x => x.Method == key); } @@ -46,7 +46,7 @@ public void Should_Contain_AllDefinedMethods(Type requestHandler, string key) [InlineData(typeof(IInlineJsonRpcNotificationHandler), "notification", null)] public void Should_Have_CorrectParams(Type requestHandler, string key, Type expected) { - var handler = new HandlerCollection(); + var handler = new HandlerCollection(Enumerable.Empty()); handler.Add((IJsonRpcHandler)Substitute.For(new Type[] { requestHandler }, new object[0])); handler.First(x => x.Method == key).Params.Should().IsSameOrEqualTo(expected); } diff --git a/test/JsonRpc.Tests/InputHandlerTests.cs b/test/JsonRpc.Tests/InputHandlerTests.cs index 9b27632b5..0574fcae9 100644 --- a/test/JsonRpc.Tests/InputHandlerTests.cs +++ b/test/JsonRpc.Tests/InputHandlerTests.cs @@ -1,334 +1,487 @@ using System; +using System.Buffers; using System.IO; +using System.IO.Pipelines; using System.Linq; +using System.Reactive.Linq; +using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; using FluentAssertions; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NSubstitute; using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.JsonRpc.Client; using OmniSharp.Extensions.JsonRpc.Serialization; using OmniSharp.Extensions.JsonRpc.Server; -using OmniSharp.Extensions.JsonRpc.Server.Messages; using Xunit; +using Xunit.Abstractions; using Request = OmniSharp.Extensions.JsonRpc.Server.Request; -using Response = OmniSharp.Extensions.JsonRpc.Client.Response; namespace JsonRpc.Tests { + [Collection("InputHandlers")] public class InputHandlerTests { - private static InputHandler NewHandler( - Stream inputStream, + private readonly TestLoggerFactory _loggerFactory; + private readonly Action _unhandledException = Substitute.For>(); + + public InputHandlerTests(ITestOutputHelper testOutputHelper) + { + _loggerFactory = new TestLoggerFactory(testOutputHelper); + } + + private InputHandler NewHandler( + PipeReader inputStream, IOutputHandler outputHandler, IReceiver receiver, IRequestProcessIdentifier requestProcessIdentifier, IRequestRouter requestRouter, - IResponseRouter responseRouter, - Action action) + ILoggerFactory loggerFactory, + IResponseRouter responseRouter) { - var cts = new CancellationTokenSource(); - if (!System.Diagnostics.Debugger.IsAttached) - cts.CancelAfter(TimeSpan.FromSeconds(5)); - action(cts); - - var handler = new InputHandler( + return new InputHandler( inputStream, outputHandler, receiver, requestProcessIdentifier, requestRouter, responseRouter, - Substitute.For(), - new JsonRpcSerializer(), - null); - handler.Start(); - cts.Wait(); - Task.Delay(10).Wait(); - return handler; + loggerFactory, + _unhandledException, + null, + TimeSpan.FromSeconds(30), + true, + null + ); } [Fact] - public void ShouldPassInRequests() + public async Task Should_Pass_In_Requests() { - var inputStream = new MemoryStream(Encoding.ASCII.GetBytes("Content-Length: 2\r\n\r\n{}")); + var pipe = new Pipe(new PipeOptions()); + var outputHandler = Substitute.For(); - var reciever = Substitute.For(); + var receiver = Substitute.For(); - using (NewHandler( - inputStream, - outputHandler, - reciever, - Substitute.For(), - Substitute.For>(), - Substitute.For(), - cts => { - reciever.When(x => x.IsValid(Arg.Any())) - .Do(x => { cts.Cancel(); }); - })) - { - reciever.Received().IsValid(Arg.Is(x => x.ToString() == "{}")); - } - } + using var handler = NewHandler(pipe.Reader, outputHandler, receiver, + Substitute.For(), Substitute.For>(), + _loggerFactory, Substitute.For()); + await pipe.Writer.WriteAsync(Encoding.UTF8.GetBytes("Content-Length: 2\r\n\r\n{}")); - [Fact] - public void ShouldHaveAThreadName() - { - var threadName = "(untouched)"; - var inputStream = new MemoryStream(Encoding.ASCII.GetBytes("Content-Length: 2\r\n\r\n{}")); - var reciever = Substitute.For(); + var cts = new CancellationTokenSource(); + cts.CancelAfter(TimeSpan.FromSeconds(5)); + var processTask = handler.ProcessInputStream(cts.Token); - using (NewHandler( - inputStream, - Substitute.For(), - reciever, - Substitute.For(), - Substitute.For>(), - Substitute.For(), - cts => { - reciever.When(x => x.IsValid(Arg.Any())) - .Do(x => { - threadName = System.Threading.Thread.CurrentThread.Name; - cts.Cancel(); - }); - })) - { - reciever.Received(); - threadName.Should().Be("ProcessInputStream", - because: "it is easier to find it in the Threads pane by it's name"); - } + await pipe.Writer.CompleteAsync(); + await processTask; + + receiver.Received().IsValid(Arg.Is(x => x.ToString() == "{}")); } [Fact] - public void ShouldPassInUtf8EncodedRequests() + public async Task Should_Handle_Multiple_Requests_At_Once() { - // Note: an ä (ä) is encoded by two bytes, so string-length is 13 and byte-length is 14 - var inputStream = new MemoryStream(Encoding.UTF8.GetBytes("Content-Length: 14\r\n\r\n{\"utf8\": \"ä\"}")); + var pipe = new Pipe(new PipeOptions()); + var outputHandler = Substitute.For(); - var reciever = Substitute.For(); + var receiver = Substitute.For(); + + using var handler = NewHandler(pipe.Reader, outputHandler, receiver, + Substitute.For(), Substitute.For>(), + _loggerFactory, Substitute.For()); + await pipe.Writer.WriteAsync( + Encoding.UTF8.GetBytes("Content-Length: 2\r\n\r\n{}") + .Concat(Encoding.UTF8.GetBytes("Content-Length: 2\r\n\r\n{}")) + .Concat(Encoding.UTF8.GetBytes("Content-Length: 2\r\n\r\n{}")) + .ToArray() + ); - using (NewHandler( - inputStream, - outputHandler, - reciever, - Substitute.For(), - Substitute.For>(), - Substitute.For(), - cts => { - reciever.When(x => x.IsValid(Arg.Any())) - .Do(x => { cts.Cancel(); }); - })) - { - reciever.Received().IsValid(Arg.Is(x => x["utf8"].ToString() == "ä")); - } + var cts = new CancellationTokenSource(); + cts.CancelAfter(TimeSpan.FromSeconds(5)); + var processTask = handler.ProcessInputStream(cts.Token); + + await pipe.Writer.CompleteAsync(); + await processTask; + + receiver.Received(3).IsValid(Arg.Is(x => x.ToString() == "{}")); } [Theory] - // Mörkö - [InlineData("{\"changes\": [{\"uri\": \"file:///M%C3%B6rk%C3%B6.cs\",\"type\": 1}]}")] - // 树 - [InlineData("{\"textDocument\": {\"uri\": \"file://abc/123/%E6%A0%91.cs\"}}")] - public void ShouldPassAdditionalUtf8EncodedReqeusts(string data) + [InlineData( + "Content-Length: 2 \r\nContent-Type: application/json\r\n\r\n{}")] + [InlineData("Content-Type: application/json\r\nContent-Length: 2\r\n\r\n{}")] + [InlineData("Content-Type: application/json\r\nNot-A-Header: really\r\nContent-Length: 2\r\n\r\n{}")] + [InlineData( + "Content-Type: application/json\r\nNot-A-Header: really\r\nContent-Length:2 \r\n\r\n{}")] + [InlineData( + "Content-Type: application/json\r\nNot-A-Header: really\r\nContent-Length: 2\r\n\r\n{}")] + public async Task Should_Handle_Different_Additional_Headers_and_Whitespace(string data) { - var inputStream = - new MemoryStream( - Encoding.UTF8.GetBytes($"Content-Length: {Encoding.UTF8.GetBytes(data).Length}\r\n\r\n{data}")); + var pipe = new Pipe(new PipeOptions()); + var outputHandler = Substitute.For(); - var reciever = Substitute.For(); + var receiver = Substitute.For(); - using (NewHandler( - inputStream, - outputHandler, - reciever, - Substitute.For(), - Substitute.For>(), - Substitute.For(), - cts => { - reciever.When(x => x.IsValid(Arg.Any())) - .Do(x => { cts.Cancel(); }); - })) - { - var calls = reciever.ReceivedCalls(); - var call = calls.Single(); - call.GetMethodInfo().Name.Should().Be("IsValid"); - call.GetArguments()[0].Should().BeAssignableTo(); - var arg = call.GetArguments()[0] as JToken; - arg.ToString().Should().Be(JToken.Parse(data).ToString()); - } + using var handler = NewHandler(pipe.Reader, outputHandler, receiver, + Substitute.For(), Substitute.For>(), + _loggerFactory, Substitute.For()); + await pipe.Writer.WriteAsync(Encoding.UTF8.GetBytes(data)); + + var cts = new CancellationTokenSource(); + cts.CancelAfter(TimeSpan.FromSeconds(5)); + var processTask = handler.ProcessInputStream(cts.Token); + + await pipe.Writer.FlushAsync(); + + await pipe.Writer.CompleteAsync(); + await processTask; + + receiver.Received(1).IsValid(Arg.Is(x => x.ToString() == "{}")); } [Fact] - public void ShouldHandleRequest() + public async Task Should_Handle_Multiple_Requests_Back_To_Back() { - var inputStream = new MemoryStream(Encoding.ASCII.GetBytes("Content-Length: 2\r\n\r\n{}")); + var pipe = new Pipe(new PipeOptions()); + var outputHandler = Substitute.For(); - var reciever = Substitute.For(); - var incomingRequestRouter = Substitute.For>(); + var receiver = Substitute.For(); - var req = new Request(1, "abc", null); - reciever.IsValid(Arg.Any()).Returns(true); - reciever.GetRequests(Arg.Any()) - .Returns(c => (new Renor[] {req}, false)); + using var handler = NewHandler(pipe.Reader, outputHandler, receiver, + Substitute.For(), Substitute.For>(), + _loggerFactory, Substitute.For()); - var response = new Response(1, req); + var cts = new CancellationTokenSource(); + cts.CancelAfter(TimeSpan.FromSeconds(5)); + var processTask = handler.ProcessInputStream(cts.Token); - incomingRequestRouter.RouteRequest(Arg.Any(), req, CancellationToken.None) - .Returns(response); + await pipe.Writer.WriteAsync(Encoding.UTF8.GetBytes("Content-Length: 2\r\n\r\n{}"), cts.Token); + await Task.Delay(20); + await pipe.Writer.WriteAsync(Encoding.UTF8.GetBytes("Content-Length: 2\r\n\r\n{}"), cts.Token); + await Task.Delay(20); + await pipe.Writer.WriteAsync(Encoding.UTF8.GetBytes("Content-Length: 2\r\n\r\n{}"), cts.Token); + await Task.Delay(20); - using (NewHandler( - inputStream, - outputHandler, - reciever, - Substitute.For(), - incomingRequestRouter, - Substitute.For(), - cts => { - outputHandler.When(x => x.Send(Arg.Any(), Arg.Any())) - .Do(x => { cts.Cancel(); }); - })) - { - outputHandler.Received().Send(Arg.Is(x => x == response), Arg.Any()); - } + await pipe.Writer.FlushAsync(cts.Token); + await pipe.Writer.CompleteAsync(); + await processTask; + + receiver.Received(3).IsValid(Arg.Is(x => x.ToString() == "{}")); } [Fact] - public void ShouldHandleError() + public async Task Should_Handle_Multiple_Requests_In_Pieces() { - var inputStream = new MemoryStream(Encoding.ASCII.GetBytes("Content-Length: 2\r\n\r\n{}")); + var pipe = new Pipe(new PipeOptions()); + var outputHandler = Substitute.For(); - var reciever = Substitute.For(); - var incomingRequestRouter = Substitute.For>(); + var receiver = Substitute.For(); - var error = new RpcError(1, new ErrorMessage(1, "abc")); - reciever.IsValid(Arg.Any()).Returns(true); - reciever.GetRequests(Arg.Any()) - .Returns(c => (new Renor[] {error}, false)); + using var handler = NewHandler(pipe.Reader, outputHandler, receiver, + Substitute.For(), Substitute.For>(), + _loggerFactory, Substitute.For()); + var cts = new CancellationTokenSource(); + cts.CancelAfter(TimeSpan.FromSeconds(5)); + var processTask = handler.ProcessInputStream(cts.Token); + + await pipe.Writer.WriteAsync(Encoding.UTF8.GetBytes("Content-Leng")); + await Task.Delay(50); + await pipe.Writer.WriteAsync(Encoding.UTF8.GetBytes("th: 2\r")); + await Task.Delay(50); + await pipe.Writer.WriteAsync(Encoding.UTF8.GetBytes("\n\r\n{}")); + await Task.Delay(50); + await pipe.Writer.WriteAsync(Encoding.UTF8.GetBytes("Cont")); + await Task.Delay(50); + await pipe.Writer.WriteAsync(Encoding.UTF8.GetBytes("ent-Length: 2\r\n\r\n{}")); + await Task.Delay(50); + await pipe.Writer.WriteAsync(Encoding.UTF8.GetBytes("Content-Length: 2\r\n\r\n{")); + await Task.Delay(50); + await pipe.Writer.WriteAsync(Encoding.UTF8.GetBytes("}")); + + await pipe.Writer.FlushAsync(); + await pipe.Writer.CompleteAsync(); + await processTask; + + receiver.Received(3).IsValid(Arg.Is(x => x.ToString() == "{}")); + } - using (NewHandler( - inputStream, - outputHandler, - reciever, - Substitute.For(), - incomingRequestRouter, - Substitute.For(), - cts => { - outputHandler.When(x => x.Send(Arg.Any(), Arg.Any())) - .Do(x => { cts.Cancel(); }); - })) + [Theory] + [InlineData("Content-Length: 894\r\n\r\n{\"edit\":{\"documentChanges\":[{\"textDocument\":{\"version\":1,\"uri\":\"file:///abc/123/d.cs\"},\"edits\":[{\"range\":{\"start\":{\"line\":1,\"character\":1},\"end\":{\"line\":2,\"character\":2}},\"newText\":\"new text\"},{\"range\":{\"start\":{\"line\":3,\"character\":3},\"end\":{\"line\":4,\"character\":4}},\"newText\":\"new text2\"}]},{\"textDocument\":{\"version\":1,\"uri\":\"file:///abc/123/b.cs\"},\"edits\":[{\"range\":{\"start\":{\"line\":1,\"character\":1},\"end\":{\"line\":2,\"character\":2}},\"newText\":\"new text2\"},{\"range\":{\"start\":{\"line\":3,\"character\":3},\"end\":{\"line\":4,\"character\":4}},\"newText\":\"new text3\"}]},{\"kind\":\"create\",\"uri\":\"file:///abc/123/b.cs\",\"options\":{\"overwrite\":true,\"ignoreIfExists\":true}},{\"kind\":\"rename\",\"oldUri\":\"file:///abc/123/b.cs\",\"newUri\":\"file:///abc/123/c.cs\",\"options\":{\"overwrite\":true,\"ignoreIfExists\":true}},{\"kind\":\"delete\",\"uri\":\"file:///abc/123/c.cs\",\"options\":{\"recursive\":false,\"ignoreIfNotExists\":true}}]}}")] + public async Task Should_Handle_Multiple_Chunked_Requests(string content) + { + var pipe = new Pipe(new PipeOptions()); + + var outputHandler = Substitute.For(); + var receiver = Substitute.For(); + + using var handler = NewHandler(pipe.Reader, outputHandler, receiver, + Substitute.For(), Substitute.For>(), + _loggerFactory, Substitute.For()); + + var cts = new CancellationTokenSource(); + cts.CancelAfter(TimeSpan.FromMinutes(2)); + var processTask = handler.ProcessInputStream(cts.Token); + + for (var i = 0; i < content.Length; i += 3) { - outputHandler.Received().Send(Arg.Is(x => x == error), Arg.Any()); + await pipe.Writer.FlushAsync(); + await pipe.Writer.WriteAsync(Encoding.UTF8.GetBytes(content.Substring(i, Math.Min(3, content.Length - i)))); + await Task.Delay(5); } + + await pipe.Writer.FlushAsync(); + await pipe.Writer.CompleteAsync(); + await processTask; + + receiver.Received(1).IsValid(Arg.Any()); } [Fact] - public async Task ShouldHandleNotification() + public async Task Should_Handle_Header_Terminiator_Being_Incomplete() { - var inputStream = new MemoryStream(Encoding.ASCII.GetBytes("Content-Length: 2\r\n\r\n{}")); + var pipe = new Pipe(new PipeOptions(readerScheduler: PipeScheduler.ThreadPool, writerScheduler: PipeScheduler.Inline, useSynchronizationContext:false)); + var outputHandler = Substitute.For(); - var reciever = Substitute.For(); - var incomingRequestRouter = Substitute.For>(); + var receiver = Substitute.For(); - var notification = new Notification("abc", null); - reciever.IsValid(Arg.Any()).Returns(true); - reciever.GetRequests(Arg.Any()) - .Returns(c => (new Renor[] {notification}, false)); + using var handler = NewHandler(pipe.Reader, outputHandler, receiver, + Substitute.For(), Substitute.For>(), + _loggerFactory, Substitute.For()); - using (NewHandler( - inputStream, - outputHandler, - reciever, - Substitute.For(), - incomingRequestRouter, - Substitute.For(), - cts => { - incomingRequestRouter.When(x => x.RouteNotification(Arg.Any(), - Arg.Any(), CancellationToken.None)) - .Do(x => { cts.Cancel(); }); - })) - { - await incomingRequestRouter.Received().RouteNotification(Arg.Any(), notification, - CancellationToken.None); - } + var cts = new CancellationTokenSource(); + cts.CancelAfter(TimeSpan.FromMinutes(5)); + var processTask = handler.ProcessInputStream(cts.Token); + + await pipe.Writer.WriteAsync(Encoding.UTF8.GetBytes("Content-Leng")); + await pipe.Writer.FlushAsync(); + await Task.Delay(50); + await pipe.Writer.WriteAsync(Encoding.UTF8.GetBytes("th: 2\r")); + await pipe.Writer.FlushAsync(); + await Task.Delay(50); + await pipe.Writer.WriteAsync(Encoding.UTF8.GetBytes("\n\r\n{}")); + await pipe.Writer.FlushAsync(); + await Task.Delay(50); + await pipe.Writer.WriteAsync(Encoding.UTF8.GetBytes("Cont")); + await pipe.Writer.FlushAsync(); + await Task.Delay(50); + await pipe.Writer.WriteAsync(Encoding.UTF8.GetBytes("ent-Length: 2\r\n\r\n{}")); + await pipe.Writer.FlushAsync(); + await Task.Delay(50); + await pipe.Writer.WriteAsync(Encoding.UTF8.GetBytes("Content-Length: 2\r\n\r")); + await pipe.Writer.FlushAsync(); + await Task.Delay(50); + await pipe.Writer.WriteAsync(Encoding.UTF8.GetBytes("\n{")); + await pipe.Writer.FlushAsync(); + await Task.Delay(50); + await pipe.Writer.WriteAsync(Encoding.UTF8.GetBytes("}")); + await pipe.Writer.FlushAsync(); + + await pipe.Writer.CompleteAsync(); + await processTask; + + receiver.Received(3).IsValid(Arg.Is(x => x.ToString() == "{}")); } - [Fact] - public void ShouldHandleResponse() + [Theory] + // Mörkö + [InlineData("{\"changes\": [{\"uri\": \"file:///M%C3%B6rk%C3%B6.cs\",\"type\": 1}]}")] + // 树 + [InlineData("{\"textDocument\": {\"uri\": \"file://abc/123/%E6%A0%91.cs\"}}")] + public async Task ShouldPassAdditionalUtf8EncodedRequests(string data) { - var inputStream = new MemoryStream(Encoding.ASCII.GetBytes("Content-Length: 2\r\n\r\n{}")); + var pipe = new Pipe(new PipeOptions()); + + var outputHandler = Substitute.For(); + var receiver = Substitute.For(); + + using var handler = NewHandler(pipe.Reader, outputHandler, receiver, + Substitute.For(), Substitute.For>(), + _loggerFactory, Substitute.For()); + + var cts = new CancellationTokenSource(); + cts.CancelAfter(TimeSpan.FromSeconds(5)); + var processTask = handler.ProcessInputStream(cts.Token); + + await pipe.Writer.WriteAsync( + Encoding.UTF8.GetBytes($"Content-Length: {Encoding.UTF8.GetBytes(data).Length}\r\n\r\n{data}")); + await Task.Yield(); + + await pipe.Writer.FlushAsync(); + await pipe.Writer.CompleteAsync(); + await processTask; + + var calls = receiver.ReceivedCalls(); + var call = calls.Single(); + call.GetMethodInfo().Name.Should().Be("IsValid"); + call.GetArguments()[0].Should().BeAssignableTo(); + var arg = call.GetArguments()[0] as JToken; + arg.ToString().Should().Be(JToken.Parse(data).ToString()); + } + + [Theory] + [ClassData(typeof(JsonRpcLogs))] + public async Task Should_Parse_Logs(string name, Func createPipeReader, ILookup messageTypes) + { + var logger = _loggerFactory.CreateLogger(); + using var scope = logger.BeginScope(name); + + logger.LogInformation("Start"); + + var reader = createPipeReader(); + var receiver = new Receiver(); + var incomingRequestRouter = Substitute.For>(); var outputHandler = Substitute.For(); - var reciever = Substitute.For(); var responseRouter = Substitute.For(); - var response = new OmniSharp.Extensions.JsonRpc.Server.ServerResponse(1L, JToken.Parse("{}")); - reciever.IsValid(Arg.Any()).Returns(true); - reciever.GetRequests(Arg.Any()) - .Returns(c => (new Renor[] {response}, true)); + using var handler = NewHandler(reader, outputHandler, receiver, + new ParallelRequestProcessIdentifier(), + incomingRequestRouter, + _loggerFactory, + responseRouter + ); + + var cts = new CancellationTokenSource(); + cts.CancelAfter(TimeSpan.FromMinutes(1)); + var processTask = handler.ProcessInputStream(cts.Token); - var tcs = new TaskCompletionSource(); - responseRouter.GetRequest(1L).Returns(tcs); + await processTask; - using (NewHandler( - inputStream, - outputHandler, - reciever, - Substitute.For(), - Substitute.For>(), - responseRouter, - cts => { - responseRouter.When(x => x.GetRequest(Arg.Any())) - .Do(x => { cts.CancelAfter(1); }); - })) + foreach (var group in messageTypes) { - responseRouter.Received().GetRequest(1L); - tcs.Task.Result.ToString().Should().Be("{}"); + { + var count = group.Count(x => x == "request"); + await incomingRequestRouter.Received(count).RouteRequest( + Arg.Any(), + Arg.Is(n => group.Key == n.Method), + Arg.Any() + ); + } + + + { + var count = group.Count(x => x == "notification"); + await incomingRequestRouter.Received(count).RouteNotification( + Arg.Any(), + Arg.Is(n => group.Key == n.Method), + Arg.Any() + ); + } } - } - [Fact] - public void ShouldCancelRequest() - { - var inputStream = new MemoryStream(Encoding.ASCII.GetBytes("Content-Length: 2\r\n\r\n{}")); - var outputHandler = Substitute.For(); - var reciever = Substitute.For(); - var incomingRequestRouter = Substitute.For>(); - var requestDescription = Substitute.For(); - requestDescription.Method.Returns("abc"); - var cancelDescription = Substitute.For(); - cancelDescription.Method.Returns(JsonRpcNames.CancelRequest); + logger.LogInformation("End"); - var req = new Request(1, "abc", null); - var cancel = new Notification(JsonRpcNames.CancelRequest, JObject.Parse("{\"id\":1}")); - reciever.IsValid(Arg.Any()).Returns(true); - reciever.GetRequests(Arg.Any()) - .Returns(c => (new Renor[] {req, cancel}, false)); + } - incomingRequestRouter.When(z => z.CancelRequest(Arg.Any())); - incomingRequestRouter.GetDescriptor(cancel).Returns(cancelDescription); - incomingRequestRouter.GetDescriptor(req).Returns(requestDescription); + class JsonRpcLogs : TheoryData, ILookup> + { + public JsonRpcLogs() + { + var assembly = GetType().Assembly; + foreach (var streamName in assembly.GetManifestResourceNames().Where(z => z.EndsWith(".jsrpc"))) + { + var data = GetData(assembly, streamName); + + var msgTypes = data.Select(z => { + if (z.MsgKind.EndsWith("response")) + { + return (type:"response", kind:z.MsgType); + } + + if (z.MsgKind.EndsWith("request")) + { + return (type:"request", kind:z.MsgType); + } + + if (z.MsgKind.EndsWith("notification") && z.MsgType != JsonRpcNames.CancelRequest) + { + return (type: "notification", kind:z.MsgType); + } + + return (type:null, kind:null); + }) + .Where(z => z.type != null) + .ToLookup(z => z.kind, z => z.type); + + Add(streamName, () => CreateReader(data), msgTypes ); + } + } - incomingRequestRouter.RouteRequest(requestDescription, req, CancellationToken.None) - .Returns(new Response(1, req)); + DataItem[] GetData(Assembly assembly, string name) + { + var stream = assembly.GetManifestResourceStream(name); + using var streamReader = new StreamReader(stream); + using var jsonReader = new JsonTextReader(streamReader); + var serializer = new JsonSerializer(); + return serializer.Deserialize(jsonReader); + } - incomingRequestRouter.RouteNotification(cancelDescription, cancel, CancellationToken.None) - .Returns(Task.CompletedTask); + PipeReader CreateReader(DataItem[] data) + { + var outputData = data + .Select(z => { + if (z.MsgKind.EndsWith("response")) + { + return new OutgoingResponse(z.MsgId, z.Arg, new Request(z.MsgId, z.MsgType, JValue.CreateNull())); + } + + if (z.MsgKind.EndsWith("request")) + { + return new OmniSharp.Extensions.JsonRpc.Client.OutgoingRequest() { + Id = z.MsgId, + Method = z.MsgType, + Params = z.Arg + }; + } + + if (z.MsgKind.EndsWith("notification")) + { + return new OmniSharp.Extensions.JsonRpc.Client.OutgoingNotification() { + Method = z.MsgType, + Params = z.Arg + }; + } + + throw new NotSupportedException("unknown message kind " + z.MsgKind); + }); + + var pipeIn = new Pipe(); + + var _serializer = new JsonRpcSerializer(); + + Task.Run(async () => { + foreach (var item in outputData) + { + + var content = _serializer.SerializeObject(item); + var contentBytes = Encoding.UTF8.GetBytes(content).AsMemory(); + + await pipeIn.Writer.WriteAsync( + Encoding.UTF8.GetBytes($"Content-Length: {contentBytes.Length}\r\n\r\n")); + await pipeIn.Writer.WriteAsync(contentBytes); + await pipeIn.Writer.FlushAsync(); + } + + await pipeIn.Writer.CompleteAsync(); + }); + + + return pipeIn.Reader; + } - using (NewHandler( - inputStream, - outputHandler, - reciever, - Substitute.For(), - incomingRequestRouter, - Substitute.For(), - cts => { - outputHandler.When(x => x.Send(Arg.Any(), Arg.Any())) - .Do(x => { cts.Cancel(); }); - })) + class DataItem { - incomingRequestRouter.Received().CancelRequest(1L); - // incomingRequestRouter.Received().RouteNotification(cancelDescription, cancel, CancellationToken.None); + public string Time { get; set; } + public string Msg { get; set; } + public string MsgKind { get; set; } + public string MsgType { get; set; } + public string MsgId { get; set; } + public JToken Arg { get; set; } } } } diff --git a/test/JsonRpc.Tests/IntegrationTests.cs b/test/JsonRpc.Tests/IntegrationTests.cs new file mode 100644 index 000000000..1014bcb02 --- /dev/null +++ b/test/JsonRpc.Tests/IntegrationTests.cs @@ -0,0 +1,183 @@ +using System; +using System.Linq; +using System.Reactive.Subjects; +using System.Threading; +using System.Threading.Tasks; +using FluentAssertions; +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.JsonRpc.Server; +using OmniSharp.Extensions.JsonRpc.Testing; +using Xunit; + +namespace JsonRpc.Tests +{ + public class IntegrationTests : JsonRpcServerTestBase + { + public IntegrationTests() : base(new JsonRpcTestOptions()) + { + } + + class Data + { + public string Value { get; set; } + } + + [Fact] + public async Task Should_Send_and_receive_requests() + { + var (client, server) = await Initialize( + client => { client.OnRequest("myrequest", async () => new Data() {Value = "myresponse"}); }, + server => { server.OnRequest("myrequest", async () => new Data() {Value = string.Join("", "myresponse".Reverse())}); } + ); + + var serverResponse = await client.SendRequest("myrequest").Returning(CancellationToken); + serverResponse.Value.Should().Be("esnopserym"); + + var clientResponse = await server.SendRequest("myrequest").Returning(CancellationToken); + clientResponse.Value.Should().Be("myresponse"); + } + + [Fact] + public async Task Should_Send_and_receive_notifications() + { + var clientNotification = new AsyncSubject(); + var serverNotification = new AsyncSubject(); + var (client, server) = await Initialize( + client => { + client.OnNotification("mynotification", (Data data) => { + clientNotification.OnNext(data); + clientNotification.OnCompleted(); + }); + }, + server => { + server.OnNotification("mynotification", (Data data) => { + serverNotification.OnNext(data); + serverNotification.OnCompleted(); + }); + } + ); + + client.SendNotification("mynotification", new Data() {Value = "myresponse"}); + var serverResponse = await serverNotification; + serverResponse.Value.Should().Be("myresponse"); + + server.SendNotification("mynotification", new Data() {Value = string.Join("", "myresponse".Reverse())}); + var clientResponse = await clientNotification; + clientResponse.Value.Should().Be("esnopserym"); + } + + [Fact] + public async Task Should_Send_and_cancel_requests_immediate() + { + var (client, server) = await Initialize( + client => { + client.OnRequest("myrequest", async (ct) => { + await Task.Delay(TimeSpan.FromMinutes(1), ct); + return new Data() {Value = "myresponse"}; + }); + }, + server => { + server.OnRequest("myrequest", async (ct) => { + await Task.Delay(TimeSpan.FromMinutes(1), ct); + return new Data() {Value = string.Join("", "myresponse".Reverse())}; + }); + } + ); + + var cts = new CancellationTokenSource(); + cts.Cancel(); + + { + Func action = () => client.SendRequest("myrequest").Returning(cts.Token); + await action.Should().ThrowAsync(); + } + + { + Func action = () => server.SendRequest("myrequest").Returning(cts.Token); + await action.Should().ThrowAsync(); + } + } + + [Fact] + public async Task Should_Send_and_cancel_requests_from_otherside() + { + var (client, server) = await Initialize( + client => { + client.OnRequest("myrequest", async (ct) => { + await Task.Delay(TimeSpan.FromMinutes(1), ct); + return new Data() {Value = "myresponse"}; + }); + }, + server => { + server.OnRequest("myrequest", async (ct) => { + await Task.Delay(TimeSpan.FromMinutes(1), ct); + return new Data() {Value = string.Join("", "myresponse".Reverse())}; + }); + } + ); + + { + var cts = new CancellationTokenSource(); + Func action = () => client.SendRequest("myrequest").Returning(cts.Token); + cts.CancelAfter(10); + await action.Should().ThrowAsync(); + } + + { + var cts = new CancellationTokenSource(); + Func action = () => server.SendRequest("myrequest").Returning(cts.Token); + cts.CancelAfter(10); + await action.Should().ThrowAsync(); + } + } + + [Fact] + public async Task Should_Cancel_Parallel_Requests_When_Options_Are_Given() + { + var (client, server) = await Initialize( + client => { + client.OnRequest( + "parallelrequest", + async (ct) => { + await Task.Delay(TimeSpan.FromSeconds(10), ct); + return new Data() {Value = "myresponse"}; + }, + new JsonRpcHandlerOptions() {RequestProcessType = RequestProcessType.Parallel}); + client.OnRequest( + "serialrequest", + async (ct) => new Data() {Value = "myresponse"}, + new JsonRpcHandlerOptions() {RequestProcessType = RequestProcessType.Serial} + ); + }, + server => { + server.OnRequest( + "parallelrequest", + async (ct) => { + await Task.Delay(TimeSpan.FromSeconds(10), ct); + return new Data() {Value = "myresponse"}; + }, + new JsonRpcHandlerOptions() {RequestProcessType = RequestProcessType.Parallel}); + server.OnRequest( + "serialrequest", + async (ct) => new Data() {Value = "myresponse"}, + new JsonRpcHandlerOptions() {RequestProcessType = RequestProcessType.Serial} + ); + } + ); + + { + var task = client.SendRequest("parallelrequest").Returning(CancellationToken); + await client.SendRequest("serialrequest").Returning(CancellationToken); + Func action = () => task; + await action.Should().ThrowAsync(); + } + + { + var task = server.SendRequest("parallelrequest").Returning(CancellationToken); + await server.SendRequest("serialrequest").Returning(CancellationToken); + Func action = () => task; + await action.Should().ThrowAsync(); + } + } + } +} diff --git a/test/JsonRpc.Tests/JsonRpc.Tests.csproj b/test/JsonRpc.Tests/JsonRpc.Tests.csproj index 8e2cba858..4f54cae4b 100644 --- a/test/JsonRpc.Tests/JsonRpc.Tests.csproj +++ b/test/JsonRpc.Tests/JsonRpc.Tests.csproj @@ -6,5 +6,7 @@ + + diff --git a/test/JsonRpc.Tests/MediatorTestsNotificationHandler.cs b/test/JsonRpc.Tests/MediatorTestsNotificationHandler.cs index 9d0052692..31b94292f 100644 --- a/test/JsonRpc.Tests/MediatorTestsNotificationHandler.cs +++ b/test/JsonRpc.Tests/MediatorTestsNotificationHandler.cs @@ -1,3 +1,4 @@ +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; @@ -27,7 +28,7 @@ public async Task ExecutesHandler() { var exitHandler = Substitute.For(); - var collection = new HandlerCollection { exitHandler }; + var collection = new HandlerCollection(Enumerable.Empty()) { exitHandler }; AutoSubstitute.Provide(collection); var router = AutoSubstitute.Resolve(); @@ -35,7 +36,7 @@ public async Task ExecutesHandler() await router.RouteNotification(router.GetDescriptor(notification), notification, CancellationToken.None); - await exitHandler.Received(1).Handle(Arg.Any(), CancellationToken.None); + await exitHandler.Received(1).Handle(Arg.Any(), Arg.Any()); } } diff --git a/test/JsonRpc.Tests/MediatorTestsNotificationHandlerOfT.cs b/test/JsonRpc.Tests/MediatorTestsNotificationHandlerOfT.cs index 26576cc93..3f76721c8 100644 --- a/test/JsonRpc.Tests/MediatorTestsNotificationHandlerOfT.cs +++ b/test/JsonRpc.Tests/MediatorTestsNotificationHandlerOfT.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; using MediatR; @@ -37,7 +38,7 @@ public async Task ExecutesHandler() var cancelRequestHandler = Substitute.For(); var mediator = Substitute.For(); - var collection = new HandlerCollection { cancelRequestHandler }; + var collection = new HandlerCollection(Enumerable.Empty()) { cancelRequestHandler }; AutoSubstitute.Provide(collection); var router = AutoSubstitute.Resolve(); diff --git a/test/JsonRpc.Tests/MediatorTestsRequestHandlerOfTRequest.cs b/test/JsonRpc.Tests/MediatorTestsRequestHandlerOfTRequest.cs index d9577e56f..c89326bd1 100644 --- a/test/JsonRpc.Tests/MediatorTestsRequestHandlerOfTRequest.cs +++ b/test/JsonRpc.Tests/MediatorTestsRequestHandlerOfTRequest.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; using MediatR; @@ -37,7 +38,7 @@ public async Task ExecutesHandler() var executeCommandHandler = Substitute.For(); var mediator = Substitute.For(); - var collection = new HandlerCollection { executeCommandHandler }; + var collection = new HandlerCollection(Enumerable.Empty()) { executeCommandHandler }; AutoSubstitute.Provide(collection); var router = AutoSubstitute.Resolve(); diff --git a/test/JsonRpc.Tests/MediatorTestsRequestHandlerOfTRequestTResponse.cs b/test/JsonRpc.Tests/MediatorTestsRequestHandlerOfTRequestTResponse.cs index f76e401af..e134f55b7 100644 --- a/test/JsonRpc.Tests/MediatorTestsRequestHandlerOfTRequestTResponse.cs +++ b/test/JsonRpc.Tests/MediatorTestsRequestHandlerOfTRequestTResponse.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using MediatR; @@ -47,7 +48,7 @@ public async Task ExecutesHandler() var codeActionHandler = Substitute.For(); var mediator = Substitute.For(); - var collection = new HandlerCollection { codeActionHandler }; + var collection = new HandlerCollection(Enumerable.Empty()) { codeActionHandler }; AutoSubstitute.Provide(collection); var router = AutoSubstitute.Resolve(); diff --git a/test/JsonRpc.Tests/OutputHandlerTests.cs b/test/JsonRpc.Tests/OutputHandlerTests.cs index d83b48e9e..9b17d06a4 100644 --- a/test/JsonRpc.Tests/OutputHandlerTests.cs +++ b/test/JsonRpc.Tests/OutputHandlerTests.cs @@ -1,8 +1,8 @@ using System; using System.IO; -using System.Threading; +using System.IO.Pipelines; +using System.Reactive.Concurrency; using System.Threading.Tasks; -using NSubstitute; using FluentAssertions; using Microsoft.Extensions.Logging.Abstractions; using OmniSharp.Extensions.JsonRpc; @@ -15,146 +15,122 @@ namespace JsonRpc.Tests { public class OutputHandlerTests { - private static (OutputHandler handler, Func wait) NewHandler(Stream Writer, Action action) + private static OutputHandler NewHandler(PipeWriter writer) { - - AppDomain.CurrentDomain.UnhandledException += (sender, args) => { - }; - var cts = new CancellationTokenSource(); - if (!System.Diagnostics.Debugger.IsAttached) - cts.CancelAfter(TimeSpan.FromSeconds(120)); - action(cts); - - var handler = new OutputHandler( - Writer, - new JsonRpcSerializer(), - NullLogger.Instance); - handler.Start(); - return (handler, () => { - cts.Wait(); - return Task.Delay(50); - } - ); + return new OutputHandler(writer, new JsonRpcSerializer(), _ => true, Scheduler.Immediate, NullLogger.Instance); + } + private static OutputHandler NewHandler(PipeWriter writer, Func filter) + { + return new OutputHandler(writer, new JsonRpcSerializer(), filter, Scheduler.Immediate, NullLogger.Instance); } [Fact] public async Task ShouldSerializeResponses() { - var w = Substitute.For(); - var received = ""; - w.CanWrite.Returns(true); - - var (handler, wait) = NewHandler(w, cts => { - w.When(x => x.Write(Arg.Any(), Arg.Any(), Arg.Any())) - .Do(c => { - received = System.Text.Encoding.UTF8.GetString(c.ArgAt(0), 0, c.ArgAt(2)); - cts.Cancel(); - }); - }); - var value = new Response(1, 1, new OmniSharp.Extensions.JsonRpc.Server.Request(1, "a", null)); - - using (handler) - { - - handler.Send(value, CancellationToken.None); - await wait(); - const string send = "Content-Length: 35\r\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":1}"; - received.Should().Be(send); - var b = System.Text.Encoding.UTF8.GetBytes(send); - w.Received().Write(Arg.Any(), 0, b.Length); // can't compare b here, because it is only value-equal and this test tests reference equality - } + var pipe = new Pipe(new PipeOptions()); + using var handler = NewHandler(pipe.Writer); + + var value = new OutgoingResponse(1, 1, new OmniSharp.Extensions.JsonRpc.Server.Request(1, "a", null)); + + + handler.Send(value); + await handler.WriteAndFlush(); + + using var reader = new StreamReader(pipe.Reader.AsStream()); + var received = await reader.ReadToEndAsync(); + + const string send = "Content-Length: 35\r\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":1}"; + received.Should().Be(send); } [Fact] public async Task ShouldSerializeNotifications() { - var w = Substitute.For(); - var received = ""; - w.CanWrite.Returns(true); - - var (handler, wait) = NewHandler(w, cts => { - w.When(x => x.Write(Arg.Any(), Arg.Any(), Arg.Any())) - .Do(c => { - received = System.Text.Encoding.UTF8.GetString(c.ArgAt(0), 0, c.ArgAt(2)); - cts.Cancel(); - }); - }); - var value = new OmniSharp.Extensions.JsonRpc.Client.Notification() { + var pipe = new Pipe(new PipeOptions()); + using var handler = NewHandler(pipe.Writer); + + var value = new OmniSharp.Extensions.JsonRpc.Client.OutgoingNotification() { Method = "method", Params = new object() }; - using (handler) - { - handler.Send(value, CancellationToken.None); - await wait(); - const string send = "Content-Length: 47\r\n\r\n{\"jsonrpc\":\"2.0\",\"method\":\"method\",\"params\":{}}"; - received.Should().Be(send); - var b = System.Text.Encoding.UTF8.GetBytes(send); - w.Received().Write(Arg.Any(), 0, b.Length); // can't compare b here, because it is only value-equal and this test tests reference equality - } + handler.Send(value); + await handler.WriteAndFlush(); + + using var reader = new StreamReader(pipe.Reader.AsStream()); + var received = await reader.ReadToEndAsync(); + + const string send = "Content-Length: 47\r\n\r\n{\"jsonrpc\":\"2.0\",\"method\":\"method\",\"params\":{}}"; + received.Should().Be(send); } [Fact] public async Task ShouldSerializeRequests() { - var w = Substitute.For(); - var received = ""; - w.CanWrite.Returns(true); - - var (handler, wait) = NewHandler(w, cts => { - w.When(x => x.Write(Arg.Any(), Arg.Any(), Arg.Any())) - .Do(c => { - received = System.Text.Encoding.UTF8.GetString(c.ArgAt(0), 0, c.ArgAt(2)); - cts.Cancel(); - }); - }); - var value = new OmniSharp.Extensions.JsonRpc.Client.Request() { + var pipe = new Pipe(new PipeOptions()); + using var handler = NewHandler(pipe.Writer); + + var value = new OmniSharp.Extensions.JsonRpc.Client.OutgoingRequest() { Method = "method", Id = 1, Params = new object(), }; - using (handler) - { - handler.Send(value, CancellationToken.None); - await wait(); - const string send = "Content-Length: 54\r\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"method\",\"params\":{}}"; - received.Should().Be(send); - var b = System.Text.Encoding.UTF8.GetBytes(send); - w.Received().Write(Arg.Any(), 0, b.Length); // can't compare b here, because it is only value-equal and this test tests reference equality - } + handler.Send(value); + await handler.WriteAndFlush(); + + using var reader = new StreamReader(pipe.Reader.AsStream()); + var received = await reader.ReadToEndAsync(); + + const string send = + "Content-Length: 54\r\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"method\",\"params\":{}}"; + received.Should().Be(send); } [Fact] public async Task ShouldSerializeErrors() { - var w = Substitute.For(); - var received = ""; - w.CanWrite.Returns(true); - - var (handler, wait) = NewHandler(w, cts => { - w.When(x => x.Write(Arg.Any(), Arg.Any(), Arg.Any())) - .Do(c => { - received = System.Text.Encoding.UTF8.GetString(c.ArgAt(0), 0, c.ArgAt(2)); - cts.Cancel(); - }); - }); + var pipe = new Pipe(new PipeOptions()); + using var handler = NewHandler(pipe.Writer); + var value = new RpcError(1, new ErrorMessage(1, "something", new object())); - using (handler) - { - handler.Send(value, CancellationToken.None); - await wait(); - const string send = "Content-Length: 75\r\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"error\":{\"code\":1,\"data\":{},\"message\":\"something\"}}"; - received.Should().Be(send); - var b = System.Text.Encoding.UTF8.GetBytes(send); - w.Received().Write(Arg.Any(), 0, b.Length); // can't compare b here, because it is only value-equal and this test tests reference equality - } + handler.Send(value); + await handler.WriteAndFlush(); + + using var reader = new StreamReader(pipe.Reader.AsStream()); + var received = await reader.ReadToEndAsync(); + + const string send = + "Content-Length: 75\r\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"error\":{\"code\":1,\"data\":{},\"message\":\"something\"}}"; + received.Should().Be(send); + } + + [Fact] + public async Task ShouldFilterMessages() + { + var pipe = new Pipe(new PipeOptions()); + using var handler = NewHandler(pipe.Writer, _ => _ is RpcError e && e.Id.Equals(2)); + + var value = new RpcError(1, new ErrorMessage(1, "something", new object())); + var value2 = new RpcError(2, new ErrorMessage(1, "something", new object())); + var value3 = new RpcError(3, new ErrorMessage(1, "something", new object())); + + handler.Send(value); + handler.Send(value2); + handler.Send(value3); + await handler.WriteAndFlush(); + + using var reader = new StreamReader(pipe.Reader.AsStream()); + var received = await reader.ReadToEndAsync(); + + const string send = + "Content-Length: 75\r\n\r\n{\"jsonrpc\":\"2.0\",\"id\":2,\"error\":{\"code\":1,\"data\":{},\"message\":\"something\"}}"; + received.Should().Be(send); } } } diff --git a/test/JsonRpc.Tests/ProcessSchedulerTests.cs b/test/JsonRpc.Tests/ProcessSchedulerTests.cs index 985d1ae73..e5427bc11 100644 --- a/test/JsonRpc.Tests/ProcessSchedulerTests.cs +++ b/test/JsonRpc.Tests/ProcessSchedulerTests.cs @@ -3,10 +3,12 @@ using System.Threading.Tasks; using Xunit; using FluentAssertions; -using System.Collections.Generic; using System.Reactive; +using System.Reactive.Disposables; using System.Reactive.Linq; +using System.Threading; using Microsoft.Reactive.Testing; +using NSubstitute; using OmniSharp.Extensions.JsonRpc; using Xunit.Abstractions; using Xunit.Sdk; @@ -23,12 +25,12 @@ public ProcessSchedulerTests(ITestOutputHelper testOutputHelper) private readonly ITestOutputHelper _testOutputHelper; - class AllRequestProcessTypes : TheoryData + class AllRequestProcessTypes : TheoryData { - public override IEnumerator GetEnumerator() + public AllRequestProcessTypes() { - yield return new object[] {RequestProcessType.Serial}; - yield return new object[] {RequestProcessType.Parallel}; + Add(RequestProcessType.Serial); + Add(RequestProcessType.Parallel); } } @@ -37,21 +39,21 @@ public void ShouldScheduleCompletedTask(RequestProcessType type) { var testScheduler = new TestScheduler(); var testObservable = testScheduler.CreateColdObservable( - OnNext(100, Unit.Default), - OnCompleted(100, Unit.Default) + OnNext(Subscribed, Unit.Default), + OnCompleted(Subscribed, Unit.Default) ); var testObserver = testScheduler.CreateObserver(); - using IScheduler s = new ProcessScheduler(new TestLoggerFactory(_testOutputHelper), null); + using var s = new ProcessScheduler(new TestLoggerFactory(_testOutputHelper), false, null, TimeSpan.FromSeconds(30), testScheduler); - s.Start(); - s.Add(type, "bogus", testObservable.Do(testObserver)); - testScheduler.AdvanceTo(50); + s.Add(type, "bogus", DoStuff(testObservable, testObserver)); + + testScheduler.AdvanceTo(Subscribed/2); testObservable.Subscriptions.Count.Should().Be(1); - testScheduler.AdvanceTo(101); + testScheduler.AdvanceTo(Subscribed + 1); testObservable.Subscriptions.Count.Should().Be(1); testObserver.Messages.Should().Contain(z => z.Value.Kind == NotificationKind.OnNext); @@ -63,16 +65,16 @@ public void ShouldScheduleSerialInOrder() { var testScheduler = new TestScheduler(); var testObservable = testScheduler.CreateColdObservable( - OnNext(100, Unit.Default), - OnCompleted(100, Unit.Default) + OnNext(Subscribed, Unit.Default), + OnCompleted(Subscribed, Unit.Default) ); var testObserver = testScheduler.CreateObserver(); - using IScheduler s = new ProcessScheduler(new TestLoggerFactory(_testOutputHelper), null); + using var s = new ProcessScheduler(new TestLoggerFactory(_testOutputHelper), false, null, TimeSpan.FromSeconds(30), testScheduler); + - s.Start(); for (var i = 0; i < 8; i++) - s.Add(RequestProcessType.Serial, "bogus", testObservable.Do(testObserver)); + s.Add(RequestProcessType.Serial, "bogus", DoStuff(testObservable, testObserver)); testScheduler.Start(); @@ -80,14 +82,14 @@ public void ShouldScheduleSerialInOrder() testObserver.Messages .Where(z => z.Value.Kind != NotificationKind.OnCompleted).Should() .ContainInOrder( - OnNext(100, Unit.Default), - OnNext(200, Unit.Default), - OnNext(300, Unit.Default), - OnNext(400, Unit.Default), - OnNext(500, Unit.Default), - OnNext(600, Unit.Default), - OnNext(700, Unit.Default), - OnNext(800, Unit.Default) + OnNext(Subscribed, Unit.Default), + OnNext(Subscribed * 2, Unit.Default), + OnNext(Subscribed * 3, Unit.Default), + OnNext(Subscribed * 4, Unit.Default), + OnNext(Subscribed * 5, Unit.Default), + OnNext(Subscribed * 6, Unit.Default), + OnNext(Subscribed * 7, Unit.Default), + OnNext(Subscribed * 8, Unit.Default) ); } @@ -96,16 +98,15 @@ public void ShouldScheduleParallelInParallel() { var testScheduler = new TestScheduler(); var testObservable = testScheduler.CreateColdObservable( - OnNext(100, Unit.Default), - OnCompleted(100, Unit.Default) + OnNext(Subscribed, Unit.Default), + OnCompleted(Subscribed, Unit.Default) ); var testObserver = testScheduler.CreateObserver(); - using IScheduler s = new ProcessScheduler(new TestLoggerFactory(_testOutputHelper), null); + using var s = new ProcessScheduler(new TestLoggerFactory(_testOutputHelper), false, null, TimeSpan.FromSeconds(30), testScheduler); - s.Start(); for (var i = 0; i < 8; i++) - s.Add(RequestProcessType.Parallel, "bogus", testObservable.Do(testObserver)); + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); testScheduler.Start(); @@ -113,14 +114,14 @@ public void ShouldScheduleParallelInParallel() testObserver.Messages .Where(z => z.Value.Kind != NotificationKind.OnCompleted).Should() .ContainInOrder( - OnNext(100, Unit.Default), - OnNext(100, Unit.Default), - OnNext(100, Unit.Default), - OnNext(100, Unit.Default), - OnNext(100, Unit.Default), - OnNext(100, Unit.Default), - OnNext(100, Unit.Default), - OnNext(100, Unit.Default) + OnNext(Subscribed, Unit.Default), + OnNext(Subscribed, Unit.Default), + OnNext(Subscribed, Unit.Default), + OnNext(Subscribed, Unit.Default), + OnNext(Subscribed, Unit.Default), + OnNext(Subscribed, Unit.Default), + OnNext(Subscribed, Unit.Default), + OnNext(Subscribed, Unit.Default) ); } @@ -129,22 +130,21 @@ public void ShouldScheduleMixed() { var testScheduler = new TestScheduler(); var testObservable = testScheduler.CreateColdObservable( - OnNext(100, Unit.Default), - OnCompleted(100, Unit.Default) + OnNext(Subscribed, Unit.Default), + OnCompleted(Subscribed, Unit.Default) ); var testObserver = testScheduler.CreateObserver(); - using IScheduler s = new ProcessScheduler(new TestLoggerFactory(_testOutputHelper), null); + using var s = new ProcessScheduler(new TestLoggerFactory(_testOutputHelper), false, null, TimeSpan.FromSeconds(30), testScheduler); - s.Start(); - s.Add(RequestProcessType.Parallel, "bogus", testObservable.Do(testObserver)); - s.Add(RequestProcessType.Parallel, "bogus", testObservable.Do(testObserver)); - s.Add(RequestProcessType.Parallel, "bogus", testObservable.Do(testObserver)); - s.Add(RequestProcessType.Parallel, "bogus", testObservable.Do(testObserver)); - s.Add(RequestProcessType.Serial, "bogus", testObservable.Do(testObserver)); - s.Add(RequestProcessType.Parallel, "bogus", testObservable.Do(testObserver)); - s.Add(RequestProcessType.Parallel, "bogus", testObservable.Do(testObserver)); - s.Add(RequestProcessType.Serial, "bogus", testObservable.Do(testObserver)); + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Serial, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Serial, "bogus", DoStuff(testObservable, testObserver)); testScheduler.Start(); @@ -152,14 +152,52 @@ public void ShouldScheduleMixed() testObserver.Messages .Where(z => z.Value.Kind != NotificationKind.OnCompleted).Should() .ContainInOrder( - OnNext(100, Unit.Default), - OnNext(100, Unit.Default), - OnNext(100, Unit.Default), - OnNext(100, Unit.Default), - OnNext(200, Unit.Default), - OnNext(300, Unit.Default), - OnNext(300, Unit.Default), - OnNext(400, Unit.Default) + OnNext(Subscribed, Unit.Default), + OnNext(Subscribed, Unit.Default), + OnNext(Subscribed, Unit.Default), + OnNext(Subscribed, Unit.Default), + OnNext(Subscribed * 2, Unit.Default), + OnNext(Subscribed * 3, Unit.Default), + OnNext(Subscribed * 3, Unit.Default), + OnNext(Subscribed * 4, Unit.Default) + ); + } + + [Fact] + public void ShouldScheduleMixed_WithContentModified() + { + var testScheduler = new TestScheduler(); + var testObservable = testScheduler.CreateColdObservable( + OnNext(Subscribed, Unit.Default), + OnCompleted(Subscribed, Unit.Default) + ); + var testObserver = testScheduler.CreateObserver(); + + using var s = new ProcessScheduler(new TestLoggerFactory(_testOutputHelper), true, null, TimeSpan.FromSeconds(30), testScheduler); + + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Serial, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Serial, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + + testScheduler.Start(); + + testObservable.Subscriptions.Count.Should().Be(11); + testObserver.Messages + .Where(z => z.Value.Kind != NotificationKind.OnCompleted).Should() + .ContainInOrder( + OnNext(Subscribed, Unit.Default), + OnNext(Subscribed * 2, Unit.Default), + OnNext(Subscribed * 3, Unit.Default), + OnNext(Subscribed * 3, Unit.Default), + OnNext(Subscribed * 3, Unit.Default) ); } @@ -168,18 +206,18 @@ public void ShouldScheduleSerial() { var testScheduler = new TestScheduler(); var testObservable = testScheduler.CreateColdObservable( - OnNext(100, Unit.Default), - OnCompleted(100, Unit.Default) + OnNext(Subscribed, Unit.Default), + OnCompleted(Subscribed, Unit.Default) ); var testObserver = testScheduler.CreateObserver(); - using IScheduler s = new ProcessScheduler(new TestLoggerFactory(_testOutputHelper), null); + using var s = new ProcessScheduler(new TestLoggerFactory(_testOutputHelper), false, null, TimeSpan.FromSeconds(30), testScheduler); + - s.Start(); - s.Add(RequestProcessType.Parallel, "bogus", testObservable.Do(testObserver)); - s.Add(RequestProcessType.Serial, "bogus", testObservable.Do(testObserver)); - s.Add(RequestProcessType.Serial, "bogus", testObservable.Do(testObserver)); - s.Add(RequestProcessType.Serial, "bogus", testObservable.Do(testObserver)); + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Serial, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Serial, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Serial, "bogus", DoStuff(testObservable, testObserver)); testScheduler.Start(); @@ -187,10 +225,10 @@ public void ShouldScheduleSerial() testObserver.Messages .Where(z => z.Value.Kind != NotificationKind.OnCompleted).Should() .ContainInOrder( - OnNext(100, Unit.Default), - OnNext(200, Unit.Default), - OnNext(300, Unit.Default), - OnNext(400, Unit.Default) + OnNext(Subscribed, Unit.Default), + OnNext(Subscribed * 2, Unit.Default), + OnNext(Subscribed * 3, Unit.Default), + OnNext(Subscribed * 4, Unit.Default) ); } @@ -199,22 +237,22 @@ public void ShouldScheduleWithConcurrency() { var testScheduler = new TestScheduler(); var testObservable = testScheduler.CreateColdObservable( - OnNext(100, Unit.Default), - OnCompleted(100, Unit.Default) + OnNext(Subscribed, Unit.Default), + OnCompleted(Subscribed, Unit.Default) ); var testObserver = testScheduler.CreateObserver(); - using IScheduler s = new ProcessScheduler(new TestLoggerFactory(_testOutputHelper), 3); + using var s = new ProcessScheduler(new TestLoggerFactory(_testOutputHelper), false, 3, TimeSpan.FromSeconds(30), testScheduler); - s.Start(); - s.Add(RequestProcessType.Parallel, "bogus", testObservable.Do(testObserver)); - s.Add(RequestProcessType.Parallel, "bogus", testObservable.Do(testObserver)); - s.Add(RequestProcessType.Parallel, "bogus", testObservable.Do(testObserver)); - s.Add(RequestProcessType.Parallel, "bogus", testObservable.Do(testObserver)); - s.Add(RequestProcessType.Serial, "bogus", testObservable.Do(testObserver)); - s.Add(RequestProcessType.Parallel, "bogus", testObservable.Do(testObserver)); - s.Add(RequestProcessType.Parallel, "bogus", testObservable.Do(testObserver)); - s.Add(RequestProcessType.Serial, "bogus", testObservable.Do(testObserver)); + + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Serial, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Serial, "bogus", DoStuff(testObservable, testObserver)); testScheduler.Start(); @@ -222,14 +260,52 @@ public void ShouldScheduleWithConcurrency() testObserver.Messages .Where(z => z.Value.Kind != NotificationKind.OnCompleted).Should() .ContainInOrder( - OnNext(100, Unit.Default), - OnNext(100, Unit.Default), - OnNext(100, Unit.Default), - OnNext(200, Unit.Default), - OnNext(300, Unit.Default), - OnNext(400, Unit.Default), - OnNext(400, Unit.Default), - OnNext(500, Unit.Default) + OnNext(Subscribed, Unit.Default), + OnNext(Subscribed, Unit.Default), + OnNext(Subscribed, Unit.Default), + OnNext(Subscribed * 2, Unit.Default), + OnNext(Subscribed * 3, Unit.Default), + OnNext(Subscribed * 4, Unit.Default), + OnNext(Subscribed * 4, Unit.Default), + OnNext(Subscribed * 5, Unit.Default) + ); + } + + [Fact] + public void ShouldScheduleWithConcurrency_WithContentModified() + { + var testScheduler = new TestScheduler(); + var testObservable = testScheduler.CreateColdObservable( + OnNext(Subscribed, Unit.Default), + OnCompleted(Subscribed, Unit.Default) + ); + var testObserver = testScheduler.CreateObserver(); + + using var s = new ProcessScheduler(new TestLoggerFactory(_testOutputHelper), true, 3, TimeSpan.FromSeconds(30), testScheduler); + + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Serial, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Serial, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Parallel, "bogus", DoStuff(testObservable, testObserver)); + + testScheduler.Start(); + + testObservable.Subscriptions.Count.Should().Be(11); + testObserver.Messages + .Where(z => z.Value.Kind != NotificationKind.OnCompleted).Should() + .ContainInOrder( + OnNext(Subscribed, Unit.Default), + OnNext(Subscribed * 2, Unit.Default), + OnNext(Subscribed * 3, Unit.Default), + OnNext(Subscribed * 3, Unit.Default), + OnNext(Subscribed * 3, Unit.Default) ); } @@ -238,31 +314,31 @@ public void Should_Handle_Cancelled_Tasks() { var testScheduler = new TestScheduler(); var testObservable = testScheduler.CreateColdObservable( - OnNext(100, Unit.Default), - OnCompleted(100, Unit.Default) + OnNext(Subscribed, Unit.Default), + OnCompleted(Subscribed, Unit.Default) ); var errorObservable = testScheduler.CreateColdObservable( - OnError(100, new TaskCanceledException(), Unit.Default) + OnError(Subscribed, new TaskCanceledException(), Unit.Default) ); var testObserver = testScheduler.CreateObserver(); - using IScheduler s = new ProcessScheduler(new TestLoggerFactory(_testOutputHelper), null); + using var s = new ProcessScheduler(new TestLoggerFactory(_testOutputHelper), false, null, TimeSpan.FromSeconds(30), testScheduler); - s.Start(); - s.Add(RequestProcessType.Serial, "bogus", testObservable.Do(testObserver)); - s.Add(RequestProcessType.Serial, "somethingelse", errorObservable.Do(testObserver)); - s.Add(RequestProcessType.Serial, "bogus", testObservable.Do(testObserver)); + s.Add(RequestProcessType.Serial, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Serial, "somethingelse", DoStuff(errorObservable, testObserver)); + s.Add(RequestProcessType.Serial, "bogus", DoStuff(testObservable, testObserver)); testScheduler.Start(); testObservable.Subscriptions.Count.Should().Be(2); + errorObservable.Subscriptions.Should().HaveCount(1); var messages = testObserver.Messages .Where(z => z.Value.Kind != NotificationKind.OnCompleted) .ToArray(); - messages.Should().Contain(x => x.Value.Kind == NotificationKind.OnNext && x.Time == 100); - messages.Should().Contain(x => x.Value.Kind == NotificationKind.OnError && x.Time == 200 && x.Value.Exception is OperationCanceledException); - messages.Should().Contain(x => x.Value.Kind == NotificationKind.OnNext && x.Time == 300); + messages.Should().Contain(x => x.Value.Kind == NotificationKind.OnNext && x.Time == Subscribed); + messages.Should().Contain(x => x.Value.Kind == NotificationKind.OnError && x.Time == Subscribed * 2 && x.Value.Exception is OperationCanceledException); + messages.Should().Contain(x => x.Value.Kind == NotificationKind.OnNext && x.Time == Subscribed * 3); } [Fact] @@ -270,32 +346,36 @@ public void Should_Handle_Exceptions_Tasks() { var testScheduler = new TestScheduler(); var testObservable = testScheduler.CreateColdObservable( - OnNext(100, Unit.Default), - OnCompleted(100, Unit.Default) + OnNext(Subscribed, Unit.Default), + OnCompleted(Subscribed, Unit.Default) ); var errorObservable = testScheduler.CreateColdObservable( - OnError(100, new NotSameException(), Unit.Default) + OnError(Subscribed, new NotSameException(), Unit.Default) ); var testObserver = testScheduler.CreateObserver(); - using IScheduler s = new ProcessScheduler(new TestLoggerFactory(_testOutputHelper), null); + using var s = new ProcessScheduler(new TestLoggerFactory(_testOutputHelper), false, null, TimeSpan.FromSeconds(30), testScheduler); - s.Start(); - s.Add(RequestProcessType.Serial, "bogus", testObservable.Do(testObserver)); - s.Add(RequestProcessType.Serial, "somethingelse", errorObservable.Do(testObserver)); - s.Add(RequestProcessType.Serial, "bogus", testObservable.Do(testObserver)); + s.Add(RequestProcessType.Serial, "bogus", DoStuff(testObservable, testObserver)); + s.Add(RequestProcessType.Serial, "somethingelse", DoStuff(errorObservable, testObserver)); + s.Add(RequestProcessType.Serial, "bogus", DoStuff(testObservable, testObserver)); testScheduler.Start(); testObservable.Subscriptions.Count.Should().Be(2); + errorObservable.Subscriptions.Should().HaveCount(1); var messages = testObserver.Messages .Where(z => z.Value.Kind != NotificationKind.OnCompleted) .ToArray(); - messages.Should().Contain(x => x.Value.Kind == NotificationKind.OnNext && x.Time == 100); - messages.Should().Contain(x => x.Value.Kind == NotificationKind.OnError && x.Time == 200 && x.Value.Exception is NotSameException); - messages.Should().Contain(x => x.Value.Kind == NotificationKind.OnNext && x.Time == 300); + messages.Should().Contain(x => x.Value.Kind == NotificationKind.OnNext && x.Time == Subscribed); + messages.Should().Contain(x => x.Value.Kind == NotificationKind.OnError && x.Time == Subscribed * 2 && x.Value.Exception is NotSameException); + messages.Should().Contain(x => x.Value.Kind == NotificationKind.OnNext && x.Time == Subscribed * 3); + } + private static SchedulerDelegate DoStuff(IObservable testObservable, IObserver testObserver) + { + return (contentModifiedToken, scheduler) => testObservable.Amb(contentModifiedToken).Do(testObserver); } } } diff --git a/test/JsonRpc.Tests/RequestRouterTests.cs b/test/JsonRpc.Tests/RequestRouterTests.cs index 342687004..77c602816 100644 --- a/test/JsonRpc.Tests/RequestRouterTests.cs +++ b/test/JsonRpc.Tests/RequestRouterTests.cs @@ -1,77 +1,92 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Reactive.Disposables; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using NSubstitute; using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.JsonRpc.Serialization; using OmniSharp.Extensions.JsonRpc.Server; using Xunit; using Xunit.Abstractions; -using System.Reactive.Disposables; -using OmniSharp.Extensions.JsonRpc.Serialization; -namespace Lsp.Tests +namespace JsonRpc.Tests { - public class TestLanguageServerRegistry : IJsonRpcHandlerRegistry + public class TestLanguageServerRegistry : JsonRpcCommonMethodsBase, IJsonRpcServerRegistry { private List Handlers { get; set; } = new List(); private List<(string name, IJsonRpcHandler handler)> NamedHandlers { get; set; } = new List<(string name, IJsonRpcHandler handler)>(); - private List<(string name, Func handlerFunc)> NamedServiceHandlers { get; set; } = new List<(string name, Func handlerFunc)>(); + + private List<(string name, Func handlerFunc)> NamedServiceHandlers { get; set; } = + new List<(string name, Func handlerFunc)>(); public TestLanguageServerRegistry() { } - public IDisposable AddHandler(string method, IJsonRpcHandler handler) + public override IJsonRpcServerRegistry AddHandler(string method, IJsonRpcHandler handler, JsonRpcHandlerOptions options = null) { NamedHandlers.Add((method, handler)); - return Disposable.Empty; + return this; } - public IDisposable AddHandler() where T : IJsonRpcHandler + public override IJsonRpcServerRegistry AddHandler(THandler handler, JsonRpcHandlerOptions options = null) => throw new NotImplementedException(); + + public override IJsonRpcServerRegistry AddHandler(JsonRpcHandlerOptions options) { throw new NotImplementedException(); } - public IDisposable AddHandler(string method, Func handlerFunc) + public override IJsonRpcServerRegistry AddHandler(string method, JsonRpcHandlerOptions options = null) => throw new NotImplementedException(); + + public override IJsonRpcServerRegistry AddHandler(Type type, JsonRpcHandlerOptions options = null) => throw new NotImplementedException(); + + public override IJsonRpcServerRegistry AddHandler(string method, Type type, JsonRpcHandlerOptions options = null) => throw new NotImplementedException(); + + public override IJsonRpcServerRegistry AddHandler(string method, Func handlerFunc, JsonRpcHandlerOptions options = null) { NamedServiceHandlers.Add((method, handlerFunc)); - return Disposable.Empty; + return this; } - public IDisposable AddHandlers(params IJsonRpcHandler[] handlers) + public override IJsonRpcServerRegistry AddHandlers(params IJsonRpcHandler[] handlers) { Handlers.AddRange(handlers); - return Disposable.Empty; + return this; } - public void Populate(HandlerCollection collection, IServiceProvider serviceProvider) + public override IJsonRpcServerRegistry AddHandler(Func handlerFunc, JsonRpcHandlerOptions options = null) => throw new NotImplementedException(); + + public void Populate(HandlerCollection collection, IServiceProvider serviceProvider, JsonRpcHandlerOptions options = null) { collection.Add(Handlers.ToArray()); foreach (var (name, handler) in NamedHandlers) { - collection.Add(name, handler); + collection.Add(name, handler, options); } + foreach (var (name, handlerFunc) in NamedServiceHandlers) { - collection.Add(name, handlerFunc(serviceProvider)); + collection.Add(name, handlerFunc(serviceProvider), options); } } } + public class RequestRouterTests : AutoTestBase { public RequestRouterTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { Services - .AddJsonRpcMediatR(new[] { typeof(RequestRouterTests).Assembly }) + .AddJsonRpcMediatR(new[] {typeof(RequestRouterTests).Assembly}) .AddSingleton(new JsonRpcSerializer()); } [Fact] public async Task ShouldRoute_CustomRequestResponse() { - var collection = new HandlerCollection() { }; + var collection = new HandlerCollection(Enumerable.Empty()) { }; var registry = new TestLanguageServerRegistry(); AutoSubstitute.Provide(collection); AutoSubstitute.Provide>(collection); @@ -91,7 +106,7 @@ public async Task ShouldRoute_CustomRequestResponse() [Fact] public async Task ShouldRoute_CustomRequest() { - var collection = new HandlerCollection() { }; + var collection = new HandlerCollection(Enumerable.Empty()) { }; var registry = new TestLanguageServerRegistry(); AutoSubstitute.Provide(collection); AutoSubstitute.Provide>(collection); @@ -111,7 +126,7 @@ public async Task ShouldRoute_CustomRequest() [Fact] public async Task ShouldRoute_CustomNotification() { - var collection = new HandlerCollection() { }; + var collection = new HandlerCollection(Enumerable.Empty()) { }; var registry = new TestLanguageServerRegistry(); AutoSubstitute.Provide(collection); AutoSubstitute.Provide>(collection); @@ -130,7 +145,7 @@ public async Task ShouldRoute_CustomNotification() [Fact] public async Task ShouldRoute_CustomEmptyNotification() { - var collection = new HandlerCollection() { }; + var collection = new HandlerCollection(Enumerable.Empty()) { }; var registry = new TestLanguageServerRegistry(); AutoSubstitute.Provide(collection); AutoSubstitute.Provide>(collection); diff --git a/test/JsonRpc.Tests/ResponseRouterTests.cs b/test/JsonRpc.Tests/ResponseRouterTests.cs index 22dcb9316..c8e3f7366 100644 --- a/test/JsonRpc.Tests/ResponseRouterTests.cs +++ b/test/JsonRpc.Tests/ResponseRouterTests.cs @@ -9,9 +9,8 @@ using OmniSharp.Extensions.JsonRpc.Client; using OmniSharp.Extensions.JsonRpc.Serialization; using Xunit; -using Notification = OmniSharp.Extensions.JsonRpc.Client.Notification; -namespace Lsp.Tests +namespace JsonRpc.Tests { public class ResponseRouterTests { @@ -22,16 +21,16 @@ public async Task WorksWithResultType() var router = new ResponseRouter(outputHandler, new JsonRpcSerializer()); outputHandler - .When(x => x.Send(Arg.Is(x => x.GetType() == typeof(Request)), Arg.Any())) + .When(x => x.Send(Arg.Is(x => x.GetType() == typeof(OutgoingRequest)))) .Do(call => { - var tcs = router.GetRequest((long) call.Arg().Id); + var (method, tcs) = router.GetRequest((long) call.Arg().Id); tcs.SetResult(new JObject()); }); var response = await router.SendRequest(new ItemParams(), CancellationToken.None); - var request = outputHandler.ReceivedCalls().Single().GetArguments()[0] as Request; + var request = outputHandler.ReceivedCalls().Single().GetArguments()[0] as OutgoingRequest; request.Method.Should().Be("abcd"); response.Should().NotBeNull(); @@ -45,16 +44,16 @@ public async Task WorksWithUnitType() var router = new ResponseRouter(outputHandler, new JsonRpcSerializer()); outputHandler - .When(x => x.Send(Arg.Is(x => x.GetType() == typeof(Request)), Arg.Any())) + .When(x => x.Send(Arg.Is(x => x.GetType() == typeof(OutgoingRequest)))) .Do(call => { - var tcs = router.GetRequest((long) call.Arg().Id); + var (method, tcs) = router.GetRequest((long) call.Arg().Id); tcs.SetResult(new JObject()); }); await router.SendRequest(new UnitParams(), CancellationToken.None); - var request = outputHandler.ReceivedCalls().Single().GetArguments()[0] as Request; + var request = outputHandler.ReceivedCalls().Single().GetArguments()[0] as OutgoingRequest; request.Method.Should().Be("unit"); } @@ -66,7 +65,7 @@ public async Task WorksWithNotification() router.SendNotification(new NotificationParams()); - var request = outputHandler.ReceivedCalls().Single().GetArguments()[0] as Notification; + var request = outputHandler.ReceivedCalls().Single().GetArguments()[0] as OutgoingNotification; request.Method.Should().Be("notification"); } diff --git a/test/JsonRpc.Tests/Server/DebugAdapterSpecifictionRecieverTests.cs b/test/JsonRpc.Tests/Server/DebugAdapterSpecifictionRecieverTests.cs deleted file mode 100644 index 655392594..000000000 --- a/test/JsonRpc.Tests/Server/DebugAdapterSpecifictionRecieverTests.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using FluentAssertions; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using OmniSharp.Extensions.JsonRpc; -using OmniSharp.Extensions.JsonRpc.Server; -using OmniSharp.Extensions.JsonRpc.Server.Messages; -using Xunit; - -namespace JsonRpc.Tests.Server -{ - public class DebugAdapterSpecifictionRecieverTests - { - [Theory] - [ClassData(typeof(SpecificationMessages))] - public void ShouldRespond_AsExpected(string json, Renor[] request) - { - var reciever = new DapReceiver(); - var (requests, _) = reciever.GetRequests(JToken.Parse(json)); - var result = requests.ToArray(); - request.Length.Should().Be(result.Length); - - for (var i = 0; i < request.Length; i++) - { - var r = request[i]; - var response = result[i]; - - JsonConvert.SerializeObject(response) - .Should().Be(JsonConvert.SerializeObject(r)); - } - } - - class SpecificationMessages : TheoryData - { - public override IEnumerable> GetValues() - { - yield return ( - @"{""seq"": ""0"", ""type"": ""request"", ""command"": ""attach"", ""arguments"": { ""__restart"": 3 }}", - new Renor[] - { - new Request(0, "attach", new JObject() { { "__restart", 3 } }) - } - ); - - yield return ( - @"{""seq"": ""1"", ""type"": ""request"", ""command"": ""attach""}", - new Renor[] - { - new Request(1, "attach", new JObject()) - } - ); - - yield return ( - @"{""seq"": ""0"", ""type"": ""event"", ""event"": ""breakpoint"", ""body"": { ""reason"": ""new"" }}", - new Renor[] - { - new Notification("breakpoint", new JObject() { { "reason", "new" } }), - } - ); - - yield return ( - @"{""seq"": ""1"", ""type"": ""event"", ""event"": ""breakpoint""}", - new Renor[] - { - new Notification("breakpoint", null) - } - ); - - yield return ( - @"{""seq"": ""1"", ""type"": ""response"", ""request_seq"": 3, ""success"": true, ""command"": ""attach"", ""body"": { }}", - new Renor[] - { - new ServerResponse(3, new JObject()), - } - ); - - yield return ( - @"{""seq"": ""1"", ""type"": ""response"", ""request_seq"": 3, ""success"": true, ""command"": ""attach"", ""body"": null}", - new Renor[] - { - new ServerResponse(3, null), - } - ); - - yield return ( - @"{""seq"": ""1"", ""type"": ""response"", ""request_seq"": 3, ""success"": false, ""command"": ""attach"", ""body"": { }}", - new Renor[] - { - new ServerError(3, new JObject()), - } - ); - - yield return ( - @"{""seq"": ""1"", ""type"": ""response"", ""request_seq"": 3, ""success"": false, ""command"": ""attach"", ""body"": null}", - new Renor[] - { - new ServerError(3, null), - } - ); - - yield return ( - @"[1]", - new Renor[] - { - new InvalidRequest("Not an object") - }); - } - } - - [Theory] - [ClassData(typeof(InvalidMessages))] - public void Should_ValidateInvalidMessages(string json, bool expected) - { - var reciever = new DapReceiver(); - var result = reciever.IsValid(JToken.Parse(json)); - result.Should().Be(expected); - } - - class InvalidMessages : TheoryData - { - public override IEnumerable> GetValues() - { - yield return (@"[]", false); - yield return (@"""""", false); - yield return (@"1", false); - yield return (@"true", false); - yield return (@"{}", true); - } - } - } -} diff --git a/test/JsonRpc.Tests/Server/SpecifictionIdTests.cs b/test/JsonRpc.Tests/Server/SpecifictionIdTests.cs index dc4a11bc5..14bb27ae0 100644 --- a/test/JsonRpc.Tests/Server/SpecifictionIdTests.cs +++ b/test/JsonRpc.Tests/Server/SpecifictionIdTests.cs @@ -1,10 +1,14 @@ using System; -using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using FluentAssertions; using Newtonsoft.Json.Linq; +using NSubstitute; using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.JsonRpc.Server; +using OmniSharp.Extensions.JsonRpc.Testing; using Xunit; +using Xunit.Abstractions; namespace JsonRpc.Tests.Server { @@ -14,8 +18,8 @@ public class SpecifictionIdTests [ClassData(typeof(SimpleTestMessages))] public void ShouldParse_SimpleMessages(string message, Type outputType, object expectedResult) { - var reciever = new Receiver(); - var (requests, _) = reciever.GetRequests(JToken.Parse(message)); + var receiver = new Receiver(); + var (requests, _) = receiver.GetRequests(JToken.Parse(message)); var result = requests.Single().Request; result.Id.Should().Be(expectedResult); @@ -27,21 +31,49 @@ public void ShouldParse_SimpleMessages(string message, Type outputType, object e class SimpleTestMessages : TheoryData { - public override IEnumerable> GetValues() + public SimpleTestMessages() { - yield return ( + Add ( @"{ ""jsonrpc"": ""2.0"", ""method"": ""method1"", ""id"": ""canbestring"" }", typeof(string), "canbestring" as object); - yield return ( + Add ( @"{ ""jsonrpc"": ""2.0"", ""method"": ""method1"", ""id"": 12345 }", typeof(long), 12345L as object); - yield return ( + Add ( @"{ ""jsonrpc"": ""2.0"", ""method"": ""method1"", ""id"": null }", typeof(object), (object)null); } } } + + public class ServerShouldThrowCorrectExceptions : JsonRpcServerTestBase + { + public ServerShouldThrowCorrectExceptions(ITestOutputHelper outputHelper) : base(new JsonRpcTestOptions().ConfigureForXUnit(outputHelper)) + { + } + + class Data + { + public string Value { get; set; } + } + + [Fact] + public async Task Should_Throw_Method_Not_Supported() + { + var (client, server) = await Initialize( + clientOptions => { + }, + serverOptions => { + serverOptions.OnRequest("method", async (Data data) => new Data() { Value = data.Value}); + }); + + Func action = () => client.SendRequest("method2", new Data() { + Value = "Echo" + }).Returning(CancellationToken); + await action.Should().ThrowAsync(); + } + } } diff --git a/test/JsonRpc.Tests/Server/SpecifictionRecieverTests.cs b/test/JsonRpc.Tests/Server/SpecifictionRecieverTests.cs index 329be7926..80429cdaf 100644 --- a/test/JsonRpc.Tests/Server/SpecifictionRecieverTests.cs +++ b/test/JsonRpc.Tests/Server/SpecifictionRecieverTests.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using System.Linq; using FluentAssertions; using Newtonsoft.Json; @@ -11,14 +9,14 @@ namespace JsonRpc.Tests.Server { - public class SpecifictionRecieverTests + public class SpecifictionReceiverTests { [Theory] [ClassData(typeof(SpecificationMessages))] - public void ShouldRespond_AsExpected(string json, Renor[] request) + public void ShouldRespond_AsExpected2(string json, Renor[] request) { - var reciever = new Receiver(); - var (requests, _) = reciever.GetRequests(JToken.Parse(json)); + var receiver = new Receiver(); + var (requests, _) = receiver.GetRequests(JToken.Parse(json)); var result = requests.ToArray(); request.Length.Should().Be(result.Length); @@ -34,9 +32,9 @@ public void ShouldRespond_AsExpected(string json, Renor[] request) class SpecificationMessages : TheoryData { - public override IEnumerable> GetValues() + public SpecificationMessages() { - yield return ( + Add ( @"{""jsonrpc"": ""2.0"", ""method"": ""subtract"", ""params"": [42, 23], ""id"": 1}", new Renor[] { @@ -44,21 +42,21 @@ public override IEnumerable> GetValues() } ); - yield return ( + Add ( @"{""jsonrpc"": ""2.0"", ""method"": ""subtract"", ""params"": {""subtrahend"": 23, ""minuend"": 42}, ""id"": 3}", new Renor[] { new Request(3, "subtract", JObject.FromObject(new {subtrahend = 23, minuend = 42})) }); - yield return ( + Add ( @"{""jsonrpc"": ""2.0"", ""method"": ""subtract"", ""params"": {""minuend"": 42, ""subtrahend"": 23 }, ""id"": 4}", new Renor[] { new Request(4, "subtract", JObject.FromObject(new {minuend = 42, subtrahend = 23})) }); - yield return ( + Add ( @"{""jsonrpc"": ""2.0"", ""method"": ""subtract"", ""id"": 4}", new Renor[] { @@ -69,21 +67,21 @@ public override IEnumerable> GetValues() // If present, parameters for the rpc call MUST be provided as a Structured value. // Some clients may serialize params as null, instead of omitting it // We're going to pretend we never got the null in the first place. - yield return ( + Add ( @"{""jsonrpc"": ""2.0"", ""method"": ""subtract"", ""params"": null, ""id"": 4}", new Renor[] { new Request(4, "subtract", new JObject()) }); - yield return ( + Add ( @"{""jsonrpc"": ""2.0"", ""method"": ""update"", ""params"": [1,2,3,4,5]}", new Renor[] { new Notification("update", new JArray(new [] {1,2,3,4,5})) }); - yield return ( + Add ( @"{""jsonrpc"": ""2.0"", ""method"": ""foobar""}", new Renor[] { @@ -94,43 +92,35 @@ public override IEnumerable> GetValues() // If present, parameters for the rpc call MUST be provided as a Structured value. // Some clients may serialize params as null, instead of omitting it // We're going to pretend we never got the null in the first place. - yield return ( + Add ( @"{""jsonrpc"": ""2.0"", ""method"": ""foobar"", ""params"": null}", new Renor[] { new Notification("foobar", new JObject()) }); - yield return ( + Add ( @"{""jsonrpc"":""2.0"",""method"":""initialized"",""params"":{}}", new Renor[] { new Notification("initialized", new JObject()), } ); - yield return ( + Add ( @"{""jsonrpc"": ""2.0"", ""method"": 1, ""params"": ""bar""}", new Renor[] { new InvalidRequest("Invalid params") }); - // TODO: Use case should be outside reciever - //yield return ( - // @"[]", - // new[] - // { - // new InvalidRequest("No Requests") - // }); - - yield return ( + Add ( @"[1]", new Renor[] { new InvalidRequest("Not an object") }); - yield return ( + Add ( @"[1,2,3]", new Renor[] { @@ -139,7 +129,7 @@ public override IEnumerable> GetValues() new InvalidRequest("Not an object") }); - yield return ( + Add ( @"[ {""jsonrpc"": ""2.0"", ""method"": ""sum"", ""params"": [1,2,4], ""id"": ""1""}, {""jsonrpc"": ""2.0"", ""method"": ""notify_hello"", ""params"": [7]}, @@ -157,6 +147,18 @@ public override IEnumerable> GetValues() new Request("5", "foo.get", JObject.FromObject(new {name = "myself"})), new Request("9", "get_data", null), }); + + Add ( + @"[ + {""jsonrpc"": ""2.0"", ""error"": {""code"": -32600, ""message"": ""Invalid Request""}, ""id"": null}, + {""jsonrpc"": ""2.0"", ""error"": {""code"": -32600, ""message"": ""Invalid Request""}, ""id"": null}, + {""jsonrpc"": ""2.0"", ""error"": {""code"": -32600, ""message"": ""Invalid Request""}, ""id"": null} + ]", + new Renor[] { + new ServerError(new ServerErrorResult(-32600, "Invalid Request")), + new ServerError(new ServerErrorResult(-32600, "Invalid Request")), + new ServerError(new ServerErrorResult(-32600, "Invalid Request")), + }); } } @@ -164,21 +166,21 @@ public override IEnumerable> GetValues() [ClassData(typeof(InvalidMessages))] public void Should_ValidateInvalidMessages(string json, bool expected) { - var reciever = new Receiver(); - var result = reciever.IsValid(JToken.Parse(json)); + var receiver = new Receiver(); + var result = receiver.IsValid(JToken.Parse(json)); result.Should().Be(expected); } class InvalidMessages : TheoryData { - public override IEnumerable> GetValues() + public InvalidMessages() { - yield return (@"[]", false); - yield return (@"""""", false); - yield return (@"1", false); - yield return (@"true", false); - yield return (@"[{}]", true); - yield return (@"{}", true); + Add (@"[]", false); + Add (@"""""", false); + Add (@"1", false); + Add (@"true", false); + Add (@"[{}]", true); + Add (@"{}", true); } } } diff --git a/test/JsonRpc.Tests/TestExtensions.cs b/test/JsonRpc.Tests/TestExtensions.cs deleted file mode 100644 index 418515e9e..000000000 --- a/test/JsonRpc.Tests/TestExtensions.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Threading; - -namespace JsonRpc.Tests -{ - public static class TestExtensions - { - public static void Wait(this CancellationTokenSource cancellationTokenSource) - { - cancellationTokenSource.Token.WaitHandle.WaitOne(); - } - } -} \ No newline at end of file diff --git a/test/JsonRpc.Tests/TestLoggerFactory.cs b/test/JsonRpc.Tests/TestLoggerFactory.cs deleted file mode 100644 index 3e9d48b35..000000000 --- a/test/JsonRpc.Tests/TestLoggerFactory.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using Microsoft.Extensions.Logging; -using Serilog; -using Serilog.Extensions.Logging; -using Xunit.Abstractions; -using ILogger = Microsoft.Extensions.Logging.ILogger; - -namespace JsonRpc.Tests -{ - public class TestLoggerFactory : ILoggerFactory - { - private readonly SerilogLoggerProvider _loggerProvider; - - public TestLoggerFactory(ITestOutputHelper testOutputHelper) - { - _loggerProvider = new SerilogLoggerProvider( - new Serilog.LoggerConfiguration() - .WriteTo.TestOutput(testOutputHelper) - .CreateLogger() - ); - } - - ILogger ILoggerFactory.CreateLogger(string categoryName) - { - return _loggerProvider.CreateLogger(categoryName); - } - - void ILoggerFactory.AddProvider(ILoggerProvider provider) { } - - void IDisposable.Dispose() { } - } -} diff --git a/test/JsonRpc.Tests/TheoryData.cs b/test/JsonRpc.Tests/TheoryData.cs deleted file mode 100644 index 189241359..000000000 --- a/test/JsonRpc.Tests/TheoryData.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; - -namespace JsonRpc.Tests -{ - public abstract class TheoryData : IEnumerable - { - public abstract IEnumerator GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } - - public abstract class TheoryData : TheoryData - { - public override IEnumerator GetEnumerator() - { - return GetValues().Select(x => new object[] { x.Item1 }).GetEnumerator(); - } - - public abstract IEnumerable> GetValues(); - } - - public abstract class TheoryData : TheoryData - { - public override IEnumerator GetEnumerator() - { - return GetValues().Select(x => new object[] { x.Item1, x.Item2 }).GetEnumerator(); - } - - public abstract IEnumerable> GetValues(); - } - - public abstract class TheoryData : TheoryData - { - public override IEnumerator GetEnumerator() - { - return GetValues().Select(x => new object[] { x.Item1, x.Item2, x.Item3 }).GetEnumerator(); - } - - public abstract IEnumerable> GetValues(); - } - - public abstract class TheoryData : TheoryData - { - public override IEnumerator GetEnumerator() - { - return GetValues().Select(x => new object[] { x.Item1, x.Item2, x.Item3, x.Item4 }).GetEnumerator(); - } - - public abstract IEnumerable> GetValues(); - } -} \ No newline at end of file diff --git a/test/JsonRpc.Tests/logs/css.jsrpc b/test/JsonRpc.Tests/logs/css.jsrpc new file mode 100644 index 000000000..2c3b45718 --- /dev/null +++ b/test/JsonRpc.Tests/logs/css.jsrpc @@ -0,0 +1,15561 @@ +[ + { + "time": "22:33:18", + "msg": "Sending request 'initialize - (0)'.", + "msgKind": "send-request", + "msgType": "initialize", + "msgId": "0", + "arg": { + "processId": 17371, + "rootPath": "/Users/pine/Code/work/lsp-inspector/ms-inspector", + "rootUri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector", + "capabilities": { + "workspace": { + "applyEdit": true, + "workspaceEdit": { + "documentChanges": true + }, + "didChangeConfiguration": { + "dynamicRegistration": true + }, + "didChangeWatchedFiles": { + "dynamicRegistration": true + }, + "symbol": { + "dynamicRegistration": true, + "symbolKind": { + "valueSet": [ + 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 + ] + } + }, + "executeCommand": { + "dynamicRegistration": true + }, + "configuration": true, + "workspaceFolders": true + }, + "textDocument": { + "publishDiagnostics": { + "relatedInformation": true + }, + "synchronization": { + "dynamicRegistration": true, + "willSave": true, + "willSaveWaitUntil": true, + "didSave": true + }, + "completion": { + "dynamicRegistration": true, + "contextSupport": true, + "completionItem": { + "snippetSupport": true, + "commitCharactersSupport": true, + "documentationFormat": [ + "markdown", + "plaintext" + ], + "deprecatedSupport": true + }, + "completionItemKind": { + "valueSet": [ + 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 + ] + } + }, + "hover": { + "dynamicRegistration": true, + "contentFormat": [ + "markdown", + "plaintext" + ] + }, + "signatureHelp": { + "dynamicRegistration": true, + "signatureInformation": { + "documentationFormat": [ + "markdown", + "plaintext" + ] + } + }, + "definition": { + "dynamicRegistration": true + }, + "references": { + "dynamicRegistration": true + }, + "documentHighlight": { + "dynamicRegistration": true + }, + "documentSymbol": { + "dynamicRegistration": true, + "symbolKind": { + "valueSet": [ + 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 + ] + } + }, + "codeAction": { + "dynamicRegistration": true + }, + "codeLens": { + "dynamicRegistration": true + }, + "formatting": { + "dynamicRegistration": true + }, + "rangeFormatting": { + "dynamicRegistration": true + }, + "onTypeFormatting": { + "dynamicRegistration": true + }, + "rename": { + "dynamicRegistration": true + }, + "documentLink": { + "dynamicRegistration": true + }, + "typeDefinition": { + "dynamicRegistration": true + }, + "implementation": { + "dynamicRegistration": true + }, + "colorProvider": { + "dynamicRegistration": true + }, + "foldingRange": { + "dynamicRegistration": false, + "rangeLimit": 5000, + "lineFoldingOnly": true + } + } + }, + "initializationOptions": {}, + "trace": "verbose", + "workspaceFolders": [ + { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector", + "name": "ms-inspector" + } + ] + } + }, + { + "time": "22:33:18", + "msg": "Received response 'initialize - (0)' in 208ms.", + "msgKind": "recv-response", + "msgType": "initialize", + "msgId": "0", + "msgLatency": "208ms", + "arg": { + "capabilities": { + "textDocumentSync": 1, + "completionProvider": { + "resolveProvider": false, + "triggerCharacters": [ + "/" + ] + }, + "hoverProvider": true, + "documentSymbolProvider": true, + "referencesProvider": true, + "definitionProvider": true, + "documentHighlightProvider": true, + "codeActionProvider": true, + "renameProvider": true, + "colorProvider": {}, + "foldingRangeProvider": true + } + } + }, + { + "time": "22:33:18", + "msg": "Sending notification 'initialized'.", + "msgKind": "send-notification", + "msgType": "initialized", + "arg": {} + }, + { + "time": "22:33:18", + "msg": "Sending notification 'workspace/didChangeConfiguration'.", + "msgKind": "send-notification", + "msgType": "workspace/didChangeConfiguration", + "arg": { + "settings": { + "css": { + "validate": true, + "colorDecorators": { + "enable": true + }, + "lint": { + "compatibleVendorPrefixes": "ignore", + "vendorPrefix": "warning", + "duplicateProperties": "ignore", + "emptyRules": "warning", + "importStatement": "ignore", + "boxModel": "ignore", + "universalSelector": "ignore", + "zeroUnits": "ignore", + "fontFaceProperties": "warning", + "hexColorLength": "error", + "argumentsInColorFunction": "error", + "unknownProperties": "warning", + "ieHack": "ignore", + "unknownVendorSpecificProperties": "ignore", + "propertyIgnoredDueToDisplay": "warning", + "important": "ignore", + "float": "ignore", + "idSelector": "ignore", + "unknownAtRules": "warning" + }, + "trace": { + "server": "verbose" + } + }, + "scss": { + "validate": true, + "colorDecorators": { + "enable": true + }, + "lint": { + "compatibleVendorPrefixes": "ignore", + "vendorPrefix": "warning", + "duplicateProperties": "ignore", + "emptyRules": "warning", + "importStatement": "ignore", + "boxModel": "ignore", + "universalSelector": "ignore", + "zeroUnits": "ignore", + "fontFaceProperties": "warning", + "hexColorLength": "error", + "argumentsInColorFunction": "error", + "unknownProperties": "warning", + "ieHack": "ignore", + "unknownVendorSpecificProperties": "ignore", + "propertyIgnoredDueToDisplay": "warning", + "important": "ignore", + "float": "ignore", + "idSelector": "ignore" + } + }, + "less": { + "validate": true, + "colorDecorators": { + "enable": true + }, + "lint": { + "compatibleVendorPrefixes": "ignore", + "vendorPrefix": "warning", + "duplicateProperties": "ignore", + "emptyRules": "warning", + "importStatement": "ignore", + "boxModel": "ignore", + "universalSelector": "ignore", + "zeroUnits": "ignore", + "fontFaceProperties": "warning", + "hexColorLength": "error", + "argumentsInColorFunction": "error", + "unknownProperties": "warning", + "ieHack": "ignore", + "unknownVendorSpecificProperties": "ignore", + "propertyIgnoredDueToDisplay": "warning", + "important": "ignore", + "float": "ignore", + "idSelector": "ignore" + } + } + } + } + }, + { + "time": "22:33:18", + "msg": "Sending notification 'textDocument/didOpen'.", + "msgKind": "send-notification", + "msgType": "textDocument/didOpen", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss", + "languageId": "scss", + "version": 1, + "text": "/**\n * Var\n */\n\n$active-bg: #6f92ba88;\n\n/**\n * Mixin\n */\n@mixin transition($property) {\n transition: $property .4s ease-in;\n}" + } + } + }, + { + "time": "22:33:18", + "msg": "Sending request 'textDocument/codeAction - (1)'.", + "msgKind": "send-request", + "msgType": "textDocument/codeAction", + "msgId": "1", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + }, + "range": { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 0, + "character": 0 + } + }, + "context": { + "diagnostics": [] + } + } + }, + { + "time": "22:33:18", + "msg": "Sending request 'textDocument/documentColor - (2)'.", + "msgKind": "send-request", + "msgType": "textDocument/documentColor", + "msgId": "2", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + } + } + }, + { + "time": "22:33:18", + "msg": "Received response 'textDocument/codeAction - (1)' in 10ms.", + "msgKind": "recv-response", + "msgType": "textDocument/codeAction", + "msgId": "1", + "msgLatency": "10ms", + "arg": [] + }, + { + "time": "22:33:18", + "msg": "Received response 'textDocument/documentColor - (2)' in 11ms.", + "msgKind": "recv-response", + "msgType": "textDocument/documentColor", + "msgId": "2", + "msgLatency": "11ms", + "arg": [ + { + "color": { + "red": 0.43529411764705883, + "green": 0.5725490196078431, + "blue": 0.7294117647058823, + "alpha": 0.5333333333333333 + }, + "range": { + "start": { + "line": 4, + "character": 12 + }, + "end": { + "line": 4, + "character": 21 + } + } + } + ] + }, + { + "time": "22:33:19", + "msg": "Sending request 'textDocument/foldingRange - (3)'.", + "msgKind": "send-request", + "msgType": "textDocument/foldingRange", + "msgId": "3", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + } + } + }, + { + "time": "22:33:19", + "msg": "Received response 'textDocument/foldingRange - (3)' in 3ms.", + "msgKind": "recv-response", + "msgType": "textDocument/foldingRange", + "msgId": "3", + "msgLatency": "3ms", + "arg": [ + { + "startLine": 0, + "endLine": 2, + "kind": "comment" + }, + { + "startLine": 6, + "endLine": 8, + "kind": "comment" + }, + { + "startLine": 9, + "endLine": 10 + } + ] + }, + { + "time": "22:33:19", + "msg": "Received request 'workspace/configuration - (0)'.", + "msgKind": "recv-request", + "msgType": "workspace/configuration", + "msgId": "0", + "arg": { + "items": [ + { + "scopeUri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss", + "section": "scss" + } + ] + } + }, + { + "time": "22:33:19", + "msg": "Sending response 'workspace/configuration - (0)'. Processing request took 5ms", + "msgKind": "send-response", + "msgType": "workspace/configuration", + "msgId": "0", + "msgLatency": "5ms", + "arg": [ + { + "validate": true, + "colorDecorators": { + "enable": true + }, + "lint": { + "compatibleVendorPrefixes": "ignore", + "vendorPrefix": "warning", + "duplicateProperties": "ignore", + "emptyRules": "warning", + "importStatement": "ignore", + "boxModel": "ignore", + "universalSelector": "ignore", + "zeroUnits": "ignore", + "fontFaceProperties": "warning", + "hexColorLength": "error", + "argumentsInColorFunction": "error", + "unknownProperties": "warning", + "ieHack": "ignore", + "unknownVendorSpecificProperties": "ignore", + "propertyIgnoredDueToDisplay": "warning", + "important": "ignore", + "float": "ignore", + "idSelector": "ignore" + } + } + ] + }, + { + "time": "22:33:19", + "msg": "Received notification 'textDocument/publishDiagnostics'.", + "msgKind": "recv-notification", + "msgType": "textDocument/publishDiagnostics", + "arg": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss", + "diagnostics": [] + } + }, + { + "time": "22:33:20", + "msg": "Sending request 'textDocument/codeAction - (4)'.", + "msgKind": "send-request", + "msgType": "textDocument/codeAction", + "msgId": "4", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + }, + "range": { + "start": { + "line": 4, + "character": 10 + }, + "end": { + "line": 4, + "character": 10 + } + }, + "context": { + "diagnostics": [] + } + } + }, + { + "time": "22:33:20", + "msg": "Received response 'textDocument/codeAction - (4)' in 0ms.", + "msgKind": "recv-response", + "msgType": "textDocument/codeAction", + "msgId": "4", + "msgLatency": "0ms", + "arg": [] + }, + { + "time": "22:33:20", + "msg": "Sending request 'textDocument/codeAction - (5)'.", + "msgKind": "send-request", + "msgType": "textDocument/codeAction", + "msgId": "5", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + }, + "range": { + "start": { + "line": 4, + "character": 12 + }, + "end": { + "line": 4, + "character": 12 + } + }, + "context": { + "diagnostics": [] + } + } + }, + { + "time": "22:33:20", + "msg": "Received response 'textDocument/codeAction - (5)' in 1ms.", + "msgKind": "recv-response", + "msgType": "textDocument/codeAction", + "msgId": "5", + "msgLatency": "1ms", + "arg": [] + }, + { + "time": "22:33:21", + "msg": "Sending notification 'textDocument/didChange'.", + "msgKind": "send-notification", + "msgType": "textDocument/didChange", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss", + "version": 2 + }, + "contentChanges": [ + { + "text": "/**\n * Var\n */\n\n$active-bg: \n\n/**\n * Mixin\n */\n@mixin transition($property) {\n transition: $property .4s ease-in;\n}" + } + ] + } + }, + { + "time": "22:33:21", + "msg": "Sending request 'textDocument/foldingRange - (6)'.", + "msgKind": "send-request", + "msgType": "textDocument/foldingRange", + "msgId": "6", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + } + } + }, + { + "time": "22:33:21", + "msg": "Received response 'textDocument/foldingRange - (6)' in 2ms.", + "msgKind": "recv-response", + "msgType": "textDocument/foldingRange", + "msgId": "6", + "msgLatency": "2ms", + "arg": [ + { + "startLine": 0, + "endLine": 2, + "kind": "comment" + }, + { + "startLine": 6, + "endLine": 8, + "kind": "comment" + }, + { + "startLine": 9, + "endLine": 10 + } + ] + }, + { + "time": "22:33:21", + "msg": "Sending notification 'textDocument/didChange'.", + "msgKind": "send-notification", + "msgType": "textDocument/didChange", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss", + "version": 3 + }, + "contentChanges": [ + { + "text": "/**\n * Var\n */\n\n$active-bg: #2a76cc88\n\n/**\n * Mixin\n */\n@mixin transition($property) {\n transition: $property .4s ease-in;\n}" + } + ] + } + }, + { + "time": "22:33:21", + "msg": "Sending request 'textDocument/foldingRange - (7)'.", + "msgKind": "send-request", + "msgType": "textDocument/foldingRange", + "msgId": "7", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + } + } + }, + { + "time": "22:33:21", + "msg": "Received response 'textDocument/foldingRange - (7)' in 1ms.", + "msgKind": "recv-response", + "msgType": "textDocument/foldingRange", + "msgId": "7", + "msgLatency": "1ms", + "arg": [ + { + "startLine": 0, + "endLine": 2, + "kind": "comment" + }, + { + "startLine": 6, + "endLine": 8, + "kind": "comment" + }, + { + "startLine": 9, + "endLine": 10 + } + ] + }, + { + "time": "22:33:21", + "msg": "Sending request 'textDocument/codeAction - (8)'.", + "msgKind": "send-request", + "msgType": "textDocument/codeAction", + "msgId": "8", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + }, + "range": { + "start": { + "line": 4, + "character": 21 + }, + "end": { + "line": 4, + "character": 21 + } + }, + "context": { + "diagnostics": [] + } + } + }, + { + "time": "22:33:21", + "msg": "Received response 'textDocument/codeAction - (8)' in 2ms.", + "msgKind": "recv-response", + "msgType": "textDocument/codeAction", + "msgId": "8", + "msgLatency": "2ms", + "arg": [] + }, + { + "time": "22:33:21", + "msg": "Sending notification 'textDocument/didChange'.", + "msgKind": "send-notification", + "msgType": "textDocument/didChange", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss", + "version": 4 + }, + "contentChanges": [ + { + "text": "/**\n * Var\n */\n\n$active-bg: #2a76cc88;\n\n/**\n * Mixin\n */\n@mixin transition($property) {\n transition: $property .4s ease-in;\n}" + } + ] + } + }, + { + "time": "22:33:21", + "msg": "Sending request 'textDocument/foldingRange - (9)'.", + "msgKind": "send-request", + "msgType": "textDocument/foldingRange", + "msgId": "9", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + } + } + }, + { + "time": "22:33:21", + "msg": "Received response 'textDocument/foldingRange - (9)' in 1ms.", + "msgKind": "recv-response", + "msgType": "textDocument/foldingRange", + "msgId": "9", + "msgLatency": "1ms", + "arg": [ + { + "startLine": 0, + "endLine": 2, + "kind": "comment" + }, + { + "startLine": 6, + "endLine": 8, + "kind": "comment" + }, + { + "startLine": 9, + "endLine": 10 + } + ] + }, + { + "time": "22:33:22", + "msg": "Sending request 'textDocument/codeAction - (10)'.", + "msgKind": "send-request", + "msgType": "textDocument/codeAction", + "msgId": "10", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + }, + "range": { + "start": { + "line": 4, + "character": 21 + }, + "end": { + "line": 4, + "character": 21 + } + }, + "context": { + "diagnostics": [] + } + } + }, + { + "time": "22:33:22", + "msg": "Sending request 'textDocument/documentColor - (11)'.", + "msgKind": "send-request", + "msgType": "textDocument/documentColor", + "msgId": "11", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + } + } + }, + { + "time": "22:33:22", + "msg": "Received response 'textDocument/codeAction - (10)' in 5ms.", + "msgKind": "recv-response", + "msgType": "textDocument/codeAction", + "msgId": "10", + "msgLatency": "5ms", + "arg": [] + }, + { + "time": "22:33:22", + "msg": "Received response 'textDocument/documentColor - (11)' in 3ms.", + "msgKind": "recv-response", + "msgType": "textDocument/documentColor", + "msgId": "11", + "msgLatency": "3ms", + "arg": [ + { + "color": { + "red": 0.16470588235294117, + "green": 0.4627450980392157, + "blue": 0.8, + "alpha": 0.5333333333333333 + }, + "range": { + "start": { + "line": 4, + "character": 12 + }, + "end": { + "line": 4, + "character": 21 + } + } + } + ] + }, + { + "time": "22:33:22", + "msg": "Sending notification 'textDocument/didSave'.", + "msgKind": "send-notification", + "msgType": "textDocument/didSave", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss", + "version": 4 + } + } + }, + { + "time": "22:33:22", + "msg": "Received notification 'textDocument/publishDiagnostics'.", + "msgKind": "recv-notification", + "msgType": "textDocument/publishDiagnostics", + "arg": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss", + "diagnostics": [] + } + }, + { + "time": "22:34:00", + "msg": "Sending request 'textDocument/documentHighlight - (12)'.", + "msgKind": "send-request", + "msgType": "textDocument/documentHighlight", + "msgId": "12", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + }, + "position": { + "line": 4, + "character": 20 + } + } + }, + { + "time": "22:34:00", + "msg": "Received response 'textDocument/documentHighlight - (12)' in 3ms.", + "msgKind": "recv-response", + "msgType": "textDocument/documentHighlight", + "msgId": "12", + "msgLatency": "3ms", + "arg": [ + { + "kind": 2, + "range": { + "start": { + "line": 4, + "character": 12 + }, + "end": { + "line": 4, + "character": 21 + } + } + } + ] + }, + { + "time": "22:34:00", + "msg": "Sending request 'textDocument/hover - (13)'.", + "msgKind": "send-request", + "msgType": "textDocument/hover", + "msgId": "13", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + }, + "position": { + "line": 4, + "character": 20 + } + } + }, + { + "time": "22:34:00", + "msg": "Sending request 'textDocument/codeAction - (14)'.", + "msgKind": "send-request", + "msgType": "textDocument/codeAction", + "msgId": "14", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + }, + "range": { + "start": { + "line": 4, + "character": 20 + }, + "end": { + "line": 4, + "character": 20 + } + }, + "context": { + "diagnostics": [] + } + } + }, + { + "time": "22:34:00", + "msg": "Received response 'textDocument/codeAction - (14)' in 0ms.", + "msgKind": "recv-response", + "msgType": "textDocument/codeAction", + "msgId": "14", + "msgLatency": "0ms", + "arg": [] + }, + { + "time": "22:34:00", + "msg": "Sending request 'textDocument/colorPresentation - (15)'.", + "msgKind": "send-request", + "msgType": "textDocument/colorPresentation", + "msgId": "15", + "arg": { + "color": { + "red": 0.16470588235294117, + "green": 0.4627450980392157, + "blue": 0.8, + "alpha": 0.5333333333333333 + }, + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + }, + "range": { + "start": { + "line": 4, + "character": 12 + }, + "end": { + "line": 4, + "character": 21 + } + } + } + }, + { + "time": "22:34:00", + "msg": "Received response 'textDocument/colorPresentation - (15)' in 2ms.", + "msgKind": "recv-response", + "msgType": "textDocument/colorPresentation", + "msgId": "15", + "msgLatency": "2ms", + "arg": [ + { + "label": "rgba(42, 118, 204, 0.5333333333333333)", + "textEdit": { + "range": { + "start": { + "line": 4, + "character": 12 + }, + "end": { + "line": 4, + "character": 21 + } + }, + "newText": "rgba(42, 118, 204, 0.5333333333333333)" + } + }, + { + "label": "#2a76cc88", + "textEdit": { + "range": { + "start": { + "line": 4, + "character": 12 + }, + "end": { + "line": 4, + "character": 21 + } + }, + "newText": "#2a76cc88" + } + }, + { + "label": "hsla(212, 66%, 48%, 0.5333333333333333)", + "textEdit": { + "range": { + "start": { + "line": 4, + "character": 12 + }, + "end": { + "line": 4, + "character": 21 + } + }, + "newText": "hsla(212, 66%, 48%, 0.5333333333333333)" + } + } + ] + }, + { + "time": "22:34:01", + "msg": "Sending notification 'textDocument/didChange'.", + "msgKind": "send-notification", + "msgType": "textDocument/didChange", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss", + "version": 5 + }, + "contentChanges": [ + { + "text": "/**\n * Var\n */\n\n$active-bg: #2a76cc\n\n/**\n * Mixin\n */\n@mixin transition($property) {\n transition: $property .4s ease-in;\n}" + } + ] + } + }, + { + "time": "22:34:01", + "msg": "Sending request 'textDocument/codeAction - (16)'.", + "msgKind": "send-request", + "msgType": "textDocument/codeAction", + "msgId": "16", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + }, + "range": { + "start": { + "line": 4, + "character": 19 + }, + "end": { + "line": 4, + "character": 19 + } + }, + "context": { + "diagnostics": [] + } + } + }, + { + "time": "22:34:01", + "msg": "Received response 'textDocument/codeAction - (16)' in 1ms.", + "msgKind": "recv-response", + "msgType": "textDocument/codeAction", + "msgId": "16", + "msgLatency": "1ms", + "arg": [] + }, + { + "time": "22:34:01", + "msg": "Sending notification 'textDocument/didChange'.", + "msgKind": "send-notification", + "msgType": "textDocument/didChange", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss", + "version": 6 + }, + "contentChanges": [ + { + "text": "/**\n * Var\n */\n\n$active-bg: #2a76cc;\n\n/**\n * Mixin\n */\n@mixin transition($property) {\n transition: $property .4s ease-in;\n}" + } + ] + } + }, + { + "time": "22:34:01", + "msg": "Sending request 'textDocument/foldingRange - (17)'.", + "msgKind": "send-request", + "msgType": "textDocument/foldingRange", + "msgId": "17", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + } + } + }, + { + "time": "22:34:01", + "msg": "Received response 'textDocument/foldingRange - (17)' in 0ms.", + "msgKind": "recv-response", + "msgType": "textDocument/foldingRange", + "msgId": "17", + "msgLatency": "0ms", + "arg": [ + { + "startLine": 0, + "endLine": 2, + "kind": "comment" + }, + { + "startLine": 6, + "endLine": 8, + "kind": "comment" + }, + { + "startLine": 9, + "endLine": 10 + } + ] + }, + { + "time": "22:34:01", + "msg": "Sending request 'textDocument/codeAction - (18)'.", + "msgKind": "send-request", + "msgType": "textDocument/codeAction", + "msgId": "18", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + }, + "range": { + "start": { + "line": 4, + "character": 19 + }, + "end": { + "line": 4, + "character": 19 + } + }, + "context": { + "diagnostics": [] + } + } + }, + { + "time": "22:34:01", + "msg": "Received response 'textDocument/codeAction - (18)' in 3ms.", + "msgKind": "recv-response", + "msgType": "textDocument/codeAction", + "msgId": "18", + "msgLatency": "3ms", + "arg": [] + }, + { + "time": "22:34:01", + "msg": "Sending notification 'textDocument/didSave'.", + "msgKind": "send-notification", + "msgType": "textDocument/didSave", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss", + "version": 6 + } + } + }, + { + "time": "22:34:01", + "msg": "Received notification 'textDocument/publishDiagnostics'.", + "msgKind": "recv-notification", + "msgType": "textDocument/publishDiagnostics", + "arg": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss", + "diagnostics": [] + } + }, + { + "time": "22:34:02", + "msg": "Sending request 'textDocument/documentColor - (19)'.", + "msgKind": "send-request", + "msgType": "textDocument/documentColor", + "msgId": "19", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + } + } + }, + { + "time": "22:34:02", + "msg": "Received response 'textDocument/documentColor - (19)' in 1ms.", + "msgKind": "recv-response", + "msgType": "textDocument/documentColor", + "msgId": "19", + "msgLatency": "1ms", + "arg": [ + { + "color": { + "red": 0.16470588235294117, + "green": 0.4627450980392157, + "blue": 0.8, + "alpha": 1 + }, + "range": { + "start": { + "line": 4, + "character": 12 + }, + "end": { + "line": 4, + "character": 19 + } + } + } + ] + }, + { + "time": "22:34:10", + "msg": "Sending request 'textDocument/hover - (20)'.", + "msgKind": "send-request", + "msgType": "textDocument/hover", + "msgId": "20", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + }, + "position": { + "line": 4, + "character": 11 + } + } + }, + { + "time": "22:34:10", + "msg": "Sending request 'textDocument/hover - (21)'.", + "msgKind": "send-request", + "msgType": "textDocument/hover", + "msgId": "21", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + }, + "position": { + "line": 4, + "character": 11 + } + } + }, + { + "time": "22:34:11", + "msg": "Sending request 'textDocument/hover - (22)'.", + "msgKind": "send-request", + "msgType": "textDocument/hover", + "msgId": "22", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + }, + "position": { + "line": 4, + "character": 11 + } + } + }, + { + "time": "22:34:12", + "msg": "Sending notification 'textDocument/didChange'.", + "msgKind": "send-notification", + "msgType": "textDocument/didChange", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss", + "version": 7 + }, + "contentChanges": [ + { + "text": "/**\n * Var\n */\n\n$active-bg: #2a76cc\n\n/**\n * Mixin\n */\n@mixin transition($property) {\n transition: $property .4s ease-in;\n}" + } + ] + } + }, + { + "time": "22:34:12", + "msg": "Sending request 'textDocument/foldingRange - (23)'.", + "msgKind": "send-request", + "msgType": "textDocument/foldingRange", + "msgId": "23", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + } + } + }, + { + "time": "22:34:12", + "msg": "Received response 'textDocument/foldingRange - (23)' in 1ms.", + "msgKind": "recv-response", + "msgType": "textDocument/foldingRange", + "msgId": "23", + "msgLatency": "1ms", + "arg": [ + { + "startLine": 0, + "endLine": 2, + "kind": "comment" + }, + { + "startLine": 6, + "endLine": 8, + "kind": "comment" + }, + { + "startLine": 9, + "endLine": 10 + } + ] + }, + { + "time": "22:34:12", + "msg": "Sending request 'textDocument/codeAction - (24)'.", + "msgKind": "send-request", + "msgType": "textDocument/codeAction", + "msgId": "24", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + }, + "range": { + "start": { + "line": 4, + "character": 19 + }, + "end": { + "line": 4, + "character": 19 + } + }, + "context": { + "diagnostics": [] + } + } + }, + { + "time": "22:34:12", + "msg": "Received response 'textDocument/codeAction - (24)' in 1ms.", + "msgKind": "recv-response", + "msgType": "textDocument/codeAction", + "msgId": "24", + "msgLatency": "1ms", + "arg": [] + }, + { + "time": "22:34:12", + "msg": "Received notification 'textDocument/publishDiagnostics'.", + "msgKind": "recv-notification", + "msgType": "textDocument/publishDiagnostics", + "arg": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss", + "diagnostics": [ + { + "code": "css-semicolonexpected", + "source": "scss", + "message": "semi-colon expected", + "severity": 1, + "range": { + "start": { + "line": 9, + "character": 0 + }, + "end": { + "line": 9, + "character": 6 + } + } + } + ] + } + }, + { + "time": "22:34:12", + "msg": "Sending notification 'textDocument/didChange'.", + "msgKind": "send-notification", + "msgType": "textDocument/didChange", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss", + "version": 8 + }, + "contentChanges": [ + { + "text": "/**\n * Var\n */\n\n$active-bg: #2a76cc88;\n\n/**\n * Mixin\n */\n@mixin transition($property) {\n transition: $property .4s ease-in;\n}" + } + ] + } + }, + { + "time": "22:34:12", + "msg": "Sending request 'textDocument/codeAction - (25)'.", + "msgKind": "send-request", + "msgType": "textDocument/codeAction", + "msgId": "25", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + }, + "range": { + "start": { + "line": 4, + "character": 19 + }, + "end": { + "line": 4, + "character": 19 + } + }, + "context": { + "diagnostics": [] + } + } + }, + { + "time": "22:34:12", + "msg": "Received response 'textDocument/codeAction - (25)' in 2ms.", + "msgKind": "recv-response", + "msgType": "textDocument/codeAction", + "msgId": "25", + "msgLatency": "2ms", + "arg": [] + }, + { + "time": "22:34:12", + "msg": "Sending request 'textDocument/foldingRange - (26)'.", + "msgKind": "send-request", + "msgType": "textDocument/foldingRange", + "msgId": "26", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + } + } + }, + { + "time": "22:34:12", + "msg": "Received response 'textDocument/foldingRange - (26)' in 3ms.", + "msgKind": "recv-response", + "msgType": "textDocument/foldingRange", + "msgId": "26", + "msgLatency": "3ms", + "arg": [ + { + "startLine": 0, + "endLine": 2, + "kind": "comment" + }, + { + "startLine": 6, + "endLine": 8, + "kind": "comment" + }, + { + "startLine": 9, + "endLine": 10 + } + ] + }, + { + "time": "22:34:12", + "msg": "Sending request 'textDocument/documentColor - (27)'.", + "msgKind": "send-request", + "msgType": "textDocument/documentColor", + "msgId": "27", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + } + } + }, + { + "time": "22:34:12", + "msg": "Received response 'textDocument/documentColor - (27)' in 2ms.", + "msgKind": "recv-response", + "msgType": "textDocument/documentColor", + "msgId": "27", + "msgLatency": "2ms", + "arg": [ + { + "color": { + "red": 0.16470588235294117, + "green": 0.4627450980392157, + "blue": 0.8, + "alpha": 0.5333333333333333 + }, + "range": { + "start": { + "line": 4, + "character": 12 + }, + "end": { + "line": 4, + "character": 21 + } + } + } + ] + }, + { + "time": "22:34:13", + "msg": "Received notification 'textDocument/publishDiagnostics'.", + "msgKind": "recv-notification", + "msgType": "textDocument/publishDiagnostics", + "arg": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss", + "diagnostics": [] + } + }, + { + "time": "22:34:13", + "msg": "Sending request 'textDocument/codeAction - (28)'.", + "msgKind": "send-request", + "msgType": "textDocument/codeAction", + "msgId": "28", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + }, + "range": { + "start": { + "line": 4, + "character": 19 + }, + "end": { + "line": 4, + "character": 19 + } + }, + "context": { + "diagnostics": [] + } + } + }, + { + "time": "22:34:13", + "msg": "Received response 'textDocument/codeAction - (28)' in 1ms.", + "msgKind": "recv-response", + "msgType": "textDocument/codeAction", + "msgId": "28", + "msgLatency": "1ms", + "arg": [] + }, + { + "time": "22:34:13", + "msg": "Sending notification 'textDocument/didSave'.", + "msgKind": "send-notification", + "msgType": "textDocument/didSave", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss", + "version": 8 + } + } + }, + { + "time": "22:34:15", + "msg": "Sending notification 'textDocument/didChange'.", + "msgKind": "send-notification", + "msgType": "textDocument/didChange", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss", + "version": 9 + }, + "contentChanges": [ + { + "text": "/**\n * Var\n */\n\n$active-bg: #2a76cc88;\n\n\n/**\n * Mixin\n */\n@mixin transition($property) {\n transition: $property .4s ease-in;\n}" + } + ] + } + }, + { + "time": "22:34:15", + "msg": "Sending request 'textDocument/foldingRange - (29)'.", + "msgKind": "send-request", + "msgType": "textDocument/foldingRange", + "msgId": "29", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + } + } + }, + { + "time": "22:34:15", + "msg": "Received response 'textDocument/foldingRange - (29)' in 2ms.", + "msgKind": "recv-response", + "msgType": "textDocument/foldingRange", + "msgId": "29", + "msgLatency": "2ms", + "arg": [ + { + "startLine": 0, + "endLine": 2, + "kind": "comment" + }, + { + "startLine": 7, + "endLine": 9, + "kind": "comment" + }, + { + "startLine": 10, + "endLine": 11 + } + ] + }, + { + "time": "22:34:16", + "msg": "Received notification 'textDocument/publishDiagnostics'.", + "msgKind": "recv-notification", + "msgType": "textDocument/publishDiagnostics", + "arg": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss", + "diagnostics": [] + } + }, + { + "time": "22:34:16", + "msg": "Sending request 'textDocument/documentColor - (30)'.", + "msgKind": "send-request", + "msgType": "textDocument/documentColor", + "msgId": "30", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + } + } + }, + { + "time": "22:34:16", + "msg": "Received response 'textDocument/documentColor - (30)' in 1ms.", + "msgKind": "recv-response", + "msgType": "textDocument/documentColor", + "msgId": "30", + "msgLatency": "1ms", + "arg": [ + { + "color": { + "red": 0.16470588235294117, + "green": 0.4627450980392157, + "blue": 0.8, + "alpha": 0.5333333333333333 + }, + "range": { + "start": { + "line": 4, + "character": 12 + }, + "end": { + "line": 4, + "character": 21 + } + } + } + ] + }, + { + "time": "22:34:16", + "msg": "Sending notification 'textDocument/didSave'.", + "msgKind": "send-notification", + "msgType": "textDocument/didSave", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss", + "version": 9 + } + } + }, + { + "time": "22:34:16", + "msg": "Sending notification 'textDocument/didChange'.", + "msgKind": "send-notification", + "msgType": "textDocument/didChange", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss", + "version": 10 + }, + "contentChanges": [ + { + "text": "/**\n * Var\n */\n\n$active-bg: #2a76cc88;\n@\n\n/**\n * Mixin\n */\n@mixin transition($property) {\n transition: $property .4s ease-in;\n}" + } + ] + } + }, + { + "time": "22:34:16", + "msg": "Sending request 'textDocument/completion - (31)'.", + "msgKind": "send-request", + "msgType": "textDocument/completion", + "msgId": "31", + "arg": { + "textDocument": { + "uri": "file:///Users/pine/Code/work/lsp-inspector/ms-inspector/src/scss/global.scss" + }, + "position": { + "line": 5, + "character": 1 + }, + "context": { + "triggerKind": 1 + } + } + }, + { + "time": "22:34:16", + "msg": "Received response 'textDocument/completion - (31)' in 52ms.", + "msgKind": "recv-response", + "msgType": "textDocument/completion", + "msgId": "31", + "msgLatency": "52ms", + "arg": { + "isIncomplete": false, + "items": [ + { + "label": "@extend", + "documentation": "Inherits the styles of another selector.", + "kind": 14, + "sortText": "d" + }, + { + "label": "@at-root", + "documentation": "Causes one or more rules to be emitted at the root of the document.", + "kind": 14, + "sortText": "d" + }, + { + "label": "@debug", + "documentation": "Prints the value of an expression to the standard error output stream. Useful for debugging complicated Sass files.", + "kind": 14, + "sortText": "d" + }, + { + "label": "@warn", + "documentation": "Prints the value of an expression to the standard error output stream. Useful for libraries that need to warn users of deprecations or recovering from minor mixin usage mistakes. Warnings can be turned off with the `--quiet` command-line option or the `:quiet` Sass option.", + "kind": 14, + "sortText": "d" + }, + { + "label": "@error", + "documentation": "Throws the value of an expression as a fatal error with stack trace. Useful for validating arguments to mixins and functions.", + "kind": 14, + "sortText": "d" + }, + { + "label": "@if", + "documentation": "Includes the body if the expression does not evaluate to `false` or `null`.", + "insertText": "@if ${1:expr} {\n\t$0\n}", + "insertTextFormat": 2, + "kind": 14, + "sortText": "d" + }, + { + "label": "@for", + "documentation": "For loop that repeatedly outputs a set of styles for each `$var` in the `from/through` or `from/to` clause.", + "insertText": "@for \\$${1:var} from ${2:start} ${3|to,through|} ${4:end} {\n\t$0\n}", + "insertTextFormat": 2, + "kind": 14, + "sortText": "d" + }, + { + "label": "@each", + "documentation": "Each loop that sets `$var` to each item in the list or map, then outputs the styles it contains using that value of `$var`.", + "insertText": "@each \\$${1:var} in ${2:list} {\n\t$0\n}", + "insertTextFormat": 2, + "kind": 14, + "sortText": "d" + }, + { + "label": "@while", + "documentation": "While loop that takes an expression and repeatedly outputs the nested styles until the statement evaluates to `false`.", + "insertText": "@while ${1:condition} {\n\t$0\n}", + "insertTextFormat": 2, + "kind": 14, + "sortText": "d" + }, + { + "label": "@mixin", + "documentation": "Defines styles that can be re-used throughout the stylesheet with `@include`.", + "insertText": "@mixin ${1:name} {\n\t$0\n}", + "insertTextFormat": 2, + "kind": 14, + "sortText": "d" + }, + { + "label": "@include", + "documentation": "Includes the styles defined by another mixin into the current rule.", + "kind": 14, + "sortText": "d" + }, + { + "label": "@charset", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": "@charset" + }, + "documentation": "Defines character set of the document.\n(Firefox 1.5, Safari 4, Chrome 2, IE 5.5, Opera 9)", + "kind": 14, + "sortText": "d" + }, + { + "label": "@counter-style", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": "@counter-style" + }, + "documentation": "Defines a custom counter style.\n(Firefox 33)", + "kind": 14, + "sortText": "d" + }, + { + "label": "@font-face", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": "@font-face" + }, + "documentation": "Allows for linking to fonts that are automatically activated when needed. This permits authors to work around the limitation of 'web-safe' fonts, allowing for consistent rendering independent of the fonts available in a given user's environment.", + "kind": 14, + "sortText": "d" + }, + { + "label": "@font-feature-values", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": "@font-feature-values" + }, + "documentation": "Defines named values for the indices used to select alternate glyphs for a given font family.\n(Firefox 34, Safari 9.1)", + "kind": 14, + "sortText": "d" + }, + { + "label": "@import", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": "@import" + }, + "documentation": "Includes content of another file.", + "kind": 14, + "sortText": "d" + }, + { + "label": "@keyframes", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": "@keyframes" + }, + "documentation": "Defines set of animation key frames.", + "kind": 14, + "sortText": "d" + }, + { + "label": "@media", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": "@media" + }, + "documentation": "Defines a stylesheet for a particular media type.", + "kind": 14, + "sortText": "d" + }, + { + "label": "@-moz-document", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": "@-moz-document" + }, + "documentation": "Gecko-specific at-rule that restricts the style rules contained within it based on the URL of the document.\n(Firefox 1.8)", + "kind": 14, + "sortText": "d" + }, + { + "label": "@-moz-keyframes", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": "@-moz-keyframes" + }, + "documentation": "Defines set of animation key frames.\n(Firefox 5)", + "kind": 14, + "sortText": "d" + }, + { + "label": "@-ms-viewport", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": "@-ms-viewport" + }, + "documentation": "Specifies the size, zoom factor, and orientation of the viewport.\n(Edge, IE 10)", + "kind": 14, + "sortText": "d" + }, + { + "label": "@namespace", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": "@namespace" + }, + "documentation": "Declares a prefix and associates it with a namespace name.", + "kind": 14, + "sortText": "d" + }, + { + "label": "@-o-keyframes", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": "@-o-keyframes" + }, + "documentation": "Defines set of animation key frames.\n(Opera 12)", + "kind": 14, + "sortText": "d" + }, + { + "label": "@-o-viewport", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": "@-o-viewport" + }, + "documentation": "Specifies the size, zoom factor, and orientation of the viewport.\n(Opera 11)", + "kind": 14, + "sortText": "d" + }, + { + "label": "@page", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": "@page" + }, + "documentation": "Directive defines various page parameters.\n(Edge, Firefox 19, Chrome 2, IE 8, Opera 6)", + "kind": 14, + "sortText": "d" + }, + { + "label": "@supports", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": "@supports" + }, + "documentation": "A conditional group rule whose condition tests whether the user agent supports CSS property:value pairs.\n(Edge 12, Firefox 22, Safari 9, Chrome 28, Opera 12.1)", + "kind": 14, + "sortText": "d" + }, + { + "label": "@-webkit-keyframes", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": "@-webkit-keyframes" + }, + "documentation": "Defines set of animation key frames.\n(Safari 4, Chrome)", + "kind": 14, + "sortText": "d" + }, + { + "label": "selector-nest", + "detail": "selector-nest($selectors…)", + "documentation": "Nests selector beneath one another like they would be nested in the stylesheet.", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": "selector-nest(\\$selectors: ${1:}…)" + }, + "insertTextFormat": 2, + "kind": 3, + "sortText": "z" + }, + { + "label": "selector-append", + "detail": "selector-append($selectors…)", + "documentation": "Appends selectors to one another without spaces in between.", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": "selector-append(\\$selectors: ${1:}…)" + }, + "insertTextFormat": 2, + "kind": 3, + "sortText": "z" + }, + { + "label": "selector-extend", + "detail": "selector-extend($selector, $extendee, $extender)", + "documentation": "Extends $extendee with $extender within $selector.", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": "selector-extend(\\$selector: ${1:}, \\$extendee: ${2:}, \\$extender: ${3:})" + }, + "insertTextFormat": 2, + "kind": 3, + "sortText": "z" + }, + { + "label": "selector-replace", + "detail": "selector-replace($selector, $original, $replacement)", + "documentation": "Replaces $original with $replacement within $selector.", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": "selector-replace(\\$selector: ${1:}, \\$original: ${2:}, \\$replacement: ${3:})" + }, + "insertTextFormat": 2, + "kind": 3, + "sortText": "z" + }, + { + "label": "selector-unify", + "detail": "selector-unify($selector1, $selector2)", + "documentation": "Unifies two selectors to produce a selector that matches elements matched by both.", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": "selector-unify(\\$selector1: ${1:}, \\$selector2: ${2:})" + }, + "insertTextFormat": 2, + "kind": 3, + "sortText": "z" + }, + { + "label": "is-superselector", + "detail": "is-superselector($super, $sub)", + "documentation": "Returns whether $super matches all the elements $sub does, and possibly more.", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": "is-superselector(\\$super: ${1:}, \\$sub: ${2:})" + }, + "insertTextFormat": 2, + "kind": 3, + "sortText": "z" + }, + { + "label": "simple-selectors", + "detail": "simple-selectors($selector)", + "documentation": "Returns the simple selectors that comprise a compound selector.", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": "simple-selectors(\\$selector: ${1:})" + }, + "insertTextFormat": 2, + "kind": 3, + "sortText": "z" + }, + { + "label": "selector-parse", + "detail": "selector-parse($selector)", + "documentation": "Parses a selector into the format returned by &.", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": "selector-parse(\\$selector: ${1:})" + }, + "insertTextFormat": 2, + "kind": 3, + "sortText": "z" + }, + { + "label": ":active", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":active" + }, + "documentation": "Applies while an element is being activated by the user. For example, between the times the user presses the mouse button and releases it.", + "kind": 3, + "sortText": "d" + }, + { + "label": ":any-link", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":any-link" + }, + "documentation": "Represents an element that acts as the source anchor of a hyperlink. Applies to both visited and unvisited links.\n(Firefox, Safari, Chrome, Opera)", + "kind": 3, + "sortText": "d" + }, + { + "label": ":checked", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":checked" + }, + "documentation": "Radio and checkbox elements can be toggled by the user. Some menu items are 'checked' when the user selects them. When such elements are toggled 'on' the :checked pseudo-class applies.", + "kind": 3, + "sortText": "d" + }, + { + "label": ":corner-present", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":corner-present" + }, + "documentation": "Non-standard. Indicates whether or not a scrollbar corner is present.\n(Safari 5, Chrome)", + "kind": 3, + "sortText": "d" + }, + { + "label": ":decrement", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":decrement" + }, + "documentation": "Non-standard. Applies to buttons and track pieces. Indicates whether or not the button or track piece will decrement the view’s position when used.\n(Safari 5, Chrome)", + "kind": 3, + "sortText": "d" + }, + { + "label": ":default", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":default" + }, + "documentation": "Applies to the one or more UI elements that are the default among a set of similar elements. Typically applies to context menu items, buttons, and select lists/menus.\n(Firefox 4, Safari 5, Chrome 10, Opera 10)", + "kind": 3, + "sortText": "d" + }, + { + "label": ":disabled", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":disabled" + }, + "documentation": "Represents user interface elements that are in a disabled state; such elements have a corresponding enabled state.", + "kind": 3, + "sortText": "d" + }, + { + "label": ":double-button", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":double-button" + }, + "documentation": "Non-standard. Applies to buttons and track pieces. Applies when both buttons are displayed together at the same end of the scrollbar.\n(Safari 5, Chrome)", + "kind": 3, + "sortText": "d" + }, + { + "label": ":empty", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":empty" + }, + "documentation": "Represents an element that has no children at all.", + "kind": 3, + "sortText": "d" + }, + { + "label": ":enabled", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":enabled" + }, + "documentation": "Represents user interface elements that are in an enabled state; such elements have a corresponding disabled state.", + "kind": 3, + "sortText": "d" + }, + { + "label": ":end", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":end" + }, + "documentation": "Non-standard. Applies to buttons and track pieces. Indicates whether the object is placed after the thumb.\n(Safari 5, Chrome)", + "kind": 3, + "sortText": "d" + }, + { + "label": ":first", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":first" + }, + "documentation": "When printing double-sided documents, the page boxes on left and right pages may be different. This can be expressed through CSS pseudo-classes defined in the page context.\n(Edge, IE 8, Opera 9.2)", + "kind": 3, + "sortText": "d" + }, + { + "label": ":first-child", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":first-child" + }, + "documentation": "Same as :nth-child(1). Represents an element that is the first child of some other element.", + "kind": 3, + "sortText": "d" + }, + { + "label": ":first-of-type", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":first-of-type" + }, + "documentation": "Same as :nth-of-type(1). Represents an element that is the first sibling of its type in the list of children of its parent element.", + "kind": 3, + "sortText": "d" + }, + { + "label": ":focus", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":focus" + }, + "documentation": "Applies while an element has the focus (accepts keyboard or mouse events, or other forms of input).", + "kind": 3, + "sortText": "d" + }, + { + "label": ":fullscreen", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":fullscreen" + }, + "documentation": "Matches any element that has its fullscreen flag set.\n(Edge 12, Firefox 9, Safari 6, Chrome 15, IE 11)", + "kind": 3, + "sortText": "d" + }, + { + "label": ":future", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":future" + }, + "documentation": "Represents any element that is defined to occur entirely after a :current element.\n(Safari 6, Chrome, Opera 16)", + "kind": 3, + "sortText": "d" + }, + { + "label": ":horizontal", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":horizontal" + }, + "documentation": "Non-standard. Applies to any scrollbar pieces that have a horizontal orientation.\n(Safari 5, Chrome)", + "kind": 3, + "sortText": "d" + }, + { + "label": ":host", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":host" + }, + "documentation": "When evaluated in the context of a shadow tree, matches the shadow tree’s host element.\n(Firefox 61, Safari, Chrome, Opera)", + "kind": 3, + "sortText": "d" + }, + { + "label": ":host()", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":host($1)" + }, + "documentation": "When evaluated in the context of a shadow tree, it matches the shadow tree’s host element if the host element, in its normal context, matches the selector argument.\n(Chrome 35, Opera 22)", + "kind": 3, + "insertTextFormat": 2, + "sortText": "d" + }, + { + "label": ":host-context()", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":host-context($1)" + }, + "documentation": "Tests whether there is an ancestor, outside the shadow tree, which matches a particular selector.\n(Chrome 35, Opera 22)", + "kind": 3, + "insertTextFormat": 2, + "sortText": "d" + }, + { + "label": ":hover", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":hover" + }, + "documentation": "Applies while the user designates an element with a pointing device, but does not necessarily activate it. For example, a visual user agent could apply this pseudo-class when the cursor (mouse pointer) hovers over a box generated by the element.", + "kind": 3, + "sortText": "d" + }, + { + "label": ":increment", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":increment" + }, + "documentation": "Non-standard. Applies to buttons and track pieces. Indicates whether or not the button or track piece will increment the view’s position when used.\n(Safari 5, Chrome)", + "kind": 3, + "sortText": "d" + }, + { + "label": ":indeterminate", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":indeterminate" + }, + "documentation": "Applies to UI elements whose value is in an indeterminate state.", + "kind": 3, + "sortText": "d" + }, + { + "label": ":in-range", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":in-range" + }, + "documentation": "Used in conjunction with the min and max attributes, whether on a range input, a number field, or any other types that accept those attributes.\n(Edge, Firefox 29, Safari, Chrome 10, Opera 11)", + "kind": 3, + "sortText": "d" + }, + { + "label": ":invalid", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":invalid" + }, + "documentation": "An element is :valid or :invalid when it is, respectively, valid or invalid with respect to data validity semantics defined by a different specification.", + "kind": 3, + "sortText": "d" + }, + { + "label": ":lang()", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":lang($1)" + }, + "documentation": "Represents an element that is in language specified.\n(Edge, Firefox 1, Safari 3, Chrome, IE 8, Opera 8)", + "kind": 3, + "insertTextFormat": 2, + "sortText": "d" + }, + { + "label": ":last-child", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":last-child" + }, + "documentation": "Same as :nth-last-child(1). Represents an element that is the last child of some other element.", + "kind": 3, + "sortText": "d" + }, + { + "label": ":last-of-type", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":last-of-type" + }, + "documentation": "Same as :nth-last-of-type(1). Represents an element that is the last sibling of its type in the list of children of its parent element.", + "kind": 3, + "sortText": "d" + }, + { + "label": ":left", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":left" + }, + "documentation": "When printing double-sided documents, the page boxes on left and right pages may be different. This can be expressed through CSS pseudo-classes defined in the page context.\n(Edge, IE 8, Opera 9.2)", + "kind": 3, + "sortText": "d" + }, + { + "label": ":link", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":link" + }, + "documentation": "Applies to links that have not yet been visited.", + "kind": 3, + "sortText": "d" + }, + { + "label": ":matches()", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":matches($1)" + }, + "documentation": "Takes a selector list as its argument. It represents an element that is represented by its argument.\n(Safari 9)", + "kind": 3, + "insertTextFormat": 2, + "sortText": "d" + }, + { + "label": ":-moz-any()", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":-moz-any($1)" + }, + "documentation": "Represents an element that is represented by the selector list passed as its argument. Standardized as :matches().\n(Firefox 4)", + "kind": 3, + "insertTextFormat": 2, + "sortText": "x" + }, + { + "label": ":-moz-any-link", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":-moz-any-link" + }, + "documentation": "Represents an element that acts as the source anchor of a hyperlink. Applies to both visited and unvisited links.\n(Firefox 1)", + "kind": 3, + "sortText": "x" + }, + { + "label": ":-moz-broken", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":-moz-broken" + }, + "documentation": "Non-standard. Matches elements representing broken images.\n(Firefox 3)", + "kind": 3, + "sortText": "x" + }, + { + "label": ":-moz-drag-over", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":-moz-drag-over" + }, + "documentation": "Non-standard. Matches elements when a drag-over event applies to it.\n(Firefox 1)", + "kind": 3, + "sortText": "x" + }, + { + "label": ":-moz-first-node", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":-moz-first-node" + }, + "documentation": "Non-standard. Represents an element that is the first child node of some other element.\n(Firefox 1)", + "kind": 3, + "sortText": "x" + }, + { + "label": ":-moz-focusring", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":-moz-focusring" + }, + "documentation": "Non-standard. Matches an element that has focus and focus ring drawing is enabled in the browser.\n(Firefox 4)", + "kind": 3, + "sortText": "x" + }, + { + "label": ":-moz-full-screen", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":-moz-full-screen" + }, + "documentation": "Matches any element that has its fullscreen flag set. Standardized as :fullscreen.\n(Firefox 9)", + "kind": 3, + "sortText": "x" + }, + { + "label": ":-moz-last-node", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":-moz-last-node" + }, + "documentation": "Non-standard. Represents an element that is the last child node of some other element.\n(Firefox 1)", + "kind": 3, + "sortText": "x" + }, + { + "label": ":-moz-loading", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":-moz-loading" + }, + "documentation": "Non-standard. Matches elements, such as images, that haven’t started loading yet.\n(Firefox 3)", + "kind": 3, + "sortText": "x" + }, + { + "label": ":-moz-only-whitespace", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":-moz-only-whitespace" + }, + "documentation": "The same as :empty, except that it additionally matches elements that only contain code points affected by whitespace processing. Standardized as :blank.\n(Firefox 1.5)", + "kind": 3, + "sortText": "x" + }, + { + "label": ":-moz-placeholder", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":-moz-placeholder" + }, + "documentation": "Deprecated. Represents placeholder text in an input field. Use ::-moz-placeholder for Firefox 19+.\n(Firefox 4)", + "kind": 3, + "sortText": "x" + }, + { + "label": ":-moz-submit-invalid", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":-moz-submit-invalid" + }, + "documentation": "Non-standard. Represents any submit button when the contents of the associated form are not valid.\n(Firefox 4)", + "kind": 3, + "sortText": "x" + }, + { + "label": ":-moz-suppressed", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":-moz-suppressed" + }, + "documentation": "Non-standard. Matches elements representing images that have been blocked from loading.\n(Firefox 3)", + "kind": 3, + "sortText": "x" + }, + { + "label": ":-moz-ui-invalid", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":-moz-ui-invalid" + }, + "documentation": "Non-standard. Represents any validated form element whose value isn't valid \n(Firefox 4)", + "kind": 3, + "sortText": "x" + }, + { + "label": ":-moz-ui-valid", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":-moz-ui-valid" + }, + "documentation": "Non-standard. Represents any validated form element whose value is valid \n(Firefox 4)", + "kind": 3, + "sortText": "x" + }, + { + "label": ":-moz-user-disabled", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":-moz-user-disabled" + }, + "documentation": "Non-standard. Matches elements representing images that have been disabled due to the user’s preferences.\n(Firefox 3)", + "kind": 3, + "sortText": "x" + }, + { + "label": ":-moz-window-inactive", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":-moz-window-inactive" + }, + "documentation": "Non-standard. Matches elements in an inactive window.\n(Firefox 4)", + "kind": 3, + "sortText": "x" + }, + { + "label": ":-ms-fullscreen", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":-ms-fullscreen" + }, + "documentation": "Matches any element that has its fullscreen flag set.\n(IE 11)", + "kind": 3, + "sortText": "x" + }, + { + "label": ":-ms-input-placeholder", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":-ms-input-placeholder" + }, + "documentation": "Represents placeholder text in an input field. Note: for Edge use the pseudo-element ::-ms-input-placeholder. Standardized as ::placeholder.\n(IE 10)", + "kind": 3, + "sortText": "x" + }, + { + "label": ":-ms-keyboard-active", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":-ms-keyboard-active" + }, + "documentation": "Windows Store apps only. Applies one or more styles to an element when it has focus and the user presses the space bar.\n(IE 10)", + "kind": 3, + "sortText": "x" + }, + { + "label": ":-ms-lang()", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":-ms-lang($1)" + }, + "documentation": "Represents an element that is in the language specified. Accepts a comma seperated list of language tokens.\n(Edge, IE 10)", + "kind": 3, + "insertTextFormat": 2, + "sortText": "x" + }, + { + "label": ":no-button", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":no-button" + }, + "documentation": "Non-standard. Applies to track pieces. Applies when there is no button at that end of the track.\n(Safari 5, Chrome)", + "kind": 3, + "sortText": "d" + }, + { + "label": ":not()", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":not($1)" + }, + "documentation": "The negation pseudo-class, :not(X), is a functional notation taking a simple selector (excluding the negation pseudo-class itself) as an argument. It represents an element that is not represented by its argument.\n(Edge, Firefox 1, Safari 2, Chrome, IE 9, Opera 9.5)", + "kind": 3, + "insertTextFormat": 2, + "sortText": "d" + }, + { + "label": ":nth-child()", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":nth-child($1)" + }, + "documentation": "Represents an element that has an+b-1 siblings before it in the document tree, for any positive integer or zero value of n, and has a parent element.\n(Edge, Firefox 3.5, Safari 3.1, Chrome, IE 9, Opera 9.5)", + "kind": 3, + "insertTextFormat": 2, + "sortText": "d" + }, + { + "label": ":nth-last-child()", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":nth-last-child($1)" + }, + "documentation": "Represents an element that has an+b-1 siblings after it in the document tree, for any positive integer or zero value of n, and has a parent element.\n(Edge, Firefox 3.5, Safari 3.1, Chrome, IE 9, Opera 9.5)", + "kind": 3, + "insertTextFormat": 2, + "sortText": "d" + }, + { + "label": ":nth-last-of-type()", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":nth-last-of-type($1)" + }, + "documentation": "Represents an element that has an+b-1 siblings with the same expanded element name after it in the document tree, for any zero or positive integer value of n, and has a parent element.\n(Edge, Firefox 3.5, Safari 3.1, Chrome, IE 9, Opera 9.5)", + "kind": 3, + "insertTextFormat": 2, + "sortText": "d" + }, + { + "label": ":nth-of-type()", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":nth-of-type($1)" + }, + "documentation": "Represents an element that has an+b-1 siblings with the same expanded element name before it in the document tree, for any zero or positive integer value of n, and has a parent element.\n(Edge, Firefox 3.5, Safari 3.1, Chrome, IE 9, Opera 9.5)", + "kind": 3, + "insertTextFormat": 2, + "sortText": "d" + }, + { + "label": ":only-child", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":only-child" + }, + "documentation": "Represents an element that has a parent element and whose parent element has no other element children. Same as :first-child:last-child or :nth-child(1):nth-last-child(1), but with a lower specificity.", + "kind": 3, + "sortText": "d" + }, + { + "label": ":only-of-type", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":only-of-type" + }, + "documentation": "Matches every element that is the only child of its type, of its parent. Same as :first-of-type:last-of-type or :nth-of-type(1):nth-last-of-type(1), but with a lower specificity.", + "kind": 3, + "sortText": "d" + }, + { + "label": ":optional", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":optional" + }, + "documentation": "A form element is :required or :optional if a value for it is, respectively, required or optional before the form it belongs to is submitted. Elements that are not form elements are neither required nor optional.", + "kind": 3, + "sortText": "d" + }, + { + "label": ":out-of-range", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":out-of-range" + }, + "documentation": "Used in conjunction with the min and max attributes, whether on a range input, a number field, or any other types that accept those attributes.\n(Edge, Firefox 29, Safari, Chrome 10, Opera 11)", + "kind": 3, + "sortText": "d" + }, + { + "label": ":past", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":past" + }, + "documentation": "Represents any element that is defined to occur entirely prior to a :current element.\n(Safari 6, Chrome, Opera 16)", + "kind": 3, + "sortText": "d" + }, + { + "label": ":read-only", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":read-only" + }, + "documentation": "An element whose contents are not user-alterable is :read-only. However, elements whose contents are user-alterable (such as text input fields) are considered to be in a :read-write state. In typical documents, most elements are :read-only.\n(Edge, Firefox, Safari, Chrome, Opera)", + "kind": 3, + "sortText": "d" + }, + { + "label": ":read-write", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":read-write" + }, + "documentation": "An element whose contents are not user-alterable is :read-only. However, elements whose contents are user-alterable (such as text input fields) are considered to be in a :read-write state. In typical documents, most elements are :read-only.\n(Edge, Firefox, Safari, Chrome, Opera)", + "kind": 3, + "sortText": "d" + }, + { + "label": ":required", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":required" + }, + "documentation": "A form element is :required or :optional if a value for it is, respectively, required or optional before the form it belongs to is submitted. Elements that are not form elements are neither required nor optional.", + "kind": 3, + "sortText": "d" + }, + { + "label": ":right", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":right" + }, + "documentation": "When printing double-sided documents, the page boxes on left and right pages may be different. This can be expressed through CSS pseudo-classes defined in the page context.\n(Edge, IE 8, Opera 9.2)", + "kind": 3, + "sortText": "d" + }, + { + "label": ":root", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":root" + }, + "documentation": "Represents an element that is the root of the document. In HTML 4, this is always the HTML element.\n(Firefox 1, Safari 1, Chrome 1, IE 9, Opera 9.5)", + "kind": 3, + "sortText": "d" + }, + { + "label": ":scope", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":scope" + }, + "documentation": "Represents any element that is in the contextual reference element set.\n(Firefox 32, Safari 7, Opera 15)", + "kind": 3, + "sortText": "d" + }, + { + "label": ":single-button", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":single-button" + }, + "documentation": "Non-standard. Applies to buttons and track pieces. Applies when both buttons are displayed separately at either end of the scrollbar.\n(Safari 5, Chrome)", + "kind": 3, + "sortText": "d" + }, + { + "label": ":start", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":start" + }, + "documentation": "Non-standard. Applies to buttons and track pieces. Indicates whether the object is placed before the thumb.\n(Safari 5, Chrome)", + "kind": 3, + "sortText": "d" + }, + { + "label": ":target", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":target" + }, + "documentation": "Some URIs refer to a location within a resource. This kind of URI ends with a 'number sign' (#) followed by an anchor identifier (called the fragment identifier).", + "kind": 3, + "sortText": "d" + }, + { + "label": ":valid", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":valid" + }, + "documentation": "An element is :valid or :invalid when it is, respectively, valid or invalid with respect to data validity semantics defined by a different specification.", + "kind": 3, + "sortText": "d" + }, + { + "label": ":vertical", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":vertical" + }, + "documentation": "Non-standard. Applies to any scrollbar pieces that have a vertical orientation.\n(Safari 5, Chrome)", + "kind": 3, + "sortText": "d" + }, + { + "label": ":visited", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":visited" + }, + "documentation": "Applies once the link has been visited by the user.", + "kind": 3, + "sortText": "d" + }, + { + "label": ":-webkit-any()", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":-webkit-any($1)" + }, + "documentation": "Represents an element that is represented by the selector list passed as its argument. Standardized as :matches().\n(Safari 5, Chrome)", + "kind": 3, + "insertTextFormat": 2, + "sortText": "x" + }, + { + "label": ":-webkit-full-screen", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":-webkit-full-screen" + }, + "documentation": "Matches any element that has its fullscreen flag set. Standardized as :fullscreen.\n(Safari 6, Chrome)", + "kind": 3, + "sortText": "x" + }, + { + "label": ":window-inactive", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":window-inactive" + }, + "documentation": "Non-standard. Applies to all scrollbar pieces. Indicates whether or not the window containing the scrollbar is currently active.\n(Safari 3, Chrome)", + "kind": 3, + "sortText": "d" + }, + { + "label": ":defined", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":defined" + }, + "documentation": "The :defined CSS pseudo-class represents any element that has been defined. This includes any standard element built in to the browser, and custom elements that have been successfully defined (i.e. with the CustomElementRegistry.define() method).\n(Safari, Chrome, Opera)", + "kind": 3, + "sortText": "d" + }, + { + "label": ":dir", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":dir" + }, + "documentation": "The :dir() CSS pseudo-class matches elements based on the directionality of the text contained in them.\n(Firefox 49)", + "kind": 3, + "sortText": "d" + }, + { + "label": ":focus-visible", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":focus-visible" + }, + "documentation": "The :focus-visible pseudo-class applies while an element matches the :focus pseudo-class and the UA determines via heuristics that the focus should be made evident on the element.", + "kind": 3, + "sortText": "d" + }, + { + "label": ":focus-within", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":focus-within" + }, + "documentation": "The :focus-within pseudo-class applies to any element for which the :focus pseudo class applies as well as to an element whose descendant in the flat tree (including non-element nodes, such as text nodes) matches the conditions for matching :focus.\n(Firefox 52, Safari 10.1, Chrome 60, Opera 47)", + "kind": 3, + "sortText": "d" + }, + { + "label": ":placeholder-shown", + "textEdit": { + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "newText": ":placeholder-shown" + }, + "documentation": "The :placeholder-shown CSS pseudo-class represents any or