diff --git a/.vsts-ci/releaseBuild.yml b/.vsts-ci/releaseBuild.yml
index 765e2b69e..225796fab 100644
--- a/.vsts-ci/releaseBuild.yml
+++ b/.vsts-ci/releaseBuild.yml
@@ -76,6 +76,7 @@ stages:
*.ps1xml
**\*.dll
!System.Runtime.InteropServices.RuntimeInformation.dll
+ !Microsoft.PowerShell.Pager.dll
useMinimatch: true
# Replace the *.psm1, *.ps1, *.psd1, *.dll files with the signed ones
diff --git a/MockPSConsole/MockPSConsole.csproj b/MockPSConsole/MockPSConsole.csproj
index 6d01503c1..4bca152fc 100644
--- a/MockPSConsole/MockPSConsole.csproj
+++ b/MockPSConsole/MockPSConsole.csproj
@@ -18,7 +18,7 @@
-
+
diff --git a/PSReadLine.build.ps1 b/PSReadLine.build.ps1
index b07930b58..620019ffd 100644
--- a/PSReadLine.build.ps1
+++ b/PSReadLine.build.ps1
@@ -142,6 +142,7 @@ task LayoutModule BuildPolyfiller, BuildMainModule, {
$binPath = "PSReadLine/bin/$Configuration/$Framework/publish"
Copy-Item $binPath/Microsoft.PowerShell.PSReadLine2.dll $targetDir
+ Copy-Item $binPath/Microsoft.PowerShell.Pager.dll $targetDir
if (Test-Path $binPath/System.Runtime.InteropServices.RuntimeInformation.dll) {
Copy-Item $binPath/System.Runtime.InteropServices.RuntimeInformation.dll $targetDir
diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs
index 0a5ce8334..c7d9902af 100644
--- a/PSReadLine/Completion.cs
+++ b/PSReadLine/Completion.cs
@@ -422,17 +422,15 @@ private static string HandleNewlinesForPossibleCompletions(string s)
return s;
}
- private class Menu
+ private class Menu : DisplayBlockBase
{
- internal PSConsoleReadLine Singleton;
- internal int Top;
-
internal int PreviousTop;
internal int ColumnWidth;
internal int BufferLines;
internal int Rows;
internal int Columns;
internal int ToolTipLines;
+
internal Collection MenuItems;
internal CompletionResult CurrentMenuItem => MenuItems[CurrentSelection];
internal int CurrentSelection;
@@ -701,39 +699,6 @@ public void MoveN(int n)
CurrentSelection += MenuItems.Count;
}
}
-
- private void MoveCursorDown(int cnt)
- {
- IConsole console = Singleton._console;
- while (cnt-- > 0)
- {
- console.Write("\n");
- }
- }
-
- private void AdjustForPossibleScroll(int cnt)
- {
- IConsole console = Singleton._console;
- var scrollCnt = console.CursorTop + cnt + 1 - console.BufferHeight;
- if (scrollCnt > 0)
- {
- Top -= scrollCnt;
- _singleton._initialY -= scrollCnt;
- _savedCursorTop -= scrollCnt;
- }
- }
-
- private int _savedCursorLeft;
- private int _savedCursorTop;
-
- public void SaveCursor()
- {
- IConsole console = Singleton._console;
- _savedCursorLeft = console.CursorLeft;
- _savedCursorTop = console.CursorTop;
- }
-
- public void RestoreCursor() => Singleton._console.SetCursorPosition(_savedCursorLeft, _savedCursorTop);
}
private Menu CreateCompletionMenu(Collection matches)
diff --git a/PSReadLine/DisplayBlockBase.cs b/PSReadLine/DisplayBlockBase.cs
new file mode 100644
index 000000000..7f3d5a0a0
--- /dev/null
+++ b/PSReadLine/DisplayBlockBase.cs
@@ -0,0 +1,51 @@
+/********************************************************************++
+Copyright (c) Microsoft Corporation. All rights reserved.
+--********************************************************************/
+
+using System;
+using Microsoft.PowerShell.Internal;
+
+namespace Microsoft.PowerShell
+{
+ public partial class PSConsoleReadLine
+ {
+ private class DisplayBlockBase
+ {
+ internal PSConsoleReadLine Singleton;
+ internal int Top;
+
+ protected void MoveCursorDown(int cnt)
+ {
+ IConsole console = Singleton._console;
+ while (cnt-- > 0)
+ {
+ console.Write("\n");
+ }
+ }
+
+ protected void AdjustForPossibleScroll(int cnt)
+ {
+ IConsole console = Singleton._console;
+ var scrollCnt = console.CursorTop + cnt + 1 - console.BufferHeight;
+ if (scrollCnt > 0)
+ {
+ Top -= scrollCnt;
+ _singleton._initialY -= scrollCnt;
+ _savedCursorTop -= scrollCnt;
+ }
+ }
+
+ private int _savedCursorLeft;
+ private int _savedCursorTop;
+
+ public void SaveCursor()
+ {
+ IConsole console = Singleton._console;
+ _savedCursorLeft = console.CursorLeft;
+ _savedCursorTop = console.CursorTop;
+ }
+
+ public void RestoreCursor() => Singleton._console.SetCursorPosition(_savedCursorLeft, _savedCursorTop);
+ }
+ }
+}
diff --git a/PSReadLine/DynamicHelp.cs b/PSReadLine/DynamicHelp.cs
new file mode 100644
index 000000000..d57eadaeb
--- /dev/null
+++ b/PSReadLine/DynamicHelp.cs
@@ -0,0 +1,289 @@
+/********************************************************************++
+Copyright (c) Microsoft Corporation. All rights reserved.
+--********************************************************************/
+
+using System;
+using System.Collections.ObjectModel;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Management.Automation;
+using System.Management.Automation.Language;
+using Microsoft.PowerShell.Internal;
+using Microsoft.PowerShell.PSReadLine;
+
+namespace Microsoft.PowerShell
+{
+ public partial class PSConsoleReadLine
+ {
+ private Microsoft.PowerShell.Pager _pager;
+
+ ///
+ /// Attempt to show help content.
+ /// Show the full help for the command on the alternate screen buffer.
+ ///
+ public static void ShowCommandHelp(ConsoleKeyInfo? key = null, object arg = null)
+ {
+ if (_singleton._console is PlatformWindows.LegacyWin32Console)
+ {
+ Collection helpBlock = new Collection()
+ {
+ string.Empty,
+ PSReadLineResources.FullHelpNotSupportedInLegacyConsole
+ };
+
+ _singleton.WriteDynamicHelpBlock(helpBlock);
+
+ return;
+ }
+
+ _singleton.DynamicHelpImpl(isFullHelp: true);
+ }
+
+ ///
+ /// Attempt to show help content.
+ /// Show the short help of the parameter next to the cursor.
+ ///
+ public static void ShowParameterHelp(ConsoleKeyInfo? key = null, object arg = null)
+ {
+ _singleton.DynamicHelpImpl(isFullHelp: false);
+ }
+
+ [ExcludeFromCodeCoverage]
+ object IPSConsoleReadLineMockableMethods.GetDynamicHelpContent(string commandName, string parameterName, bool isFullHelp)
+ {
+ if (string.IsNullOrEmpty(commandName))
+ {
+ return null;
+ }
+
+ System.Management.Automation.PowerShell ps = null;
+
+ try
+ {
+ if (!_mockableMethods.RunspaceIsRemote(_runspace))
+ {
+ ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace);
+ }
+ else
+ {
+ ps = System.Management.Automation.PowerShell.Create();
+ ps.Runspace = _runspace;
+ }
+
+ if (isFullHelp)
+ {
+ return ps
+ .AddCommand($"Microsoft.PowerShell.Core\\Get-Help")
+ .AddParameter("Name", commandName)
+ .AddParameter("Full", value: true)
+ .AddCommand($"Microsoft.PowerShell.Utility\\Out-String")
+ .Invoke()
+ .FirstOrDefault();
+ }
+
+ if (string.IsNullOrEmpty(parameterName))
+ {
+ return null;
+ }
+
+ return ps
+ .AddCommand("Microsoft.PowerShell.Core\\Get-Help")
+ .AddParameter("Name", commandName)
+ .AddParameter("Parameter", parameterName)
+ .Invoke()
+ .FirstOrDefault();
+
+ }
+ catch (Exception)
+ {
+ return null;
+ }
+ finally
+ {
+ ps?.Dispose();
+
+ // GetDynamicHelpContent could scroll the screen, e.g. via Write-Progress. For example,
+ // Get-Help for unknown command under the CloudShell Azure drive will show the progress bar while searching for command.
+ // We need to update the _initialY in case the current cursor postion has changed.
+ if (_singleton._initialY > _console.CursorTop)
+ {
+ _singleton._initialY = _console.CursorTop;
+ }
+ }
+ }
+
+ void IPSConsoleReadLineMockableMethods.RenderFullHelp(string content, string regexPatternToScrollTo)
+ {
+ _pager.Write(content, regexPatternToScrollTo);
+ }
+
+ private void WriteDynamicHelpContent(string commandName, string parameterName, bool isFullHelp)
+ {
+ var helpContent = _mockableMethods.GetDynamicHelpContent(commandName, parameterName, isFullHelp);
+
+ if (helpContent is string fullHelp && fullHelp.Length > 0)
+ {
+ string regexPatternToScrollTo = null;
+
+ if (!string.IsNullOrEmpty(parameterName))
+ {
+ regexPatternToScrollTo = $"-{parameterName} [<|\\[]";
+ }
+
+ _mockableMethods.RenderFullHelp(fullHelp, regexPatternToScrollTo);
+ }
+ else if (helpContent is PSObject paramHelp)
+ {
+ WriteParameterHelp(paramHelp);
+ }
+ }
+
+ private void DynamicHelpImpl(bool isFullHelp)
+ {
+ if (isFullHelp)
+ {
+ _pager ??= new Pager();
+ }
+
+ int cursor = _singleton._current;
+ string commandName = null;
+ string parameterName = null;
+
+ foreach(var token in _singleton._tokens)
+ {
+ var extent = token.Extent;
+
+ if (extent.StartOffset > cursor)
+ {
+ break;
+ }
+
+ if (token.TokenFlags == TokenFlags.CommandName)
+ {
+ commandName = token.Text;
+ }
+
+ if (extent.StartOffset <= cursor && extent.EndOffset >= cursor)
+ {
+ if (token.Kind == TokenKind.Parameter)
+ {
+ parameterName = ((ParameterToken)token).ParameterName;
+ break;
+ }
+ }
+ }
+
+ WriteDynamicHelpContent(commandName, parameterName, isFullHelp);
+ }
+
+ private void WriteDynamicHelpBlock(Collection helpBlock)
+ {
+ var dynHelp = new MultilineDisplayBlock
+ {
+ Singleton = this,
+ ItemsToDisplay = helpBlock
+ };
+
+ dynHelp.DrawMultilineBlock();
+ ReadKey();
+ dynHelp.Clear();
+ }
+
+ private void WriteParameterHelp(dynamic helpContent)
+ {
+ Collection helpBlock;
+
+ if (string.IsNullOrEmpty(helpContent?.Description?[0]?.Text))
+ {
+ helpBlock = new Collection()
+ {
+ String.Empty,
+ PSReadLineResources.NeedsUpdateHelp
+ };
+ }
+ else
+ {
+ string syntax = $"-{helpContent.name} <{helpContent.type.name}>";
+ string desc = "DESC: " + helpContent.Description[0].Text;
+
+ // trim new line characters as some help content has it at the end of the first list on the description.
+ desc = desc.Trim('\r', '\n');
+
+ string details = $"Required: {helpContent.required}, Position: {helpContent.position}, Default Value: {helpContent.defaultValue}, Pipeline Input: {helpContent.pipelineInput}, WildCard: {helpContent.globbing}";
+
+ helpBlock = new Collection
+ {
+ string.Empty,
+ syntax,
+ string.Empty,
+ desc,
+ details
+ };
+ }
+
+ WriteDynamicHelpBlock(helpBlock);
+ }
+
+ private class MultilineDisplayBlock : DisplayBlockBase
+ {
+ internal Collection ItemsToDisplay;
+
+ private int multilineItems = 0;
+
+ public void DrawMultilineBlock()
+ {
+ IConsole console = Singleton._console;
+
+ multilineItems = 0;
+
+ this.SaveCursor();
+
+ // Move cursor to the start of the first line after our input.
+ var bufferEndPoint = Singleton.ConvertOffsetToPoint(Singleton._buffer.Length);
+ console.SetCursorPosition(bufferEndPoint.X, bufferEndPoint.Y);
+ // Top must be initialized before calling AdjustForPossibleScroll, otherwise
+ // on the last line of the buffer, the scroll operation causes Top to point
+ // past the buffer, which in turn causes the menu to be printed twice.
+ this.Top = bufferEndPoint.Y + 1;
+ AdjustForPossibleScroll(1);
+ MoveCursorDown(1);
+
+ var bufferWidth = console.BufferWidth;
+
+ var items = this.ItemsToDisplay;
+
+ for (var index = 0; index < items.Count; index++)
+ {
+ var itemLength = LengthInBufferCells(items[index]);
+
+ if (itemLength > bufferWidth)
+ {
+ multilineItems += itemLength / bufferWidth;
+
+ if (itemLength % bufferWidth == 0)
+ {
+ multilineItems--;
+ }
+ }
+
+ console.Write(items[index]);
+
+ // Explicit newline so consoles see each row as distinct lines, but skip the
+ // last line so we don't scroll.
+ if (index != (items.Count - 1))
+ {
+ AdjustForPossibleScroll(1);
+ MoveCursorDown(1);
+ }
+ }
+
+ this.RestoreCursor();
+ }
+
+ public void Clear()
+ {
+ _singleton.WriteBlankLines(Top, ItemsToDisplay.Count + multilineItems);
+ }
+ }
+ }
+}
diff --git a/PSReadLine/KeyBindings.cs b/PSReadLine/KeyBindings.cs
index edd049a5e..8d0bf7be6 100644
--- a/PSReadLine/KeyBindings.cs
+++ b/PSReadLine/KeyBindings.cs
@@ -234,6 +234,8 @@ void SetDefaultWindowsBindings()
{ Keys.AltD, MakeKeyHandler(KillWord, "KillWord") },
{ Keys.CtrlAt, MakeKeyHandler(MenuComplete, "MenuComplete") },
{ Keys.CtrlW, MakeKeyHandler(BackwardKillWord, "BackwardKillWord") },
+ { Keys.AltH, MakeKeyHandler(ShowParameterHelp, "ShowParameterHelp") },
+ { Keys.F1, MakeKeyHandler(ShowCommandHelp, "ShowCommandHelp") },
};
// Some bindings are not available on certain platforms
@@ -328,6 +330,8 @@ void SetDefaultEmacsBindings()
{ Keys.AltPeriod, MakeKeyHandler(YankLastArg, "YankLastArg") },
{ Keys.AltUnderbar, MakeKeyHandler(YankLastArg, "YankLastArg") },
{ Keys.CtrlAltY, MakeKeyHandler(YankNthArg, "YankNthArg") },
+ { Keys.AltH, MakeKeyHandler(ShowParameterHelp, "ShowParameterHelp") },
+ { Keys.F1, MakeKeyHandler(ShowCommandHelp, "ShowCommandHelp") },
};
// Some bindings are not available on certain platforms
@@ -561,6 +565,8 @@ public static KeyHandlerGroup GetDisplayGrouping(string function)
case nameof(NextSuggestion):
case nameof(PreviousSuggestion):
case nameof(SwitchPredictionView):
+ case nameof(ShowCommandHelp):
+ case nameof(ShowParameterHelp):
return KeyHandlerGroup.Miscellaneous;
case nameof(CharacterSearch):
diff --git a/PSReadLine/KeyBindings.vi.cs b/PSReadLine/KeyBindings.vi.cs
index e77729550..63629db82 100644
--- a/PSReadLine/KeyBindings.vi.cs
+++ b/PSReadLine/KeyBindings.vi.cs
@@ -88,6 +88,8 @@ private void SetDefaultViBindings()
{ Keys.CtrlAltQuestion, MakeKeyHandler(ShowKeyBindings, "ShowKeyBindings") },
{ Keys.CtrlR, MakeKeyHandler(ViSearchHistoryBackward,"ViSearchHistoryBackward") },
{ Keys.CtrlS, MakeKeyHandler(SearchForward, "SearchForward") },
+ { Keys.AltH, MakeKeyHandler(ShowParameterHelp, "ShowParameterHelp") },
+ { Keys.F1, MakeKeyHandler(ShowCommandHelp, "ShowCommandHelp") },
};
// Some bindings are not available on certain platforms
@@ -208,6 +210,8 @@ private void SetDefaultViBindings()
{ Keys.Period, MakeKeyHandler(RepeatLastCommand, "RepeatLastCommand") },
{ Keys.Semicolon, MakeKeyHandler(RepeatLastCharSearch, "RepeatLastCharSearch") },
{ Keys.Comma, MakeKeyHandler(RepeatLastCharSearchBackwards, "RepeatLastCharSearchBackwards") },
+ { Keys.AltH, MakeKeyHandler(ShowParameterHelp, "ShowParameterHelp") },
+ { Keys.F1, MakeKeyHandler(ShowCommandHelp, "ShowCommandHelp") },
};
// Some bindings are not available on certain platforms
@@ -216,7 +220,6 @@ private void SetDefaultViBindings()
_viCmdKeyMap.Add(Keys.CtrlDelete, MakeKeyHandler(KillWord, "KillWord"));
}
-
_viChordDTable = new Dictionary
{
{ Keys.D, MakeKeyHandler( DeleteLine, "DeleteLine") },
diff --git a/PSReadLine/PSReadLine.csproj b/PSReadLine/PSReadLine.csproj
index 281e926b6..2dfd2a7e7 100644
--- a/PSReadLine/PSReadLine.csproj
+++ b/PSReadLine/PSReadLine.csproj
@@ -21,7 +21,11 @@
-
+
+
+
+
+
diff --git a/PSReadLine/PSReadLineResources.Designer.cs b/PSReadLine/PSReadLineResources.Designer.cs
index d26a5e4b4..b91a55260 100644
--- a/PSReadLine/PSReadLineResources.Designer.cs
+++ b/PSReadLine/PSReadLineResources.Designer.cs
@@ -2067,5 +2067,47 @@ internal static string YankPopDescription {
return ResourceManager.GetString("YankPopDescription", resourceCulture);
}
}
+
+ ///
+ /// Looks up a localized string similar to No help content available. Please use Update-Help to download the latest help content.
+ ///
+ internal static string NeedsUpdateHelp {
+ get {
+ return ResourceManager.GetString("NeedsUpdateHelp", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Displaying help content is not supported on this console as it does not support alternate screen buffer.
+ ///
+ internal static string FullHelpNotSupportedInLegacyConsole
+ {
+ get
+ {
+ return ResourceManager.GetString("FullHelpNotSupportedInLegacyConsole", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Shows help for the command at the cursor in an alternate screen buffer.
+ ///
+ internal static string ShowCommandHelpDescription
+ {
+ get
+ {
+ return ResourceManager.GetString("ShowCommandHelpDescription", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Shows help for the parameter at the cursor.
+ ///
+ internal static string ShowParameterHelpDescription
+ {
+ get
+ {
+ return ResourceManager.GetString("ShowParameterHelpDescription", resourceCulture);
+ }
+ }
}
}
diff --git a/PSReadLine/PSReadLineResources.resx b/PSReadLine/PSReadLineResources.resx
index 5cf93c530..236c8da7e 100644
--- a/PSReadLine/PSReadLineResources.resx
+++ b/PSReadLine/PSReadLineResources.resx
@@ -829,4 +829,16 @@ Or not saving history with:
The prediction 'ListView' is temporarily disabled because the current window size of the console is too small. To use the 'ListView', please make sure the 'WindowWidth' is not less than '{0}' and the 'WindowHeight' is not less than '{1}'.
+
+ No help content available. Please use Update-Help to download the latest help content.
+
+
+ Displaying help content is not supported on this console as it does not support alternate screen buffer.
+
+
+ Shows help for the command at the cursor in an alternate screen buffer.
+
+
+ Shows help for the parameter at the cursor.
+
diff --git a/PSReadLine/PublicAPI.cs b/PSReadLine/PublicAPI.cs
index e7a140a23..7a932661f 100644
--- a/PSReadLine/PublicAPI.cs
+++ b/PSReadLine/PublicAPI.cs
@@ -29,6 +29,8 @@ public interface IPSConsoleReadLineMockableMethods
Task> PredictInput(Ast ast, Token[] tokens);
void OnCommandLineAccepted(IReadOnlyList history);
void OnSuggestionAccepted(Guid predictorId, string suggestionText);
+ object GetDynamicHelpContent(string commandName, string parameterName, bool isFullHelp);
+ void RenderFullHelp(string content, string regexPatternToScrollTo);
}
[SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")]
diff --git a/Polyfill/Polyfill.csproj b/Polyfill/Polyfill.csproj
index ac024e8d4..95cafac7d 100644
--- a/Polyfill/Polyfill.csproj
+++ b/Polyfill/Polyfill.csproj
@@ -12,7 +12,7 @@
-
+
diff --git a/test/DynamicHelpTest.cs b/test/DynamicHelpTest.cs
new file mode 100644
index 000000000..5470f9307
--- /dev/null
+++ b/test/DynamicHelpTest.cs
@@ -0,0 +1,292 @@
+using System;
+using System.Management.Automation;
+using Xunit;
+
+namespace Test
+{
+ public partial class ReadLine
+ {
+ private static readonly string fullHelp = @"
+
+NAME
+ Get-Date
+
+SYNOPSIS
+ Gets the current date and time.
+
+PARAMETERS
+ -AsUTC
+ Converts the date value to the equivalent time in UTC.
+
+ This parameter was introduced in PowerShell 7.1.
+
+ Required? false
+ Position? named
+ Default value False
+ Accept pipeline input? False
+ Accept wildcard characters? false
+";
+
+ internal static object GetDynamicHelpContent(string commandName, string parameterName, bool isFullHelp)
+ {
+ if (string.IsNullOrEmpty(commandName))
+ {
+ return null;
+ }
+
+ if (isFullHelp)
+ {
+ return fullHelp;
+ }
+
+ if (string.IsNullOrEmpty(parameterName))
+ {
+ return null;
+ }
+
+ PSObject paramHelp = new PSObject();
+
+ if (string.Equals(commandName, "Get-FakeHelp", StringComparison.OrdinalIgnoreCase) && string.Equals(parameterName, "Fake", StringComparison.OrdinalIgnoreCase))
+ {
+ PSObject[] descDetails = new PSObject[1];
+ descDetails[0] = new PSObject();
+ descDetails[0].Members.Add(new PSNoteProperty("Text", null));
+ }
+ else if (string.Equals(commandName, "Get-Date", StringComparison.OrdinalIgnoreCase) && string.Equals(parameterName, "Date", StringComparison.OrdinalIgnoreCase))
+ {
+ return GetParameterHelpObject(description: "Specifies a date and time.");
+ }
+ else if (string.Equals(commandName, "Get-MultiLineHelp", StringComparison.OrdinalIgnoreCase))
+ {
+ if(string.Equals(parameterName, "OneAndHalf", StringComparison.OrdinalIgnoreCase))
+ {
+ string multiLineDesc = "Some very long description that is over the buffer width of 60 characters but shorter than 120.";
+ return GetParameterHelpObject(multiLineDesc);
+ }
+ else if(string.Equals(parameterName, "ExactlyTwo", StringComparison.OrdinalIgnoreCase))
+ {
+ string multiLineDesc = "Some very long description that is over the buffer width of 60 characters and exactly the length of 120 characters";
+ return GetParameterHelpObject(multiLineDesc);
+ }
+ }
+
+ return paramHelp;
+ }
+
+ private static PSObject GetParameterHelpObject(string description)
+ {
+ PSObject paramHelp = new PSObject();
+ PSObject[] descDetails = new PSObject[1];
+ descDetails[0] = new PSObject();
+ descDetails[0].Members.Add(new PSNoteProperty("Text", description));
+
+ var np = new PSNoteProperty("name", "System.Datetime");
+ np.Value = "System.Datetime";
+
+ var typeName = new PSObject(np);
+
+ paramHelp.Members.Add(new PSNoteProperty("Description", descDetails));
+ paramHelp.Members.Add(new PSNoteProperty("Name", "Date"));
+ paramHelp.Members.Add(new PSNoteProperty("type", typeName));
+ paramHelp.Members.Add(new PSNoteProperty("required", "false"));
+ paramHelp.Members.Add(new PSNoteProperty("position", "0"));
+ paramHelp.Members.Add(new PSNoteProperty("defaultValue", "None"));
+ paramHelp.Members.Add(new PSNoteProperty("pipelineInput", "True (ByPropertyName, ByValue)"));
+ paramHelp.Members.Add(new PSNoteProperty("globbing", "false"));
+
+ return paramHelp;
+ }
+
+ [SkippableFact]
+ public void DynHelp_GetFullHelp()
+ {
+ TestSetup(KeyMode.Cmd);
+ Test("Get-Date", Keys(
+ "Get-Date", _.F1,
+ CheckThat(() => Assert.Equal(fullHelp, _mockedMethods.helpContentRendered)),
+ _.Enter
+ ));
+ }
+
+ [SkippableFact]
+ public void DynHelp_GetParameterHelp_And_Clear()
+ {
+ TestSetup(KeyMode.Cmd);
+ string emptyLine = new string(' ', _console.BufferWidth);
+
+ Test("Get-Date -Date", Keys(
+ "Get-Date -Date", _.Alt_h,
+ CheckThat(() => AssertScreenIs(9,
+ TokenClassification.Command, "Get-Date",
+ TokenClassification.None, " ",
+ TokenClassification.Parameter, "-Date",
+ NextLine,
+ emptyLine,
+ TokenClassification.None, $"-Date ",
+ NextLine,
+ emptyLine,
+ TokenClassification.None, "DESC: Specifies a date and time.",
+ NextLine,
+ TokenClassification.None, "Required: false, Position: 0, Default Value: None, Pipeline ",
+ NextLine,
+ "Input: True (ByPropertyName, ByValue), WildCard: false")),
+ _.Escape,
+ CheckThat(() => AssertScreenIs(1,
+ TokenClassification.Command, "Get-Date",
+ TokenClassification.None, " ",
+ TokenClassification.Parameter, "-Date")),
+ _.Enter
+ ));
+ }
+
+ [SkippableFact]
+ public void DynHelp_GetParameterHelpMultiLine_And_Clear()
+ {
+ TestSetup(KeyMode.Cmd);
+ string emptyLine = new string(' ', _console.BufferWidth);
+
+ Test("Get-MultiLineHelp -OneAndHalf", Keys(
+ "Get-MultiLineHelp -OneAndHalf", _.Alt_h,
+ CheckThat(() => AssertScreenIs(9,
+ TokenClassification.Command, "Get-MultiLineHelp",
+ TokenClassification.None, " ",
+ TokenClassification.Parameter, "-OneAndHalf",
+ NextLine,
+ emptyLine,
+ TokenClassification.None, $"-Date ",
+ NextLine,
+ emptyLine,
+ TokenClassification.None, "DESC: Some very long description that is over the buffer width of ",
+ TokenClassification.None, "60 characters but shorter than 120.",
+ NextLine,
+ TokenClassification.None, "Required: false, Position: 0, Default Value: None, Pipeline ",
+ NextLine,
+ "Input: True (ByPropertyName, ByValue), WildCard: false")),
+ _.LeftArrow,
+ CheckThat(() => AssertScreenIs(1,
+ TokenClassification.Command, "Get-MultiLineHelp",
+ TokenClassification.None, " ",
+ TokenClassification.Parameter, "-OneAndHalf")),
+ _.Enter
+ ));
+ }
+
+ [SkippableFact]
+ public void DynHelp_GetParameterHelpTwoLines_And_Clear()
+ {
+ TestSetup(KeyMode.Cmd);
+ string emptyLine = new string(' ', _console.BufferWidth);
+
+ Test("Get-MultiLineHelp -ExactlyTwo", Keys(
+ "Get-MultiLineHelp -ExactlyTwo", _.Alt_h,
+ CheckThat(() => AssertScreenIs(9,
+ TokenClassification.Command, "Get-MultiLineHelp",
+ TokenClassification.None, " ",
+ TokenClassification.Parameter, "-ExactlyTwo",
+ NextLine,
+ emptyLine,
+ TokenClassification.None, $"-Date ",
+ NextLine,
+ emptyLine,
+ TokenClassification.None, "DESC: Some very long description that is over the buffer wid",
+ TokenClassification.None, "th of 60 characters and exactly the length of 120 characters",
+ NextLine,
+ TokenClassification.None, "Required: false, Position: 0, Default Value: None, Pipeline ",
+ NextLine,
+ "Input: True (ByPropertyName, ByValue), WildCard: false")),
+ _.RightArrow,
+ CheckThat(() => AssertScreenIs(1,
+ TokenClassification.Command, "Get-MultiLineHelp",
+ TokenClassification.None, " ",
+ TokenClassification.Parameter, "-ExactlyTwo")),
+ _.Enter
+ ));
+ }
+
+ [SkippableFact]
+ public void DynHelp_GetParameterHelpTwoLines_And_Clear_Emacs()
+ {
+ TestSetup(KeyMode.Emacs);
+ string emptyLine = new string(' ', _console.BufferWidth);
+
+ Test("Get-MultiLineHelp -ExactlyTwo", Keys(
+ "Get-MultiLineHelp -ExactlyTwo", _.Alt_h,
+ CheckThat(() => AssertScreenIs(9,
+ TokenClassification.Command, "Get-MultiLineHelp",
+ TokenClassification.None, " ",
+ TokenClassification.Parameter, "-ExactlyTwo",
+ NextLine,
+ emptyLine,
+ TokenClassification.None, $"-Date ",
+ NextLine,
+ emptyLine,
+ TokenClassification.None, "DESC: Some very long description that is over the buffer wid",
+ TokenClassification.None, "th of 60 characters and exactly the length of 120 characters",
+ NextLine,
+ TokenClassification.None, "Required: false, Position: 0, Default Value: None, Pipeline ",
+ NextLine,
+ "Input: True (ByPropertyName, ByValue), WildCard: false")),
+ _.RightArrow,
+ CheckThat(() => AssertScreenIs(1,
+ TokenClassification.Command, "Get-MultiLineHelp",
+ TokenClassification.None, " ",
+ TokenClassification.Parameter, "-ExactlyTwo")),
+ _.Enter
+ ));
+ }
+
+ [SkippableFact]
+ public void DynHelp_GetParameterHelpTwoLines_And_Clear_Vi()
+ {
+ TestSetup(KeyMode.Vi);
+ string emptyLine = new string(' ', _console.BufferWidth);
+
+ Test("Get-MultiLineHelp -ExactlyTwo", Keys(
+ "Get-MultiLineHelp -ExactlyTwo", _.Alt_h,
+ CheckThat(() => AssertScreenIs(9,
+ TokenClassification.Command, "Get-MultiLineHelp",
+ TokenClassification.None, " ",
+ TokenClassification.Parameter, "-ExactlyTwo",
+ NextLine,
+ emptyLine,
+ TokenClassification.None, $"-Date ",
+ NextLine,
+ emptyLine,
+ TokenClassification.None, "DESC: Some very long description that is over the buffer wid",
+ TokenClassification.None, "th of 60 characters and exactly the length of 120 characters",
+ NextLine,
+ TokenClassification.None, "Required: false, Position: 0, Default Value: None, Pipeline ",
+ NextLine,
+ "Input: True (ByPropertyName, ByValue), WildCard: false")),
+ _.RightArrow,
+ CheckThat(() => AssertScreenIs(1,
+ TokenClassification.Command, "Get-MultiLineHelp",
+ TokenClassification.None, " ",
+ TokenClassification.Parameter, "-ExactlyTwo")),
+ _.Enter
+ ));
+ }
+
+ [SkippableFact]
+ public void DynHelp_GetParameterHelpErrorMessage()
+ {
+ TestSetup(KeyMode.Cmd);
+ string emptyLine = new string(' ', _console.BufferWidth);
+
+ Test("Get-FakeHelp -Fake", Keys(
+ "Get-FakeHelp -Fake", _.Alt_h,
+ CheckThat(() => AssertScreenIs(4,
+ TokenClassification.Command, "Get-FakeHelp",
+ TokenClassification.None, " ",
+ TokenClassification.Parameter, "-Fake",
+ NextLine,
+ emptyLine,
+ TokenClassification.None, "No help content available. Please use Update-Help to downloa",
+ NextLine,
+ "d the latest help content.")),
+ _.RightArrow,
+ _.Enter
+ ));
+ }
+ }
+}
diff --git a/test/PSReadLine.Tests.csproj b/test/PSReadLine.Tests.csproj
index 1edff47a1..4c1a993d1 100644
--- a/test/PSReadLine.Tests.csproj
+++ b/test/PSReadLine.Tests.csproj
@@ -24,7 +24,7 @@
-
+
diff --git a/test/UnitTestReadLine.cs b/test/UnitTestReadLine.cs
index 7e9cb8803..21d05ea4e 100644
--- a/test/UnitTestReadLine.cs
+++ b/test/UnitTestReadLine.cs
@@ -25,6 +25,7 @@ internal class MockedMethods : IPSConsoleReadLineMockableMethods
internal IReadOnlyList commandHistory;
internal Guid acceptedPredictorId;
internal string acceptedSuggestion;
+ internal string helpContentRendered;
internal void ClearPredictionFields()
{
@@ -67,6 +68,16 @@ public void OnSuggestionAccepted(Guid predictorId, string suggestionText)
acceptedPredictorId = predictorId;
acceptedSuggestion = suggestionText;
}
+
+ public object GetDynamicHelpContent(string commandName, string parameterName, bool isFullHelp)
+ {
+ return ReadLine.GetDynamicHelpContent(commandName, parameterName, isFullHelp);
+ }
+
+ public void RenderFullHelp(string content, string regexPatternToScrollTo)
+ {
+ helpContentRendered = content;
+ }
}
public enum TokenClassification