Skip to content

Commit 0b3ad15

Browse files
committed
Consume Editor Services host as PowerShell module
This change refactors the existing PowerShell Editor Services host management and editor client code to use the PSES host as a PowerShell module rather than its previous executable form. This involves launching the powershell.exe process and loading up the PSES host as a module in that process. It also involves communicating with the PSES host via named pipes instead of standard in/out as we did previously. This change can be understood further by looking at the following pull request at the PowerShellEditorServices repo: PowerShell/PowerShellEditorServices#256
1 parent 2e9b339 commit 0b3ad15

File tree

9 files changed

+416
-111
lines changed

9 files changed

+416
-111
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ examples/Release/
33
examples/Tests/foo*.txt
44
out/
55
node_modules/
6+
logs/
7+
modules/*
8+
!modules/README.md
69
vscode-powershell.zip
710
vscps-preview.zip
811
*.vsix

.vscodeignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ build/**
77
bin/EditorServices.log
88
bin/DebugAdapter.log
99
bin/*.vshost.*
10+
logs/
11+

modules/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
## `modules` folder README
2+
3+
This folder contains modules that are bundled with the vscode-powershell extension.
4+
All subfolders are not included in our GitHub repository, they are added here just
5+
before the module is published to the Visual Studio Marketplace.
6+
7+
This file serves as a placeholder so that the `modules` folder will be included
8+
in our Git repository.

package.json

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@
2929
"onLanguage:powershell"
3030
],
3131
"dependencies": {
32-
"vscode-languageclient": "1.3.1"
32+
"vscode-languageclient": "1.3.1",
33+
"vscode-debugadapter": "1.8.0"
3334
},
3435
"devDependencies": {
3536
"vscode": "^0.11.12",
@@ -118,19 +119,8 @@
118119
"powershell"
119120
]
120121
},
121-
"windows": {
122-
"program": "bin/Microsoft.PowerShell.EditorServices.Host.exe"
123-
},
124-
"winx86": {
125-
"program": "bin/Microsoft.PowerShell.EditorServices.Host.x86.exe"
126-
},
127-
"args": [
128-
"/debugAdapter",
129-
"/logLevel:Verbose",
130-
"/hostName:\"Visual Studio Code Host\"",
131-
"/hostProfileId:Microsoft.VSCode",
132-
"/hostVersion:0.6.1"
133-
],
122+
"program": "./out/debugAdapter.js",
123+
"runtime": "node",
134124
"configurationAttributes": {
135125
"launch": {
136126
"required": [
@@ -183,16 +173,8 @@
183173
"powershell"
184174
]
185175
},
186-
"windows": {
187-
"program": "bin/Microsoft.PowerShell.EditorServices.Host.x86.exe"
188-
},
189-
"args": [
190-
"/debugAdapter",
191-
"/logLevel:Verbose",
192-
"/hostName:\"Visual Studio Code Host\"",
193-
"/hostProfileId:Microsoft.VSCode",
194-
"/hostVersion:0.6.1"
195-
],
176+
"program": "./out/debugAdapter.js",
177+
"runtime": "node",
196178
"configurationAttributes": {
197179
"launch": {
198180
"required": [
@@ -255,10 +237,10 @@
255237
"default": "",
256238
"description": "Specifies the path to a PowerShell Script Analyzer settings file. Use either an absolute path (to override the default settings for all projects) or use a path relative to your workspace."
257239
},
258-
"powershell.developer.editorServicesHostPath": {
240+
"powershell.developer.bundledModulesPath": {
259241
"type": "string",
260-
"default": "../bin/",
261-
"description": "Specifies the path to the folder containing the PowerShell Editor Services host executables."
242+
"default": "../modules/",
243+
"description": "Specifies the path to the folder containing modules that are bundled with the PowerShell extension (i.e. PowerShell Editor Services, PowerShell Script Analyzer, Plaster)"
262244
},
263245
"powershell.developer.editorServicesLogLevel": {
264246
"type": "string",

scripts/Start-EditorServices.ps1

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
param(
2+
[Parameter(Mandatory=$true)]
3+
[ValidateNotNullOrEmpty()]
4+
[string]
5+
$EditorServicesVersion,
6+
7+
[Parameter(Mandatory=$true)]
8+
[ValidateNotNullOrEmpty()]
9+
[string]
10+
$HostName,
11+
12+
[Parameter(Mandatory=$true)]
13+
[ValidateNotNullOrEmpty()]
14+
[string]
15+
$HostProfileId,
16+
17+
[Parameter(Mandatory=$true)]
18+
[ValidateNotNullOrEmpty()]
19+
[string]
20+
$HostVersion,
21+
22+
[Parameter(Mandatory=$true)]
23+
[ValidateNotNullOrEmpty()]
24+
[string]
25+
$LanguageServicePipeName,
26+
27+
[Parameter(Mandatory=$true)]
28+
[ValidateNotNullOrEmpty()]
29+
[string]
30+
$DebugServicePipeName,
31+
32+
[ValidateNotNullOrEmpty()]
33+
[string]
34+
$BundledModulesPath,
35+
36+
[ValidateNotNullOrEmpty()]
37+
$LogPath,
38+
39+
[ValidateSet("Normal", "Verbose", "Error")]
40+
$LogLevel,
41+
42+
[switch]
43+
$WaitForCompletion,
44+
45+
[switch]
46+
$WaitForDebugger
47+
)
48+
49+
# Add BundledModulesPath to $env:PSModulePath
50+
if ($BundledModulesPath) {
51+
$env:PSModulePath = $BundledModulesPath + ";" + $env:PSModulePath
52+
}
53+
54+
$parsedVersion = [System.Version]::new($EditorServicesVersion)
55+
Import-Module PowerShellEditorServices -RequiredVersion $parsedVersion -ErrorAction Stop
56+
57+
Start-EditorServicesHost `
58+
-HostName $HostName `
59+
-HostProfileId $HostProfileId `
60+
-HostVersion $HostVersion `
61+
-LogPath $LogPath `
62+
-LogLevel $LogLevel `
63+
-LanguageServicePipeName $LanguageServicePipeName `
64+
-DebugServicePipeName $DebugServicePipeName `
65+
-BundledModulesPath $BundledModulesPath `
66+
-WaitForCompletion:$WaitForCompletion.IsPresent `
67+
-WaitForDebugger:$WaitForDebugger.IsPresent

src/debugAdapter.ts

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles, ErrorDestination, Event } from 'vscode-debugadapter';
2+
import { DebugProtocol } from 'vscode-debugprotocol';
3+
import fs = require('fs');
4+
import path = require('path');
5+
import net = require('net');
6+
import logging = require('./logging');
7+
import * as ee from 'events';
8+
9+
var logBasePath = path.resolve(__dirname, "../logs");
10+
logging.ensurePathExists(logBasePath);
11+
12+
var debugAdapterLogWriter =
13+
fs.createWriteStream(
14+
path.resolve(
15+
logBasePath,
16+
logging.getLogName("DebugAdapterClient")));
17+
18+
// Pause the stdin buffer until we're connected to the
19+
// debug server
20+
process.stdin.pause();
21+
22+
class PowerShellDebugSession extends DebugSession {
23+
24+
private static TWOCRLF = '\r\n\r\n';
25+
26+
private readBuffer: Buffer;
27+
private currentContentLength: number;
28+
private debugServiceSocket: net.Socket;
29+
30+
public constructor(debuggerLinesAndColumnsStartAt1?: boolean, isServer?: boolean) {
31+
super();
32+
this.readBuffer = new Buffer(0);
33+
34+
// Establish connection before setting up the session
35+
let pipeName = "\\\\.\\pipe\\PSES-VSCode-DebugService-" + process.env.VSCODE_PID;
36+
debugAdapterLogWriter.write("Connecting to named pipe: " + pipeName + "\r\n");
37+
this.debugServiceSocket = net.connect(pipeName);
38+
this.debugServiceSocket.on(
39+
'error',
40+
(e) => {
41+
debugAdapterLogWriter.write("Named pipe ERROR: " + e + "\r\n");
42+
}
43+
)
44+
this.debugServiceSocket.on(
45+
'data',
46+
(data: Buffer) => this.readData(data));
47+
48+
// When the connection completes, start the debug session
49+
this.debugServiceSocket.on(
50+
'connect',
51+
() => {
52+
debugAdapterLogWriter.write("Connecting to named pipe: " + pipeName + "\r\n");
53+
54+
// Resume the stdin stream
55+
process.stdin.resume();
56+
});
57+
}
58+
59+
protected dispatchRequest(request: DebugProtocol.Request): void {
60+
debugAdapterLogWriter.write("Sending request '" + request.command + "'\r\n");
61+
62+
var messageJson = JSON.stringify(request);
63+
this.debugServiceSocket.write(
64+
`Content-Length: ${Buffer.byteLength(messageJson, 'utf8')}\r\n\r\n${messageJson}`,
65+
'utf8');
66+
}
67+
68+
private readData(data: Buffer): void {
69+
this.readBuffer = Buffer.concat([this.readBuffer, data]);
70+
71+
while (true) {
72+
if (this.currentContentLength >= 0) {
73+
if (this.readBuffer.length >= this.currentContentLength) {
74+
const message = this.readBuffer.toString('utf8', 0, this.currentContentLength);
75+
this.readBuffer = this.readBuffer.slice(this.currentContentLength);
76+
this.currentContentLength = -1;
77+
78+
if (message.length > 0) {
79+
try {
80+
let msg: DebugProtocol.ProtocolMessage = JSON.parse(message);
81+
if (msg.type === 'response') {
82+
var response = <DebugProtocol.Response> msg;
83+
debugAdapterLogWriter.write("Received response for command '" + response.command + "'\r\n")
84+
this.sendResponse(response);
85+
86+
// If this is the response to disconnect, shut down after the
87+
// response is sent back to the client
88+
if (response.command === 'disconnect') {
89+
this.shutdown();
90+
}
91+
}
92+
else if (msg.type === 'event') {
93+
var evt = <DebugProtocol.Event> msg;
94+
debugAdapterLogWriter.write("Received event '" + evt.event + "'\r\n")
95+
this.sendEvent(evt);
96+
}
97+
}
98+
catch (e) {
99+
this.emitEvent(new Event('error'));
100+
}
101+
}
102+
continue; // there may be more complete messages to process
103+
}
104+
} else {
105+
const idx = this.readBuffer.indexOf(PowerShellDebugSession.TWOCRLF);
106+
if (idx !== -1) {
107+
const header = this.readBuffer.toString('utf8', 0, idx);
108+
const lines = header.split('\r\n');
109+
for (let i = 0; i < lines.length; i++) {
110+
const pair = lines[i].split(/: +/);
111+
if (pair[0] == 'Content-Length') {
112+
this.currentContentLength = +pair[1];
113+
}
114+
}
115+
this.readBuffer = this.readBuffer.slice(idx + PowerShellDebugSession.TWOCRLF.length);
116+
continue;
117+
}
118+
}
119+
break;
120+
}
121+
}
122+
123+
private emitEvent(event: DebugProtocol.Event) {
124+
this.emit(event.event, event);
125+
}
126+
}
127+
128+
// Run the debug session
129+
DebugSession.run(PowerShellDebugSession);

src/logging.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import fs = require('fs');
2+
3+
export function ensurePathExists(targetPath: string) {
4+
// Ensure that the path exists
5+
try {
6+
fs.mkdirSync(targetPath);
7+
}
8+
catch (e) {
9+
// If the exception isn't to indicate that the folder
10+
// exists already, rethrow it.
11+
if (e.code != 'EEXIST') {
12+
throw e;
13+
}
14+
}
15+
}
16+
17+
export function getLogName(baseName: string): string {
18+
return Math.floor(Date.now() / 1000) + '-' + baseName + '.log';
19+
}

0 commit comments

Comments
 (0)