Skip to content

Commit

Permalink
Merge pull request #188 from ezolotko/textdocumentclient-lsp-capabili…
Browse files Browse the repository at this point in the history
…ties-support

Added support for Definition, DocumentHighlights, FoldingRanges to TextDocumentClient.
  • Loading branch information
tintoy authored Nov 6, 2019
2 parents 83f3f51 + cebad41 commit 09fc4f3
Show file tree
Hide file tree
Showing 4 changed files with 354 additions and 0 deletions.
66 changes: 66 additions & 0 deletions src/Client/Clients/TextDocumentClient.Definition.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using OmniSharp.Extensions.LanguageServer.Client.Utilities;
using OmniSharp.Extensions.LanguageServer.Protocol;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;

namespace OmniSharp.Extensions.LanguageServer.Client.Clients
{
/// <summary>
/// Client for the LSP Text Document API.
/// </summary>
public partial class TextDocumentClient
{
/// <summary>
/// Request definition at the specified document position.
/// </summary>
/// <param name="filePath">
/// The full file-system path of the text document.
/// </param>
/// <param name="line">
/// The target line (0-based).
/// </param>
/// <param name="column">
/// The target column (0-based).
/// </param>
/// <param name="cancellationToken">
/// An optional <see cref="CancellationToken"/> that can be used to cancel the request.
/// </param>
/// <returns>
/// A <see cref="Task{TResult}"/> that resolves to the completions or <c>null</c> if no definitions are available at the specified position.
/// </returns>
public Task<LocationOrLocationLinks> Definition(string filePath, int line, int column, CancellationToken cancellationToken = default(CancellationToken))
{
if (string.IsNullOrWhiteSpace(filePath))
throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(filePath)}.", nameof(filePath));

var documentUri = DocumentUri.FromFileSystemPath(filePath);

return Definition(documentUri, line, column, cancellationToken);
}

/// <summary>
/// Request definition at the specified document position.
/// </summary>
/// <param name="documentUri">
/// The document URI.
/// </param>
/// <param name="line">
/// The target line (0-based).
/// </param>
/// <param name="column">
/// The target column (0-based).
/// </param>
/// <param name="cancellationToken">
/// An optional <see cref="CancellationToken"/> that can be used to cancel the request.
/// </param>
/// <returns>
/// A <see cref="Task{TResult}"/> that resolves to the completions or <c>null</c> if no definitions are available at the specified position.
/// </returns>
public Task<LocationOrLocationLinks> Definition(Uri documentUri, int line, int column, CancellationToken cancellationToken = default(CancellationToken))
{
return PositionalRequest<LocationOrLocationLinks>(DocumentNames.Definition, documentUri, line, column, cancellationToken);
}
}
}
66 changes: 66 additions & 0 deletions src/Client/Clients/TextDocumentClient.DocumentHighlights.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using OmniSharp.Extensions.LanguageServer.Client.Utilities;
using OmniSharp.Extensions.LanguageServer.Protocol;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;

namespace OmniSharp.Extensions.LanguageServer.Client.Clients
{
/// <summary>
/// Client for the LSP Text Document API.
/// </summary>
public partial class TextDocumentClient
{
/// <summary>
/// Request document highlights at the specified document position.
/// </summary>
/// <param name="filePath">
/// The full file-system path of the text document.
/// </param>
/// <param name="line">
/// The target line (0-based).
/// </param>
/// <param name="column">
/// The target column (0-based).
/// </param>
/// <param name="cancellationToken">
/// An optional <see cref="CancellationToken"/> that can be used to cancel the request.
/// </param>
/// <returns>
/// A <see cref="Task{TResult}"/> that resolves to the completions or <c>null</c> if no document highlights are available at the specified position.
/// </returns>
public Task<DocumentHighlightContainer> DocumentHighlights(string filePath, int line, int column, CancellationToken cancellationToken = default(CancellationToken))
{
if (string.IsNullOrWhiteSpace(filePath))
throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(filePath)}.", nameof(filePath));

var documentUri = DocumentUri.FromFileSystemPath(filePath);

return DocumentHighlights(documentUri, line, column, cancellationToken);
}

/// <summary>
/// Request document highlights at the specified document position.
/// </summary>
/// <param name="documentUri">
/// The document URI.
/// </param>
/// <param name="line">
/// The target line (0-based).
/// </param>
/// <param name="column">
/// The target column (0-based).
/// </param>
/// <param name="cancellationToken">
/// An optional <see cref="CancellationToken"/> that can be used to cancel the request.
/// </param>
/// <returns>
/// A <see cref="Task{TResult}"/> that resolves to the completions or <c>null</c> if no document highlights are available at the specified position.
/// </returns>
public Task<DocumentHighlightContainer> DocumentHighlights(Uri documentUri, int line, int column, CancellationToken cancellationToken = default(CancellationToken))
{
return PositionalRequest<DocumentHighlightContainer>(DocumentNames.DocumentHighlight, documentUri, line, column, cancellationToken);
}
}
}
66 changes: 66 additions & 0 deletions src/Client/Clients/TextDocumentClient.FoldingRanges.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using OmniSharp.Extensions.LanguageServer.Client.Utilities;
using OmniSharp.Extensions.LanguageServer.Protocol;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;

namespace OmniSharp.Extensions.LanguageServer.Client.Clients
{
/// <summary>
/// Client for the LSP Text Document API.
/// </summary>
public partial class TextDocumentClient
{
/// <summary>
/// Request document folding ranges.
/// </summary>
/// <param name="filePath">
/// The full file-system path of the text document.
/// </param>
/// <param name="cancellationToken">
/// An optional <see cref="CancellationToken"/> that can be used to cancel the request.
/// </param>
/// <returns>
/// A <see cref="Task{TResult}"/> that resolves to the completions or <c>null</c> if no document highlights are available at the specified position.
/// </returns>
public Task<Container<FoldingRange>> FoldingRanges(string filePath, CancellationToken cancellationToken = default(CancellationToken))
{
if (string.IsNullOrWhiteSpace(filePath))
throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(filePath)}.", nameof(filePath));

var documentUri = DocumentUri.FromFileSystemPath(filePath);

return FoldingRanges(documentUri, cancellationToken);
}

/// <summary>
/// Request document highlights at the specified document position.
/// </summary>
/// <param name="documentUri">
/// The document URI.
/// </param>
/// <param name="line">
/// The target line (0-based).
/// </param>
/// <param name="column">
/// The target column (0-based).
/// </param>
/// <param name="cancellationToken">
/// An optional <see cref="CancellationToken"/> that can be used to cancel the request.
/// </param>
/// <returns>
/// A <see cref="Task{TResult}"/> that resolves to the completions or <c>null</c> if no document highlights are available at the specified position.
/// </returns>
public async Task<Container<FoldingRange>> FoldingRanges(Uri documentUri, CancellationToken cancellationToken = default(CancellationToken))
{
var request = new FoldingRangeRequestParam {
TextDocument = new TextDocumentItem {
Uri = documentUri
}
};

return await Client.SendRequest<Container<FoldingRange>>(DocumentNames.FoldingRange, request, cancellationToken).ConfigureAwait(false);
}
}
}
156 changes: 156 additions & 0 deletions test/Client.Tests/ClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,162 @@ public async Task SignatureHelp_Success()
});
}

/// <summary>
/// Ensure that the language client can successfully request Definition.
/// </summary>
[Fact(DisplayName = "Language client can successfully request definition", Skip = "Periodic failures")]
public async Task Definition_Success()
{
await Connect();

const int line = 5;
const int column = 5;
var expectedDocumentPath = AbsoluteDocumentPath;
var expectedDocumentUri = DocumentUri.FromFileSystemPath(expectedDocumentPath);

var expectedDefinitions = new LocationOrLocationLinks(
new LocationOrLocationLink(new Location {
Uri = expectedDocumentUri,
Range = new Range {
Start = new Position {
Line = line,
Character = column
},
End = new Position {
Line = line,
Character = column
}
},
}));

ServerDispatcher.HandleRequest<TextDocumentPositionParams, LocationOrLocationLinks>(DocumentNames.Definition, (request, cancellationToken) => {
Assert.NotNull(request.TextDocument);

Assert.Equal(expectedDocumentUri, request.TextDocument.Uri);

Assert.Equal(line, request.Position.Line);
Assert.Equal(column, request.Position.Character);

return Task.FromResult(expectedDefinitions);
});

var definitions = await LanguageClient.TextDocument.Definition(AbsoluteDocumentPath, line, column);

var actualDefinitions = definitions.ToArray();
Assert.Collection(actualDefinitions, actualDefinition => {
var expectedDefinition = expectedDefinitions.Single();

Assert.NotNull(actualDefinition.Location);
Assert.Equal(expectedDefinition.Location.Uri, actualDefinition.Location.Uri);

Assert.NotNull(actualDefinition.Location.Range);
Assert.NotNull(actualDefinition.Location.Range.Start);
Assert.NotNull(actualDefinition.Location.Range.End);
Assert.Equal(expectedDefinition.Location.Range.Start.Line, actualDefinition.Location.Range.Start.Line);
Assert.Equal(expectedDefinition.Location.Range.Start.Character, actualDefinition.Location.Range.Start.Character);
Assert.Equal(expectedDefinition.Location.Range.End.Line, actualDefinition.Location.Range.End.Line);
Assert.Equal(expectedDefinition.Location.Range.End.Character, actualDefinition.Location.Range.End.Character);
});
}

/// <summary>
/// Ensure that the language client can successfully request DocumentHighlight.
/// </summary>
[Fact(DisplayName = "Language client can successfully request document highlights", Skip = "Periodic failures")]
public async Task DocumentHighlights_Success()
{
await Connect();

const int line = 5;
const int column = 5;
var expectedDocumentPath = AbsoluteDocumentPath;
var expectedDocumentUri = DocumentUri.FromFileSystemPath(expectedDocumentPath);

var expectedHighlights = new DocumentHighlightContainer(
new DocumentHighlight {
Kind = DocumentHighlightKind.Write,
Range = new Range {
Start = new Position {
Line = line,
Character = column
},
End = new Position {
Line = line,
Character = column
}
},
});

ServerDispatcher.HandleRequest<DocumentHighlightParams, DocumentHighlightContainer>(DocumentNames.DocumentHighlight, (request, cancellationToken) => {
Assert.NotNull(request.TextDocument);

Assert.Equal(expectedDocumentUri, request.TextDocument.Uri);

Assert.Equal(line, request.Position.Line);
Assert.Equal(column, request.Position.Character);

return Task.FromResult(expectedHighlights);
});

var definitions = await LanguageClient.TextDocument.DocumentHighlights(AbsoluteDocumentPath, line, column);

var actualDefinitions = definitions.ToArray();
Assert.Collection(actualDefinitions, actualHighlight => {
var expectedHighlight = expectedHighlights.Single();

Assert.Equal(DocumentHighlightKind.Write, expectedHighlight.Kind);

Assert.NotNull(actualHighlight.Range);
Assert.NotNull(actualHighlight.Range.Start);
Assert.NotNull(actualHighlight.Range.End);
Assert.Equal(expectedHighlight.Range.Start.Line, actualHighlight.Range.Start.Line);
Assert.Equal(expectedHighlight.Range.Start.Character, actualHighlight.Range.Start.Character);
Assert.Equal(expectedHighlight.Range.End.Line, actualHighlight.Range.End.Line);
Assert.Equal(expectedHighlight.Range.End.Character, actualHighlight.Range.End.Character);
});
}

/// <summary>
/// Ensure that the language client can successfully request FoldingRanges.
/// </summary>
[Fact(DisplayName = "Language client can successfully request document folding ranges", Skip = "Periodic failures")]
public async Task FoldingRanges_Success()
{
await Connect();

var expectedDocumentPath = AbsoluteDocumentPath;
var expectedDocumentUri = DocumentUri.FromFileSystemPath(expectedDocumentPath);

var expectedFoldingRanges = new Container<FoldingRange>(
new FoldingRange {
Kind = FoldingRangeKind.Region,
StartLine = 5,
StartCharacter = 1,
EndLine = 7,
EndCharacter = 2,
});

ServerDispatcher.HandleRequest<FoldingRangeRequestParam, Container<FoldingRange>>(DocumentNames.FoldingRange, (request, cancellationToken) => {
Assert.NotNull(request.TextDocument);
Assert.Equal(expectedDocumentUri, request.TextDocument.Uri);
return Task.FromResult(expectedFoldingRanges);
});

var foldingRanges = await LanguageClient.TextDocument.FoldingRanges(AbsoluteDocumentPath);

var actualFoldingRanges = foldingRanges.ToArray();
Assert.Collection(actualFoldingRanges, actualFoldingRange => {
var expectedFoldingRange = expectedFoldingRanges.Single();

Assert.Equal(FoldingRangeKind.Region, expectedFoldingRange.Kind);

Assert.Equal(expectedFoldingRange.StartLine, actualFoldingRange.StartLine);
Assert.Equal(expectedFoldingRange.StartCharacter, actualFoldingRange.StartCharacter);
Assert.Equal(expectedFoldingRange.EndLine, actualFoldingRange.EndLine);
Assert.Equal(expectedFoldingRange.EndCharacter, actualFoldingRange.EndCharacter);
});
}

/// <summary>
/// Ensure that the language client can successfully receive Diagnostics from the server.
/// </summary>
Expand Down

0 comments on commit 09fc4f3

Please sign in to comment.