Skip to content

Commit 090448c

Browse files
Merge pull request #1799 from SeeminglyScience/fix-intellisense
Fix a lot of IntelliSense issues
2 parents afc9fd1 + 1199612 commit 090448c

File tree

13 files changed

+153
-52
lines changed

13 files changed

+153
-52
lines changed

src/PowerShellEditorServices/Server/PsesLanguageServer.cs

+9-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host;
1313
using Microsoft.PowerShell.EditorServices.Services.Template;
1414
using Newtonsoft.Json.Linq;
15+
using OmniSharp.Extensions.JsonRpc;
1516
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
1617
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
1718
using OmniSharp.Extensions.LanguageServer.Server;
@@ -100,7 +101,14 @@ public async Task StartAsync()
100101
.WithHandler<PsesCodeLensHandlers>()
101102
.WithHandler<PsesCodeActionHandler>()
102103
.WithHandler<InvokeExtensionCommandHandler>()
103-
.WithHandler<PsesCompletionHandler>()
104+
// If PsesCompletionHandler is not marked as serial, then DidChangeTextDocument
105+
// notifications will end up cancelling completion. So quickly typing `Get-`
106+
// would result in no completions.
107+
//
108+
// This also lets completion requests interrupt time consuming background tasks
109+
// like the references code lens.
110+
.WithHandler<PsesCompletionHandler>(
111+
new JsonRpcHandlerOptions() { RequestProcessType = RequestProcessType.Serial })
104112
.WithHandler<PsesHoverHandler>()
105113
.WithHandler<PsesSignatureHelpHandler>()
106114
.WithHandler<PsesDefinitionHandler>()

src/PowerShellEditorServices/Services/CodeLens/ICodeLensProvider.cs

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
using System.Threading;
45
using System.Threading.Tasks;
56
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
67
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
@@ -25,8 +26,9 @@ internal interface ICodeLensProvider
2526
/// <param name="scriptFile">
2627
/// The document for which CodeLenses should be provided.
2728
/// </param>
29+
/// <param name="cancellationToken"></param>
2830
/// <returns>An array of CodeLenses.</returns>
29-
CodeLens[] ProvideCodeLenses(ScriptFile scriptFile);
31+
CodeLens[] ProvideCodeLenses(ScriptFile scriptFile, CancellationToken cancellationToken);
3032

3133
/// <summary>
3234
/// Resolves a CodeLens that was created without a Command.
@@ -37,11 +39,13 @@ internal interface ICodeLensProvider
3739
/// <param name="scriptFile">
3840
/// The ScriptFile to resolve it in (sometimes unused).
3941
/// </param>
42+
/// <param name="cancellationToken"></param>
4043
/// <returns>
4144
/// A Task which returns the resolved CodeLens when completed.
4245
/// </returns>
4346
Task<CodeLens> ResolveCodeLens(
4447
CodeLens codeLens,
45-
ScriptFile scriptFile);
48+
ScriptFile scriptFile,
49+
CancellationToken cancellationToken);
4650
}
4751
}

src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs

+6-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33
using System;
44
using System.Collections.Generic;
5+
using System.Threading;
56
using System.Threading.Tasks;
67
using Microsoft.PowerShell.EditorServices.Services;
78
using Microsoft.PowerShell.EditorServices.Services.Symbols;
@@ -99,8 +100,9 @@ private static CodeLens[] GetPesterLens(PesterSymbolReference pesterSymbol, Scri
99100
/// Get all Pester CodeLenses for a given script file.
100101
/// </summary>
101102
/// <param name="scriptFile">The script file to get Pester CodeLenses for.</param>
103+
/// <param name="cancellationToken"></param>
102104
/// <returns>All Pester CodeLenses for the given script file.</returns>
103-
public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile)
105+
public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile, CancellationToken cancellationToken)
104106
{
105107
// Don't return anything if codelens setting is disabled
106108
if (!_configurationService.CurrentSettings.Pester.CodeLens)
@@ -116,6 +118,7 @@ public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile)
116118
continue;
117119
}
118120

121+
cancellationToken.ThrowIfCancellationRequested();
119122
if (_configurationService.CurrentSettings.Pester.UseLegacyCodeLens
120123
&& pesterSymbol.Command != PesterCommandType.Describe)
121124
{
@@ -133,8 +136,9 @@ public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile)
133136
/// </summary>
134137
/// <param name="codeLens">The code lens to resolve.</param>
135138
/// <param name="scriptFile">The script file.</param>
139+
/// <param name="cancellationToken"></param>
136140
/// <returns>The given CodeLens, wrapped in a task.</returns>
137-
public Task<CodeLens> ResolveCodeLens(CodeLens codeLens, ScriptFile scriptFile) =>
141+
public Task<CodeLens> ResolveCodeLens(CodeLens codeLens, ScriptFile scriptFile, CancellationToken cancellationToken) =>
138142
// This provider has no specific behavior for
139143
// resolving CodeLenses.
140144
Task.FromResult(codeLens);

src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs

+21-9
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Text;
7+
using System.Threading;
78
using System.Threading.Tasks;
89
using Microsoft.PowerShell.EditorServices.Services;
910
using Microsoft.PowerShell.EditorServices.Services.Symbols;
@@ -53,12 +54,14 @@ public ReferencesCodeLensProvider(WorkspaceService workspaceService, SymbolsServ
5354
/// Get all reference code lenses for a given script file.
5455
/// </summary>
5556
/// <param name="scriptFile">The PowerShell script file to get code lenses for.</param>
57+
/// <param name="cancellationToken"></param>
5658
/// <returns>An array of CodeLenses describing all functions in the given script file.</returns>
57-
public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile)
59+
public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile, CancellationToken cancellationToken)
5860
{
5961
List<CodeLens> acc = new();
6062
foreach (SymbolReference sym in _symbolProvider.ProvideDocumentSymbols(scriptFile))
6163
{
64+
cancellationToken.ThrowIfCancellationRequested();
6265
if (sym.SymbolType == SymbolType.Function)
6366
{
6467
acc.Add(new CodeLens
@@ -68,7 +71,7 @@ public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile)
6871
Uri = scriptFile.DocumentUri,
6972
ProviderId = nameof(ReferencesCodeLensProvider)
7073
}, LspSerializer.Instance.JsonSerializer),
71-
Range = sym.ScriptRegion.ToRange()
74+
Range = sym.ScriptRegion.ToRange(),
7275
});
7376
}
7477
}
@@ -81,8 +84,12 @@ public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile)
8184
/// </summary>
8285
/// <param name="codeLens">The old code lens to get updated references for.</param>
8386
/// <param name="scriptFile"></param>
87+
/// <param name="cancellationToken"></param>
8488
/// <returns>A new code lens object describing the same data as the old one but with updated references.</returns>
85-
public async Task<CodeLens> ResolveCodeLens(CodeLens codeLens, ScriptFile scriptFile)
89+
public async Task<CodeLens> ResolveCodeLens(
90+
CodeLens codeLens,
91+
ScriptFile scriptFile,
92+
CancellationToken cancellationToken)
8693
{
8794
ScriptFile[] references = _workspaceService.ExpandScriptReferences(
8895
scriptFile);
@@ -93,9 +100,10 @@ public async Task<CodeLens> ResolveCodeLens(CodeLens codeLens, ScriptFile script
93100
codeLens.Range.Start.Character + 1);
94101

95102
List<SymbolReference> referencesResult = await _symbolsService.FindReferencesOfSymbol(
96-
foundSymbol,
97-
references,
98-
_workspaceService).ConfigureAwait(false);
103+
foundSymbol,
104+
references,
105+
_workspaceService,
106+
cancellationToken).ConfigureAwait(false);
99107

100108
Location[] referenceLocations;
101109
if (referencesResult == null)
@@ -107,6 +115,10 @@ public async Task<CodeLens> ResolveCodeLens(CodeLens codeLens, ScriptFile script
107115
List<Location> acc = new();
108116
foreach (SymbolReference foundReference in referencesResult)
109117
{
118+
// This async method is pretty dense with synchronous code
119+
// so it's helpful to add some yields.
120+
await Task.Yield();
121+
cancellationToken.ThrowIfCancellationRequested();
110122
if (IsReferenceDefinition(foundSymbol, foundReference))
111123
{
112124
continue;
@@ -140,9 +152,9 @@ public async Task<CodeLens> ResolveCodeLens(CodeLens codeLens, ScriptFile script
140152
Title = GetReferenceCountHeader(referenceLocations.Length),
141153
Arguments = JArray.FromObject(new object[]
142154
{
143-
scriptFile.DocumentUri,
144-
codeLens.Range.Start,
145-
referenceLocations
155+
scriptFile.DocumentUri,
156+
codeLens.Range.Start,
157+
referenceLocations
146158
},
147159
LspSerializer.Instance.JsonSerializer)
148160
}

src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ protected SynchronousTask(
3535
CancellationToken cancellationToken)
3636
{
3737
Logger = logger;
38-
_taskCompletionSource = new TaskCompletionSource<TResult>();
38+
_taskCompletionSource = new TaskCompletionSource<TResult>(TaskCreationOptions.RunContinuationsAsynchronously);
3939
_taskRequesterCancellationToken = cancellationToken;
4040
_executionCanceled = false;
4141
}
@@ -76,6 +76,7 @@ public void ExecuteSynchronously(CancellationToken executorCancellationToken)
7676
{
7777
if (IsCanceled)
7878
{
79+
SetCanceled();
7980
return;
8081
}
8182

src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs

+18-11
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility
1616
/// </summary>
1717
internal static class CommandHelpers
1818
{
19+
public record struct AliasMap(
20+
Dictionary<string, List<string>> CmdletToAliases,
21+
Dictionary<string, string> AliasToCmdlets);
22+
1923
private static readonly HashSet<string> s_nounExclusionList = new()
2024
{
2125
// PowerShellGet v2 nouns
@@ -180,19 +184,21 @@ not CommandTypes.Function and
180184
/// Gets all aliases found in the runspace
181185
/// </summary>
182186
/// <param name="executionService"></param>
183-
public static async Task<(Dictionary<string, List<string>>, Dictionary<string, string>)> GetAliasesAsync(IInternalPowerShellExecutionService executionService)
187+
/// <param name="cancellationToken"></param>
188+
public static async Task<AliasMap> GetAliasesAsync(
189+
IInternalPowerShellExecutionService executionService,
190+
CancellationToken cancellationToken = default)
184191
{
185192
Validate.IsNotNull(nameof(executionService), executionService);
186193

187-
IEnumerable<CommandInfo> aliases = await executionService.ExecuteDelegateAsync(
188-
nameof(GetAliasesAsync),
189-
executionOptions: null,
190-
(pwsh, _) =>
191-
{
192-
CommandInvocationIntrinsics invokeCommand = pwsh.Runspace.SessionStateProxy.InvokeCommand;
193-
return invokeCommand.GetCommands("*", CommandTypes.Alias, nameIsPattern: true);
194-
},
195-
CancellationToken.None).ConfigureAwait(false);
194+
// Need to execute a PSCommand here as Runspace.SessionStateProxy cannot be used from
195+
// our PSRL on idle handler.
196+
IReadOnlyList<CommandInfo> aliases = await executionService.ExecutePSCommandAsync<CommandInfo>(
197+
new PSCommand()
198+
.AddCommand("Microsoft.PowerShell.Core\\Get-Command")
199+
.AddParameter("ListImported", true)
200+
.AddParameter("CommandType", CommandTypes.Alias),
201+
cancellationToken).ConfigureAwait(false);
196202

197203
foreach (AliasInfo aliasInfo in aliases)
198204
{
@@ -206,7 +212,8 @@ not CommandTypes.Function and
206212
s_aliasToCmdletCache.TryAdd(aliasInfo.Name, aliasInfo.Definition);
207213
}
208214

209-
return (new Dictionary<string, List<string>>(s_cmdletToAliasCache),
215+
return new AliasMap(
216+
new Dictionary<string, List<string>>(s_cmdletToAliasCache),
210217
new Dictionary<string, string>(s_aliasToCmdletCache));
211218
}
212219
}

src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs

+17-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using System.Management.Automation.Language;
1212
using System.Runtime.InteropServices;
1313
using System.Text.RegularExpressions;
14+
using System.Threading;
1415
using System.Threading.Tasks;
1516
using Microsoft.Extensions.Logging;
1617
using Microsoft.PowerShell.EditorServices.CodeLenses;
@@ -160,18 +161,25 @@ public static SymbolReference FindSymbolAtLocation(
160161
/// <param name="foundSymbol">The symbol to find all references for</param>
161162
/// <param name="referencedFiles">An array of scriptFiles too search for references in</param>
162163
/// <param name="workspace">The workspace that will be searched for symbols</param>
164+
/// <param name="cancellationToken"></param>
163165
/// <returns>FindReferencesResult</returns>
164166
public async Task<List<SymbolReference>> FindReferencesOfSymbol(
165167
SymbolReference foundSymbol,
166168
ScriptFile[] referencedFiles,
167-
WorkspaceService workspace)
169+
WorkspaceService workspace,
170+
CancellationToken cancellationToken = default)
168171
{
169172
if (foundSymbol == null)
170173
{
171174
return null;
172175
}
173176

174-
(Dictionary<string, List<string>> cmdletToAliases, Dictionary<string, string> aliasToCmdlets) = await CommandHelpers.GetAliasesAsync(_executionService).ConfigureAwait(false);
177+
CommandHelpers.AliasMap aliases = await CommandHelpers.GetAliasesAsync(
178+
_executionService,
179+
cancellationToken).ConfigureAwait(false);
180+
181+
Dictionary<string, List<string>> cmdletToAliases = aliases.CmdletToAliases;
182+
Dictionary<string, string> aliasToCmdlets = aliases.AliasToCmdlets;
175183

176184
// We want to look for references first in referenced files, hence we use ordered dictionary
177185
// TODO: File system case-sensitivity is based on filesystem not OS, but OS is a much cheaper heuristic
@@ -188,6 +196,10 @@ public async Task<List<SymbolReference>> FindReferencesOfSymbol(
188196
{
189197
if (!fileMap.Contains(filePath))
190198
{
199+
// This async method is pretty dense with synchronous code
200+
// so it's helpful to add some yields.
201+
await Task.Yield();
202+
cancellationToken.ThrowIfCancellationRequested();
191203
if (!workspace.TryGetFile(filePath, out ScriptFile scriptFile))
192204
{
193205
// If we can't access the file for some reason, just ignore it
@@ -223,6 +235,9 @@ public async Task<List<SymbolReference>> FindReferencesOfSymbol(
223235
reference.FilePath = file.FilePath;
224236
symbolReferences.Add(reference);
225237
}
238+
239+
await Task.Yield();
240+
cancellationToken.ThrowIfCancellationRequested();
226241
}
227242

228243
return symbolReferences;

src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs

+29
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using Microsoft.Extensions.Logging;
1515
using Microsoft.PowerShell.EditorServices.Services.PowerShell;
1616
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution;
17+
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host;
1718

1819
namespace Microsoft.PowerShell.EditorServices.Services.Symbols
1920
{
@@ -87,6 +88,34 @@ await executionService.ExecuteDelegateAsync(
8788
(pwsh, _) =>
8889
{
8990
stopwatch.Start();
91+
92+
// If the current runspace is not out of process, then we call TabExpansion2 so
93+
// that we have the ability to issue pipeline stop requests on cancellation.
94+
if (executionService is PsesInternalHost psesInternalHost
95+
&& !psesInternalHost.Runspace.RunspaceIsRemote)
96+
{
97+
IReadOnlyList<CommandCompletion> completionResults = new SynchronousPowerShellTask<CommandCompletion>(
98+
logger,
99+
psesInternalHost,
100+
new PSCommand()
101+
.AddCommand("TabExpansion2")
102+
.AddParameter("ast", scriptAst)
103+
.AddParameter("tokens", currentTokens)
104+
.AddParameter("positionOfCursor", cursorPosition),
105+
executionOptions: null,
106+
cancellationToken)
107+
.ExecuteAndGetResult(cancellationToken);
108+
109+
if (completionResults is { Count: > 0 })
110+
{
111+
commandCompletion = completionResults[0];
112+
}
113+
114+
return;
115+
}
116+
117+
// If the current runspace is out of process, we can't call TabExpansion2
118+
// because the output will be serialized.
90119
commandCompletion = CommandCompletion.CompleteInput(
91120
scriptAst,
92121
currentTokens,

src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeLensHandlers.cs

+7-6
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@ public PsesCodeLensHandlers(ILoggerFactory factory, SymbolsService symbolsServic
4141
public override Task<CodeLensContainer> Handle(CodeLensParams request, CancellationToken cancellationToken)
4242
{
4343
ScriptFile scriptFile = _workspaceService.GetFile(request.TextDocument.Uri);
44-
CodeLens[] codeLensResults = ProvideCodeLenses(scriptFile);
44+
CodeLens[] codeLensResults = ProvideCodeLenses(scriptFile, cancellationToken);
4545
return Task.FromResult(new CodeLensContainer(codeLensResults));
4646
}
4747

48-
public override Task<CodeLens> Handle(CodeLens request, CancellationToken cancellationToken)
48+
public override async Task<CodeLens> Handle(CodeLens request, CancellationToken cancellationToken)
4949
{
5050
// TODO: Catch deserialization exception on bad object
5151
CodeLensData codeLensData = request.Data.ToObject<CodeLensData>();
@@ -55,18 +55,19 @@ public override Task<CodeLens> Handle(CodeLens request, CancellationToken cancel
5555
.FirstOrDefault(provider => provider.ProviderId.Equals(codeLensData.ProviderId, StringComparison.Ordinal));
5656

5757
ScriptFile scriptFile = _workspaceService.GetFile(codeLensData.Uri);
58-
59-
return originalProvider.ResolveCodeLens(request, scriptFile);
58+
return await originalProvider.ResolveCodeLens(request, scriptFile, cancellationToken)
59+
.ConfigureAwait(false);
6060
}
6161

6262
/// <summary>
6363
/// Get all the CodeLenses for a given script file.
6464
/// </summary>
6565
/// <param name="scriptFile">The PowerShell script file to get CodeLenses for.</param>
66+
/// <param name="cancellationToken"></param>
6667
/// <returns>All generated CodeLenses for the given script file.</returns>
67-
private CodeLens[] ProvideCodeLenses(ScriptFile scriptFile)
68+
private CodeLens[] ProvideCodeLenses(ScriptFile scriptFile, CancellationToken cancellationToken)
6869
{
69-
return InvokeProviders(provider => provider.ProvideCodeLenses(scriptFile))
70+
return InvokeProviders(provider => provider.ProvideCodeLenses(scriptFile, cancellationToken))
7071
.SelectMany(codeLens => codeLens)
7172
.ToArray();
7273
}

0 commit comments

Comments
 (0)