Skip to content
This repository was archived by the owner on Apr 14, 2022. It is now read-only.

Commit bf60725

Browse files
astenmanAnders Stenmanjakebailey
authored and
Mikhail Arkhipov
committed
Initial support added for textDocument/documentHighlight (#1767)
* Initial support added for textDocument/documentHighlight Change-Id: Ib13d86bc96a3702b0e0d79b27b7791898388e104 * Added two tests for DocumentHighlightSource Change-Id: I411c1d4daac84a6a12a95b11fa5781eaf42eeae9 * Code refactoring according to comments from reviewer Change-Id: If45beeafc9a40af5ac12b79d124c8021e9e492a9 Co-authored-by: Anders Stenman <[email protected]> Co-authored-by: Jake Bailey <[email protected]> (cherry picked from commit 1a9d6e5)
1 parent 84d44ee commit bf60725

File tree

5 files changed

+164
-5
lines changed

5 files changed

+164
-5
lines changed

src/LanguageServer/Impl/Implementation/Server.Editor.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,12 @@ public Task<Reference[]> FindReferences(ReferencesParams @params, CancellationTo
108108
return new ReferenceSource(Services).FindAllReferencesAsync(uri, @params.position, ReferenceSearchOptions.All, cancellationToken);
109109
}
110110

111+
public Task<DocumentHighlight[]> DocumentHighlight(ReferencesParams @params, CancellationToken cancellationToken) {
112+
var uri = @params.textDocument.uri;
113+
_log?.Log(TraceEventType.Verbose, $"Document highlight in {uri} at {@params.position}");
114+
return new DocumentHighlightSource(Services).DocumentHighlightAsync(uri, @params.position, cancellationToken);
115+
}
116+
111117
public Task<WorkspaceEdit> Rename(RenameParams @params, CancellationToken cancellationToken) {
112118
var uri = @params.textDocument.uri;
113119
_log?.Log(TraceEventType.Verbose, $"Rename in {uri} at {@params.position}");

src/LanguageServer/Impl/Implementation/Server.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ private InitializeResult GetInitializeResult() {
9898
referencesProvider = true,
9999
workspaceSymbolProvider = true,
100100
documentSymbolProvider = true,
101+
documentHighlightProvider = true,
101102
renameProvider = true,
102103
declarationProvider = true,
103104
documentOnTypeFormattingProvider = new DocumentOnTypeFormattingOptions {

src/LanguageServer/Impl/LanguageServer.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -211,11 +211,14 @@ public async Task<Reference[]> FindReferences(JToken token, CancellationToken ca
211211
}
212212
}
213213

214-
//[JsonRpcMethod("textDocument/documentHighlight")]
215-
//public async Task<DocumentHighlight[]> DocumentHighlight(JToken token, CancellationToken cancellationToken) {
216-
// await _prioritizer.DefaultPriorityAsync(cancellationToken);
217-
// return await _server.DocumentHighlight(ToObject<TextDocumentPositionParams>(token), cancellationToken);
218-
//}
214+
[JsonRpcMethod("textDocument/documentHighlight")]
215+
public async Task<DocumentHighlight[]> DocumentHighlight(JToken token, CancellationToken cancellationToken) {
216+
using (_requestTimer.Time("textDocument/documentHighlight")) {
217+
await _prioritizer.DefaultPriorityAsync(cancellationToken);
218+
Debug.Assert(_initialized);
219+
return await _server.DocumentHighlight(ToObject<ReferencesParams>(token), cancellationToken);
220+
}
221+
}
219222

220223
[JsonRpcMethod("textDocument/documentSymbol")]
221224
public async Task<DocumentSymbol[]> DocumentSymbol(JToken token, CancellationToken cancellationToken) {
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright(c) Microsoft Corporation
2+
// All rights reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the License); you may not use
5+
// this file except in compliance with the License. You may obtain a copy of the
6+
// License at http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
9+
// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
10+
// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
11+
// MERCHANTABILITY OR NON-INFRINGEMENT.
12+
//
13+
// See the Apache Version 2.0 License for specific language governing
14+
// permissions and limitations under the License.
15+
16+
using System;
17+
using System.Collections.Generic;
18+
using System.IO;
19+
using System.Linq;
20+
using System.Threading;
21+
using System.Threading.Tasks;
22+
using Microsoft.Python.Analysis;
23+
using Microsoft.Python.Analysis.Documents;
24+
using Microsoft.Python.Analysis.Modules;
25+
using Microsoft.Python.Analysis.Types;
26+
using Microsoft.Python.Core;
27+
using Microsoft.Python.Core.IO;
28+
using Microsoft.Python.Core.Text;
29+
using Microsoft.Python.LanguageServer.Documents;
30+
using Microsoft.Python.LanguageServer.Protocol;
31+
32+
namespace Microsoft.Python.LanguageServer.Sources {
33+
internal sealed class DocumentHighlightSource {
34+
private const int DocumentHighlightAnalysisTimeout = 10000;
35+
private readonly IServiceContainer _services;
36+
37+
public DocumentHighlightSource(IServiceContainer services) {
38+
_services = services;
39+
}
40+
41+
public async Task<DocumentHighlight[]> DocumentHighlightAsync(Uri uri, SourceLocation location, CancellationToken cancellationToken = default) {
42+
if (uri == null) {
43+
return Array.Empty<DocumentHighlight>();
44+
}
45+
46+
var analysis = await Document.GetAnalysisAsync(uri, _services, DocumentHighlightAnalysisTimeout, cancellationToken);
47+
var definitionSource = new DefinitionSource(_services);
48+
49+
var definition = definitionSource.FindDefinition(analysis, location, out var definingMember);
50+
if (definition == null || definingMember == null) {
51+
return Array.Empty<DocumentHighlight>();
52+
}
53+
54+
var rootDefinition = definingMember.GetRootDefinition();
55+
56+
var result = rootDefinition.References
57+
.Where(r => r.DocumentUri.Equals(uri))
58+
.Select((r, i) => new DocumentHighlight { kind = (i == 0) ? DocumentHighlightKind.Write : DocumentHighlightKind.Read, range = r.Span })
59+
.ToArray();
60+
61+
return result;
62+
}
63+
}
64+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright(c) Microsoft Corporation
2+
// All rights reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the License); you may not use
5+
// this file except in compliance with the License. You may obtain a copy of the
6+
// License at http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
9+
// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
10+
// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
11+
// MERCHANTABILITY OR NON-INFRINGEMENT.
12+
//
13+
// See the Apache Version 2.0 License for specific language governing
14+
// permissions and limitations under the License.
15+
16+
using System;
17+
using System.IO;
18+
using System.Threading.Tasks;
19+
using FluentAssertions;
20+
using Microsoft.Python.Analysis.Analyzer;
21+
using Microsoft.Python.Analysis.Documents;
22+
using Microsoft.Python.Core.Text;
23+
using Microsoft.Python.LanguageServer.Protocol;
24+
using Microsoft.Python.LanguageServer.Sources;
25+
using Microsoft.Python.LanguageServer.Tests.FluentAssertions;
26+
using Microsoft.Python.Parsing.Tests;
27+
using Microsoft.VisualStudio.TestTools.UnitTesting;
28+
using TestUtilities;
29+
30+
namespace Microsoft.Python.LanguageServer.Tests {
31+
[TestClass]
32+
public class DocumentHighlightTests : LanguageServerTestBase {
33+
public TestContext TestContext { get; set; }
34+
35+
[TestInitialize]
36+
public void TestInitialize()
37+
=> TestEnvironmentImpl.TestInitialize($"{TestContext.FullyQualifiedTestClassName}.{TestContext.TestName}");
38+
39+
[TestCleanup]
40+
public void Cleanup() => TestEnvironmentImpl.TestCleanup();
41+
42+
43+
[TestMethod, Priority(0)]
44+
public async Task HighlightBasic() {
45+
const string code = @"
46+
x = 1
47+
48+
def func(x):
49+
return x
50+
51+
y = func(x)
52+
x = 2
53+
";
54+
var analysis = await GetAnalysisAsync(code);
55+
var dhs = new DocumentHighlightSource(Services);
56+
57+
// Test global scope
58+
var highlights1 = await dhs.DocumentHighlightAsync(analysis.Document.Uri, new SourceLocation(8, 1));
59+
60+
highlights1.Should().HaveCount(3);
61+
highlights1[0].range.Should().Be(1, 0, 1, 1);
62+
highlights1[0].kind.Should().Be(DocumentHighlightKind.Write);
63+
highlights1[1].range.Should().Be(6, 9, 6, 10);
64+
highlights1[1].kind.Should().Be(DocumentHighlightKind.Read);
65+
highlights1[2].range.Should().Be(7, 0, 7, 1);
66+
67+
// Test local scope in func()
68+
var highlights2 = await dhs.DocumentHighlightAsync(analysis.Document.Uri, new SourceLocation(4, 10));
69+
70+
highlights2.Should().HaveCount(2);
71+
highlights2[0].range.Should().Be(3, 9, 3, 10);
72+
highlights2[0].kind.Should().Be(DocumentHighlightKind.Write);
73+
highlights2[1].range.Should().Be(4, 11, 4, 12);
74+
highlights2[1].kind.Should().Be(DocumentHighlightKind.Read);
75+
}
76+
77+
[TestMethod, Priority(0)]
78+
public async Task HighlightEmptyDocument() {
79+
await GetAnalysisAsync(string.Empty);
80+
var dhs = new DocumentHighlightSource(Services);
81+
var references = await dhs.DocumentHighlightAsync(null, new SourceLocation(1, 1));
82+
references.Should().BeEmpty();
83+
}
84+
}
85+
}

0 commit comments

Comments
 (0)