Skip to content

Commit 3f92dd0

Browse files
authored
Merge pull request #529 from PowerShell/daviwil/console-redux
Add PowerShell interactive console
2 parents fd00c13 + 4cc9fd1 commit 3f92dd0

File tree

8 files changed

+168
-75
lines changed

8 files changed

+168
-75
lines changed

examples/PromptExamples.ps1

+34-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,41 @@
1+
<# ------ Input Prompts ------ #>
2+
3+
$fields = @(
4+
New-Object "System.Management.Automation.Host.FieldDescription" "Input"
5+
New-Object "System.Management.Automation.Host.FieldDescription" "Input List"
6+
)
7+
$fields[1].SetParameterType([int[]])
8+
9+
$host.UI.Prompt("Caption", "Message", $fields)
10+
11+
Get-Credential
12+
Get-Credential -Message "Test!"
13+
Get-Credential -UserName "myuser" -Message "Password stealer"
14+
15+
$host.UI.PromptForCredential("Caption", "Message", $null, $null, [System.Management.Automation.PSCredentialTypes]::Default, [System.Management.Automation.PSCredentialUIOptions]::Default)
16+
$host.UI.PromptForCredential("Caption", "Message", "testuser", $null, [System.Management.Automation.PSCredentialTypes]::Default, [System.Management.Automation.PSCredentialUIOptions]::Default)
17+
18+
Read-Host -AsSecureString
19+
Read-Host -Prompt "Enter a secure string" -AsSecureString
20+
21+
$field = New-Object "System.Management.Automation.Host.FieldDescription" "SecureString"
22+
$field.SetParameterType([SecureString])
23+
$host.UI.Prompt("Caption", "Message", $field)
24+
25+
$field = New-Object "System.Management.Automation.Host.FieldDescription" "PSCredential"
26+
$field.SetParameterType([PSCredential])
27+
$host.UI.Prompt("Caption", "Message", $field)
28+
29+
<# ------ Choice Prompts ------ #>
130

2-
# Multi-choice prompt
331
$choices = @(
432
New-Object "System.Management.Automation.Host.ChoiceDescription" "&Apple", "Apple"
533
New-Object "System.Management.Automation.Host.ChoiceDescription" "&Banana", "Banana"
634
New-Object "System.Management.Automation.Host.ChoiceDescription" "&Orange", "Orange"
735
)
836

9-
$defaults = [int[]]@(0, 2)
10-
$host.UI.PromptForChoice("Choose a fruit", "You may choose more than one", $choices, $defaults)
37+
# Single-choice prompt
38+
$host.UI.PromptForChoice("Choose a fruit", "You may choose one", $choices, 1)
39+
40+
# Multi-choice prompt
41+
$host.UI.PromptForChoice("Choose a fruit", "You may choose more than one", $choices, [int[]]@(0, 2))

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,8 @@
134134
"category": "PowerShell"
135135
},
136136
{
137-
"command": "PowerShell.ShowSessionOutput",
138-
"title": "Show Session Output",
137+
"command": "PowerShell.ShowSessionConsole",
138+
"title": "Show Session Interactive Console",
139139
"category": "PowerShell"
140140
},
141141
{

scripts/Start-EditorServices.ps1

+23-2
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,28 @@ param(
4747
[ValidateSet("Normal", "Verbose", "Error")]
4848
$LogLevel,
4949

50+
[Parameter(Mandatory=$true)]
51+
[ValidateNotNullOrEmpty()]
52+
[string]
53+
$SessionDetailsPath,
54+
55+
[switch]
56+
$EnableConsoleRepl,
57+
58+
[string]
59+
$DebugServiceOnly,
60+
5061
[switch]
5162
$WaitForDebugger,
5263

5364
[switch]
5465
$ConfirmInstall
5566
)
5667

68+
function WriteSessionFile($sessionInfo) {
69+
ConvertTo-Json -InputObject $sessionInfo -Compress | Set-Content -Force -Path "$SessionDetailsPath" -ErrorAction Stop
70+
}
71+
5772
# Are we running in PowerShell 2 or earlier?
5873
if ($PSVersionTable.PSVersion.Major -le 2) {
5974
$resultDetails = @{
@@ -63,7 +78,9 @@ if ($PSVersionTable.PSVersion.Major -le 2) {
6378
};
6479

6580
# Notify the client that the services have started
66-
Write-Output (ConvertTo-Json -InputObject $resultDetails -Compress)
81+
WriteSessionFile $resultDetails
82+
83+
Write-Host "Unsupported PowerShell version $($PSVersionTable.PSVersion), language features are disabled.`n"
6784

6885
exit 0;
6986
}
@@ -181,6 +198,8 @@ else {
181198
$languageServicePort = Get-AvailablePort
182199
$debugServicePort = Get-AvailablePort
183200

201+
Write-Host "Starting PowerShell...`n" -ForegroundColor Blue
202+
184203
# Create the Editor Services host
185204
$editorServicesHost =
186205
Start-EditorServicesHost `
@@ -192,6 +211,8 @@ $editorServicesHost =
192211
-LanguageServicePort $languageServicePort `
193212
-DebugServicePort $debugServicePort `
194213
-BundledModulesPath $BundledModulesPath `
214+
-EnableConsoleRepl:$EnableConsoleRepl.IsPresent `
215+
-DebugServiceOnly:$DebugServiceOnly.IsPresent `
195216
-WaitForDebugger:$WaitForDebugger.IsPresent
196217

197218
# TODO: Verify that the service is started
@@ -204,7 +225,7 @@ $resultDetails = @{
204225
};
205226

206227
# Notify the client that the services have started
207-
Write-Output (ConvertTo-Json -InputObject $resultDetails -Compress)
228+
WriteSessionFile $resultDetails
208229

209230
try {
210231
# Wait for the host to complete execution before exiting

src/features/Console.ts

+2-15
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,6 @@ function onInputEntered(responseText: string): ShowInputPromptResponseBody {
190190
export class ConsoleFeature implements IFeature {
191191
private commands: vscode.Disposable[];
192192
private languageClient: LanguageClient;
193-
private consoleChannel: vscode.OutputChannel;
194193

195194
constructor() {
196195
this.commands = [
@@ -217,17 +216,10 @@ export class ConsoleFeature implements IFeature {
217216
expression: editor.document.getText(selectionRange)
218217
});
219218

220-
// Show the output window if it isn't already visible
221-
this.consoleChannel.show(vscode.ViewColumn.Three);
222-
}),
223-
224-
vscode.commands.registerCommand('PowerShell.ShowSessionOutput', () => {
225-
// Show the output window if it isn't already visible
226-
this.consoleChannel.show(vscode.ViewColumn.Three);
219+
// Show the integrated console if it isn't already visible
220+
vscode.commands.executeCommand("PowerShell.ShowSessionConsole");
227221
})
228222
];
229-
230-
this.consoleChannel = vscode.window.createOutputChannel("PowerShell Output");
231223
}
232224

233225
public setLanguageClient(languageClient: LanguageClient) {
@@ -240,14 +232,9 @@ export class ConsoleFeature implements IFeature {
240232
this.languageClient.onRequest(
241233
ShowInputPromptRequest.type,
242234
promptDetails => showInputPrompt(promptDetails, this.languageClient));
243-
244-
this.languageClient.onNotification(OutputNotification.type, (output) => {
245-
this.consoleChannel.append(output.output);
246-
});
247235
}
248236

249237
public dispose() {
250238
this.commands.forEach(command => command.dispose());
251-
this.consoleChannel.dispose();
252239
}
253240
}

src/features/DebugSession.ts

+8
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export class DebugSessionFeature implements IFeature {
2424
}
2525

2626
private startDebugSession(config: any) {
27+
2728
if (!config.request) {
2829
// No launch.json, create the default configuration
2930
config.type = 'PowerShell';
@@ -57,6 +58,13 @@ export class DebugSessionFeature implements IFeature {
5758
}
5859
}
5960

61+
// Prevent the Debug Console from opening
62+
config.internalConsoleOptions = "neverOpen";
63+
64+
// Create or show the interactive console
65+
// TODO #367: Check if "newSession" mode is configured
66+
vscode.commands.executeCommand('PowerShell.ShowSessionConsole');
67+
6068
vscode.commands.executeCommand('vscode.startDebug', config);
6169
}
6270
}

src/session.ts

+70-52
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,11 @@ export class SessionManager {
6464
private hostVersion: string;
6565
private isWindowsOS: boolean;
6666
private sessionStatus: SessionStatus;
67-
private powerShellProcess: cp.ChildProcess;
6867
private statusBarItem: vscode.StatusBarItem;
6968
private sessionConfiguration: SessionConfiguration;
7069
private versionDetails: PowerShellVersionDetails;
7170
private registeredCommands: vscode.Disposable[] = [];
71+
private consoleTerminal: vscode.Terminal = undefined;
7272
private languageServerClient: LanguageClient = undefined;
7373
private sessionSettings: Settings.ISettings = undefined;
7474

@@ -136,7 +136,8 @@ export class SessionManager {
136136
"-HostName 'Visual Studio Code Host' " +
137137
"-HostProfileId 'Microsoft.VSCode' " +
138138
"-HostVersion '" + this.hostVersion + "' " +
139-
"-BundledModulesPath '" + bundledModulesPath + "' ";
139+
"-BundledModulesPath '" + bundledModulesPath + "' " +
140+
"-EnableConsoleRepl ";
140141

141142
if (this.sessionSettings.developer.editorServicesWaitForDebugger) {
142143
startArgs += '-WaitForDebugger ';
@@ -169,7 +170,7 @@ export class SessionManager {
169170
// Before moving further, clear out the client and process if
170171
// the process is already dead (i.e. it crashed)
171172
this.languageServerClient = undefined;
172-
this.powerShellProcess = undefined;
173+
this.consoleTerminal = undefined;
173174
}
174175

175176
this.sessionStatus = SessionStatus.Stopping;
@@ -184,10 +185,10 @@ export class SessionManager {
184185
utils.deleteSessionFile();
185186

186187
// Kill the PowerShell process we spawned via the console
187-
if (this.powerShellProcess !== undefined) {
188+
if (this.consoleTerminal !== undefined) {
188189
this.log.write(os.EOL + "Terminating PowerShell process...");
189-
this.powerShellProcess.kill();
190-
this.powerShellProcess = undefined;
190+
this.consoleTerminal.dispose();
191+
this.consoleTerminal = undefined;
191192
}
192193

193194
this.sessionStatus = SessionStatus.NotStarted;
@@ -242,7 +243,8 @@ export class SessionManager {
242243
this.registeredCommands = [
243244
vscode.commands.registerCommand('PowerShell.RestartSession', () => { this.restartSession(); }),
244245
vscode.commands.registerCommand(this.ShowSessionMenuCommandName, () => { this.showSessionMenu(); }),
245-
vscode.workspace.onDidChangeConfiguration(() => this.onConfigurationUpdated())
246+
vscode.workspace.onDidChangeConfiguration(() => this.onConfigurationUpdated()),
247+
vscode.commands.registerCommand('PowerShell.ShowSessionConsole', () => { this.showSessionConsole(); })
246248
]
247249
}
248250

@@ -264,7 +266,9 @@ export class SessionManager {
264266

265267
var editorServicesLogPath = this.log.getLogFilePath("EditorServices");
266268

267-
startArgs += "-LogPath '" + editorServicesLogPath + "' ";
269+
startArgs +=
270+
"-LogPath '" + editorServicesLogPath + "' " +
271+
"-SessionDetailsPath '" + utils.getSessionFilePath() + "' ";
268272

269273
var powerShellArgs = [
270274
"-NoProfile",
@@ -291,57 +295,63 @@ export class SessionManager {
291295
delete process.env.DEVPATH;
292296
}
293297

294-
// Launch PowerShell as child process
295-
this.powerShellProcess =
296-
cp.spawn(
298+
// Make sure no old session file exists
299+
utils.deleteSessionFile();
300+
301+
// Launch PowerShell in the integrated terminal
302+
this.consoleTerminal =
303+
vscode.window.createTerminal(
304+
"PowerShell Integrated Console",
297305
powerShellExePath,
298-
powerShellArgs,
299-
{ env: process.env });
306+
powerShellArgs);
300307

301-
var decoder = new StringDecoder('utf8');
302-
this.powerShellProcess.stdout.on(
303-
'data',
304-
(data: Buffer) => {
305-
this.log.write("OUTPUT: " + data);
306-
var response = JSON.parse(decoder.write(data).trim());
308+
this.consoleTerminal.show();
307309

308-
if (response["status"] === "started") {
309-
let sessionDetails: utils.EditorServicesSessionDetails = response;
310+
// Start the language client
311+
utils.waitForSessionFile(
312+
(sessionDetails, error) => {
313+
if (sessionDetails) {
314+
if (sessionDetails.status === "started") {
315+
// Write out the session configuration file
316+
utils.writeSessionFile(sessionDetails);
310317

311-
// Start the language service client
312-
this.startLanguageClient(sessionDetails);
313-
}
314-
else if (response["status"] === "failed") {
315-
if (response["reason"] === "unsupported") {
316-
this.setSessionFailure(
317-
`PowerShell language features are only supported on PowerShell version 3 and above. The current version is ${response["powerShellVersion"]}.`)
318+
// Start the language service client
319+
this.startLanguageClient(sessionDetails);
320+
}
321+
else if (sessionDetails.status === "failed") {
322+
if (sessionDetails.reason === "unsupported") {
323+
this.setSessionFailure(
324+
`PowerShell language features are only supported on PowerShell version 3 and above. The current version is ${sessionDetails.powerShellVersion}.`)
325+
}
326+
else {
327+
this.setSessionFailure(`PowerShell could not be started for an unknown reason '${sessionDetails.reason}'`)
328+
}
318329
}
319330
else {
320-
this.setSessionFailure(`PowerShell could not be started for an unknown reason '${response["reason"]}'`)
331+
// TODO: Handle other response cases
321332
}
322333
}
323334
else {
324-
// TODO: Handle other response cases
335+
this.setSessionFailure("Could not start language service: ", error);
325336
}
326337
});
327338

328-
this.powerShellProcess.stderr.on(
329-
'data',
330-
(data) => {
331-
this.log.writeError("ERROR: " + data);
339+
// this.powerShellProcess.stderr.on(
340+
// 'data',
341+
// (data) => {
342+
// this.log.writeError("ERROR: " + data);
332343

333-
if (this.sessionStatus === SessionStatus.Initializing) {
334-
this.setSessionFailure("PowerShell could not be started, click 'Show Logs' for more details.");
335-
}
336-
else if (this.sessionStatus === SessionStatus.Running) {
337-
this.promptForRestart();
338-
}
339-
});
344+
// if (this.sessionStatus === SessionStatus.Initializing) {
345+
// this.setSessionFailure("PowerShell could not be started, click 'Show Logs' for more details.");
346+
// }
347+
// else if (this.sessionStatus === SessionStatus.Running) {
348+
// this.promptForRestart();
349+
// }
350+
// });
340351

341-
this.powerShellProcess.on(
342-
'close',
343-
(exitCode) => {
344-
this.log.write(os.EOL + "powershell.exe terminated with exit code: " + exitCode + os.EOL);
352+
vscode.window.onDidCloseTerminal(
353+
terminal => {
354+
this.log.write(os.EOL + "powershell.exe terminated or terminal UI was closed" + os.EOL);
345355

346356
if (this.languageServerClient != undefined) {
347357
this.languageServerClient.stop();
@@ -353,13 +363,15 @@ export class SessionManager {
353363
}
354364
});
355365

356-
console.log("powershell.exe started, pid: " + this.powerShellProcess.pid + ", exe: " + powerShellExePath);
357-
this.log.write(
358-
"powershell.exe started --",
359-
" pid: " + this.powerShellProcess.pid,
360-
" exe: " + powerShellExePath,
361-
" bundledModulesPath: " + bundledModulesPath,
362-
" args: " + startScriptPath + ' ' + startArgs + os.EOL + os.EOL);
366+
this.consoleTerminal.processId.then(
367+
pid => {
368+
console.log("powershell.exe started, pid: " + pid + ", exe: " + powerShellExePath);
369+
this.log.write(
370+
"powershell.exe started --",
371+
" pid: " + pid,
372+
" exe: " + powerShellExePath,
373+
" args: " + startScriptPath + ' ' + startArgs + os.EOL + os.EOL);
374+
});
363375
}
364376
catch (e)
365377
{
@@ -595,6 +607,12 @@ export class SessionManager {
595607
return resolvedPath;
596608
}
597609

610+
private showSessionConsole() {
611+
if (this.consoleTerminal) {
612+
this.consoleTerminal.show();
613+
}
614+
}
615+
598616
private showSessionMenu() {
599617
var menuItems: SessionMenuItem[] = [];
600618

0 commit comments

Comments
 (0)