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