Skip to content

Commit a6dedaa

Browse files
authored
Merge pull request #320 from PowerShell/daviwil/plaster-integration
Add TemplateService for Plaster integration
2 parents ddb2bba + 6ce82f2 commit a6dedaa

16 files changed

+519
-32
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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 Microsoft.PowerShell.EditorServices.Templates;
8+
9+
namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer
10+
{
11+
public class NewProjectFromTemplateRequest
12+
{
13+
public static readonly
14+
RequestType<NewProjectFromTemplateRequest, NewProjectFromTemplateResponse> Type =
15+
RequestType<NewProjectFromTemplateRequest, NewProjectFromTemplateResponse>.Create("powerShell/newProjectFromTemplate");
16+
17+
public string DestinationPath { get; set; }
18+
19+
public string TemplatePath { get; set; }
20+
}
21+
22+
public class NewProjectFromTemplateResponse
23+
{
24+
public bool CreationSuccessful { get; set; }
25+
}
26+
27+
public class GetProjectTemplatesRequest
28+
{
29+
public static readonly
30+
RequestType<GetProjectTemplatesRequest, GetProjectTemplatesResponse> Type =
31+
RequestType<GetProjectTemplatesRequest, GetProjectTemplatesResponse>.Create("powerShell/getProjectTemplates");
32+
33+
public bool IncludeInstalledModules { get; set; }
34+
}
35+
36+
public class GetProjectTemplatesResponse
37+
{
38+
public bool NeedsModuleInstall { get; set; }
39+
40+
public TemplateDetails[] Templates { get; set; }
41+
}
42+
}

src/PowerShellEditorServices.Protocol/Messages/PromptEvents.cs

+4-2
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,22 @@ public static readonly
1313
RequestType<ShowChoicePromptRequest, ShowChoicePromptResponse> Type =
1414
RequestType<ShowChoicePromptRequest, ShowChoicePromptResponse>.Create("powerShell/showChoicePrompt");
1515

16+
public bool IsMultiChoice { get; set; }
17+
1618
public string Caption { get; set; }
1719

1820
public string Message { get; set; }
1921

2022
public ChoiceDetails[] Choices { get; set; }
2123

22-
public int DefaultChoice { get; set; }
24+
public int[] DefaultChoices { get; set; }
2325
}
2426

2527
public class ShowChoicePromptResponse
2628
{
2729
public bool PromptCancelled { get; set; }
2830

29-
public string ChosenItem { get; set; }
31+
public string ResponseText { get; set; }
3032
}
3133

3234
public class ShowInputPromptRequest

src/PowerShellEditorServices.Protocol/PowerShellEditorServices.Protocol.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
<Compile Include="LanguageServer\InstallModuleRequest.cs" />
6363
<Compile Include="LanguageServer\PowerShellVersionRequest.cs" />
6464
<Compile Include="LanguageServer\SetPSSARulesRequest.cs" />
65+
<Compile Include="LanguageServer\ProjectTemplate.cs" />
6566
<Compile Include="MessageProtocol\Channel\NamedPipeClientChannel.cs" />
6667
<Compile Include="MessageProtocol\Channel\NamedPipeServerChannel.cs" />
6768
<Compile Include="MessageProtocol\Channel\TcpSocketClientChannel.cs" />

src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs

+53
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
99
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel;
1010
using Microsoft.PowerShell.EditorServices.Session;
11+
using Microsoft.PowerShell.EditorServices.Templates;
1112
using Microsoft.PowerShell.EditorServices.Utility;
1213
using Newtonsoft.Json.Linq;
1314
using System;
@@ -107,6 +108,9 @@ protected override void Initialize()
107108

108109
this.SetRequestHandler(PowerShellVersionRequest.Type, this.HandlePowerShellVersionRequest);
109110

111+
this.SetRequestHandler(NewProjectFromTemplateRequest.Type, this.HandleNewProjectFromTemplateRequest);
112+
this.SetRequestHandler(GetProjectTemplatesRequest.Type, this.HandleGetProjectTemplatesRequest);
113+
110114
this.SetRequestHandler(DebugAdapterMessages.EvaluateRequest.Type, this.HandleEvaluateRequest);
111115

112116
this.SetRequestHandler(GetPSSARulesRequest.Type, this.HandleGetPSSARulesRequest);
@@ -280,6 +284,55 @@ private Task HandleInvokeExtensionCommandRequest(
280284
return Task.FromResult(true);
281285
}
282286

287+
private Task HandleNewProjectFromTemplateRequest(
288+
NewProjectFromTemplateRequest newProjectArgs,
289+
RequestContext<NewProjectFromTemplateResponse> requestContext)
290+
{
291+
// Don't await the Task here so that we don't block the session
292+
this.editorSession.TemplateService
293+
.CreateFromTemplate(newProjectArgs.TemplatePath, newProjectArgs.DestinationPath)
294+
.ContinueWith(
295+
async task =>
296+
{
297+
await requestContext.SendResult(
298+
new NewProjectFromTemplateResponse
299+
{
300+
CreationSuccessful = task.Result
301+
});
302+
});
303+
304+
return Task.FromResult(true);
305+
}
306+
307+
private async Task HandleGetProjectTemplatesRequest(
308+
GetProjectTemplatesRequest requestArgs,
309+
RequestContext<GetProjectTemplatesResponse> requestContext)
310+
{
311+
bool plasterInstalled = await this.editorSession.TemplateService.ImportPlasterIfInstalled();
312+
313+
if (plasterInstalled)
314+
{
315+
var availableTemplates =
316+
await this.editorSession.TemplateService.GetAvailableTemplates(
317+
requestArgs.IncludeInstalledModules);
318+
319+
await requestContext.SendResult(
320+
new GetProjectTemplatesResponse
321+
{
322+
Templates = availableTemplates
323+
});
324+
}
325+
else
326+
{
327+
await requestContext.SendResult(
328+
new GetProjectTemplatesResponse
329+
{
330+
NeedsModuleInstall = true,
331+
Templates = new TemplateDetails[0]
332+
});
333+
}
334+
}
335+
283336
private async Task HandleExpandAliasRequest(
284337
string content,
285338
RequestContext<string> requestContext)

src/PowerShellEditorServices.Protocol/Server/PromptHandlers.cs

+7-3
Original file line numberDiff line numberDiff line change
@@ -40,30 +40,34 @@ public InputPromptHandler GetInputPromptHandler()
4040
}
4141
}
4242

43-
internal class ProtocolChoicePromptHandler : ChoicePromptHandler
43+
internal class ProtocolChoicePromptHandler : ConsoleChoicePromptHandler
4444
{
4545
private IMessageSender messageSender;
4646
private ConsoleService consoleService;
4747

4848
public ProtocolChoicePromptHandler(
4949
IMessageSender messageSender,
5050
ConsoleService consoleService)
51+
: base(consoleService)
5152
{
5253
this.messageSender = messageSender;
5354
this.consoleService = consoleService;
5455
}
5556

5657
protected override void ShowPrompt(PromptStyle promptStyle)
5758
{
59+
base.ShowPrompt(promptStyle);
60+
5861
messageSender
5962
.SendRequest(
6063
ShowChoicePromptRequest.Type,
6164
new ShowChoicePromptRequest
6265
{
66+
IsMultiChoice = this.IsMultiChoice,
6367
Caption = this.Caption,
6468
Message = this.Message,
6569
Choices = this.Choices,
66-
DefaultChoice = this.DefaultChoice
70+
DefaultChoices = this.DefaultChoices
6771
}, true)
6872
.ContinueWith(HandlePromptResponse)
6973
.ConfigureAwait(false);
@@ -79,7 +83,7 @@ private void HandlePromptResponse(
7983
if (!response.PromptCancelled)
8084
{
8185
this.consoleService.ReceivePromptResponse(
82-
response.ChosenItem,
86+
response.ResponseText,
8387
false);
8488
}
8589
else

src/PowerShellEditorServices/Console/ChoiceDetails.cs

+3
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ public static ChoiceDetails Create(ChoiceDescription choiceDescription)
118118
/// <returns>True if the input string is a match for the choice.</returns>
119119
public bool MatchesInput(string inputString)
120120
{
121+
// Make sure the input string is trimmed of whitespace
122+
inputString = inputString.Trim();
123+
121124
// Is it the hotkey?
122125
return
123126
string.Equals(inputString, this.hotKeyString, StringComparison.CurrentCultureIgnoreCase) ||

src/PowerShellEditorServices/Console/ChoicePromptHandler.cs

+87-14
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
//
55

66
using System;
7+
using System.Collections.Generic;
8+
using System.Collections.ObjectModel;
9+
using System.Linq;
710
using System.Threading;
811
using System.Threading.Tasks;
912

@@ -37,12 +40,17 @@ public abstract class ChoicePromptHandler : PromptHandler
3740
{
3841
#region Private Fields
3942

40-
private TaskCompletionSource<int> promptTask;
43+
private TaskCompletionSource<int[]> promptTask;
4144

4245
#endregion
4346

4447
#region Properties
4548

49+
/// <summary>
50+
/// Returns true if the choice prompt allows multiple selections.
51+
/// </summary>
52+
protected bool IsMultiChoice { get; private set; }
53+
4654
/// <summary>
4755
/// Gets the caption (title) string to display with the prompt.
4856
/// </summary>
@@ -62,7 +70,7 @@ public abstract class ChoicePromptHandler : PromptHandler
6270
/// Gets the index of the default choice so that the user
6371
/// interface can make it easy to select this option.
6472
/// </summary>
65-
protected int DefaultChoice { get; private set; }
73+
protected int[] DefaultChoices { get; private set; }
6674

6775
#endregion
6876

@@ -102,18 +110,71 @@ public Task<int> PromptForChoice(
102110
this.Caption = promptCaption;
103111
this.Message = promptMessage;
104112
this.Choices = choices;
105-
this.DefaultChoice = defaultChoice;
106-
this.promptTask = new TaskCompletionSource<int>();
113+
this.promptTask = new TaskCompletionSource<int[]>();
114+
115+
this.DefaultChoices =
116+
defaultChoice == -1
117+
? new int[] { }
118+
: new int[] { defaultChoice };
107119

108120
// Cancel the TaskCompletionSource if the caller cancels the task
109121
cancellationToken.Register(this.CancelPrompt, true);
110122

111123
// Show the prompt to the user
112124
this.ShowPrompt(PromptStyle.Full);
113125

114-
return this.promptTask.Task;
126+
// Convert the int[] result to int
127+
return this.promptTask.Task.ContinueWith(
128+
t => t.Result.DefaultIfEmpty(-1).First());
115129
}
116130

131+
/// <summary>
132+
/// Prompts the user to make a choice of one or more options using the
133+
/// provided details.
134+
/// </summary>
135+
/// <param name="promptCaption">
136+
/// The caption string which will be displayed to the user.
137+
/// </param>
138+
/// <param name="promptMessage">
139+
/// The descriptive message which will be displayed to the user.
140+
/// </param>
141+
/// <param name="choices">
142+
/// The list of choices from which the user will select.
143+
/// </param>
144+
/// <param name="defaultChoices">
145+
/// The default choice(s) to highlight for the user.
146+
/// </param>
147+
/// <param name="cancellationToken">
148+
/// A CancellationToken that can be used to cancel the prompt.
149+
/// </param>
150+
/// <returns>
151+
/// A Task instance that can be monitored for completion to get
152+
/// the user's choices.
153+
/// </returns>
154+
public Task<int[]> PromptForChoice(
155+
string promptCaption,
156+
string promptMessage,
157+
ChoiceDetails[] choices,
158+
int[] defaultChoices,
159+
CancellationToken cancellationToken)
160+
{
161+
// TODO: Guard against multiple calls
162+
163+
this.Caption = promptCaption;
164+
this.Message = promptMessage;
165+
this.Choices = choices;
166+
this.DefaultChoices = defaultChoices;
167+
this.IsMultiChoice = true;
168+
this.promptTask = new TaskCompletionSource<int[]>();
169+
170+
// Cancel the TaskCompletionSource if the caller cancels the task
171+
cancellationToken.Register(this.CancelPrompt, true);
172+
173+
// Show the prompt to the user
174+
this.ShowPrompt(PromptStyle.Full);
175+
176+
return this.promptTask.Task;
177+
}
117178
/// <summary>
118179
/// Implements behavior to handle the user's response.
119180
/// </summary>
@@ -124,29 +185,41 @@ public Task<int> PromptForChoice(
124185
/// </returns>
125186
public override bool HandleResponse(string responseString)
126187
{
127-
int choiceIndex = -1;
188+
List<int> choiceIndexes = new List<int>();
128189

129-
// Clean up the response string
130-
responseString = responseString.Trim();
190+
// Clean up the response string and split it
191+
var choiceStrings =
192+
responseString.Trim().Split(
193+
new char[] { ',' },
194+
StringSplitOptions.RemoveEmptyEntries);
131195

132-
for (int i = 0; i < this.Choices.Length; i++)
196+
foreach (string choiceString in choiceStrings)
133197
{
134-
if (this.Choices[i].MatchesInput(responseString))
198+
for (int i = 0; i < this.Choices.Length; i++)
135199
{
136-
choiceIndex = i;
137-
break;
200+
if (this.Choices[i].MatchesInput(choiceString))
201+
{
202+
choiceIndexes.Add(i);
203+
204+
// If this is a single-choice prompt, break out after
205+
// the first matched choice
206+
if (!this.IsMultiChoice)
207+
{
208+
break;
209+
}
210+
}
138211
}
139212
}
140213

141-
if (choiceIndex == -1)
214+
if (choiceIndexes.Count == 0)
142215
{
143216
// The user did not respond with a valid choice,
144217
// show the prompt again to give another chance
145218
this.ShowPrompt(PromptStyle.Minimal);
146219
return false;
147220
}
148221

149-
this.promptTask.SetResult(choiceIndex);
222+
this.promptTask.SetResult(choiceIndexes.ToArray());
150223
return true;
151224
}
152225

0 commit comments

Comments
 (0)