Skip to content

Commit 09fc4f3

Browse files
authored
Merge pull request #188 from ezolotko/textdocumentclient-lsp-capabilities-support
Added support for Definition, DocumentHighlights, FoldingRanges to TextDocumentClient.
2 parents 83f3f51 + cebad41 commit 09fc4f3

File tree

4 files changed

+354
-0
lines changed

4 files changed

+354
-0
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using OmniSharp.Extensions.LanguageServer.Client.Utilities;
5+
using OmniSharp.Extensions.LanguageServer.Protocol;
6+
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
7+
8+
namespace OmniSharp.Extensions.LanguageServer.Client.Clients
9+
{
10+
/// <summary>
11+
/// Client for the LSP Text Document API.
12+
/// </summary>
13+
public partial class TextDocumentClient
14+
{
15+
/// <summary>
16+
/// Request definition at the specified document position.
17+
/// </summary>
18+
/// <param name="filePath">
19+
/// The full file-system path of the text document.
20+
/// </param>
21+
/// <param name="line">
22+
/// The target line (0-based).
23+
/// </param>
24+
/// <param name="column">
25+
/// The target column (0-based).
26+
/// </param>
27+
/// <param name="cancellationToken">
28+
/// An optional <see cref="CancellationToken"/> that can be used to cancel the request.
29+
/// </param>
30+
/// <returns>
31+
/// A <see cref="Task{TResult}"/> that resolves to the completions or <c>null</c> if no definitions are available at the specified position.
32+
/// </returns>
33+
public Task<LocationOrLocationLinks> Definition(string filePath, int line, int column, CancellationToken cancellationToken = default(CancellationToken))
34+
{
35+
if (string.IsNullOrWhiteSpace(filePath))
36+
throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(filePath)}.", nameof(filePath));
37+
38+
var documentUri = DocumentUri.FromFileSystemPath(filePath);
39+
40+
return Definition(documentUri, line, column, cancellationToken);
41+
}
42+
43+
/// <summary>
44+
/// Request definition at the specified document position.
45+
/// </summary>
46+
/// <param name="documentUri">
47+
/// The document URI.
48+
/// </param>
49+
/// <param name="line">
50+
/// The target line (0-based).
51+
/// </param>
52+
/// <param name="column">
53+
/// The target column (0-based).
54+
/// </param>
55+
/// <param name="cancellationToken">
56+
/// An optional <see cref="CancellationToken"/> that can be used to cancel the request.
57+
/// </param>
58+
/// <returns>
59+
/// A <see cref="Task{TResult}"/> that resolves to the completions or <c>null</c> if no definitions are available at the specified position.
60+
/// </returns>
61+
public Task<LocationOrLocationLinks> Definition(Uri documentUri, int line, int column, CancellationToken cancellationToken = default(CancellationToken))
62+
{
63+
return PositionalRequest<LocationOrLocationLinks>(DocumentNames.Definition, documentUri, line, column, cancellationToken);
64+
}
65+
}
66+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using OmniSharp.Extensions.LanguageServer.Client.Utilities;
5+
using OmniSharp.Extensions.LanguageServer.Protocol;
6+
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
7+
8+
namespace OmniSharp.Extensions.LanguageServer.Client.Clients
9+
{
10+
/// <summary>
11+
/// Client for the LSP Text Document API.
12+
/// </summary>
13+
public partial class TextDocumentClient
14+
{
15+
/// <summary>
16+
/// Request document highlights at the specified document position.
17+
/// </summary>
18+
/// <param name="filePath">
19+
/// The full file-system path of the text document.
20+
/// </param>
21+
/// <param name="line">
22+
/// The target line (0-based).
23+
/// </param>
24+
/// <param name="column">
25+
/// The target column (0-based).
26+
/// </param>
27+
/// <param name="cancellationToken">
28+
/// An optional <see cref="CancellationToken"/> that can be used to cancel the request.
29+
/// </param>
30+
/// <returns>
31+
/// A <see cref="Task{TResult}"/> that resolves to the completions or <c>null</c> if no document highlights are available at the specified position.
32+
/// </returns>
33+
public Task<DocumentHighlightContainer> DocumentHighlights(string filePath, int line, int column, CancellationToken cancellationToken = default(CancellationToken))
34+
{
35+
if (string.IsNullOrWhiteSpace(filePath))
36+
throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(filePath)}.", nameof(filePath));
37+
38+
var documentUri = DocumentUri.FromFileSystemPath(filePath);
39+
40+
return DocumentHighlights(documentUri, line, column, cancellationToken);
41+
}
42+
43+
/// <summary>
44+
/// Request document highlights at the specified document position.
45+
/// </summary>
46+
/// <param name="documentUri">
47+
/// The document URI.
48+
/// </param>
49+
/// <param name="line">
50+
/// The target line (0-based).
51+
/// </param>
52+
/// <param name="column">
53+
/// The target column (0-based).
54+
/// </param>
55+
/// <param name="cancellationToken">
56+
/// An optional <see cref="CancellationToken"/> that can be used to cancel the request.
57+
/// </param>
58+
/// <returns>
59+
/// A <see cref="Task{TResult}"/> that resolves to the completions or <c>null</c> if no document highlights are available at the specified position.
60+
/// </returns>
61+
public Task<DocumentHighlightContainer> DocumentHighlights(Uri documentUri, int line, int column, CancellationToken cancellationToken = default(CancellationToken))
62+
{
63+
return PositionalRequest<DocumentHighlightContainer>(DocumentNames.DocumentHighlight, documentUri, line, column, cancellationToken);
64+
}
65+
}
66+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using OmniSharp.Extensions.LanguageServer.Client.Utilities;
5+
using OmniSharp.Extensions.LanguageServer.Protocol;
6+
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
7+
8+
namespace OmniSharp.Extensions.LanguageServer.Client.Clients
9+
{
10+
/// <summary>
11+
/// Client for the LSP Text Document API.
12+
/// </summary>
13+
public partial class TextDocumentClient
14+
{
15+
/// <summary>
16+
/// Request document folding ranges.
17+
/// </summary>
18+
/// <param name="filePath">
19+
/// The full file-system path of the text document.
20+
/// </param>
21+
/// <param name="cancellationToken">
22+
/// An optional <see cref="CancellationToken"/> that can be used to cancel the request.
23+
/// </param>
24+
/// <returns>
25+
/// A <see cref="Task{TResult}"/> that resolves to the completions or <c>null</c> if no document highlights are available at the specified position.
26+
/// </returns>
27+
public Task<Container<FoldingRange>> FoldingRanges(string filePath, CancellationToken cancellationToken = default(CancellationToken))
28+
{
29+
if (string.IsNullOrWhiteSpace(filePath))
30+
throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(filePath)}.", nameof(filePath));
31+
32+
var documentUri = DocumentUri.FromFileSystemPath(filePath);
33+
34+
return FoldingRanges(documentUri, cancellationToken);
35+
}
36+
37+
/// <summary>
38+
/// Request document highlights at the specified document position.
39+
/// </summary>
40+
/// <param name="documentUri">
41+
/// The document URI.
42+
/// </param>
43+
/// <param name="line">
44+
/// The target line (0-based).
45+
/// </param>
46+
/// <param name="column">
47+
/// The target column (0-based).
48+
/// </param>
49+
/// <param name="cancellationToken">
50+
/// An optional <see cref="CancellationToken"/> that can be used to cancel the request.
51+
/// </param>
52+
/// <returns>
53+
/// A <see cref="Task{TResult}"/> that resolves to the completions or <c>null</c> if no document highlights are available at the specified position.
54+
/// </returns>
55+
public async Task<Container<FoldingRange>> FoldingRanges(Uri documentUri, CancellationToken cancellationToken = default(CancellationToken))
56+
{
57+
var request = new FoldingRangeRequestParam {
58+
TextDocument = new TextDocumentItem {
59+
Uri = documentUri
60+
}
61+
};
62+
63+
return await Client.SendRequest<Container<FoldingRange>>(DocumentNames.FoldingRange, request, cancellationToken).ConfigureAwait(false);
64+
}
65+
}
66+
}

test/Client.Tests/ClientTests.cs

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,162 @@ public async Task SignatureHelp_Success()
258258
});
259259
}
260260

261+
/// <summary>
262+
/// Ensure that the language client can successfully request Definition.
263+
/// </summary>
264+
[Fact(DisplayName = "Language client can successfully request definition", Skip = "Periodic failures")]
265+
public async Task Definition_Success()
266+
{
267+
await Connect();
268+
269+
const int line = 5;
270+
const int column = 5;
271+
var expectedDocumentPath = AbsoluteDocumentPath;
272+
var expectedDocumentUri = DocumentUri.FromFileSystemPath(expectedDocumentPath);
273+
274+
var expectedDefinitions = new LocationOrLocationLinks(
275+
new LocationOrLocationLink(new Location {
276+
Uri = expectedDocumentUri,
277+
Range = new Range {
278+
Start = new Position {
279+
Line = line,
280+
Character = column
281+
},
282+
End = new Position {
283+
Line = line,
284+
Character = column
285+
}
286+
},
287+
}));
288+
289+
ServerDispatcher.HandleRequest<TextDocumentPositionParams, LocationOrLocationLinks>(DocumentNames.Definition, (request, cancellationToken) => {
290+
Assert.NotNull(request.TextDocument);
291+
292+
Assert.Equal(expectedDocumentUri, request.TextDocument.Uri);
293+
294+
Assert.Equal(line, request.Position.Line);
295+
Assert.Equal(column, request.Position.Character);
296+
297+
return Task.FromResult(expectedDefinitions);
298+
});
299+
300+
var definitions = await LanguageClient.TextDocument.Definition(AbsoluteDocumentPath, line, column);
301+
302+
var actualDefinitions = definitions.ToArray();
303+
Assert.Collection(actualDefinitions, actualDefinition => {
304+
var expectedDefinition = expectedDefinitions.Single();
305+
306+
Assert.NotNull(actualDefinition.Location);
307+
Assert.Equal(expectedDefinition.Location.Uri, actualDefinition.Location.Uri);
308+
309+
Assert.NotNull(actualDefinition.Location.Range);
310+
Assert.NotNull(actualDefinition.Location.Range.Start);
311+
Assert.NotNull(actualDefinition.Location.Range.End);
312+
Assert.Equal(expectedDefinition.Location.Range.Start.Line, actualDefinition.Location.Range.Start.Line);
313+
Assert.Equal(expectedDefinition.Location.Range.Start.Character, actualDefinition.Location.Range.Start.Character);
314+
Assert.Equal(expectedDefinition.Location.Range.End.Line, actualDefinition.Location.Range.End.Line);
315+
Assert.Equal(expectedDefinition.Location.Range.End.Character, actualDefinition.Location.Range.End.Character);
316+
});
317+
}
318+
319+
/// <summary>
320+
/// Ensure that the language client can successfully request DocumentHighlight.
321+
/// </summary>
322+
[Fact(DisplayName = "Language client can successfully request document highlights", Skip = "Periodic failures")]
323+
public async Task DocumentHighlights_Success()
324+
{
325+
await Connect();
326+
327+
const int line = 5;
328+
const int column = 5;
329+
var expectedDocumentPath = AbsoluteDocumentPath;
330+
var expectedDocumentUri = DocumentUri.FromFileSystemPath(expectedDocumentPath);
331+
332+
var expectedHighlights = new DocumentHighlightContainer(
333+
new DocumentHighlight {
334+
Kind = DocumentHighlightKind.Write,
335+
Range = new Range {
336+
Start = new Position {
337+
Line = line,
338+
Character = column
339+
},
340+
End = new Position {
341+
Line = line,
342+
Character = column
343+
}
344+
},
345+
});
346+
347+
ServerDispatcher.HandleRequest<DocumentHighlightParams, DocumentHighlightContainer>(DocumentNames.DocumentHighlight, (request, cancellationToken) => {
348+
Assert.NotNull(request.TextDocument);
349+
350+
Assert.Equal(expectedDocumentUri, request.TextDocument.Uri);
351+
352+
Assert.Equal(line, request.Position.Line);
353+
Assert.Equal(column, request.Position.Character);
354+
355+
return Task.FromResult(expectedHighlights);
356+
});
357+
358+
var definitions = await LanguageClient.TextDocument.DocumentHighlights(AbsoluteDocumentPath, line, column);
359+
360+
var actualDefinitions = definitions.ToArray();
361+
Assert.Collection(actualDefinitions, actualHighlight => {
362+
var expectedHighlight = expectedHighlights.Single();
363+
364+
Assert.Equal(DocumentHighlightKind.Write, expectedHighlight.Kind);
365+
366+
Assert.NotNull(actualHighlight.Range);
367+
Assert.NotNull(actualHighlight.Range.Start);
368+
Assert.NotNull(actualHighlight.Range.End);
369+
Assert.Equal(expectedHighlight.Range.Start.Line, actualHighlight.Range.Start.Line);
370+
Assert.Equal(expectedHighlight.Range.Start.Character, actualHighlight.Range.Start.Character);
371+
Assert.Equal(expectedHighlight.Range.End.Line, actualHighlight.Range.End.Line);
372+
Assert.Equal(expectedHighlight.Range.End.Character, actualHighlight.Range.End.Character);
373+
});
374+
}
375+
376+
/// <summary>
377+
/// Ensure that the language client can successfully request FoldingRanges.
378+
/// </summary>
379+
[Fact(DisplayName = "Language client can successfully request document folding ranges", Skip = "Periodic failures")]
380+
public async Task FoldingRanges_Success()
381+
{
382+
await Connect();
383+
384+
var expectedDocumentPath = AbsoluteDocumentPath;
385+
var expectedDocumentUri = DocumentUri.FromFileSystemPath(expectedDocumentPath);
386+
387+
var expectedFoldingRanges = new Container<FoldingRange>(
388+
new FoldingRange {
389+
Kind = FoldingRangeKind.Region,
390+
StartLine = 5,
391+
StartCharacter = 1,
392+
EndLine = 7,
393+
EndCharacter = 2,
394+
});
395+
396+
ServerDispatcher.HandleRequest<FoldingRangeRequestParam, Container<FoldingRange>>(DocumentNames.FoldingRange, (request, cancellationToken) => {
397+
Assert.NotNull(request.TextDocument);
398+
Assert.Equal(expectedDocumentUri, request.TextDocument.Uri);
399+
return Task.FromResult(expectedFoldingRanges);
400+
});
401+
402+
var foldingRanges = await LanguageClient.TextDocument.FoldingRanges(AbsoluteDocumentPath);
403+
404+
var actualFoldingRanges = foldingRanges.ToArray();
405+
Assert.Collection(actualFoldingRanges, actualFoldingRange => {
406+
var expectedFoldingRange = expectedFoldingRanges.Single();
407+
408+
Assert.Equal(FoldingRangeKind.Region, expectedFoldingRange.Kind);
409+
410+
Assert.Equal(expectedFoldingRange.StartLine, actualFoldingRange.StartLine);
411+
Assert.Equal(expectedFoldingRange.StartCharacter, actualFoldingRange.StartCharacter);
412+
Assert.Equal(expectedFoldingRange.EndLine, actualFoldingRange.EndLine);
413+
Assert.Equal(expectedFoldingRange.EndCharacter, actualFoldingRange.EndCharacter);
414+
});
415+
}
416+
261417
/// <summary>
262418
/// Ensure that the language client can successfully receive Diagnostics from the server.
263419
/// </summary>

0 commit comments

Comments
 (0)