Skip to content

Commit

Permalink
Implement support for LSP call/type hierarchy in code windows #686
Browse files Browse the repository at this point in the history
  • Loading branch information
falko17 committed Jul 26, 2024
1 parent d6aa622 commit a975f0c
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 43 deletions.
129 changes: 86 additions & 43 deletions Assets/SEE/UI/Window/CodeWindow/CodeWindowContextMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using Cysharp.Threading.Tasks;
using Cysharp.Threading.Tasks.Linq;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using SEE.Controls;
using SEE.Controls.Actions;
Expand Down Expand Up @@ -47,19 +48,27 @@ private void ShowContextMenu(int line, int column, Vector2 position, string cont
}
if (lspHandler.ServerCapabilities.DeclarationProvider != null)
{
actions.Add(new("Show Declaration", ShowDeclaration, Icons.OutgoingEdge));
actions.Add(new("Go to Declaration", ShowDeclaration, Icons.OutgoingEdge));
}
if (lspHandler.ServerCapabilities.DefinitionProvider != null)
{
actions.Add(new("Show Definition", ShowDefinition, Icons.OutgoingEdge));
actions.Add(new("Go to Definition", ShowDefinition, Icons.OutgoingEdge));
}
if (lspHandler.ServerCapabilities.ImplementationProvider != null)
{
actions.Add(new("Show Implementation", ShowImplementation, Icons.OutgoingEdge));
actions.Add(new("Go to Implementation", ShowImplementation, Icons.OutgoingEdge));
}
if (lspHandler.ServerCapabilities.TypeDefinitionProvider != null)
{
actions.Add(new("Show Type Definition", ShowTypeDefinition, Icons.OutgoingEdge));
actions.Add(new("Go to Type Definition", ShowTypeDefinition, Icons.OutgoingEdge));
}
if (lspHandler.ServerCapabilities.CallHierarchyProvider != null)
{
actions.Add(new("Show Outgoing Calls", ShowOutgoingCalls, Icons.Sitemap));
}
if (lspHandler.ServerCapabilities.TypeHierarchyProvider != null)
{
actions.Add(new("Show Supertypes", ShowSupertypes, Icons.Sitemap));
}

if (actions.Count == 0)
Expand All @@ -70,6 +79,22 @@ private void ShowContextMenu(int line, int column, Vector2 position, string cont

return;

#region Local Functions

void ShowOutgoingCalls()
{
MenuEntriesForLocationsAsync(lspHandler.OutgoingCalls(_ => true, FilePath, line, column)
.Select(x => (x.Uri.ToUri(), Range.FromLspRange(x.Range), x.Name)))
.ContinueWith(entries => ShowEntries(entries, "Outgoing Calls")).Forget();
}

void ShowSupertypes()
{
MenuEntriesForLocationsAsync(lspHandler.Supertypes(_ => true, FilePath, line, column)
.Select(x => (x.Uri.ToUri(), Range.FromLspRange(x.Range), x.Name)))
.ContinueWith(entries => ShowEntries(entries, "Supertypes")).Forget();
}

void ShowReferences() =>
ShowLocationsAsync(lspHandler.References(FilePath, line, column, includeDeclaration: true),
"References").Forget();
Expand All @@ -86,60 +111,76 @@ void ShowImplementation() =>
void ShowTypeDefinition() =>
ShowLocationsAsync(lspHandler.TypeDefinition(FilePath, line, column), "Type Definitions").Forget();

// Opens a menu with the given locations and name. Clicking on an entry will open the location.
async UniTaskVoid ShowLocationsAsync(IUniTaskAsyncEnumerable<LocationOrLocationLink> locations,
string name)
async UniTask ShowLocationsAsync(IUniTaskAsyncEnumerable<LocationOrLocationLink> locations, string name)
{
IList<MenuEntry> entries = await MenuEntriesForLocationsAsync(locations.Select(DeconstructLocation));
ShowEntries(entries, name);
}

(Uri, Range, string) DeconstructLocation(LocationOrLocationLink location)
{
Range targetRange;
Uri targetUri;
if (location.IsLocation)
{
Location loc = location.Location!;
targetRange = Range.FromLspRange(loc.Range);
targetUri = loc.Uri.ToUri();
}
else
{
LocationLink locLink = location.LocationLink!;
targetRange = Range.FromLspRange(locLink.TargetRange);
targetUri = locLink.TargetUri.ToUri();
}
return (targetUri, targetRange, null);
}

// Generates menu entries for the given locations. Clicking on an entry will open the location.
async UniTask<IList<MenuEntry>> MenuEntriesForLocationsAsync(IUniTaskAsyncEnumerable<(Uri, Range, string)> locations)
{
IList<MenuEntry> entries = new List<MenuEntry>();
using (LoadingSpinner.ShowIndeterminate($"Loading {name} for \"{contextWord}\"..."))
{
IList<MenuEntry> entries = new List<MenuEntry>();
await foreach (LocationOrLocationLink location in locations)
await foreach ((Uri targetUri, Range targetRange, string title) in locations)
{
Range targetRange;
Uri targetUri;
if (location.IsLocation)
{
Location loc = location.Location!;
targetRange = Range.FromLspRange(loc.Range);
targetUri = loc.Uri.ToUri();
}
else
{
LocationLink locLink = location.LocationLink!;
targetRange = Range.FromLspRange(locLink.TargetRange);
targetUri = locLink.TargetUri.ToUri();
}
Uri path = targetUri;
if (lspHandler.ProjectUri?.IsBaseOf(path) ?? false)
{
// Truncate path above the project's base path to make the result more readable.
path = lspHandler.ProjectUri.MakeRelativeUri(path);
}
entries.Add(new(SelectAction: () => OpenSelection(targetUri, targetRange),
Title: $"{path}: {targetRange}",
Title: title ?? $"{path}: {targetRange}",
// TODO: Icon
EntryColor: new Color(0.051f, 0.3608f, 0.1333f)));
}
await UniTask.SwitchToMainThread();
switch (entries.Count)
{
case 0:
ShowNotification.Info("No results", $"No {name} found for \"{contextWord}\".", log: false);
break;
case 1:
// We can directly open the only result.
entries.First().SelectAction();
break;
default:
// The user needs to select one of the results.
simpleListMenu.ClearEntries();
simpleListMenu.AddEntries(entries);
simpleListMenu.Title = name;
simpleListMenu.Description = $"Listing {name} for {contextWord}.";
simpleListMenu.Icon = Resources.Load<Sprite>("Materials/Notification/info");
simpleListMenu.ShowMenu = true;
break;
}
}
return entries;
}

// Opens a menu with the given menu entries.
void ShowEntries(IList<MenuEntry> entries, string name)
{
switch (entries.Count)
{
case 0:
ShowNotification.Info("No results", $"No {name} found for \"{contextWord}\".", log: false);
break;
case 1:
// We can directly open the only result.
entries.First().SelectAction();
break;
default:
// The user needs to select one of the results.
simpleListMenu.ClearEntries();
simpleListMenu.AddEntries(entries);
simpleListMenu.Title = name;
simpleListMenu.Description = $"Listing {name.ToLower()} for {contextWord}.";
simpleListMenu.Icon = Resources.Load<Sprite>("Materials/Notification/info");
simpleListMenu.ShowMenu = true;
break;
}
}

Expand Down Expand Up @@ -170,6 +211,8 @@ void OpenSelection(Uri uri, Range range)
}
}
}

#endregion
}
}
}
1 change: 1 addition & 0 deletions Assets/SEE/Utils/Icons.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,6 @@ public static class Icons
public const char CircleCheckmark = '\uF058';
public const char CircleQuestionMark = '\uF059';
public const char CircleExclamationMark = '\uF06A';
public const char Sitemap = '\uF0E8';
}
}

0 comments on commit a975f0c

Please sign in to comment.