Skip to content

Commit 06d3c22

Browse files
authored
Reduce allocations in the CodeLens providers (#719)
* Stop creating new JsonSerializers * Reduce allocating operations in CodeLens providers * Fix style mistake * Fix style mistake * Remove unused class * Use types instead of var * Use SelectMany in simple non-linq case * Fix empty array handling
1 parent f1ab462 commit 06d3c22

File tree

3 files changed

+273
-154
lines changed

3 files changed

+273
-154
lines changed

src/PowerShellEditorServices.Host/CodeLens/CodeLensFeature.cs

Lines changed: 90 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Microsoft.PowerShell.EditorServices.Utility;
1010
using Newtonsoft.Json;
1111
using Newtonsoft.Json.Linq;
12+
using System.Collections.Generic;
1213
using System.Linq;
1314
using System.Threading;
1415
using System.Threading.Tasks;
@@ -17,43 +18,43 @@
1718

1819
namespace Microsoft.PowerShell.EditorServices.CodeLenses
1920
{
21+
/// <summary>
22+
/// Implements the CodeLens feature for EditorServices.
23+
/// </summary>
2024
internal class CodeLensFeature :
2125
FeatureComponentBase<ICodeLensProvider>,
2226
ICodeLenses
2327
{
24-
private EditorSession editorSession;
25-
26-
private JsonSerializer jsonSerializer =
27-
JsonSerializer.Create(
28-
Constants.JsonSerializerSettings);
29-
30-
public CodeLensFeature(
31-
EditorSession editorSession,
32-
IMessageHandlers messageHandlers,
33-
ILogger logger)
34-
: base(logger)
35-
{
36-
this.editorSession = editorSession;
37-
38-
messageHandlers.SetRequestHandler(
39-
CodeLensRequest.Type,
40-
this.HandleCodeLensRequest);
41-
42-
messageHandlers.SetRequestHandler(
43-
CodeLensResolveRequest.Type,
44-
this.HandleCodeLensResolveRequest);
45-
}
4628

29+
/// <summary>
30+
/// Create a new CodeLens instance around a given editor session
31+
/// from the component registry.
32+
/// </summary>
33+
/// <param name="components">
34+
/// The component registry to provider other components and to register the CodeLens provider in.
35+
/// </param>
36+
/// <param name="editorSession">The editor session context of the CodeLens provider.</param>
37+
/// <returns>A new CodeLens provider for the given editor session.</returns>
4738
public static CodeLensFeature Create(
4839
IComponentRegistry components,
4940
EditorSession editorSession)
5041
{
5142
var codeLenses =
5243
new CodeLensFeature(
5344
editorSession,
54-
components.Get<IMessageHandlers>(),
45+
JsonSerializer.Create(Constants.JsonSerializerSettings),
5546
components.Get<ILogger>());
5647

48+
var messageHandlers = components.Get<IMessageHandlers>();
49+
50+
messageHandlers.SetRequestHandler(
51+
CodeLensRequest.Type,
52+
codeLenses.HandleCodeLensRequest);
53+
54+
messageHandlers.SetRequestHandler(
55+
CodeLensResolveRequest.Type,
56+
codeLenses.HandleCodeLensResolveRequest);
57+
5758
codeLenses.Providers.Add(
5859
new ReferencesCodeLensProvider(
5960
editorSession));
@@ -67,42 +68,78 @@ public static CodeLensFeature Create(
6768
return codeLenses;
6869
}
6970

71+
/// <summary>
72+
/// The editor session context to get workspace and language server data from.
73+
/// </summary>
74+
private readonly EditorSession _editorSession;
75+
76+
/// <summary>
77+
/// The json serializer instance for CodeLens object translation.
78+
/// </summary>
79+
private readonly JsonSerializer _jsonSerializer;
80+
81+
/// <summary>
82+
///
83+
/// </summary>
84+
/// <param name="editorSession"></param>
85+
/// <param name="jsonSerializer"></param>
86+
/// <param name="logger"></param>
87+
private CodeLensFeature(
88+
EditorSession editorSession,
89+
JsonSerializer jsonSerializer,
90+
ILogger logger)
91+
: base(logger)
92+
{
93+
_editorSession = editorSession;
94+
_jsonSerializer = jsonSerializer;
95+
}
96+
97+
/// <summary>
98+
/// Get all the CodeLenses for a given script file.
99+
/// </summary>
100+
/// <param name="scriptFile">The PowerShell script file to get CodeLenses for.</param>
101+
/// <returns>All generated CodeLenses for the given script file.</returns>
70102
public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile)
71103
{
72-
return
73-
this.InvokeProviders(p => p.ProvideCodeLenses(scriptFile))
74-
.SelectMany(r => r)
75-
.ToArray();
104+
return InvokeProviders(provider => provider.ProvideCodeLenses(scriptFile))
105+
.SelectMany(codeLens => codeLens)
106+
.ToArray();
76107
}
77108

109+
/// <summary>
110+
/// Handles a request for CodeLenses from VSCode.
111+
/// </summary>
112+
/// <param name="codeLensParams">Parameters on the CodeLens request that was received.</param>
113+
/// <param name="requestContext"></param>
78114
private async Task HandleCodeLensRequest(
79115
CodeLensRequest codeLensParams,
80116
RequestContext<LanguageServer.CodeLens[]> requestContext)
81117
{
82-
JsonSerializer jsonSerializer =
83-
JsonSerializer.Create(
84-
Constants.JsonSerializerSettings);
118+
ScriptFile scriptFile = _editorSession.Workspace.GetFile(
119+
codeLensParams.TextDocument.Uri);
85120

86-
var scriptFile =
87-
this.editorSession.Workspace.GetFile(
88-
codeLensParams.TextDocument.Uri);
121+
CodeLens[] codeLensResults = ProvideCodeLenses(scriptFile);
89122

90-
var codeLenses =
91-
this.ProvideCodeLenses(scriptFile)
92-
.Select(
93-
codeLens =>
94-
codeLens.ToProtocolCodeLens(
95-
new CodeLensData
96-
{
97-
Uri = codeLens.File.ClientFilePath,
98-
ProviderId = codeLens.Provider.ProviderId
99-
},
100-
this.jsonSerializer))
101-
.ToArray();
102-
103-
await requestContext.SendResult(codeLenses);
123+
var codeLensResponse = new LanguageServer.CodeLens[codeLensResults.Length];
124+
for (int i = 0; i < codeLensResults.Length; i++)
125+
{
126+
codeLensResponse[i] = codeLensResults[i].ToProtocolCodeLens(
127+
new CodeLensData
128+
{
129+
Uri = codeLensResults[i].File.ClientFilePath,
130+
ProviderId = codeLensResults[i].Provider.ProviderId
131+
},
132+
_jsonSerializer);
133+
}
134+
135+
await requestContext.SendResult(codeLensResponse);
104136
}
105137

138+
/// <summary>
139+
/// Handle a CodeLens resolve request from VSCode.
140+
/// </summary>
141+
/// <param name="codeLens">The CodeLens to be resolved/updated.</param>
142+
/// <param name="requestContext"></param>
106143
private async Task HandleCodeLensResolveRequest(
107144
LanguageServer.CodeLens codeLens,
108145
RequestContext<LanguageServer.CodeLens> requestContext)
@@ -113,13 +150,13 @@ private async Task HandleCodeLensResolveRequest(
113150
CodeLensData codeLensData = codeLens.Data.ToObject<CodeLensData>();
114151

115152
ICodeLensProvider originalProvider =
116-
this.Providers.FirstOrDefault(
153+
Providers.FirstOrDefault(
117154
provider => provider.ProviderId.Equals(codeLensData.ProviderId));
118155

119156
if (originalProvider != null)
120157
{
121158
ScriptFile scriptFile =
122-
this.editorSession.Workspace.GetFile(
159+
_editorSession.Workspace.GetFile(
123160
codeLensData.Uri);
124161

125162
ScriptRegion region = new ScriptRegion
@@ -143,7 +180,7 @@ await originalProvider.ResolveCodeLensAsync(
143180

144181
await requestContext.SendResult(
145182
resolvedCodeLens.ToProtocolCodeLens(
146-
this.jsonSerializer));
183+
_jsonSerializer));
147184
}
148185
else
149186
{
@@ -153,6 +190,9 @@ await requestContext.SendError(
153190
}
154191
}
155192

193+
/// <summary>
194+
/// Represents data expected back in an LSP CodeLens response.
195+
/// </summary>
156196
private class CodeLensData
157197
{
158198
public string Uri { get; set; }

src/PowerShellEditorServices.Host/CodeLens/PesterCodeLensProvider.cs

Lines changed: 63 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -15,71 +15,90 @@ namespace Microsoft.PowerShell.EditorServices.CodeLenses
1515
{
1616
internal class PesterCodeLensProvider : FeatureProviderBase, ICodeLensProvider
1717
{
18-
private static char[] QuoteChars = new char[] { '\'', '"'};
18+
/// <summary>
19+
/// The editor session context to provide CodeLenses for.
20+
/// </summary>
21+
private EditorSession _editorSession;
1922

20-
private EditorSession editorSession;
21-
private IDocumentSymbolProvider symbolProvider;
23+
/// <summary>
24+
/// The symbol provider to get symbols from to build code lenses with.
25+
/// </summary>
26+
private IDocumentSymbolProvider _symbolProvider;
2227

28+
/// <summary>
29+
/// Create a new Pester CodeLens provider for a given editor session.
30+
/// </summary>
31+
/// <param name="editorSession">The editor session context for which to provide Pester CodeLenses.</param>
2332
public PesterCodeLensProvider(EditorSession editorSession)
2433
{
25-
this.editorSession = editorSession;
26-
this.symbolProvider = new PesterDocumentSymbolProvider();
34+
_editorSession = editorSession;
35+
_symbolProvider = new PesterDocumentSymbolProvider();
2736
}
2837

29-
private IEnumerable<CodeLens> GetPesterLens(
38+
/// <summary>
39+
/// Get the Pester CodeLenses for a given Pester symbol.
40+
/// </summary>
41+
/// <param name="pesterSymbol">The Pester symbol to get CodeLenses for.</param>
42+
/// <param name="scriptFile">The script file the Pester symbol comes from.</param>
43+
/// <returns>All CodeLenses for the given Pester symbol.</returns>
44+
private CodeLens[] GetPesterLens(
3045
PesterSymbolReference pesterSymbol,
3146
ScriptFile scriptFile)
3247
{
33-
var clientCommands = new ClientCommand[]
48+
var codeLensResults = new CodeLens[]
3449
{
35-
new ClientCommand(
36-
"PowerShell.RunPesterTests",
37-
"Run tests",
38-
new object[]
39-
{
40-
scriptFile.ClientFilePath,
41-
false, // Don't debug
42-
pesterSymbol.TestName,
43-
}),
50+
new CodeLens(
51+
this,
52+
scriptFile,
53+
pesterSymbol.ScriptRegion,
54+
new ClientCommand(
55+
"PowerShell.RunPesterTests",
56+
"Run tests",
57+
new object[] { scriptFile.ClientFilePath, false /* No debug */, pesterSymbol.TestName })),
4458

45-
new ClientCommand(
46-
"PowerShell.RunPesterTests",
47-
"Debug tests",
48-
new object[]
49-
{
50-
scriptFile.ClientFilePath,
51-
true, // Run in debugger
52-
pesterSymbol.TestName,
53-
}),
59+
new CodeLens(
60+
this,
61+
scriptFile,
62+
pesterSymbol.ScriptRegion,
63+
new ClientCommand(
64+
"PowerShell.RunPesterTests",
65+
"Debug tests",
66+
new object[] { scriptFile.ClientFilePath, true /* Run in debugger */, pesterSymbol.TestName })),
5467
};
5568

56-
return
57-
clientCommands.Select(
58-
command =>
59-
new CodeLens(
60-
this,
61-
scriptFile,
62-
pesterSymbol.ScriptRegion,
63-
command));
69+
return codeLensResults;
6470
}
6571

72+
/// <summary>
73+
/// Get all Pester CodeLenses for a given script file.
74+
/// </summary>
75+
/// <param name="scriptFile">The script file to get Pester CodeLenses for.</param>
76+
/// <returns>All Pester CodeLenses for the given script file.</returns>
6677
public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile)
6778
{
68-
var symbols =
69-
this.symbolProvider
70-
.ProvideDocumentSymbols(scriptFile);
79+
var lenses = new List<CodeLens>();
80+
foreach (SymbolReference symbol in _symbolProvider.ProvideDocumentSymbols(scriptFile))
81+
{
82+
if (symbol is PesterSymbolReference pesterSymbol)
83+
{
84+
if (pesterSymbol.Command != PesterCommandType.Describe)
85+
{
86+
continue;
87+
}
7188

72-
var lenses =
73-
symbols
74-
.OfType<PesterSymbolReference>()
75-
.Where(s => s.Command == PesterCommandType.Describe)
76-
.SelectMany(s => this.GetPesterLens(s, scriptFile))
77-
.Where(codeLens => codeLens != null)
78-
.ToArray();
89+
lenses.AddRange(GetPesterLens(pesterSymbol, scriptFile));
90+
}
91+
}
7992

80-
return lenses;
93+
return lenses.ToArray();
8194
}
8295

96+
/// <summary>
97+
/// Resolve the CodeLens provision asynchronously -- just wraps the CodeLens argument in a task.
98+
/// </summary>
99+
/// <param name="codeLens">The code lens to resolve.</param>
100+
/// <param name="cancellationToken"></param>
101+
/// <returns>The given CodeLens, wrapped in a task.</returns>
83102
public Task<CodeLens> ResolveCodeLensAsync(
84103
CodeLens codeLens,
85104
CancellationToken cancellationToken)

0 commit comments

Comments
 (0)