From e2b4c9ad06397177ad4b0d6bb71deb48b4b96ed2 Mon Sep 17 00:00:00 2001 From: Bryan Kenote <9691017+bryankenote@users.noreply.github.com> Date: Tue, 5 Dec 2023 23:03:50 +0000 Subject: [PATCH] add document symbol handler (#12) * add document symbol handler * increase range of field to include type * add error interfaces * refactor: move symbol parsing out of request handler --- .../FsdDocumentSymbolHandler.cs | 34 +++++ .../FsdSymbolUtility.cs | 118 ++++++++++++++++++ src/Facility.LanguageServer/Program.cs | 1 + 3 files changed, 153 insertions(+) create mode 100644 src/Facility.LanguageServer/FsdDocumentSymbolHandler.cs create mode 100644 src/Facility.LanguageServer/FsdSymbolUtility.cs diff --git a/src/Facility.LanguageServer/FsdDocumentSymbolHandler.cs b/src/Facility.LanguageServer/FsdDocumentSymbolHandler.cs new file mode 100644 index 0000000..df3596d --- /dev/null +++ b/src/Facility.LanguageServer/FsdDocumentSymbolHandler.cs @@ -0,0 +1,34 @@ +using Facility.Definition; +using OmniSharp.Extensions.LanguageServer.Protocol; +using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; +using OmniSharp.Extensions.LanguageServer.Protocol.Document; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; + +namespace Facility.LanguageServer +{ + internal sealed class FsdDocumentSymbolHandler : FsdRequestHandler, IDocumentSymbolHandler + { + public FsdDocumentSymbolHandler(ILanguageServerFacade router, ILanguageServerConfiguration configuration, IDictionary serviceInfos) + : base(router, configuration, serviceInfos) + { + } + + public async Task Handle(DocumentSymbolParams request, CancellationToken cancellationToken) + { + var documentUri = request.TextDocument.Uri; + var service = GetService(documentUri); + if (service == null) + return null; + + var symbols = service.GetServiceSymbols(); + return symbols; + } + + public DocumentSymbolRegistrationOptions GetRegistrationOptions(DocumentSymbolCapability capability, ClientCapabilities clientCapabilities) + => new() + { + DocumentSelector = DocumentSelector.ForLanguage("fsd"), + }; + } +} diff --git a/src/Facility.LanguageServer/FsdSymbolUtility.cs b/src/Facility.LanguageServer/FsdSymbolUtility.cs new file mode 100644 index 0000000..dc5081f --- /dev/null +++ b/src/Facility.LanguageServer/FsdSymbolUtility.cs @@ -0,0 +1,118 @@ +using Facility.Definition; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range; + +namespace Facility.LanguageServer; + +internal static class FsdSymbolUtility +{ + public static List GetServiceSymbols(this ServiceInfo service) + { + var symbols = new List(); + foreach (var member in service.Members) + { + var memberNamePart = member.GetPart(ServicePartKind.Name); + if (memberNamePart == null) + continue; + + var symbolKind = member switch + { + ServiceMethodInfo => SymbolKind.Method, + ServiceDtoInfo => SymbolKind.Interface, + ServiceEnumInfo => SymbolKind.Enum, + ServiceExternalDtoInfo => SymbolKind.Interface, + ServiceExternalEnumInfo => SymbolKind.Enum, + ServiceErrorSetInfo => SymbolKind.Interface, + _ => SymbolKind.Null, + }; + + var maxColumn = memberNamePart.EndPosition.ColumnNumber; + var maxLine = memberNamePart.EndPosition.LineNumber; + + var childSymbols = new List(); + foreach (var child in member.GetDescendants().OfType()) + { + var childNamePart = child.GetPart(ServicePartKind.Name); + if (childNamePart == null) + continue; + + maxColumn = Math.Max(maxColumn, childNamePart.EndPosition.ColumnNumber); + maxLine = Math.Max(maxLine, childNamePart.EndPosition.LineNumber); + + var childTypePart = child.GetPart(ServicePartKind.TypeName); + + var childName = child switch + { + ServiceFieldInfo field => field.Name, + ServiceErrorInfo error => error.Name, + _ => null, + }; + + if (childName == null) + continue; + + var childSymbol = new DocumentSymbol + { + Name = childName, + Kind = SymbolKind.Field, + Range = new Range( + new Position(childNamePart.Position), + new Position( + new ServiceDefinitionPosition( + childName, + childTypePart?.EndPosition.LineNumber ?? childNamePart.EndPosition.LineNumber, + childTypePart?.EndPosition.ColumnNumber ?? childNamePart.EndPosition.ColumnNumber))), + SelectionRange = new Range( + new Position(childNamePart.Position), + new Position(childNamePart.EndPosition)), + }; + childSymbols.Add(childSymbol); + } + + foreach (var child in member.GetDescendants().OfType()) + { + var childNamePart = child.GetPart(ServicePartKind.Name); + if (childNamePart == null) + continue; + + maxColumn = Math.Max(maxColumn, childNamePart.EndPosition.ColumnNumber); + maxLine = Math.Max(maxLine, childNamePart.EndPosition.LineNumber); + var childSymbol = new DocumentSymbol + { + Name = child.Name, + Kind = SymbolKind.EnumMember, + Range = new Range( + new Position(childNamePart.Position), + new Position(childNamePart.EndPosition)), + SelectionRange = new Range( + new Position(childNamePart.Position), + new Position(childNamePart.EndPosition)), + }; + childSymbols.Add(childSymbol); + } + + var keywordPart = member.GetPart(ServicePartKind.Keyword); + var minColumn = keywordPart?.Position.ColumnNumber ?? memberNamePart.Position.ColumnNumber; + var minLine = keywordPart?.Position.LineNumber ?? memberNamePart.Position.LineNumber; + + var symbol = new DocumentSymbol + { + Name = member.Name, + Kind = symbolKind, + + Range = new Range( + new Position( + new ServiceDefinitionPosition(member.Name, minLine, minColumn)), + new Position( + new ServiceDefinitionPosition(member.Name, maxLine + 1, maxColumn))), + SelectionRange = new Range( + new Position(memberNamePart.Position), + new Position(memberNamePart.EndPosition)), + Children = childSymbols, + }; + symbols.Add(symbol); + } + + return symbols; + } +} diff --git a/src/Facility.LanguageServer/Program.cs b/src/Facility.LanguageServer/Program.cs index 7f8bf72..7378d3b 100644 --- a/src/Facility.LanguageServer/Program.cs +++ b/src/Facility.LanguageServer/Program.cs @@ -28,6 +28,7 @@ .WithHandler() .WithHandler() .WithHandler() + .WithHandler() .WithServices( x => x .AddLogging(b => b.SetMinimumLevel(LogLevel.Trace)))).ConfigureAwait(false);