Skip to content

Commit e02498f

Browse files
committed
Add ICodeLenses and ICodeLensProvider interfaces and implementations
This change adds the ICodeLenses and ICodeLensProvider interfaces and their implementations to establish the provider model for CodeLenses in the editor.
1 parent ecdd1ac commit e02498f

File tree

18 files changed

+781
-8
lines changed

18 files changed

+781
-8
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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.Language;
7+
using Microsoft.PowerShell.EditorServices.CodeLenses;
8+
using Newtonsoft.Json;
9+
using Newtonsoft.Json.Linq;
10+
using LanguageServer = Microsoft.PowerShell.EditorServices.Protocol.LanguageServer;
11+
12+
namespace Microsoft.PowerShell.EditorServices
13+
{
14+
public static class ICodeLensExtensions
15+
{
16+
public static LanguageServer.CodeLens ToProtocolCodeLens(
17+
this CodeLens codeLens,
18+
JsonSerializer jsonSerializer)
19+
{
20+
return new LanguageServer.CodeLens
21+
{
22+
Range = codeLens.ScriptExtent.ToRange(),
23+
Command = codeLens.Command.ToProtocolCommand(jsonSerializer)
24+
};
25+
}
26+
27+
public static LanguageServer.CodeLens ToProtocolCodeLens(
28+
this CodeLens codeLens,
29+
object codeLensData,
30+
JsonSerializer jsonSerializer)
31+
{
32+
LanguageServer.ServerCommand command = null;
33+
34+
if (codeLens.Command != null)
35+
{
36+
command = codeLens.Command.ToProtocolCommand(jsonSerializer);
37+
}
38+
39+
return new LanguageServer.CodeLens
40+
{
41+
Range = codeLens.ScriptExtent.ToRange(),
42+
Data = JToken.FromObject(codeLensData, jsonSerializer),
43+
Command = command
44+
};
45+
}
46+
}
47+
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
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 Microsoft.PowerShell.EditorServices.Components;
7+
using Microsoft.PowerShell.EditorServices.Protocol.LanguageServer;
8+
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
9+
using Microsoft.PowerShell.EditorServices.Utility;
10+
using Newtonsoft.Json;
11+
using Newtonsoft.Json.Linq;
12+
using System.Linq;
13+
using System.Threading;
14+
using System.Threading.Tasks;
15+
16+
using LanguageServer = Microsoft.PowerShell.EditorServices.Protocol.LanguageServer;
17+
18+
namespace Microsoft.PowerShell.EditorServices.CodeLenses
19+
{
20+
internal class CodeLensFeature : ICodeLenses
21+
{
22+
private EditorSession editorSession;
23+
24+
private JsonSerializer jsonSerializer =
25+
JsonSerializer.Create(
26+
Constants.JsonSerializerSettings);
27+
28+
public IProviderCollection<ICodeLensProvider> Providers { get; } =
29+
new ProviderCollection<ICodeLensProvider>();
30+
31+
public CodeLensFeature(
32+
EditorSession editorSession,
33+
IMessageHandlers messageHandlers,
34+
ILogger 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+
}
46+
47+
public static CodeLensFeature Create(
48+
IComponentRegistry components,
49+
EditorSession editorSession)
50+
{
51+
var codeLenses =
52+
new CodeLensFeature(
53+
editorSession,
54+
components.Get<IMessageHandlers>(),
55+
components.Get<ILogger>());
56+
57+
codeLenses.Providers.Add(
58+
new ReferencesCodeLensProvider(
59+
editorSession));
60+
61+
62+
editorSession.Components.Register<ICodeLenses>(codeLenses);
63+
64+
return codeLenses;
65+
}
66+
67+
public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile)
68+
{
69+
return
70+
this.Providers
71+
.SelectMany(p => p.ProvideCodeLenses(scriptFile))
72+
.ToArray();
73+
}
74+
75+
private async Task HandleCodeLensRequest(
76+
CodeLensRequest codeLensParams,
77+
RequestContext<LanguageServer.CodeLens[]> requestContext)
78+
{
79+
JsonSerializer jsonSerializer =
80+
JsonSerializer.Create(
81+
Constants.JsonSerializerSettings);
82+
83+
var scriptFile =
84+
this.editorSession.Workspace.GetFile(
85+
codeLensParams.TextDocument.Uri);
86+
87+
var codeLenses =
88+
this.ProvideCodeLenses(scriptFile)
89+
.Select(
90+
codeLens =>
91+
codeLens.ToProtocolCodeLens(
92+
new CodeLensData
93+
{
94+
Uri = codeLens.File.ClientFilePath,
95+
ProviderId = codeLens.Provider.ProviderId
96+
},
97+
this.jsonSerializer))
98+
.ToArray();
99+
100+
await requestContext.SendResult(codeLenses);
101+
}
102+
103+
private async Task HandleCodeLensResolveRequest(
104+
LanguageServer.CodeLens codeLens,
105+
RequestContext<LanguageServer.CodeLens> requestContext)
106+
{
107+
if (codeLens.Data != null)
108+
{
109+
// TODO: Catch deserializtion exception on bad object
110+
CodeLensData codeLensData = codeLens.Data.ToObject<CodeLensData>();
111+
112+
ICodeLensProvider originalProvider =
113+
this.Providers.FirstOrDefault(
114+
provider => provider.ProviderId.Equals(codeLensData.ProviderId));
115+
116+
if (originalProvider != null)
117+
{
118+
ScriptFile scriptFile =
119+
this.editorSession.Workspace.GetFile(
120+
codeLensData.Uri);
121+
122+
ScriptRegion region = new ScriptRegion
123+
{
124+
StartLineNumber = codeLens.Range.Start.Line + 1,
125+
StartColumnNumber = codeLens.Range.Start.Character + 1,
126+
EndLineNumber = codeLens.Range.End.Line + 1,
127+
EndColumnNumber = codeLens.Range.End.Character + 1
128+
};
129+
130+
CodeLens originalCodeLens =
131+
new CodeLens(
132+
originalProvider,
133+
scriptFile,
134+
region);
135+
136+
var resolvedCodeLens =
137+
await originalProvider.ResolveCodeLensAsync(
138+
originalCodeLens,
139+
CancellationToken.None);
140+
141+
await requestContext.SendResult(
142+
resolvedCodeLens.ToProtocolCodeLens(
143+
this.jsonSerializer));
144+
}
145+
else
146+
{
147+
// TODO: Write error!
148+
}
149+
}
150+
}
151+
152+
private class CodeLensData
153+
{
154+
public string Uri { get; set; }
155+
156+
public string ProviderId {get; set; }
157+
}
158+
}
159+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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.Language;
7+
using Microsoft.PowerShell.EditorServices.Protocol.LanguageServer;
8+
9+
namespace Microsoft.PowerShell.EditorServices
10+
{
11+
public static class IScriptExtentExtensions
12+
{
13+
public static Range ToRange(this IScriptExtent scriptExtent)
14+
{
15+
return new Range
16+
{
17+
Start = new Position
18+
{
19+
Line = scriptExtent.StartLineNumber - 1,
20+
Character = scriptExtent.StartColumnNumber - 1
21+
},
22+
End = new Position
23+
{
24+
Line = scriptExtent.EndLineNumber - 1,
25+
Character = scriptExtent.EndColumnNumber - 1
26+
}
27+
};
28+
}
29+
}
30+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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;
7+
using System.Collections.Generic;
8+
using System.Linq;
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
using Microsoft.PowerShell.EditorServices.Commands;
12+
using Microsoft.PowerShell.EditorServices.Protocol.LanguageServer;
13+
using Microsoft.PowerShell.EditorServices.Symbols;
14+
15+
namespace Microsoft.PowerShell.EditorServices.CodeLenses
16+
{
17+
internal class ReferencesCodeLensProvider : ProviderBase, ICodeLensProvider
18+
{
19+
private EditorSession editorSession;
20+
private IDocumentSymbolProvider symbolProvider;
21+
22+
public ReferencesCodeLensProvider(EditorSession editorSession)
23+
{
24+
this.editorSession = editorSession;
25+
26+
// TODO: Pull this from components
27+
this.symbolProvider =
28+
new ScriptDocumentSymbolProvider(
29+
editorSession.PowerShellContext.LocalPowerShellVersion.Version);
30+
}
31+
32+
public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile)
33+
{
34+
return
35+
this.symbolProvider
36+
.ProvideDocumentSymbols(scriptFile)
37+
.Where(symbol => symbol.SymbolType == SymbolType.Function)
38+
.Select(
39+
symbol =>
40+
new CodeLens(
41+
this,
42+
scriptFile,
43+
symbol.ScriptRegion))
44+
.ToArray();
45+
}
46+
47+
public async Task<CodeLens> ResolveCodeLensAsync(
48+
CodeLens codeLens,
49+
CancellationToken cancellationToken)
50+
{
51+
ScriptFile[] references =
52+
editorSession.Workspace.ExpandScriptReferences(
53+
codeLens.File);
54+
55+
var foundSymbol =
56+
this.editorSession.LanguageService.FindFunctionDefinitionAtLocation(
57+
codeLens.File,
58+
codeLens.ScriptExtent.StartLineNumber,
59+
codeLens.ScriptExtent.StartColumnNumber);
60+
61+
FindReferencesResult referencesResult =
62+
await editorSession.LanguageService.FindReferencesOfSymbol(
63+
foundSymbol,
64+
references,
65+
editorSession.Workspace);
66+
67+
var referenceLocations =
68+
referencesResult
69+
.FoundReferences
70+
.Select(
71+
r => new Location
72+
{
73+
Uri = GetFileUri(r.FilePath),
74+
Range = r.ScriptRegion.ToRange()
75+
})
76+
.ToArray();
77+
78+
return
79+
new CodeLens(
80+
codeLens,
81+
new ClientCommand(
82+
"editor.action.showReferences",
83+
referenceLocations.Length == 1
84+
? "1 reference"
85+
: $"{referenceLocations.Length} references",
86+
new object[]
87+
{
88+
codeLens.File.ClientFilePath,
89+
codeLens.ScriptExtent.ToRange().Start,
90+
referenceLocations,
91+
}
92+
));
93+
}
94+
95+
private static string GetFileUri(string filePath)
96+
{
97+
// If the file isn't untitled, return a URI-style path
98+
return
99+
!filePath.StartsWith("untitled")
100+
? new Uri("file://" + filePath).AbsoluteUri
101+
: filePath;
102+
}
103+
}
104+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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 Microsoft.PowerShell.EditorServices.Commands;
7+
using Newtonsoft.Json;
8+
using Newtonsoft.Json.Linq;
9+
10+
using LanguageServer = Microsoft.PowerShell.EditorServices.Protocol.LanguageServer;
11+
12+
namespace Microsoft.PowerShell.EditorServices
13+
{
14+
public static class ClientCommandExtensions
15+
{
16+
public static LanguageServer.ServerCommand ToProtocolCommand(
17+
this ClientCommand clientCommand,
18+
JsonSerializer jsonSerializer)
19+
{
20+
return new LanguageServer.ServerCommand
21+
{
22+
Command = clientCommand.Name,
23+
Title = clientCommand.Title,
24+
Arguments =
25+
JArray.FromObject(
26+
clientCommand.Arguments,
27+
jsonSerializer)
28+
};
29+
}
30+
}
31+
}

src/PowerShellEditorServices.Host/EditorServicesHost.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
44
//
55

6+
using Microsoft.PowerShell.EditorServices.CodeLenses;
67
using Microsoft.PowerShell.EditorServices.Extensions;
78
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
89
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel;
@@ -353,6 +354,7 @@ private EditorSession CreateSession(
353354
editorSession.Components.Register(messageSender);
354355
editorSession.Components.Register(powerShellContext);
355356

357+
CodeLensFeature.Create(editorSession.Components, editorSession);
356358
DocumentSymbolFeature.Create(editorSession.Components, editorSession);
357359

358360
return editorSession;

0 commit comments

Comments
 (0)