Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 69 additions & 1 deletion src/CSharpLanguageServer/Server.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -273,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
}
}
}

Expand Down Expand Up @@ -1056,6 +1062,66 @@ let setupServerHandlers options (lspClient: LspClient) =
| None ->
None |> success |> async.Return

let toSemanticToken (lines: TextLineCollection) (textSpan: TextSpan, spans: IEnumerable<ClassifiedSpan>) =
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<Types.SemanticTokens option> = 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<Types.SemanticTokens option> =
getSemanticTokensRange scope semParams.TextDocument.Uri None

let handleSemanticTokensRange (scope: ServerRequestScope) (semParams: Types.SemanticTokensRangeParams): AsyncLspResult<Types.SemanticTokens option> =
getSemanticTokensRange scope semParams.TextDocument.Uri (Some semParams.Range)

let handleWorkspaceSymbol (scope: ServerRequestScope) (symbolParams: Types.WorkspaceSymbolParams): AsyncLspResult<Types.SymbolInformation [] option> = async {
let! symbols = findSymbolsInSolution scope.Solution symbolParams.Query (Some 20)
return symbols |> Array.ofSeq |> Some |> success
Expand Down Expand Up @@ -1231,6 +1297,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
Expand Down
71 changes: 71 additions & 0 deletions src/CSharpLanguageServer/Util.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -22,3 +23,73 @@ let makeFileUri (path: string): string =
| false -> "file://" + fullPath

type AsyncLogFn = string -> Async<unit>


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