From 8324880c0b4ed39755999bff2a2f27ab61af9f25 Mon Sep 17 00:00:00 2001 From: Adam Tao Date: Sun, 26 Feb 2023 12:10:49 +0800 Subject: [PATCH 1/3] feat(Util.fs): Add datas for semantic tokens Signed-off-by: Adam Tao --- src/CSharpLanguageServer/Util.fs | 71 ++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/src/CSharpLanguageServer/Util.fs b/src/CSharpLanguageServer/Util.fs index 4f986328..6f4e9658 100644 --- a/src/CSharpLanguageServer/Util.fs +++ b/src/CSharpLanguageServer/Util.fs @@ -3,6 +3,7 @@ module CSharpLanguageServer.Util open System open System.Runtime.InteropServices open System.IO +open Microsoft.CodeAnalysis.Classification; let parseFileUri s: string = Uri(s).LocalPath @@ -22,3 +23,73 @@ let makeFileUri (path: string): string = | false -> "file://" + fullPath type AsyncLogFn = string -> Async + + +let ClassificationTypeMap = Map [ + (ClassificationTypeNames.ClassName, "class"); + (ClassificationTypeNames.Comment, "comment"); + (ClassificationTypeNames.ControlKeyword, "keyword"); + (ClassificationTypeNames.EnumMemberName, "enumMember"); + (ClassificationTypeNames.EnumName, "enum"); + (ClassificationTypeNames.EventName, "event"); + (ClassificationTypeNames.ExtensionMethodName, "method"); + (ClassificationTypeNames.FieldName, "property"); + (ClassificationTypeNames.InterfaceName, "interface"); + (ClassificationTypeNames.LabelName, "label"); + (ClassificationTypeNames.LocalName, "variable"); + (ClassificationTypeNames.Keyword, "keyword"); + (ClassificationTypeNames.MethodName, "method"); + (ClassificationTypeNames.NamespaceName, "namespace"); + (ClassificationTypeNames.NumericLiteral, "number"); + (ClassificationTypeNames.Operator, "operator"); + (ClassificationTypeNames.OperatorOverloaded, "operator"); + (ClassificationTypeNames.ParameterName, "parameter"); + (ClassificationTypeNames.PropertyName, "property"); + (ClassificationTypeNames.RecordClassName, "class"); + (ClassificationTypeNames.RecordStructName, "struct"); + (ClassificationTypeNames.RegexText, "regex"); + (ClassificationTypeNames.StringLiteral, "string"); + (ClassificationTypeNames.StructName, "struct"); + (ClassificationTypeNames.TypeParameterName, "typeParameter") +] + +let ClassificationModifierMap = Map [ + (ClassificationTypeNames.StaticSymbol, "static") +] + +let SemanticTokenTypeMap = + ClassificationTypeMap + |> Map.values + |> Seq.distinct + |> fun types -> Seq.zip types (Seq.initInfinite (id >> uint32)) // There is no `flip` in F#, sadly + |> Map.ofSeq + +let SemanticTokenModifierMap = + ClassificationModifierMap + |> Map.values + |> Seq.distinct + |> fun modifiers -> Seq.zip modifiers (Seq.initInfinite (id >> uint32)) + |> Map.ofSeq + +let SemanticTokenTypes = + SemanticTokenTypeMap + |> Seq.sortBy (fun kvp -> kvp.Value) + |> Seq.map (fun kvp -> kvp.Key) + +let SemanticTokenModifiers = + SemanticTokenModifierMap + |> Seq.sortBy (fun kvp -> kvp.Value) + |> Seq.map (fun kvp -> kvp.Key) + +let GetSemanticTokenIdFromClassification (classification: string) = + ClassificationTypeMap + |> Map.tryFind classification + |> Option.bind (fun t -> Map.tryFind t SemanticTokenTypeMap) + +let GetSemanticTokenModifierFlagFromClassification (classification: string) = + ClassificationModifierMap + |> Map.tryFind classification + |> Option.bind (fun m -> Map.tryFind m SemanticTokenModifierMap) + |> Option.defaultValue 0u + |> int32 + |> (<<<) 1u From 6ebeb03c0b143ec202dbc04ba36cc5b418efb8c0 Mon Sep 17 00:00:00 2001 From: Adam Tao Date: Sun, 26 Feb 2023 12:15:57 +0800 Subject: [PATCH 2/3] feat(Server.fs): Implement semantic token Signed-off-by: Adam Tao --- src/CSharpLanguageServer/Server.fs | 63 ++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/src/CSharpLanguageServer/Server.fs b/src/CSharpLanguageServer/Server.fs index eeab9352..c7e231cc 100644 --- a/src/CSharpLanguageServer/Server.fs +++ b/src/CSharpLanguageServer/Server.fs @@ -13,6 +13,7 @@ open Microsoft.CodeAnalysis.Completion open Microsoft.CodeAnalysis.Rename open Microsoft.CodeAnalysis.CSharp.Syntax open Microsoft.CodeAnalysis.CodeFixes +open Microsoft.CodeAnalysis.Classification open Microsoft.Build.Locator open Newtonsoft.Json open Newtonsoft.Json.Linq @@ -1056,6 +1057,66 @@ let setupServerHandlers options (lspClient: LspClient) = | None -> None |> success |> async.Return + let toSemanticToken (lines: TextLineCollection) (textSpan: TextSpan, spans: IEnumerable) = + let (typeId, modifiers) = + spans + |> Seq.fold (fun (t, m) s -> + if ClassificationTypeNames.AdditiveTypeNames.Contains(s.ClassificationType) then + (t, m ||| (GetSemanticTokenModifierFlagFromClassification s.ClassificationType)) + else + (GetSemanticTokenIdFromClassification s.ClassificationType, m) + ) (None, 0u) + let pos = lines.GetLinePositionSpan(textSpan) + (uint32 pos.Start.Line, uint32 pos.Start.Character, uint32 (pos.End.Character - pos.Start.Character), typeId, modifiers) + + let computePosition (((pLine, pChar, _, _, _), (cLine, cChar, cLen, cToken, cModifiers)): ((uint32 * uint32 * uint32 * uint32 * uint32) * (uint32 * uint32 * uint32 * uint32 * uint32))) = + let deltaLine = cLine - pLine + let deltaChar = + if deltaLine = 0u then + cChar - pChar + else + cChar + (deltaLine, deltaChar, cLen, cToken, cModifiers) + + let getSemanticTokensRange (scope: ServerRequestScope) (uri: string) (range: Range option): AsyncLspResult = async { + let docMaybe = scope.GetUserDocumentForUri uri + match docMaybe with + | None -> return None |> success + | Some doc -> + let! sourceText = doc.GetTextAsync() |> Async.AwaitTask + let textSpan = + match range with + | Some r -> + r + |> roslynLinePositionSpanForLspRange + |> sourceText.Lines.GetTextSpan + | None -> + TextSpan(0, sourceText.Length) + let! spans = Classifier.GetClassifiedSpansAsync(doc, textSpan) |> Async.AwaitTask + let tokens = + spans + |> Seq.groupBy (fun span -> span.TextSpan) + |> Seq.map (toSemanticToken sourceText.Lines) + |> Seq.filter (fun (_, _, _, oi, _) -> Option.isSome oi) + |> Seq.map (fun (line, startChar, len, tokenId, modifiers) -> (line, startChar, len, Option.get tokenId, modifiers)) + + let response = + { Data = + Seq.zip (seq {yield (0u,0u,0u,0u,0u); yield! tokens}) tokens + |> Seq.map computePosition + |> Seq.map (fun (a,b,c,d,e) -> [a;b;c;d;e]) + |> Seq.concat + |> Seq.toArray + ResultId = None } // TODO: add a result id after we support delta semantic tokens + return Some response |> success + } + + let handleSemanticTokensFull (scope: ServerRequestScope) (semParams: Types.SemanticTokensParams): AsyncLspResult = + getSemanticTokensRange scope semParams.TextDocument.Uri None + + let handleSemanticTokensRange (scope: ServerRequestScope) (semParams: Types.SemanticTokensRangeParams): AsyncLspResult = + getSemanticTokensRange scope semParams.TextDocument.Uri (Some semParams.Range) + let handleWorkspaceSymbol (scope: ServerRequestScope) (symbolParams: Types.WorkspaceSymbolParams): AsyncLspResult = async { let! symbols = findSymbolsInSolution scope.Solution symbolParams.Query (Some 20) return symbols |> Array.ofSeq |> Some |> success @@ -1231,6 +1292,8 @@ let setupServerHandlers options (lspClient: LspClient) = ("textDocument/prepareRename" , handleTextDocumentPrepareRename) |> requestHandlingWithReadOnlyScope ("textDocument/rename" , handleTextDocumentRename) |> requestHandlingWithReadOnlyScope ("textDocument/signatureHelp" , handleTextDocumentSignatureHelp) |> requestHandlingWithReadOnlyScope + ("textDocument/semanticTokens/full", handleSemanticTokensFull) |> requestHandlingWithReadOnlyScope + ("textDocument/semanticTokens/range", handleSemanticTokensRange) |> requestHandlingWithReadOnlyScope ("workspace/symbol" , handleWorkspaceSymbol) |> requestHandlingWithReadOnlyScope ("workspace/didChangeWatchedFiles", handleWorkspaceDidChangeWatchedFiles) |> requestHandlingWithReadWriteScope ("csharp/metadata" , handleCSharpMetadata) |> requestHandlingWithReadOnlyScope From e4eadf61c26b2ab4ca19272ffa47a6d6e08cad5e Mon Sep 17 00:00:00 2001 From: Adam Tao Date: Sun, 26 Feb 2023 12:16:41 +0800 Subject: [PATCH 3/3] feat(Server.fs): Add semantic token to server capability Signed-off-by: Adam Tao --- src/CSharpLanguageServer/Server.fs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/CSharpLanguageServer/Server.fs b/src/CSharpLanguageServer/Server.fs index c7e231cc..df02d0cb 100644 --- a/src/CSharpLanguageServer/Server.fs +++ b/src/CSharpLanguageServer/Server.fs @@ -274,7 +274,12 @@ let setupServerHandlers options (lspClient: LspClient) = } FoldingRangeProvider = None SelectionRangeProvider = None - SemanticTokensProvider = None + SemanticTokensProvider = + Some { Legend = { TokenTypes = SemanticTokenTypes |> Seq.toArray + TokenModifiers = SemanticTokenModifiers |> Seq.toArray } + Range = Some true + Full = true |> First |> Some + } } }