Skip to content

Commit

Permalink
Add the 'Go to Definition' feature in the Elm language server and VSCode
Browse files Browse the repository at this point in the history
  • Loading branch information
Viir committed Dec 15, 2024
1 parent 766d4fc commit eae6855
Show file tree
Hide file tree
Showing 13 changed files with 438 additions and 9 deletions.
4 changes: 4 additions & 0 deletions implement/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,7 @@ Published the first version of the Elm developer tools VSCode extension, with a
+ Added feature: Completions: Shows completion suggestions matching the current context
+ Added feature: Hover tips: Shows type annotations and documentation for a type alias, module, custom type or function

## 2024-12-15 - Expanded VSCode Extension and Language Server

+ Added feature: 'Go to Definition'

10 changes: 10 additions & 0 deletions implement/Pine.Core/LanguageServerProtocol/LocationLink.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Pine.Core.LanguageServerProtocol;

/// <summary>
/// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#locationLink
/// </summary>
public record LocationLink(
Range? OriginSelectionRange,
string TargetUri,
Range TargetRange,
Range TargetSelectionRange);
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ public record ServerCapabilities(
TextDocumentSyncOptions? TextDocumentSync = null,
ServerCapabilitiesWorkspace? Workspace = null,
bool? HoverProvider = null,
CompletionOptions? CompletionProvider = null);
CompletionOptions? CompletionProvider = null,
bool? DeclarationProvider = null,
bool? DefinitionProvider = null,
bool? TypeDefinitionProvider = null);

/// <summary>
/// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentSyncKind
Expand Down
6 changes: 3 additions & 3 deletions implement/Pine.Core/Pine.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<AssemblyVersion>0.3.27</AssemblyVersion>
<FileVersion>0.3.27</FileVersion>
<AssemblyVersion>0.3.28</AssemblyVersion>
<FileVersion>0.3.28</FileVersion>
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
</PropertyGroup>

<PropertyGroup>
<PackageId>Pine.Core</PackageId>
<Version>0.3.27</Version>
<Version>0.3.28</Version>
<Description>The cross-platform Elm runtime environment</Description>
<PackageTags>Functional;Elm;Runtime;Compiler;VM;DBMS</PackageTags>
<RepositoryUrl>https://github.com/pine-vm/pine.git</RepositoryUrl>
Expand Down
144 changes: 144 additions & 0 deletions implement/pine/Elm/LanguageServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ private void Log(string message)
AllCommitCharacters: null,
ResolveProvider: null),

DefinitionProvider = true,

Workspace = new ServerCapabilitiesWorkspace
{
WorkspaceFolders = new WorkspaceFoldersServerCapabilities(Supported: true, ChangeNotifications: true),
Expand Down Expand Up @@ -319,6 +321,8 @@ public void TextDocument_didChange(
{
var textDocumentUri = System.Uri.UnescapeDataString(textDocument.Uri);

allSeenDocumentUris[textDocumentUri] = textDocumentUri;

Log(
"TextDocument_didChange: " + textDocumentUri +
" (" + contentChanges.Count + " content changes)");
Expand Down Expand Up @@ -694,6 +698,116 @@ public CompletionItem[] TextDocument_completion(
];
}

public IReadOnlyList<Location> TextDocument_definition(
TextDocumentPositionParams positionParams)
{
var textDocumentUri = System.Uri.UnescapeDataString(positionParams.TextDocument.Uri);

var clock = System.Diagnostics.Stopwatch.StartNew();

Log("TextDocument_definition: " + textDocumentUri + " at " + positionParams.Position);

var localPathResult = DocumentUriAsLocalPath(textDocumentUri);
{
if (localPathResult.IsErrOrNull() is { } err)
{
Log("Ignoring URI: " + err + ": " + textDocumentUri);
return [];
}
}

if (localPathResult.IsOkOrNull() is not { } localPath)
{
throw new System.NotImplementedException(
"Unexpected result type: " + localPathResult.GetType());
}

var filePathOpenedInEditor = PathItemsFromFlatPath(localPath);

var provideDefinitionResult =
ProvideDefinition(
new Interface.ProvideHoverRequestStruct(
filePathOpenedInEditor,
PositionLineNumber: (int)positionParams.Position.Line + 1,
PositionColumn: (int)positionParams.Position.Character + 1));

{
if (provideDefinitionResult.IsErrOrNull() is { } err)
{
Log("Failed to provide definition: " + err);
return [];
}
}

if (provideDefinitionResult.IsOkOrNull() is not { } provideDefinitionOk)
{
throw new System.NotImplementedException(
"Unexpected result type: " + provideDefinitionResult.GetType());
}

Log(
"Completed provide definition in " +
CommandLineInterface.FormatIntegerForDisplay(clock.ElapsedMilliseconds) + " ms, returning " +
provideDefinitionOk.Count + " items");


string? correspondingUri(IReadOnlyList<string> path)
{
var flatPathForward = string.Join('/', path);
var flatPathBackward = string.Join('\\', path);

var fromSeenUris =
allSeenDocumentUris
.FirstOrDefault(uri =>
DocumentUriAsLocalPath(uri.Value).IsOkOrNull() is { } asLocalOk &&
(asLocalOk == flatPathBackward ||
asLocalOk == flatPathBackward)).Key;

if (fromSeenUris is not null)
return fromSeenUris;

return
System.Uri.TryCreate(flatPathForward, System.UriKind.Absolute, out var uri)
?
uri.AbsoluteUri
:
null;
}

var locations =
provideDefinitionOk
.SelectMany(location =>
{
if (correspondingUri(location.FilePath) is not { } uri)
{
Log("No corresponding URI for " + string.Join('/', location.FilePath));

return (IReadOnlyList<Location>)[];
}

return
[new Location(
uri,
new Range(
Start: new Position(
Line: (uint)location.Range.StartLineNumber - 1,
Character: (uint)location.Range.StartColumn - 1),
End: new Position(
Line: (uint)location.Range.EndLineNumber - 1,
Character: (uint)location.Range.EndColumn - 1)))];
})
.ToImmutableArray();

Log(
"Returning " + locations.Length + " locations: " +
string.Join(
", ",
locations
.Select(l => l.Uri + ": " + l.Range.Start.Line)));

return locations;
}

public void TextDocument_didSave(
DidSaveTextDocumentParams didSaveParams,
System.Action<TextDocumentIdentifier, IReadOnlyList<Diagnostic>> publishDiagnostics)
Expand Down Expand Up @@ -1049,6 +1163,36 @@ public Result<string, IReadOnlyList<string>> ProvideHover(
provideCompletionItemsResponse.CompletionItems);
}

public Result<string, IReadOnlyList<Interface.LocationUnderFilePath>>
ProvideDefinition(
Interface.ProvideHoverRequestStruct provideDefinitionRequest)
{
var genericRequestResult =
HandleRequest(
new Interface.Request.ProvideDefinitionRequest(provideDefinitionRequest));

if (genericRequestResult.IsErrOrNull() is { } err)
{
return err;
}

if (genericRequestResult.IsOkOrNull() is not { } requestOk)
{
throw new System.NotImplementedException(
"Unexpected request result type: " + genericRequestResult.GetType());
}

if (requestOk is not Interface.Response.ProvideDefinitionResponse provideDefinitionResponse)
{
throw new System.NotImplementedException(
"Unexpected request result type: " + requestOk.GetType());
}

return
Result<string, IReadOnlyList<Interface.LocationUnderFilePath>>.ok(
provideDefinitionResponse.Locations);
}

public Result<string, Interface.Response> HandleRequest(
Interface.Request request)
{
Expand Down
10 changes: 10 additions & 0 deletions implement/pine/Elm/LanguageServerRpcTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,16 @@ public CompletionItem[] TextDocument_completion(
return Server.TextDocument_completion(positionParams);
}

/// <summary>
/// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_definition
/// </summary>
[JsonRpcMethod("textDocument/definition", UseSingleObjectParameterDeserialization = true)]
public IReadOnlyList<Location> TextDocument_definition(
TextDocumentPositionParams positionParams)
{
return Server.TextDocument_definition(positionParams);
}


/// <summary>
/// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_didSave
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ public record ProvideHoverRequest(ProvideHoverRequestStruct Request)

public record ProvideCompletionItemsRequest(ProvideCompletionItemsRequestStruct Request)
: Request;

public record ProvideDefinitionRequest(ProvideHoverRequestStruct Request)
: Request;
}

public record ProvideHoverRequestStruct(
Expand Down Expand Up @@ -116,6 +119,13 @@ addFileRequest.Blob.AsText is { } asText
Encode(provideCompletionItemsRequest.Request)
]),

Request.ProvideDefinitionRequest provideDefinitionRequest =>
ElmValueEncoding.TagAsPineValue(
"ProvideDefinitionRequest",
[
Encode(provideDefinitionRequest.Request)
]),

_ =>
throw new System.NotImplementedException(
"Unexpected request type: " + request.GetType())
Expand Down
Loading

0 comments on commit eae6855

Please sign in to comment.