|
1 | 1 | // Copyright (c) Microsoft Corporation. |
2 | 2 | // Licensed under the MIT License. |
3 | 3 |
|
| 4 | +using System.Collections.Generic; |
4 | 5 | using System.Management.Automation; |
5 | 6 | using System.Threading; |
6 | 7 | using System.Threading.Tasks; |
|
11 | 12 |
|
12 | 13 | namespace Microsoft.PowerShell.EditorServices.Handlers |
13 | 14 | { |
14 | | - [Serial, Method("powerShell/showHelp")] |
15 | | - internal interface IShowHelpHandler : IJsonRpcNotificationHandler<ShowHelpParams> { } |
| 15 | + [Serial, Method("powerShell/showHelp", Direction.ClientToServer)] |
| 16 | + internal interface IShowHelpHandler : IJsonRpcRequestHandler<ShowHelpParams, ShowHelpResult> { } |
16 | 17 |
|
17 | | - internal class ShowHelpParams : IRequest |
| 18 | + internal class ShowHelpParams : IRequest<ShowHelpResult> |
18 | 19 | { |
19 | 20 | public string Text { get; set; } |
20 | 21 | } |
21 | 22 |
|
| 23 | + internal class ShowHelpResult |
| 24 | + { |
| 25 | + public string HelpText { get; set; } |
| 26 | + } |
| 27 | + |
22 | 28 | internal class ShowHelpHandler : IShowHelpHandler |
23 | 29 | { |
24 | 30 | private readonly IInternalPowerShellExecutionService _executionService; |
25 | 31 |
|
26 | 32 | public ShowHelpHandler(IInternalPowerShellExecutionService executionService) => _executionService = executionService; |
27 | 33 |
|
28 | | - public async Task<Unit> Handle(ShowHelpParams request, CancellationToken cancellationToken) |
| 34 | + public async Task<ShowHelpResult> Handle(ShowHelpParams request, CancellationToken cancellationToken) |
29 | 35 | { |
30 | | - // TODO: Refactor to not rerun the function definition every time. |
31 | | - const string CheckHelpScript = @" |
| 36 | + // Resolves the command and returns its full help as a string so the |
| 37 | + // client can display it (e.g. in an editor pane) or pass it to a |
| 38 | + // language model tool. Returns a friendly message if the command |
| 39 | + // cannot be found rather than throwing. |
| 40 | + const string GetHelpScript = @" |
32 | 41 | [System.Diagnostics.DebuggerHidden()] |
33 | 42 | [CmdletBinding()] |
34 | 43 | param ( |
35 | 44 | [String]$CommandName |
36 | 45 | ) |
37 | | - try { |
38 | | - $command = Microsoft.PowerShell.Core\Get-Command $CommandName -ErrorAction Stop |
39 | | - } catch [System.Management.Automation.CommandNotFoundException] { |
40 | | - $PSCmdlet.ThrowTerminatingError($PSItem) |
| 46 | + $command = Microsoft.PowerShell.Core\Get-Command $CommandName -ErrorAction Ignore |
| 47 | + if ($null -eq $command) { |
| 48 | + return ""No command named '$CommandName' was found."" |
41 | 49 | } |
42 | | - try { |
43 | | - $helpUri = [Microsoft.PowerShell.Commands.GetHelpCodeMethods]::GetHelpUri($command) |
44 | | -
|
45 | | - $oldSslVersion = [System.Net.ServicePointManager]::SecurityProtocol |
46 | | - [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 |
47 | | -
|
48 | | - # HEAD means we don't need the content itself back, just the response header |
49 | | - $status = (Microsoft.PowerShell.Utility\Invoke-WebRequest -Method Head -Uri $helpUri -TimeoutSec 5 -ErrorAction Stop).StatusCode |
50 | | - if ($status -lt 400) { |
51 | | - $null = Microsoft.PowerShell.Core\Get-Help $CommandName -Online |
52 | | - return |
53 | | - } |
54 | | - } catch { |
55 | | - # Ignore - we want to drop out to Get-Help -Full |
56 | | - } finally { |
57 | | - [System.Net.ServicePointManager]::SecurityProtocol = $oldSslVersion |
58 | | - } |
59 | | -
|
60 | | - return Microsoft.PowerShell.Core\Get-Help $CommandName -Full |
| 50 | + return (Microsoft.PowerShell.Core\Get-Help $CommandName -Full | Microsoft.PowerShell.Utility\Out-String) |
61 | 51 | "; |
62 | 52 |
|
63 | | - string helpParams = request.Text; |
64 | | - if (string.IsNullOrEmpty(helpParams)) { helpParams = "Get-Help"; } |
| 53 | + string commandName = request.Text; |
| 54 | + if (string.IsNullOrEmpty(commandName)) { commandName = "Get-Help"; } |
65 | 55 |
|
66 | | - PSCommand checkHelpPSCommand = new PSCommand() |
67 | | - .AddScript(CheckHelpScript, useLocalScope: true) |
68 | | - .AddArgument(helpParams); |
| 56 | + PSCommand getHelpCommand = new PSCommand() |
| 57 | + .AddScript(GetHelpScript, useLocalScope: true) |
| 58 | + .AddArgument(commandName); |
69 | 59 |
|
70 | | - // TODO: Rather than print the help in the console, we should send the string back |
71 | | - // to VSCode to display in a help pop-up (or similar) |
72 | | - await _executionService.ExecutePSCommandAsync<PSObject>( |
73 | | - checkHelpPSCommand, |
| 60 | + IReadOnlyList<string> results = await _executionService.ExecutePSCommandAsync<string>( |
| 61 | + getHelpCommand, |
74 | 62 | cancellationToken, |
75 | 63 | new PowerShellExecutionOptions |
76 | 64 | { |
77 | | - RequiresForeground = true, |
78 | | - WriteOutputToHost = true, |
79 | 65 | ThrowOnError = false |
80 | 66 | }).ConfigureAwait(false); |
81 | | - return Unit.Value; |
| 67 | + |
| 68 | + // Get-Help piped through Out-String is padded with a leading blank |
| 69 | + // line and trailing blank lines (a console-formatter artifact); trim |
| 70 | + // both so the help pane and the language model tool get clean output. |
| 71 | + string helpText = results is { Count: > 0 } |
| 72 | + ? string.Concat(results).Trim() |
| 73 | + : string.Empty; |
| 74 | + |
| 75 | + return new ShowHelpResult { HelpText = helpText }; |
82 | 76 | } |
83 | 77 | } |
84 | 78 | } |
0 commit comments