Skip to content

Commit ec844df

Browse files
Last LSP messages (#1016)
* support CommandExporer commands and powerShell/runspaceChanged * expand alias
1 parent 3754d99 commit ec844df

File tree

8 files changed

+342
-7
lines changed

8 files changed

+342
-7
lines changed

src/PowerShellEditorServices.Engine/LanguageServer/OmnisharpLanguageServer.cs

+3
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ public async Task StartAsync()
117117
.WithHandler<TemplateHandlers>()
118118
.WithHandler<GetCommentHelpHandler>()
119119
.WithHandler<EvaluateHandler>()
120+
.WithHandler<GetCommandHandler>()
121+
.WithHandler<ShowHelpHandler>()
122+
.WithHandler<ExpandAliasHandler>()
120123
.OnInitialize(
121124
async (languageServer, request) =>
122125
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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.Linq;
7+
using System.Management.Automation;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
using Microsoft.Extensions.Logging;
11+
using Microsoft.PowerShell.EditorServices;
12+
using OmniSharp.Extensions.Embedded.MediatR;
13+
using OmniSharp.Extensions.JsonRpc;
14+
15+
namespace PowerShellEditorServices.Engine.Services.Handlers
16+
{
17+
[Serial, Method("powerShell/expandAlias")]
18+
public interface IExpandAliasHandler : IJsonRpcRequestHandler<ExpandAliasParams, ExpandAliasResult> { }
19+
20+
public class ExpandAliasParams : IRequest<ExpandAliasResult>
21+
{
22+
public string Text { get; set; }
23+
}
24+
25+
public class ExpandAliasResult
26+
{
27+
public string Text { get; set; }
28+
}
29+
30+
public class ExpandAliasHandler : IExpandAliasHandler
31+
{
32+
private readonly ILogger _logger;
33+
private readonly PowerShellContextService _powerShellContextService;
34+
35+
public ExpandAliasHandler(ILoggerFactory factory, PowerShellContextService powerShellContextService)
36+
{
37+
_logger = factory.CreateLogger<ExpandAliasHandler>();
38+
_powerShellContextService = powerShellContextService;
39+
}
40+
41+
public async Task<ExpandAliasResult> Handle(ExpandAliasParams request, CancellationToken cancellationToken)
42+
{
43+
const string script = @"
44+
function __Expand-Alias {
45+
46+
param($targetScript)
47+
48+
[ref]$errors=$null
49+
50+
$tokens = [System.Management.Automation.PsParser]::Tokenize($targetScript, $errors).Where({$_.type -eq 'command'}) |
51+
Sort-Object Start -Descending
52+
53+
foreach ($token in $tokens) {
54+
$definition=(Get-Command ('`'+$token.Content) -CommandType Alias -ErrorAction SilentlyContinue).Definition
55+
56+
if($definition) {
57+
$lhs=$targetScript.Substring(0, $token.Start)
58+
$rhs=$targetScript.Substring($token.Start + $token.Length)
59+
60+
$targetScript=$lhs + $definition + $rhs
61+
}
62+
}
63+
64+
$targetScript
65+
}";
66+
67+
// TODO: Refactor to not rerun the function definition every time.
68+
var psCommand = new PSCommand();
69+
psCommand
70+
.AddScript(script)
71+
.AddStatement()
72+
.AddCommand("__Expand-Alias")
73+
.AddArgument(request.Text);
74+
var result = await _powerShellContextService.ExecuteCommandAsync<string>(psCommand);
75+
76+
return new ExpandAliasResult
77+
{
78+
Text = result.First()
79+
};
80+
}
81+
}
82+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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.Collections.Generic;
7+
using System.Management.Automation;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
using Microsoft.Extensions.Logging;
11+
using Microsoft.PowerShell.EditorServices;
12+
using OmniSharp.Extensions.Embedded.MediatR;
13+
using OmniSharp.Extensions.JsonRpc;
14+
15+
namespace PowerShellEditorServices.Engine.Services.Handlers
16+
{
17+
[Serial, Method("powerShell/getCommand")]
18+
public interface IGetCommandHandler : IJsonRpcRequestHandler<GetCommandParams, List<PSCommandMessage>> { }
19+
20+
public class GetCommandParams : IRequest<List<PSCommandMessage>> { }
21+
22+
/// <summary>
23+
/// Describes the message to get the details for a single PowerShell Command
24+
/// from the current session
25+
/// </summary>
26+
public class PSCommandMessage
27+
{
28+
public string Name { get; set; }
29+
public string ModuleName { get; set; }
30+
public string DefaultParameterSet { get; set; }
31+
public Dictionary<string, ParameterMetadata> Parameters { get; set; }
32+
public System.Collections.ObjectModel.ReadOnlyCollection<CommandParameterSetInfo> ParameterSets { get; set; }
33+
}
34+
35+
public class GetCommandHandler : IGetCommandHandler
36+
{
37+
private readonly ILogger<GetCommandHandler> _logger;
38+
private readonly PowerShellContextService _powerShellContextService;
39+
40+
public GetCommandHandler(ILoggerFactory factory, PowerShellContextService powerShellContextService)
41+
{
42+
_logger = factory.CreateLogger<GetCommandHandler>();
43+
_powerShellContextService = powerShellContextService;
44+
}
45+
46+
public async Task<List<PSCommandMessage>> Handle(GetCommandParams request, CancellationToken cancellationToken)
47+
{
48+
PSCommand psCommand = new PSCommand();
49+
50+
// Executes the following:
51+
// Get-Command -CommandType Function,Cmdlet,ExternalScript | Select-Object -Property Name,ModuleName | Sort-Object -Property Name
52+
psCommand
53+
.AddCommand("Microsoft.PowerShell.Core\\Get-Command")
54+
.AddParameter("CommandType", new[] { "Function", "Cmdlet", "ExternalScript" })
55+
.AddCommand("Microsoft.PowerShell.Utility\\Select-Object")
56+
.AddParameter("Property", new[] { "Name", "ModuleName" })
57+
.AddCommand("Microsoft.PowerShell.Utility\\Sort-Object")
58+
.AddParameter("Property", "Name");
59+
60+
IEnumerable<PSObject> result = await _powerShellContextService.ExecuteCommandAsync<PSObject>(psCommand);
61+
62+
var commandList = new List<PSCommandMessage>();
63+
if (result != null)
64+
{
65+
foreach (dynamic command in result)
66+
{
67+
commandList.Add(new PSCommandMessage
68+
{
69+
Name = command.Name,
70+
ModuleName = command.ModuleName,
71+
Parameters = command.Parameters,
72+
ParameterSets = command.ParameterSets,
73+
DefaultParameterSet = command.DefaultParameterSet
74+
});
75+
}
76+
}
77+
78+
return commandList;
79+
}
80+
}
81+
}

src/PowerShellEditorServices.Engine/Services/PowerShellContext/Handlers/GetVersionHandler.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public GetVersionHandler(ILoggerFactory factory)
1515
_logger = factory.CreateLogger<GetVersionHandler>();
1616
}
1717

18-
public Task<PowerShellVersionDetails> Handle(GetVersionParams request, CancellationToken cancellationToken)
18+
public Task<PowerShellVersion> Handle(GetVersionParams request, CancellationToken cancellationToken)
1919
{
2020
var architecture = PowerShellProcessArchitecture.Unknown;
2121
// This should be changed to using a .NET call sometime in the future... but it's just for logging purposes.
@@ -32,7 +32,8 @@ public Task<PowerShellVersionDetails> Handle(GetVersionParams request, Cancellat
3232
}
3333
}
3434

35-
return Task.FromResult(new PowerShellVersionDetails {
35+
return Task.FromResult(new PowerShellVersion
36+
{
3637
Version = VersionUtils.PSVersion.ToString(),
3738
Edition = VersionUtils.PSEdition,
3839
DisplayVersion = VersionUtils.PSVersion.ToString(2),
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,43 @@
1+
using Microsoft.PowerShell.EditorServices.Session;
12
using OmniSharp.Extensions.Embedded.MediatR;
23
using OmniSharp.Extensions.JsonRpc;
34

45
namespace PowerShellEditorServices.Engine.Services.Handlers
56
{
67
[Serial, Method("powerShell/getVersion")]
7-
public interface IGetVersionHandler : IJsonRpcRequestHandler<GetVersionParams, PowerShellVersionDetails> { }
8+
public interface IGetVersionHandler : IJsonRpcRequestHandler<GetVersionParams, PowerShellVersion> { }
89

9-
public class GetVersionParams : IRequest<PowerShellVersionDetails> { }
10+
public class GetVersionParams : IRequest<PowerShellVersion> { }
1011

11-
public class PowerShellVersionDetails {
12+
public class PowerShellVersion
13+
{
1214
public string Version { get; set; }
1315
public string DisplayVersion { get; set; }
1416
public string Edition { get; set; }
1517
public string Architecture { get; set; }
18+
19+
public PowerShellVersion()
20+
{
21+
}
22+
23+
public PowerShellVersion(PowerShellVersionDetails versionDetails)
24+
{
25+
this.Version = versionDetails.VersionString;
26+
this.DisplayVersion = $"{versionDetails.Version.Major}.{versionDetails.Version.Minor}";
27+
this.Edition = versionDetails.Edition;
28+
29+
switch (versionDetails.Architecture)
30+
{
31+
case PowerShellProcessArchitecture.X64:
32+
this.Architecture = "x64";
33+
break;
34+
case PowerShellProcessArchitecture.X86:
35+
this.Architecture = "x86";
36+
break;
37+
default:
38+
this.Architecture = "Architecture Unknown";
39+
break;
40+
}
41+
}
1642
}
1743
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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.Management.Automation;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Microsoft.Extensions.Logging;
10+
using Microsoft.PowerShell.EditorServices;
11+
using OmniSharp.Extensions.Embedded.MediatR;
12+
using OmniSharp.Extensions.JsonRpc;
13+
14+
namespace PowerShellEditorServices.Engine.Services.Handlers
15+
{
16+
[Serial, Method("powerShell/showHelp")]
17+
public interface IShowHelpHandler : IJsonRpcNotificationHandler<ShowHelpParams> { }
18+
19+
public class ShowHelpParams : IRequest
20+
{
21+
public string Text { get; set; }
22+
}
23+
24+
public class ShowHelpHandler : IShowHelpHandler
25+
{
26+
private readonly ILogger _logger;
27+
private readonly PowerShellContextService _powerShellContextService;
28+
29+
public ShowHelpHandler(ILoggerFactory factory, PowerShellContextService powerShellContextService)
30+
{
31+
_logger = factory.CreateLogger<ShowHelpHandler>();
32+
_powerShellContextService = powerShellContextService;
33+
}
34+
35+
public async Task<Unit> Handle(ShowHelpParams request, CancellationToken cancellationToken)
36+
{
37+
const string CheckHelpScript = @"
38+
[CmdletBinding()]
39+
param (
40+
[String]$CommandName
41+
)
42+
try {
43+
$command = Microsoft.PowerShell.Core\Get-Command $CommandName -ErrorAction Stop
44+
} catch [System.Management.Automation.CommandNotFoundException] {
45+
$PSCmdlet.ThrowTerminatingError($PSItem)
46+
}
47+
try {
48+
$helpUri = [Microsoft.PowerShell.Commands.GetHelpCodeMethods]::GetHelpUri($command)
49+
50+
$oldSslVersion = [System.Net.ServicePointManager]::SecurityProtocol
51+
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
52+
53+
# HEAD means we don't need the content itself back, just the response header
54+
$status = (Microsoft.PowerShell.Utility\Invoke-WebRequest -Method Head -Uri $helpUri -TimeoutSec 5 -ErrorAction Stop).StatusCode
55+
if ($status -lt 400) {
56+
$null = Microsoft.PowerShell.Core\Get-Help $CommandName -Online
57+
return
58+
}
59+
} catch {
60+
# Ignore - we want to drop out to Get-Help -Full
61+
} finally {
62+
[System.Net.ServicePointManager]::SecurityProtocol = $oldSslVersion
63+
}
64+
65+
return Microsoft.PowerShell.Core\Get-Help $CommandName -Full
66+
";
67+
68+
string helpParams = request.Text;
69+
if (string.IsNullOrEmpty(helpParams)) { helpParams = "Get-Help"; }
70+
71+
PSCommand checkHelpPSCommand = new PSCommand()
72+
.AddScript(CheckHelpScript, useLocalScope: true)
73+
.AddArgument(helpParams);
74+
75+
// TODO: Rather than print the help in the console, we should send the string back
76+
// to VSCode to display in a help pop-up (or similar)
77+
await _powerShellContextService.ExecuteCommandAsync<PSObject>(checkHelpPSCommand, sendOutputToHost: true);
78+
return Unit.Value;
79+
}
80+
}
81+
}

src/PowerShellEditorServices.Engine/Services/PowerShellContext/PowerShellContextService.cs

+35
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using Microsoft.PowerShell.EditorServices.Engine;
2121
using Microsoft.PowerShell.EditorServices.Session;
2222
using Microsoft.PowerShell.EditorServices.Utility;
23+
using PowerShellEditorServices.Engine.Services.Handlers;
2324

2425
namespace Microsoft.PowerShell.EditorServices
2526
{
@@ -155,6 +156,7 @@ public PowerShellContextService(
155156
this.logger = logger;
156157
this.isPSReadLineEnabled = isPSReadLineEnabled;
157158

159+
RunspaceChanged += PowerShellContext_RunspaceChangedAsync;
158160
ExecutionStatusChanged += PowerShellContext_ExecutionStatusChangedAsync;
159161
}
160162

@@ -1726,6 +1728,39 @@ private void OnExecutionStatusChanged(
17261728
hadErrors));
17271729
}
17281730

1731+
private void PowerShellContext_RunspaceChangedAsync(object sender, RunspaceChangedEventArgs e)
1732+
{
1733+
_languageServer.SendNotification(
1734+
"powerShell/runspaceChanged",
1735+
new MinifiedRunspaceDetails(e.NewRunspace));
1736+
}
1737+
1738+
1739+
// TODO: Refactor this, RunspaceDetails, PowerShellVersion, and PowerShellVersionDetails
1740+
// It's crazy that this is 4 different types.
1741+
// P.S. MinifiedRunspaceDetails use to be called RunspaceDetails... as in, there were 2 DIFFERENT
1742+
// RunspaceDetails types in this codebase but I've changed it to be minified since the type is
1743+
// slightly simpler than the other RunspaceDetails.
1744+
public class MinifiedRunspaceDetails
1745+
{
1746+
public PowerShellVersion PowerShellVersion { get; set; }
1747+
1748+
public RunspaceLocation RunspaceType { get; set; }
1749+
1750+
public string ConnectionString { get; set; }
1751+
1752+
public MinifiedRunspaceDetails()
1753+
{
1754+
}
1755+
1756+
public MinifiedRunspaceDetails(RunspaceDetails eventArgs)
1757+
{
1758+
this.PowerShellVersion = new PowerShellVersion(eventArgs.PowerShellVersion);
1759+
this.RunspaceType = eventArgs.Location;
1760+
this.ConnectionString = eventArgs.ConnectionString;
1761+
}
1762+
}
1763+
17291764
/// <summary>
17301765
/// Event hook on the PowerShell context to listen for changes in script execution status
17311766
/// </summary>

0 commit comments

Comments
 (0)