├── .gitattributes
├── .gitignore
├── NuGet.Config
├── Peachpie.LanguageServer.sln
├── README.md
├── samples
└── diagnostics
│ ├── .vscode
│ ├── launch.json
│ └── tasks.json
│ ├── project.json
│ ├── undefined-types-members.php
│ └── undefined-variables.php
└── src
├── Peachpie.LanguageServer
├── .vscode
│ ├── launch.json
│ └── tasks.json
├── CompilationDiagnosticBroker.cs
├── EnvironmentUtils.cs
├── JsonRpc
│ ├── RpcNotification.cs
│ ├── RpcRequest.cs
│ └── RpcResponse.cs
├── MessageReader.cs
├── MessageWriter.cs
├── Peachpie.LanguageServer.csproj
├── PhpLanguageServer.cs
├── Program.cs
├── ProjectHandler.cs
├── Properties
│ └── AssemblyInfo.cs
├── Protocol
│ ├── Diagnostic.cs
│ ├── DidChangeTextDocumentParams.cs
│ ├── DidChangeWatchedFilesParams.cs
│ ├── DidOpenTextDocumentParams.cs
│ ├── FileEvent.cs
│ ├── Hover.cs
│ ├── InitializeParams.cs
│ ├── InitializeResult.cs
│ ├── Location.cs
│ ├── LogMessageParams.cs
│ ├── MarkedString.cs
│ ├── Position.cs
│ ├── PublishDiagnosticsParams.cs
│ ├── Range.cs
│ ├── ServerCapabilities.cs
│ ├── ShowMessageParams.cs
│ ├── TextDocumentContentChangeEvent.cs
│ ├── TextDocumentIdentifier.cs
│ ├── TextDocumentItem.cs
│ ├── TextDocumentPositionParams.cs
│ └── VersionedTextDocumentIdentifier.cs
├── ServerOptions.cs
├── SourceSymbolSearcher.cs
├── StrongKeys
│ └── core.snk
├── ToolTipInfo.cs
├── Utils
│ ├── ILogTarget.cs
│ ├── PathUtils.cs
│ ├── ProjectUtils.cs
│ ├── SpanUtils.cs
│ └── ToolTipUtils.cs
└── Workspaces
│ └── XmlDocumentationProvider.cs
└── Peachpie.VSCode
├── .gitignore
├── .vscode
├── launch.json
├── settings.json
└── tasks.json
├── .vscodeignore
├── CHANGELOG.md
├── README.md
├── icons
├── peachpie-icon-new.png
├── peachpie-icon.png
└── peachpie-vscode.png
├── images
├── README.md
├── Screen Shot 2017-04-17 at 14.59.19.png
├── breakpoint-cropped.png
├── breakpoint.png
├── create-project-cropped.png
├── create-project.png
├── debug-cropped.png
├── debug.png
├── diagnostics.gif
├── new-peachpie-project.gif
├── peachpie-icon.png
├── peachpie-round.png
├── project-icon.psd
├── round-orange-196x196.png
├── syntax-error.png
├── tEDLQt.gif
├── tooltips.gif
├── unresolved-diagnostics.png
├── vscode-diagnostics-long-cropped.png
└── vscode-diagnostics-long.png
├── package-lock.json
├── package.json
├── publish.ps1
├── src
├── defaults.ts
└── extension.ts
├── templates
├── console.msbuildproj
└── library.msbuildproj
├── test
├── extension.test.ts
└── index.ts
├── tsconfig.json
└── vsc-extension-quickstart.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 |
7 | # Standard to msysgit
8 | *.doc diff=astextplain
9 | *.DOC diff=astextplain
10 | *.docx diff=astextplain
11 | *.DOCX diff=astextplain
12 | *.dot diff=astextplain
13 | *.DOT diff=astextplain
14 | *.pdf diff=astextplain
15 | *.PDF diff=astextplain
16 | *.rtf diff=astextplain
17 | *.RTF diff=astextplain
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 | .vs/
13 |
14 | # Build results
15 | [Dd]ebug/
16 | [Dd]ebugPublic/
17 | [Rr]elease/
18 | [Rr]eleases/
19 | x64/
20 | x86/
21 | bld/
22 | [Bb]in/
23 | [Oo]bj/
24 |
25 | # Windows image file caches
26 | Thumbs.db
27 | ehthumbs.db
28 |
29 | # Folder config file
30 | Desktop.ini
31 |
32 | # Recycle Bin used on file shares
33 | $RECYCLE.BIN/
34 |
35 | # Windows Installer files
36 | *.cab
37 | *.msi
38 | *.msm
39 | *.msp
40 |
41 | # Windows shortcuts
42 | *.lnk
43 |
44 | # =========================
45 | # Operating System Files
46 | # =========================
47 |
48 | # OSX
49 | # =========================
50 |
51 | .DS_Store
52 | .AppleDouble
53 | .LSOverride
54 |
55 | # Thumbnails
56 | ._*
57 |
58 | # Files that might appear in the root of a volume
59 | .DocumentRevisions-V100
60 | .fseventsd
61 | .Spotlight-V100
62 | .TemporaryItems
63 | .Trashes
64 | .VolumeIcon.icns
65 |
66 | # Directories potentially created on remote AFP share
67 | .AppleDB
68 | .AppleDesktop
69 | Network Trash Folder
70 | Temporary Items
71 | .apdisk
72 |
--------------------------------------------------------------------------------
/NuGet.Config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Peachpie.LanguageServer.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26228.9
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Peachpie.LanguageServer", "src\Peachpie.LanguageServer\Peachpie.LanguageServer.csproj", "{A6BBDC42-26B3-4510-8284-FCB648877C1B}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{D069240A-A4E3-4C05-B069-C19B7A31E642}"
9 | ProjectSection(SolutionItems) = preProject
10 | NuGet.Config = NuGet.Config
11 | EndProjectSection
12 | EndProject
13 | Global
14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
15 | Debug|Any CPU = Debug|Any CPU
16 | Release|Any CPU = Release|Any CPU
17 | EndGlobalSection
18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
19 | {A6BBDC42-26B3-4510-8284-FCB648877C1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
20 | {A6BBDC42-26B3-4510-8284-FCB648877C1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
21 | {A6BBDC42-26B3-4510-8284-FCB648877C1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
22 | {A6BBDC42-26B3-4510-8284-FCB648877C1B}.Release|Any CPU.Build.0 = Release|Any CPU
23 | EndGlobalSection
24 | GlobalSection(SolutionProperties) = preSolution
25 | HideSolutionNode = FALSE
26 | EndGlobalSection
27 | GlobalSection(ExtensibilityGlobals) = postSolution
28 | SolutionGuid = {4756084D-827B-4466-80DA-6895D92AEFC2}
29 | EndGlobalSection
30 | EndGlobal
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | # Peachpie Visual Studio Code Extension
7 | This repository contains the projects necessary for the integration into Visual Studio Code.
8 | See the [repository of Peachpie - The PHP Compiler and Runtime for .NET](https://github.com/peachpiecompiler/peachpie) for more information about the whole project.
9 |
10 | ## Folder Structure
11 |
12 | - [src/Peachpie.VSCode](https://github.com/iolevel/peachpie-vscode/tree/master/src/Peachpie.VSCode) contains the Typescript Visual Studio Code Extension
13 |
14 |
15 | # Installing the Extension
16 | You can find the Peachpie extension for VSCode in the [marketplace](https://marketplace.visualstudio.com/items?itemName=iolevel.peachpie-vscode).
17 |
18 | Install it by launching VS Code Quick Open (Ctrl+P), then paste the following command, and press enter: ` ext install peachpie-vscode`
19 |
--------------------------------------------------------------------------------
/samples/diagnostics/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": ".NET Core Launch (console)",
6 | "type": "coreclr",
7 | "request": "launch",
8 | "preLaunchTask": "build",
9 | "program": "${workspaceRoot}/bin/Debug/netcoreapp1.0/diagnostics.dll",
10 | "args": [],
11 | "cwd": "${workspaceRoot}",
12 | "externalConsole": false,
13 | "stopAtEntry": false,
14 | "internalConsoleOptions": "openOnSessionStart"
15 | },
16 | {
17 | "name": ".NET Core Attach",
18 | "type": "coreclr",
19 | "request": "attach",
20 | "processId": "${command.pickProcess}"
21 | }
22 | ]
23 | }
--------------------------------------------------------------------------------
/samples/diagnostics/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.1.0",
3 | "command": "dotnet",
4 | "isShellCommand": true,
5 | "args": [],
6 | "tasks": [
7 | {
8 | "taskName": "build",
9 | "args": [
10 | "${workspaceRoot}/project.json"
11 | ],
12 | "isBuildCommand": true,
13 | "problemMatcher": "$msCompile"
14 | }
15 | ]
16 | }
--------------------------------------------------------------------------------
/samples/diagnostics/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "diagnostics",
3 | "version": "1.0.0",
4 | "description": "Demonstration of Peachpie diagnostics",
5 | "buildOptions": {
6 | "compilerName": "php",
7 | "compile": "**/*.php",
8 | "emitEntryPoint": true,
9 | "debugType": "portable"
10 | },
11 | "dependencies": {
12 | "Peachpie.App": "0.5.0-preview5"
13 | },
14 | "tools": {
15 | "Peachpie.Compiler.Tools": "0.5.0-preview5"
16 | },
17 | "frameworks": {
18 | "netcoreapp1.0": {
19 | "dependencies": {
20 | "Microsoft.NETCore.App": {
21 | "type": "platform",
22 | "version": "1.0.0"
23 | }
24 | }
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/samples/diagnostics/undefined-types-members.php:
--------------------------------------------------------------------------------
1 | property = $goodInstance->property + 1;
41 | $goodInstance->method();
42 |
43 | if ($goodInstance instanceof DefinedClass) {}
44 |
45 | try {
46 | } catch (DefinedException $exception) {
47 | }
48 |
49 | // Undefined types - diagnostics will appear for Undefined* (and nothing else)
50 |
51 | $b = undefinedFunction(5, 4);
52 | $b = UndefinedClass::Constant;
53 | $b = UndefinedClass::$staticProperty;
54 | $b = UndefinedClass::staticMethod();
55 | $b = UndefinedClass::undefinedStaticMethod();
56 |
57 | $badInstance = new UndefinedClass();
58 | $badInstance->property = $badInstance->property + 1;
59 | $badInstance->method();
60 |
61 | if ($badInstance instanceof UndefinedClass) {}
62 |
63 | try {
64 | } catch (UndefinedException $exception) {
65 | }
66 |
67 | // Unimplemented - diagnostics might appear in the future
68 |
69 | $b = DefinedClass::UndefinedStaticMethod();
70 |
71 | $goodInstance->UndefinedMethod();
72 | $goodInstance->undefinedProperty = $goodInstance->undefinedProperty + 1;
73 |
74 | $className = "OtherUndefinedClass";
75 | $d = new $className();
76 |
--------------------------------------------------------------------------------
/samples/diagnostics/undefined-variables.php:
--------------------------------------------------------------------------------
1 | 0) {
20 | $maybeUndefined = 0;
21 | }
22 | echo $maybeUndefined;
23 | }
24 |
25 | bar(3);
26 |
--------------------------------------------------------------------------------
/src/Peachpie.LanguageServer/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": ".NET Core Attach",
9 | "type": "coreclr",
10 | "request": "attach",
11 | "processId": "${command:pickProcess}"
12 | }
13 | ]
14 | }
--------------------------------------------------------------------------------
/src/Peachpie.LanguageServer/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}/Peachpie.LanguageServer.csproj"
11 | ],
12 | "problemMatcher": "$msCompile"
13 | }
14 | ]
15 | }
--------------------------------------------------------------------------------
/src/Peachpie.LanguageServer/CompilationDiagnosticBroker.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CodeAnalysis;
2 | using Pchp.CodeAnalysis;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 |
8 | namespace Peachpie.LanguageServer
9 | {
10 | public class CompilationDiagnosticBroker
11 | {
12 | // How long to wait before running the analysis after a modification
13 | private const int MillisecondsDelay = 1000;
14 |
15 | private readonly Action> _resultHandler;
16 | private Task> _diagnosticTask;
17 |
18 | public CompilationDiagnosticBroker(Action> resultHandler)
19 | {
20 | _resultHandler = resultHandler;
21 | }
22 |
23 | public PhpCompilation Compilation { get; private set; }
24 |
25 | public PhpCompilation LastAnalysedCompilation { get; private set; }
26 |
27 | public async void UpdateCompilation(PhpCompilation updatedCompilation)
28 | {
29 | Compilation = updatedCompilation;
30 |
31 | await AnalyseLazily(updatedCompilation);
32 | }
33 |
34 | private async Task AnalyseLazily(PhpCompilation updatedCompilation)
35 | {
36 | await Task.Delay(MillisecondsDelay);
37 |
38 | // Don't run the analysis if the compilation was modified since then (to enforce the delay)
39 | // or it is already running (to prevent running two tasks simultaneously)
40 | if (this.Compilation == updatedCompilation
41 | && (_diagnosticTask == null || _diagnosticTask.IsCompleted))
42 | {
43 | var analysedCompilation = this.Compilation;
44 | try
45 | {
46 | _diagnosticTask = analysedCompilation.BindAndAnalyseTask();
47 |
48 | var diagnostics = await _diagnosticTask;
49 | this.LastAnalysedCompilation = analysedCompilation;
50 | _resultHandler(diagnostics);
51 | }
52 | catch (DllNotFoundException)
53 | {
54 |
55 | }
56 |
57 | // If the compilation was changed during the analysis, attempt to run it again (with respect to the delay),
58 | // as it was blocked by the previous condition
59 | if (this.Compilation != updatedCompilation)
60 | {
61 | await AnalyseLazily(this.Compilation);
62 | }
63 | }
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Peachpie.LanguageServer/EnvironmentUtils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.IO;
5 | using System.Text;
6 | using System.Text.RegularExpressions;
7 | using System.Threading.Tasks;
8 |
9 | namespace Peachpie.LanguageServer
10 | {
11 | public static class EnvironmentUtils
12 | {
13 | const string DOTNET_CLI_UI_LANGUAGE = "DOTNET_CLI_UI_LANGUAGE";
14 |
15 | public static string NetCoreRuntimePath { get; private set; }
16 |
17 | public static string MSBuildSDKsPath { get; private set; }
18 |
19 | ///
20 | /// Initializes the properties.
21 | ///
22 | public static async Task InitializeAsync()
23 | {
24 | if (NetCoreRuntimePath != null)
25 | {
26 | return;
27 | }
28 |
29 | NetCoreRuntimePath = await GetNetCorePathAsync();
30 |
31 | MSBuildSDKsPath = Environment.GetEnvironmentVariable("MSBuildSDKsPath");
32 | if (MSBuildSDKsPath == null)
33 | {
34 | MSBuildSDKsPath = Path.Combine(NetCoreRuntimePath, "Sdks");
35 | }
36 | }
37 |
38 | private static async Task GetNetCorePathAsync()
39 | {
40 | // Ensure that we set the DOTNET_CLI_UI_LANGUAGE environment variable to "en-US" before
41 | // running 'dotnet --info'. Otherwise, we may get localized results.
42 | string originalCliLanguage = Environment.GetEnvironmentVariable(DOTNET_CLI_UI_LANGUAGE);
43 | Environment.SetEnvironmentVariable(DOTNET_CLI_UI_LANGUAGE, "en-US");
44 |
45 | using (var process = new Process()
46 | {
47 | StartInfo =
48 | {
49 | UseShellExecute = false,
50 | RedirectStandardOutput = true,
51 | RedirectStandardError = true,
52 | CreateNoWindow = true,
53 | FileName = "dotnet",
54 | Arguments = "--info"
55 | },
56 | })
57 | {
58 | try
59 | {
60 | process.Start();
61 | }
62 | catch
63 | {
64 | // no `dotnet`
65 | return null;
66 | }
67 | finally
68 | {
69 | Environment.SetEnvironmentVariable(DOTNET_CLI_UI_LANGUAGE, originalCliLanguage);
70 | }
71 |
72 | var output = await process.StandardOutput.ReadToEndAsync();
73 |
74 | if (!string.IsNullOrEmpty(output))
75 | {
76 | var regex = new Regex("Base Path:(.+)");
77 | var matches = regex.Match(output);
78 | if (matches.Groups.Count >= 2)
79 | {
80 | return matches.Groups[1].Value.Trim();
81 | }
82 | }
83 | }
84 |
85 | return null;
86 | }
87 |
88 | private static void ProcessDotnetInfoOutput(string output, ref TaskCompletionSource taskSource)
89 | {
90 | if (!string.IsNullOrEmpty(output))
91 | {
92 | var regex = new Regex("Base Path:(.+)");
93 | var matches = regex.Match(output);
94 | if (matches.Groups.Count >= 2)
95 | {
96 | string result = matches.Groups[1].Value.Trim();
97 | taskSource.SetResult(result);
98 |
99 | return;
100 | }
101 | }
102 |
103 | taskSource.SetException(new IOException("Cannot obtain the base path of .NET Core"));
104 | }
105 |
106 | public static Dictionary GetCoreGlobalProperties(string projectPath, string toolsPath)
107 | {
108 | string solutionDir = Path.GetDirectoryName(projectPath);
109 | string extensionsPath = toolsPath;
110 | string sdksPath = MSBuildSDKsPath;
111 | string roslynTargetsPath = Path.Combine(toolsPath, "Roslyn");
112 |
113 | return new Dictionary
114 | {
115 | { "SolutionDir", solutionDir },
116 | { "MSBuildExtensionsPath", extensionsPath },
117 | { "MSBuildSDKsPath", sdksPath },
118 | { "RoslynTargetsPath", roslynTargetsPath },
119 | { "DesignTimeBuild", "true" },
120 | //{ "SkipCompilerExecution", "true" },
121 | //{ "ProvideCommandLineArgs", "true" },
122 | };
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/Peachpie.LanguageServer/JsonRpc/RpcNotification.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Newtonsoft.Json.Linq;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 |
8 | namespace Peachpie.LanguageServer.JsonRpc
9 | {
10 | [JsonObject]
11 | internal class RpcNotification
12 | {
13 | [JsonProperty("method")]
14 | public string Method { get; set; }
15 |
16 | [JsonProperty("params")]
17 | public JObject Params { get; set; }
18 | }
19 |
20 | [JsonObject]
21 | internal class RpcNotification
22 | {
23 | [JsonProperty("method")]
24 | public string Method { get; set; }
25 |
26 | [JsonProperty("params")]
27 | public T Params { get; set; }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Peachpie.LanguageServer/JsonRpc/RpcRequest.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Newtonsoft.Json.Linq;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 |
8 | namespace Peachpie.LanguageServer.JsonRpc
9 | {
10 | [JsonObject]
11 | internal class RpcRequest
12 | {
13 | [JsonProperty("id")]
14 | public object Id { get; set; }
15 |
16 | [JsonProperty("method")]
17 | public string Method { get; set; }
18 |
19 | [JsonProperty("params")]
20 | public JObject Params { get; set; }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Peachpie.LanguageServer/JsonRpc/RpcResponse.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 |
7 | namespace Peachpie.LanguageServer.JsonRpc
8 | {
9 | [JsonObject]
10 | internal class RpcResponse
11 | {
12 | [JsonProperty("id")]
13 | public object Id { get; set; }
14 |
15 | [JsonProperty("result")]
16 | public object Result { get; set; }
17 |
18 | [JsonProperty("error", NullValueHandling = NullValueHandling.Ignore)]
19 | public object Error { get; set; }
20 | }
21 |
22 | [JsonObject]
23 | internal class RpcResponse
24 | {
25 | [JsonProperty("id")]
26 | public object Id { get; set; }
27 |
28 | [JsonProperty("result")]
29 | public T Result { get; set; }
30 |
31 | [JsonProperty("error", NullValueHandling = NullValueHandling.Ignore)]
32 | public object Error { get; set; }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Peachpie.LanguageServer/MessageReader.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Peachpie.LanguageServer.JsonRpc;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Diagnostics;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Threading;
10 | using System.Threading.Tasks;
11 |
12 | namespace Peachpie.LanguageServer
13 | {
14 | internal class MessageReader
15 | {
16 | private Stream _inputStream;
17 |
18 | public MessageReader(Stream inputStream)
19 | {
20 | _inputStream = inputStream;
21 | }
22 |
23 | public async Task ReadRequestAsync()
24 | {
25 | // TODO: Handle format errors (such as missing Content-Length)
26 | int? contentLength = await ReadHeader();
27 |
28 | var buffer = new byte[contentLength.Value];
29 | int readBytes = 0;
30 | while (readBytes < contentLength.Value)
31 | {
32 | readBytes += await _inputStream.ReadAsync(buffer, readBytes, contentLength.Value - readBytes);
33 | }
34 |
35 | var requestJson = Encoding.UTF8.GetString(buffer);
36 | var request = JsonConvert.DeserializeObject(requestJson);
37 |
38 | return request;
39 | }
40 |
41 | private async Task ReadHeader()
42 | {
43 | int? contentLength = null;
44 |
45 | var lineBytes = new List();
46 |
47 | // We use this workaround to perform the read of the next request asynchronously - there might be
48 | // significant delays between the requests and we do not want to block the thread during them.
49 | var singleByteBuffer = new byte[1];
50 | await _inputStream.ReadAsync(singleByteBuffer, 0, 1);
51 | int currentByte = singleByteBuffer[0];
52 |
53 | while (true)
54 | {
55 | if (currentByte == -1)
56 | {
57 | throw new IOException("Unexpected end of the stream in the request header");
58 | }
59 | else if (currentByte == '\r')
60 | {
61 | if (_inputStream.ReadByte() != '\n')
62 | {
63 | throw new IOException(@"Invalid end of the line, \r\n expected");
64 | }
65 |
66 | string line = Encoding.ASCII.GetString(lineBytes.ToArray());
67 | lineBytes.Clear();
68 |
69 | if (line == "")
70 | {
71 | break;
72 | }
73 |
74 | var tokens = line.Split(new string[] { ": " }, StringSplitOptions.None);
75 | if (tokens[0] == "Content-Length")
76 | {
77 | contentLength = int.Parse(tokens[1]);
78 | }
79 | }
80 | else
81 | {
82 | lineBytes.Add((byte)currentByte);
83 | }
84 |
85 | currentByte = _inputStream.ReadByte();
86 | }
87 |
88 | return contentLength;
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/Peachpie.LanguageServer/MessageWriter.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Peachpie.LanguageServer.JsonRpc;
3 | using System.IO;
4 | using System.Text;
5 |
6 | namespace Peachpie.LanguageServer
7 | {
8 | internal class MessageWriter
9 | {
10 | private Stream _outputStream;
11 |
12 | public MessageWriter(Stream outputStream)
13 | {
14 | _outputStream = outputStream;
15 | }
16 |
17 | public void WriteResponse(object requestId, T result)
18 | {
19 | var response = new RpcResponse()
20 | {
21 | Id = requestId,
22 | Result = result
23 | };
24 |
25 | SerializeAndSend(response);
26 | }
27 |
28 | public void WriteNotification(string method, T parameters)
29 | {
30 | var notification = new RpcNotification()
31 | {
32 | Method = method,
33 | Params = parameters
34 | };
35 |
36 | SerializeAndSend(notification);
37 | }
38 |
39 | private void SerializeAndSend(T data)
40 | {
41 | lock (this)
42 | {
43 | var dataText = JsonConvert.SerializeObject(data);
44 | var dataBytes = Encoding.UTF8.GetBytes(dataText);
45 |
46 | var headerText = $"Content-Length: {dataBytes.Length}\r\n\r\n";
47 | var headerBytes = Encoding.ASCII.GetBytes(headerText);
48 |
49 | _outputStream.Write(headerBytes, 0, headerBytes.Length);
50 | _outputStream.Write(dataBytes, 0, dataBytes.Length);
51 | }
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Peachpie.LanguageServer/Peachpie.LanguageServer.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 1.0.23
5 | net5.0
6 | Peachpie.LanguageServer
7 | latest
8 | Exe
9 | Peachpie.LanguageServer
10 | false
11 | false
12 | false
13 | True
14 | StrongKeys\core.snk
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/Peachpie.LanguageServer/PhpLanguageServer.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CodeAnalysis;
2 | using Newtonsoft.Json;
3 | using Pchp.CodeAnalysis;
4 | using Peachpie.LanguageServer.Protocol;
5 | using Peachpie.LanguageServer.Utils;
6 | using System;
7 | using System.Collections.Generic;
8 | using System.Collections.Immutable;
9 | using System.Diagnostics;
10 | using System.IO;
11 | using System.Linq;
12 | using System.Threading.Tasks;
13 |
14 | namespace Peachpie.LanguageServer
15 | {
16 | internal class PhpLanguageServer : ILogTarget
17 | {
18 | private const string DiagnosticSource = "peachpie";
19 |
20 | private ServerOptions _options;
21 | private MessageReader _requestReader;
22 | private MessageWriter _messageWriter;
23 |
24 | private string _rootPath;
25 | private ProjectHandler _project;
26 |
27 | static Version LatestPeachpieVersion
28 | {
29 | get
30 | {
31 | var v4 = typeof(PhpCompilation).Assembly.GetName().Version;
32 | return new Version(v4.Major, v4.Minor, v4.Build); // v3
33 | }
34 | }
35 |
36 | public PhpLanguageServer(ServerOptions options, MessageReader requestReader, MessageWriter messageWriter)
37 | {
38 | _options = options;
39 | _requestReader = requestReader;
40 | _messageWriter = messageWriter;
41 | }
42 |
43 | public async Task Run()
44 | {
45 | while (true)
46 | {
47 | var request = await _requestReader.ReadRequestAsync();
48 | //if (_options.IsDebug)
49 | //{
50 | // SendLogMessage($"Received: {JsonConvert.SerializeObject(request)}");
51 | //}
52 |
53 | switch (request.Method)
54 | {
55 | // initialize
56 |
57 | case "initialize":
58 | var initializeParams = request.Params.ToObject();
59 | SendInitializationResponse(request);
60 | await OpenFolder(initializeParams.RootPath);
61 | break;
62 | case "initialized":
63 | // ignore
64 | break;
65 |
66 | // shutdown/exit
67 |
68 | case "shutdown":
69 | _project?.Dispose();
70 | _messageWriter.WriteResponse