Skip to content

Commit ff524df

Browse files
committed
Add text input prompt support
This change adds a PSHostUserInterface implementation for both the Prompt and ReadLine methods so that input can be requested from the user. The implementation is made with the IPromptHandler interface so that the prompt handling strategy can be changed based on the context of the user's session.
1 parent 652e3fc commit ff524df

14 files changed

+1054
-14
lines changed

src/PowerShellEditorServices.Protocol/Server/PromptHandlers.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
44
//
55

6+
using System;
67
using Microsoft.PowerShell.EditorServices.Console;
78
using Microsoft.PowerShell.EditorServices.Protocol.Messages;
89

@@ -21,6 +22,11 @@ public ChoicePromptHandler GetChoicePromptHandler()
2122
{
2223
return new ProtocolChoicePromptHandler(this.eventWriter);
2324
}
25+
26+
public InputPromptHandler GetInputPromptHandler()
27+
{
28+
throw new NotImplementedException();
29+
}
2430
}
2531

2632
internal class ProtocolChoicePromptHandler : ChoicePromptHandler

src/PowerShellEditorServices/Console/ChoicePromptHandler.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,8 @@ public virtual bool HandleResponse(string responseString)
147147
/// </summary>
148148
public void CancelPrompt()
149149
{
150-
// Setting the result to -1 will cause the prompt to be cancelled
151-
this.promptTask.SetResult(-1);
150+
// Cancel the prompt task
151+
this.promptTask.TrySetCanceled();
152152
}
153153

154154
#endregion
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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 System;
7+
8+
namespace Microsoft.PowerShell.EditorServices.Console
9+
{
10+
/// <summary>
11+
/// Provides a standard implementation of InputPromptHandler
12+
/// for use in the interactive console (REPL).
13+
/// </summary>
14+
public class ConsoleInputPromptHandler : InputPromptHandler
15+
{
16+
#region Private Fields
17+
18+
private IConsoleHost consoleHost;
19+
20+
#endregion
21+
22+
#region Constructors
23+
24+
/// <summary>
25+
/// Creates an instance of the ConsoleInputPromptHandler class.
26+
/// </summary>
27+
/// <param name="consoleHost">
28+
/// The IConsoleHost implementation to use for writing to the
29+
/// console.
30+
/// </param>
31+
public ConsoleInputPromptHandler(IConsoleHost consoleHost)
32+
{
33+
this.consoleHost = consoleHost;
34+
}
35+
36+
#endregion
37+
38+
#region Public Methods
39+
40+
/// <summary>
41+
/// Called when the prompt caption and message should be
42+
/// displayed to the user.
43+
/// </summary>
44+
/// <param name="caption">The caption string to be displayed.</param>
45+
/// <param name="message">The message string to be displayed.</param>
46+
protected override void ShowPromptMessage(string caption, string message)
47+
{
48+
if (!string.IsNullOrEmpty(caption))
49+
{
50+
this.consoleHost.WriteOutput(caption, true);
51+
}
52+
53+
if (!string.IsNullOrEmpty(message))
54+
{
55+
this.consoleHost.WriteOutput(message, true);
56+
}
57+
}
58+
59+
/// <summary>
60+
/// Called when a prompt should be displayed for a specific
61+
/// input field.
62+
/// </summary>
63+
/// <param name="fieldDetails">The details of the field to be displayed.</param>
64+
protected override void ShowFieldPrompt(FieldDetails fieldDetails)
65+
{
66+
// For a simple prompt there won't be any field name.
67+
// In this case don't write anything
68+
if (!string.IsNullOrEmpty(fieldDetails.Name))
69+
{
70+
this.consoleHost.WriteOutput(
71+
fieldDetails.Name + ": ",
72+
false);
73+
}
74+
}
75+
76+
/// <summary>
77+
/// Called when an error should be displayed, such as when the
78+
/// user types in a string with an incorrect format for the
79+
/// current field.
80+
/// </summary>
81+
/// <param name="e">
82+
/// The Exception containing the error to be displayed.
83+
/// </param>
84+
protected override void ShowErrorMessage(Exception e)
85+
{
86+
this.consoleHost.WriteOutput(
87+
e.Message,
88+
true,
89+
OutputType.Error);
90+
}
91+
92+
#endregion
93+
}
94+
}
95+

src/PowerShellEditorServices/Console/ConsolePromptHandlerContext.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@ public ChoicePromptHandler GetChoicePromptHandler()
5050
return new ConsoleChoicePromptHandler(this.consoleHost);
5151
}
5252

53+
/// <summary>
54+
/// Creates a new InputPromptHandler instance so that
55+
/// the caller can display an input prompt to the user.
56+
/// </summary>
57+
/// <returns>
58+
/// A new InputPromptHandler instance.
59+
/// </returns>
60+
public InputPromptHandler GetInputPromptHandler()
61+
{
62+
return new ConsoleInputPromptHandler(this.consoleHost);
63+
}
64+
5365
#endregion
5466
}
5567
}

src/PowerShellEditorServices/Console/ConsoleService.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ public void ReceiveInputString(string inputString, bool echoToConsole)
8787
{
8888
if (this.activePromptHandler != null)
8989
{
90+
if (echoToConsole)
91+
{
92+
this.WriteOutput(inputString, true);
93+
}
94+
9095
if (this.activePromptHandler.HandleResponse(inputString))
9196
{
9297
// If the prompt handler is finished, clear it for
@@ -197,6 +202,12 @@ ChoicePromptHandler IConsoleHost.GetChoicePromptHandler()
197202
factory => factory.GetChoicePromptHandler());
198203
}
199204

205+
InputPromptHandler IConsoleHost.GetInputPromptHandler()
206+
{
207+
return this.GetPromptHandler(
208+
factory => factory.GetInputPromptHandler());
209+
}
210+
200211
private TPromptHandler GetPromptHandler<TPromptHandler>(
201212
Func<IPromptHandlerContext, TPromptHandler> factoryInvoker)
202213
where TPromptHandler : IPromptHandler
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
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.Utility;
7+
using System;
8+
using System.Collections;
9+
using System.Collections.Generic;
10+
using System.Management.Automation;
11+
using System.Management.Automation.Host;
12+
13+
namespace Microsoft.PowerShell.EditorServices.Console
14+
{
15+
/// <summary>
16+
/// Contains the details of an input field shown from an
17+
/// InputPromptHandler. This class is meant to be
18+
/// serializable to the user's UI.
19+
/// </summary>
20+
public class FieldDetails
21+
{
22+
#region Properties
23+
24+
/// <summary>
25+
/// Gets or sets the name of the field.
26+
/// </summary>
27+
public string Name { get; set; }
28+
29+
/// <summary>
30+
/// Gets or sets the descriptive label for the field.
31+
/// </summary>
32+
public string Label { get; set; }
33+
34+
/// <summary>
35+
/// Gets or sets the field's value type.
36+
/// </summary>
37+
public Type FieldType { get; set; }
38+
39+
/// <summary>
40+
/// Gets or sets the field's help message.
41+
/// </summary>
42+
public string HelpMessage { get; set; }
43+
44+
/// <summary>
45+
/// Gets or sets a boolean that is true if the user
46+
/// must enter a value for the field.
47+
/// </summary>
48+
public bool IsMandatory { get; set; }
49+
50+
/// <summary>
51+
/// Gets or sets the default value for the field.
52+
/// </summary>
53+
public object DefaultValue { get; set; }
54+
55+
/// <summary>
56+
/// Gets or sets a boolean that is true if the field
57+
/// represents a collection of values.
58+
/// </summary>
59+
public bool IsCollection { get; set; }
60+
61+
/// <summary>
62+
/// Gets or sets the expected type for individual items
63+
/// in the field's collection.
64+
/// </summary>
65+
public Type ElementType { get; set; }
66+
67+
#endregion
68+
69+
#region Constructors
70+
71+
/// <summary>
72+
/// Creates an instance of the FieldDetails class.
73+
/// </summary>
74+
/// <param name="name">The field's name.</param>
75+
/// <param name="label">The field's label.</param>
76+
/// <param name="fieldType">The field's value type.</param>
77+
/// <param name="isMandatory">If true, marks the field as mandatory.</param>
78+
/// <param name="defaultValue">The field's default value.</param>
79+
public FieldDetails(
80+
string name,
81+
string label,
82+
Type fieldType,
83+
bool isMandatory,
84+
object defaultValue)
85+
{
86+
this.Name = name;
87+
this.Label = label;
88+
this.FieldType = fieldType;
89+
this.IsMandatory = isMandatory;
90+
this.DefaultValue = defaultValue;
91+
92+
if (typeof(IList).IsAssignableFrom(fieldType))
93+
{
94+
this.IsCollection = true;
95+
this.ElementType = typeof(object);
96+
97+
if (fieldType.IsArray)
98+
{
99+
this.ElementType = fieldType.GetElementType();
100+
}
101+
}
102+
else if (fieldType.IsGenericType)
103+
{
104+
throw new PSArgumentException(
105+
"Generic types are not supported for input fields at this time.");
106+
}
107+
}
108+
109+
#endregion
110+
111+
#region Internal Methods
112+
113+
internal static FieldDetails Create(FieldDescription fieldDescription)
114+
{
115+
return new FieldDetails(
116+
fieldDescription.Name,
117+
fieldDescription.Label,
118+
GetFieldTypeFromTypeName(fieldDescription.ParameterAssemblyFullName),
119+
fieldDescription.IsMandatory,
120+
fieldDescription.DefaultValue);
121+
}
122+
123+
private static Type GetFieldTypeFromTypeName(string assemblyFullName)
124+
{
125+
Type fieldType = typeof(string);
126+
127+
if (!string.IsNullOrEmpty(assemblyFullName))
128+
{
129+
if (!LanguagePrimitives.TryConvertTo<Type>(assemblyFullName, out fieldType))
130+
{
131+
Logger.Write(
132+
LogLevel.Warning,
133+
string.Format(
134+
"Could not resolve type of field: {0}",
135+
assemblyFullName));
136+
}
137+
}
138+
139+
return fieldType;
140+
}
141+
142+
#endregion
143+
}
144+
}
145+

src/PowerShellEditorServices/Console/IConsoleHost.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ void WriteOutput(
4747
/// <returns>A new ChoicePromptHandler instance.</returns>
4848
ChoicePromptHandler GetChoicePromptHandler();
4949

50+
/// <summary>
51+
/// Creates an InputPrompt handle to use for displaying input
52+
/// prompts to the user.
53+
/// </summary>
54+
/// <returns>A new InputPromptHandler instance.</returns>
55+
InputPromptHandler GetInputPromptHandler();
56+
5057
/// <summary>
5158
/// Sends a progress update event to the user.
5259
/// </summary>

src/PowerShellEditorServices/Console/IPromptHandlerContext.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ public interface IPromptHandlerContext
1919
/// A new ChoicePromptHandler instance.
2020
/// </returns>
2121
ChoicePromptHandler GetChoicePromptHandler();
22+
23+
/// <summary>
24+
/// Creates a new InputPromptHandler instance so that
25+
/// the caller can display an input prompt to the user.
26+
/// </summary>
27+
/// <returns>
28+
/// A new InputPromptHandler instance.
29+
/// </returns>
30+
InputPromptHandler GetInputPromptHandler();
2231
}
2332
}
2433

0 commit comments

Comments
 (0)