Skip to content

Commit 6516a6a

Browse files
committed
Add choice prompt handling for PSHostUserInterface
This change adds a general solution for handling the PromptForChoice method in our PSHostUserInterface implementation. It abstracts the display and response handling for prompts so that different prompt interfaces can be used in different contexts, all within the same session. The initial implementation includes a console-based prompt handler which operates much like the standard prompt display in the PowerShell console. Another implementation is provided which sends an event through the JSON protocol so that an editor client can display the prompt in an appropriate user interface. Resolves #85.
1 parent 3c5cceb commit 6516a6a

32 files changed

+1895
-829
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//
2+
// Copyright (c) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
4+
//
5+
6+
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
7+
8+
namespace Microsoft.PowerShell.EditorServices.Protocol.Messages
9+
{
10+
public class ShowChoicePromptNotification
11+
{
12+
public static readonly
13+
EventType<ShowChoicePromptNotification> Type =
14+
EventType<ShowChoicePromptNotification>.Create("powerShell/showChoicePrompt");
15+
16+
public string Caption { get; set; }
17+
18+
public string Message { get; set; }
19+
20+
public ChoiceDetails[] Choices { get; set; }
21+
22+
public int DefaultChoice { get; set; }
23+
}
24+
25+
public class CompleteChoicePromptNotification
26+
{
27+
public static readonly
28+
EventType<CompleteChoicePromptNotification> Type =
29+
EventType<CompleteChoicePromptNotification>.Create("powerShell/completeChoicePrompt");
30+
31+
public bool PromptCancelled { get; set; }
32+
33+
public string ChosenItem { get; set; }
34+
}
35+
}
36+

src/PowerShellEditorServices.Protocol/PowerShellEditorServices.Protocol.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
<Compile Include="DebugAdapter\AttachRequest.cs" />
6565
<Compile Include="DebugAdapter\Breakpoint.cs" />
6666
<Compile Include="DebugAdapter\ContinueRequest.cs" />
67+
<Compile Include="Messages\PromptEvents.cs" />
6768
<Compile Include="Server\DebugAdapter.cs" />
6869
<Compile Include="Server\DebugAdapterBase.cs" />
6970
<Compile Include="Client\DebugAdapterClientBase.cs" />
@@ -101,6 +102,7 @@
101102
<Compile Include="LanguageServer\ExpandAliasRequest.cs" />
102103
<Compile Include="LanguageServer\Hover.cs" />
103104
<Compile Include="Client\LanguageClientBase.cs" />
105+
<Compile Include="Server\IEventWriter.cs" />
104106
<Compile Include="Server\LanguageServer.cs" />
105107
<Compile Include="Server\LanguageServerBase.cs" />
106108
<Compile Include="LanguageServer\ShowOnlineHelpRequest.cs" />
@@ -132,6 +134,7 @@
132134
<Compile Include="Properties\AssemblyInfo.cs" />
133135
<Compile Include="LanguageServer\References.cs" />
134136
<Compile Include="Server\LanguageServerSettings.cs" />
137+
<Compile Include="Server\PromptHandlers.cs" />
135138
<Compile Include="Server\ProtocolServer.cs" />
136139
</ItemGroup>
137140
<ItemGroup>

src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public DebugAdapter(ChannelBase serverChannel) : base(serverChannel)
2929
this.editorSession = new EditorSession();
3030
this.editorSession.StartSession();
3131
this.editorSession.DebugService.DebuggerStopped += this.DebugService_DebuggerStopped;
32-
this.editorSession.PowerShellContext.OutputWritten += this.powerShellContext_OutputWritten;
32+
this.editorSession.ConsoleService.OutputWritten += this.powerShellContext_OutputWritten;
3333
}
3434

3535
protected override void Initialize()
@@ -312,27 +312,37 @@ protected async Task HandleEvaluateRequest(
312312
EvaluateRequestArguments evaluateParams,
313313
RequestContext<EvaluateResponseBody> requestContext)
314314
{
315+
string valueString = null;
316+
int variableId = 0;
317+
315318
bool isFromRepl =
316319
string.Equals(
317320
evaluateParams.Context,
318321
"repl",
319322
StringComparison.InvariantCultureIgnoreCase);
320323

321-
VariableDetails result =
322-
await editorSession.DebugService.EvaluateExpression(
324+
if (isFromRepl)
325+
{
326+
// Send the input through the console service
327+
editorSession.ConsoleService.ReceiveInputString(
323328
evaluateParams.Expression,
324-
evaluateParams.FrameId,
325-
isFromRepl);
326-
327-
string valueString = null;
328-
int variableId = 0;
329-
330-
if (result != null)
329+
false);
330+
}
331+
else
331332
{
332-
valueString = result.ValueString;
333-
variableId =
334-
result.IsExpandable ?
335-
result.Id : 0;
333+
VariableDetails result =
334+
await editorSession.DebugService.EvaluateExpression(
335+
evaluateParams.Expression,
336+
evaluateParams.FrameId,
337+
isFromRepl);
338+
339+
if (result != null)
340+
{
341+
valueString = result.ValueString;
342+
variableId =
343+
result.IsExpandable ?
344+
result.Id : 0;
345+
}
336346
}
337347

338348
await requestContext.SendResult(
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//
2+
// Copyright (c) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
4+
//
5+
6+
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
7+
using System.Threading.Tasks;
8+
9+
namespace Microsoft.PowerShell.EditorServices.Protocol.Server
10+
{
11+
internal interface IEventWriter
12+
{
13+
Task SendEvent<TParams>(
14+
EventType<TParams> eventType,
15+
TParams eventParams);
16+
}
17+
}
18+

src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,22 @@
66
using Microsoft.PowerShell.EditorServices.Protocol.LanguageServer;
77
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
88
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel;
9+
using Microsoft.PowerShell.EditorServices.Protocol.Messages;
910
using Microsoft.PowerShell.EditorServices.Utility;
1011
using Nito.AsyncEx;
1112
using System;
1213
using System.Collections.Generic;
1314
using System.IO;
1415
using System.Linq;
1516
using System.Management.Automation;
16-
using System.Management.Automation.Language;
1717
using System.Text.RegularExpressions;
1818
using System.Threading;
1919
using System.Threading.Tasks;
2020
using DebugAdapterMessages = Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter;
2121

2222
namespace Microsoft.PowerShell.EditorServices.Protocol.Server
2323
{
24-
public class LanguageServer : LanguageServerBase
24+
public class LanguageServer : LanguageServerBase, IEventWriter
2525
{
2626
private static CancellationTokenSource existingRequestCancellation;
2727

@@ -36,7 +36,13 @@ public LanguageServer(ChannelBase serverChannel) : base(serverChannel)
3636
{
3737
this.editorSession = new EditorSession();
3838
this.editorSession.StartSession();
39-
this.editorSession.PowerShellContext.OutputWritten += this.powerShellContext_OutputWritten;
39+
this.editorSession.ConsoleService.OutputWritten += this.powerShellContext_OutputWritten;
40+
41+
// Always send console prompts through the UI in the language service
42+
// TODO: This will change later once we have a general REPL available
43+
// in VS Code.
44+
this.editorSession.ConsoleService.PushPromptHandlerContext(
45+
new ProtocolPromptHandlerContext(this));
4046
}
4147

4248
protected override void Initialize()
@@ -62,6 +68,7 @@ protected override void Initialize()
6268

6369
this.SetRequestHandler(ShowOnlineHelpRequest.Type, this.HandleShowOnlineHelpRequest);
6470
this.SetRequestHandler(ExpandAliasRequest.Type, this.HandleExpandAliasRequest);
71+
this.SetEventHandler(CompleteChoicePromptNotification.Type, this.HandleCompleteChoicePromptNotification);
6572

6673
this.SetRequestHandler(DebugAdapterMessages.EvaluateRequest.Type, this.HandleEvaluateRequest);
6774
}
@@ -166,6 +173,25 @@ Sort Start -Descending
166173
await requestContext.SendResult(result.First().ToString());
167174
}
168175

176+
protected Task HandleCompleteChoicePromptNotification(
177+
CompleteChoicePromptNotification completeChoicePromptParams,
178+
EventContext eventContext)
179+
{
180+
if (!completeChoicePromptParams.PromptCancelled)
181+
{
182+
this.editorSession.ConsoleService.ReceiveInputString(
183+
completeChoicePromptParams.ChosenItem,
184+
false);
185+
}
186+
else
187+
{
188+
// Cancel the current prompt
189+
this.editorSession.ConsoleService.SendControlC();
190+
}
191+
192+
return Task.FromResult(true);
193+
}
194+
169195
protected Task HandleDidOpenTextDocumentNotification(
170196
DidOpenTextDocumentNotification openParams,
171197
EventContext eventContext)
@@ -676,11 +702,15 @@ protected async Task HandleEvaluateRequest(
676702
DebugAdapterMessages.EvaluateRequestArguments evaluateParams,
677703
RequestContext<DebugAdapterMessages.EvaluateResponseBody> requestContext)
678704
{
679-
var results =
680-
await this.editorSession.PowerShellContext.ExecuteScriptString(
705+
// We don't await the result of the execution here because we want
706+
// to be able to receive further messages while the current script
707+
// is executing. This important in cases where the pipeline thread
708+
// gets blocked by something in the script like a prompt to the user.
709+
var executeTask =
710+
this.editorSession.PowerShellContext.ExecuteScriptString(
681711
evaluateParams.Expression,
682712
true,
683-
true);
713+
true).ConfigureAwait(false);
684714

685715
// Return an empty result since the result value is irrelevant
686716
// for this request in the LanguageServer
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//
2+
// Copyright (c) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
4+
//
5+
6+
using Microsoft.PowerShell.EditorServices.Console;
7+
using Microsoft.PowerShell.EditorServices.Protocol.Messages;
8+
9+
namespace Microsoft.PowerShell.EditorServices.Protocol.Server
10+
{
11+
internal class ProtocolPromptHandlerContext : IPromptHandlerContext
12+
{
13+
private IEventWriter eventWriter;
14+
15+
public ProtocolPromptHandlerContext(IEventWriter eventWriter)
16+
{
17+
this.eventWriter = eventWriter;
18+
}
19+
20+
public ChoicePromptHandler GetChoicePromptHandler()
21+
{
22+
return new ProtocolChoicePromptHandler(this.eventWriter);
23+
}
24+
}
25+
26+
internal class ProtocolChoicePromptHandler : ChoicePromptHandler
27+
{
28+
private IEventWriter eventWriter;
29+
30+
public ProtocolChoicePromptHandler(IEventWriter eventWriter)
31+
{
32+
this.eventWriter = eventWriter;
33+
}
34+
35+
protected override void ShowPrompt(PromptStyle promptStyle)
36+
{
37+
eventWriter.SendEvent(
38+
ShowChoicePromptNotification.Type,
39+
new ShowChoicePromptNotification
40+
{
41+
Caption = this.Caption,
42+
Message = this.Message,
43+
Choices = this.Choices,
44+
DefaultChoice = this.DefaultChoice
45+
}).ConfigureAwait(false);
46+
}
47+
}
48+
}
49+

src/PowerShellEditorServices.Protocol/Server/ProtocolServer.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
77
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel;
8-
using Newtonsoft.Json.Linq;
98
using System;
109
using System.Threading;
1110
using System.Threading.Tasks;
@@ -110,15 +109,19 @@ public Task SendEvent<TParams>(
110109

111110
if (!this.serverChannel.MessageDispatcher.InMessageLoopThread)
112111
{
112+
TaskCompletionSource<bool> writeTask = new TaskCompletionSource<bool>();
113+
113114
this.serverChannel.MessageDispatcher.SynchronizationContext.Post(
114115
async (obj) =>
115116
{
116117
await this.serverChannel.MessageWriter.WriteEvent(
117118
eventType,
118119
eventParams);
120+
121+
writeTask.SetResult(true);
119122
}, null);
120123

121-
return Task.FromResult(true);
124+
return writeTask.Task;
122125
}
123126
else
124127
{

0 commit comments

Comments
 (0)