Skip to content

Commit

Permalink
fixup! fixup! fixup! Implement LSP-based code city creation #686
Browse files Browse the repository at this point in the history
  • Loading branch information
falko17 committed Apr 29, 2024
1 parent 9ddd8ca commit 9802936
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 68 deletions.
83 changes: 47 additions & 36 deletions Assets/SEE/DataModel/DG/IO/LSPImporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,9 @@ public async UniTask LoadAsync(Graph graph, Action<float> changePercentage = nul
{
// Query all documents whose file extension is supported by the language server.
List<string> relevantExtensions = Handler.Server.Languages.SelectMany(x => x.Extensions).ToList();
List<string> relevantDocuments = SourcePaths.SelectMany(RelevantDocumentsForPath).Where(x => ExcludedPaths.All(y => !x.StartsWith(y))).ToList();
List<string> relevantDocuments = SourcePaths.SelectMany(RelevantDocumentsForPath)
.Where(x => ExcludedPaths.All(y => !x.StartsWith(y)))
.Distinct().ToList();
List<Node> originalNodes = graph.Nodes();
NodeAtDirectory.Clear();
newEdges = 0;
Expand All @@ -171,6 +173,7 @@ public async UniTask LoadAsync(Graph graph, Action<float> changePercentage = nul
for (documentCount = 0; documentCount < relevantDocuments.Count; documentCount++)
{
string path = relevantDocuments[documentCount];
Handler.OpenDocument(path);
Node fileNode = new()
{
ID = Path.GetRelativePath(Handler.ProjectPath, path)
Expand All @@ -196,6 +199,7 @@ public async UniTask LoadAsync(Graph graph, Action<float> changePercentage = nul
await AddSymbolNodeAsync(symbol.DocumentSymbol, path, graph, fileNode, token);
}

Handler.CloseDocument(path);
// ~20% of the progress is made by loading the documents and its symbols.
changePercentage?.Invoke(0.2f * documentCount / relevantDocuments.Count);
}
Expand All @@ -215,44 +219,50 @@ public async UniTask LoadAsync(Graph graph, Action<float> changePercentage = nul

// We build a range tree for each file, so that we can quickly find the nodes with the smallest size
// that contain the given range.
RangeTrees = relevantNodes.GroupBy(x => x.Path()).ToDictionary(x => x.Key, x => new KDIntervalTree<Node>(x, node => node.SourceRange));
Dictionary<string, List<Node>> relevantNodesByPath = relevantNodes.GroupBy(x => x.Path()).ToDictionary(x => x.Key, x => x.ToList());
RangeTrees = relevantNodesByPath.ToDictionary(x => x.Key, x => new KDIntervalTree<Node>(x.Value, node => node.SourceRange));

for (int i = 0; i < relevantNodes.Count; i++)
int i = 0;
foreach ((string path, List<Node> nodes) in relevantNodesByPath)
{
Node node = relevantNodes[i];
// Depending on capabilities and settings, we connect the nodes with edges.
if (IncludeEdgeTypes.HasFlag(EdgeKind.Definition) && Handler.ServerCapabilities.DefinitionProvider.TrueOrValue())
Handler.OpenDocument(path);
foreach (Node node in nodes)
{
await ConnectNodeViaAsync(Handler.Definition, "Definition", node, graph, token: token);
}
if (IncludeEdgeTypes.HasFlag(EdgeKind.Declaration) && Handler.ServerCapabilities.DeclarationProvider.TrueOrValue())
{
await ConnectNodeViaAsync(Handler.Declaration, "Declaration", node, graph, token: token);
}
if (IncludeEdgeTypes.HasFlag(EdgeKind.TypeDefinition) && Handler.ServerCapabilities.TypeDefinitionProvider.TrueOrValue())
{
await ConnectNodeViaAsync(Handler.TypeDefinition, "Of_Type", node, graph, token: token);
}
if (IncludeEdgeTypes.HasFlag(EdgeKind.Implementation) && Handler.ServerCapabilities.ImplementationProvider.TrueOrValue())
{
await ConnectNodeViaAsync(Handler.Implementation, "Implementation_Of", node, graph, reverseDirection: true, token);
}
if (IncludeEdgeTypes.HasFlag(EdgeKind.Reference) && Handler.ServerCapabilities.ReferencesProvider.TrueOrValue())
{
await ConnectNodeViaAsync((path, line, character) => Handler.References(path, line, character), "Reference", node, graph, reverseDirection: true, token);
}
if (IncludeEdgeTypes.HasFlag(EdgeKind.Call) && Handler.ServerCapabilities.CallHierarchyProvider.TrueOrValue())
{
// FIXME (UniTask-internal problem, sends wrong method name):
// await HandleCallHierarchyAsync(node, graph, token);
}
if (IncludeEdgeTypes.HasFlag(EdgeKind.Extend) && Handler.ServerCapabilities.TypeHierarchyProvider.TrueOrValue())
{
await HandleTypeHierarchyAsync(node, graph, token);
}
// Depending on capabilities and settings, we connect the nodes with edges.
if (IncludeEdgeTypes.HasFlag(EdgeKind.Definition) && Handler.ServerCapabilities.DefinitionProvider.TrueOrValue())
{
await ConnectNodeViaAsync(Handler.Definition, "Definition", node, graph, token: token);
}
if (IncludeEdgeTypes.HasFlag(EdgeKind.Declaration) && Handler.ServerCapabilities.DeclarationProvider.TrueOrValue())
{
await ConnectNodeViaAsync(Handler.Declaration, "Declaration", node, graph, token: token);
}
if (IncludeEdgeTypes.HasFlag(EdgeKind.TypeDefinition) && Handler.ServerCapabilities.TypeDefinitionProvider.TrueOrValue())
{
await ConnectNodeViaAsync(Handler.TypeDefinition, "Of_Type", node, graph, token: token);
}
if (IncludeEdgeTypes.HasFlag(EdgeKind.Implementation) && Handler.ServerCapabilities.ImplementationProvider.TrueOrValue())
{
await ConnectNodeViaAsync(Handler.Implementation, "Implementation_Of", node, graph, reverseDirection: true, token);
}
if (IncludeEdgeTypes.HasFlag(EdgeKind.Reference) && Handler.ServerCapabilities.ReferencesProvider.TrueOrValue())
{
await ConnectNodeViaAsync((p, line, character) => Handler.References(p, line, character), "Reference", node, graph, reverseDirection: true, token);
}
if (IncludeEdgeTypes.HasFlag(EdgeKind.Call) && Handler.ServerCapabilities.CallHierarchyProvider.TrueOrValue())
{
// FIXME (UniTask-internal problem, sends wrong method name):
// await HandleCallHierarchyAsync(node, graph, token);
}
if (IncludeEdgeTypes.HasFlag(EdgeKind.Extend) && Handler.ServerCapabilities.TypeHierarchyProvider.TrueOrValue())
{
await HandleTypeHierarchyAsync(node, graph, token);
}

// The remaining 80% of the progress is made by connecting the nodes.
changePercentage?.Invoke(0.2f + 0.8f * i / relevantNodes.Count);
// The remaining 80% of the progress is made by connecting the nodes.
changePercentage?.Invoke(0.2f + 0.8f * i++ / relevantNodes.Count);
}
Handler.CloseDocument(path);
}
Debug.Log($"LSPImporter: Imported {graph.Nodes().Except(originalNodes).Count()} new nodes and {newEdges} new edges.\n");

Expand Down Expand Up @@ -426,7 +436,8 @@ private static string MarkupToRichText(MarkedStringsOrMarkupContent content)
switch (markup.Kind)
{
case MarkupKind.PlainText: return $"<noparse>{markup.Value}</noparse>";
case MarkupKind.Markdown: markdown = markup.Value;
case MarkupKind.Markdown:
markdown = markup.Value;
break;
default:
Debug.LogError($"Unsupported markup kind: {markup.Kind}");
Expand Down
46 changes: 46 additions & 0 deletions Assets/SEE/Tools/LSP/LSPHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Cysharp.Threading.Tasks.Linq;
using OmniSharp.Extensions.JsonRpc.Server;
using OmniSharp.Extensions.LanguageServer.Client;
using OmniSharp.Extensions.LanguageServer.Protocol;
using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities;
using OmniSharp.Extensions.LanguageServer.Protocol.Document;
using OmniSharp.Extensions.LanguageServer.Protocol.General;
Expand Down Expand Up @@ -308,6 +309,11 @@ async UniTaskVoid MonitorInitialWorkDoneProgress(ProgressToken token)
}
}

/// <summary>
/// Handles the ShowMessage notification with the given <paramref name="messageParams"/>
/// by showing a notification to the user.
/// </summary>
/// <param name="showMessageParams">The parameters of the ShowMessage notification.</param>
private void ShowMessage(ShowMessageParams showMessageParams)
{
string languageServerName = Server?.Name ?? "Language Server";
Expand All @@ -329,6 +335,11 @@ private void ShowMessage(ShowMessageParams showMessageParams)
}
}

/// <summary>
/// Handles the LogMessage notification with the given <paramref name="messageParams"/>
/// by logging the message to the Unity console.
/// </summary>
/// <param name="messageParams">The parameters of the LogMessage notification.</param>
private static void LogMessage(LogMessageParams messageParams)
{
switch (messageParams.Type)
Expand All @@ -347,6 +358,41 @@ private static void LogMessage(LogMessageParams messageParams)
}
}

/// <summary>
/// Opens the document at the given <paramref name="path"/> in the language server.
///
/// Note that the document needs to be closed manually after it is no longer needed.
/// </summary>
/// <param name="path">The path to the document.</param>
public void OpenDocument(string path)
{
DidOpenTextDocumentParams parameters = new()
{
TextDocument = new TextDocumentItem
{
Uri = DocumentUri.File(path),
LanguageId = Server.LanguageIdFor(Path.GetExtension(path).TrimStart('.')),
Version = 1,
Text = File.ReadAllText(path)
}
};
Client.DidOpenTextDocument(parameters);
}

/// <summary>
/// Closes the document at the given <paramref name="path"/> in the language server.
/// The document needs to have been opened before.
/// </summary>
/// <param name="path">The path to the document.</param>
public void CloseDocument(string path)
{
DidCloseTextDocumentParams parameters = new()
{
TextDocument = new TextDocumentIdentifier(path)
};
Client.DidCloseTextDocument(parameters);
}

/// <summary>
/// Retrieves the symbols in the document at the given <paramref name="path"/>.
///
Expand Down
35 changes: 30 additions & 5 deletions Assets/SEE/Tools/LSP/LSPLanguage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,38 @@ public record LSPLanguage
/// </summary>
public ISet<string> Extensions { get; }

/// <summary>
/// A mapping from file extensions to LSP language IDs.
///
/// Note that the empty string key is used for the default language ID.
/// </summary>
public IDictionary<string, string> LanguageIds { get; }

/// <summary>
/// Constructor.
/// </summary>
/// <param name="name">The name of the language.</param>
/// <param name="extensions">The file extensions associated with this language.</param>
public LSPLanguage(string name, ISet<string> extensions)
/// <param name="languageIds">A mapping from file extensions to LSP language IDs.</param>
private LSPLanguage(string name, ISet<string> extensions, IDictionary<string, string> languageIds = null)
{
Name = name;
Extensions = extensions;
LanguageIds = languageIds ?? new Dictionary<string, string>();
All.Add(this);
}

/// <summary>
/// Constructor.
/// </summary>
/// <param name="name">The name of the language.</param>
/// <param name="extensions">The file extensions associated with this language.</param>
/// <param name="languageId">The LSP language ID for this language.</param>
private LSPLanguage(string name, ISet<string> extensions, string languageId) : this(name, extensions)
{
LanguageIds = new Dictionary<string, string> { { string.Empty, languageId } };
}

/// <summary>
/// Returns the language with the given <paramref name="name"/>.
/// </summary>
Expand All @@ -47,9 +67,14 @@ public override string ToString()
}

public static readonly IList<LSPLanguage> All = new List<LSPLanguage>();
public static readonly LSPLanguage Rust = new("Rust", new HashSet<string> { "rs" });
public static readonly LSPLanguage Python = new("Python", new HashSet<string> { "py" });
public static readonly LSPLanguage Dart = new("Dart", new HashSet<string> { "dart" });
public static readonly LSPLanguage CSharp = new("C#", new HashSet<string> { "cs", "vb" });
public static readonly LSPLanguage C = new("C", new HashSet<string> { "c", "h" }, "c");
public static readonly LSPLanguage CPP = new("C++", new HashSet<string>
{
"c", "C", "cc", "cpp", "cxx", "c++", "h", "H", "hh", "hpp", "hxx", "h++", "cppm", "ixx"
}, "cpp");
public static readonly LSPLanguage CSharp = new("C#", new HashSet<string> { "cs", "vb" }, "csharp");
public static readonly LSPLanguage Dart = new("Dart", new HashSet<string> { "dart" }, "dart");
public static readonly LSPLanguage Python = new("Python", new HashSet<string> { "py" }, "python");
public static readonly LSPLanguage Rust = new("Rust", new HashSet<string> { "rs" }, "rust");
}
}
Loading

0 comments on commit 9802936

Please sign in to comment.