From e2600736dd3a0cccdcc823cc4ac9c63d7f4b3905 Mon Sep 17 00:00:00 2001 From: alsi-lawr Date: Thu, 18 Sep 2025 17:41:16 +0100 Subject: [PATCH 1/2] chore: format with fantomas --- src/CSharpLanguageServer/Conversions.fs | 100 +-- src/CSharpLanguageServer/DocumentationUtil.fs | 67 +- src/CSharpLanguageServer/FormatUtil.fs | 70 ++- .../Handlers/CSharpMetadata.fs | 20 +- .../Handlers/CallHierarchy.fs | 98 +-- .../Handlers/CodeAction.fs | 554 +++++++++-------- src/CSharpLanguageServer/Handlers/CodeLens.fs | 53 +- src/CSharpLanguageServer/Handlers/Color.fs | 2 +- .../Handlers/Completion.fs | 271 ++++---- .../Handlers/Declaration.fs | 8 +- .../Handlers/Definition.fs | 28 +- .../Handlers/Diagnostic.fs | 156 ++--- .../Handlers/DocumentFormatting.fs | 6 +- .../Handlers/DocumentHighlight.fs | 85 +-- .../Handlers/DocumentOnTypeFormatting.fs | 20 +- .../Handlers/DocumentRangeFormatting.fs | 10 +- .../Handlers/DocumentSymbol.fs | 236 +++---- src/CSharpLanguageServer/Handlers/Hover.fs | 15 +- .../Handlers/Implementation.fs | 36 +- .../Handlers/Initialization.fs | 215 ++++--- .../Handlers/InlayHint.fs | 113 ++-- .../Handlers/LinkedEditingRange.fs | 5 +- .../Handlers/References.fs | 3 +- src/CSharpLanguageServer/Handlers/Rename.fs | 126 ++-- .../Handlers/SemanticTokens.fs | 190 +++--- .../Handlers/SignatureHelp.fs | 63 +- .../Handlers/TextDocumentSync.fs | 97 ++- .../Handlers/TypeDefinition.fs | 52 +- .../Handlers/TypeHierarchy.fs | 105 ++-- .../Handlers/Workspace.fs | 103 ++-- .../Handlers/WorkspaceSymbol.fs | 44 +- src/CSharpLanguageServer/Logging.fs | 10 +- src/CSharpLanguageServer/Lsp/Client.fs | 22 +- src/CSharpLanguageServer/Lsp/Server.fs | 474 +++++++++----- src/CSharpLanguageServer/Program.fs | 44 +- src/CSharpLanguageServer/ProgressReporter.fs | 50 +- src/CSharpLanguageServer/RoslynHelpers.fs | 576 +++++++++--------- src/CSharpLanguageServer/Types.fs | 32 +- src/CSharpLanguageServer/Util.fs | 51 +- 39 files changed, 2283 insertions(+), 1927 deletions(-) diff --git a/src/CSharpLanguageServer/Conversions.fs b/src/CSharpLanguageServer/Conversions.fs index 43bb9846..6811c489 100644 --- a/src/CSharpLanguageServer/Conversions.fs +++ b/src/CSharpLanguageServer/Conversions.fs @@ -17,18 +17,20 @@ module Uri = /// "#/ProjDir" is Fragment instead of part of LocalPath. let unescape (uri: string) = uri.Replace("%3a", ":", true, null) - let toPath (uri: string) = Uri.UnescapeDataString(Uri(unescape(uri)).LocalPath) + let toPath (uri: string) = + Uri.UnescapeDataString(Uri(unescape uri).LocalPath) let fromPath (path: string) = let metadataPrefix = "$metadata$/" - if path.StartsWith(metadataPrefix) then - "csharp:/metadata/" + path.Substring(metadataPrefix.Length) + + if path.StartsWith metadataPrefix then + "csharp:/metadata/" + path.Substring metadataPrefix.Length else Uri(path).ToString() - let toWorkspaceFolder(uri: string): WorkspaceFolder = + let toWorkspaceFolder (uri: string) : WorkspaceFolder = { Uri = uri - Name = Uri.UnescapeDataString(Uri(unescape(uri)).Segments |> Array.last) } + Name = Uri.UnescapeDataString(Uri(unescape uri).Segments |> Array.last) } module Path = @@ -40,36 +42,38 @@ module Path = module Position = - let fromLinePosition (pos: LinePosition): Position = - { Line = uint32 pos.Line ; Character = uint32 pos.Character } + let fromLinePosition (pos: LinePosition) : Position = + { Line = uint32 pos.Line + Character = uint32 pos.Character } - let toLinePosition (lines: TextLineCollection) (pos: Position): LinePosition = - if (int pos.Line) >= lines.Count then + let toLinePosition (lines: TextLineCollection) (pos: Position) : LinePosition = + if int pos.Line >= lines.Count then LinePosition(lines.Count - 1, lines[lines.Count - 1].EndIncludingLineBreak - lines[lines.Count - 1].Start) else LinePosition(int pos.Line, int pos.Character) - let toRoslynPosition (lines: TextLineCollection) = toLinePosition lines >> lines.GetPosition + let toRoslynPosition (lines: TextLineCollection) = + toLinePosition lines >> lines.GetPosition module Range = - let toLinePositionSpan (lines: TextLineCollection) (range: Range): LinePositionSpan = - LinePositionSpan( - Position.toLinePosition lines range.Start, - Position.toLinePosition lines range.End) + let toLinePositionSpan (lines: TextLineCollection) (range: Range) : LinePositionSpan = + LinePositionSpan(Position.toLinePosition lines range.Start, Position.toLinePosition lines range.End) - let fromLinePositionSpan (pos: LinePositionSpan): Range = + let fromLinePositionSpan (pos: LinePositionSpan) : Range = { Start = Position.fromLinePosition pos.Start End = Position.fromLinePosition pos.End } - let toTextSpan (lines: TextLineCollection) = toLinePositionSpan lines >> lines.GetTextSpan + let toTextSpan (lines: TextLineCollection) = + toLinePositionSpan lines >> lines.GetTextSpan - let fromTextSpan (lines: TextLineCollection) = lines.GetLinePositionSpan >> fromLinePositionSpan + let fromTextSpan (lines: TextLineCollection) = + lines.GetLinePositionSpan >> fromLinePositionSpan module Location = - let fromRoslynLocation (loc: Microsoft.CodeAnalysis.Location): option = - let toLspLocation (path: string) span: Location = + let fromRoslynLocation (loc: Microsoft.CodeAnalysis.Location) : option = + let toLspLocation (path: string) span : Location = { Uri = path |> Path.toUri Range = span |> Range.fromLinePositionSpan } @@ -78,14 +82,18 @@ module Location = let mappedSourceLocation = loc.GetMappedLineSpan() |> Some - |> Option.bind (fun mappedLoc -> if mappedLoc.IsValid && File.Exists(mappedLoc.Path) then Some mappedLoc else None) + |> Option.bind (fun mappedLoc -> + if mappedLoc.IsValid && File.Exists mappedLoc.Path then + Some mappedLoc + else + None) |> Option.map (fun mappedLoc -> toLspLocation mappedLoc.Path mappedLoc.Span) let sourceLocation = loc.SourceTree |> Option.ofObj |> Option.map _.FilePath - |> Option.bind (fun filePath -> if File.Exists(filePath) then Some filePath else None) + |> Option.bind (fun filePath -> if File.Exists filePath then Some filePath else None) |> Option.map (fun filePath -> toLspLocation filePath (loc.GetLineSpan().Span)) mappedSourceLocation |> Option.orElse sourceLocation @@ -94,17 +102,21 @@ module Location = module TextEdit = - let fromTextChange (lines: TextLineCollection) (changes: TextChange): TextEdit = + let fromTextChange (lines: TextLineCollection) (changes: TextChange) : TextEdit = { Range = changes.Span |> Range.fromTextSpan lines NewText = changes.NewText } module SymbolKind = - let fromSymbol (symbol: ISymbol): SymbolKind = + let fromSymbol (symbol: ISymbol) : SymbolKind = match symbol with | :? ILocalSymbol -> SymbolKind.Variable | :? IFieldSymbol as fs -> - if not(isNull fs.ContainingType) && fs.ContainingType.TypeKind = TypeKind.Enum && fs.HasConstantValue then + if + not (isNull fs.ContainingType) + && fs.ContainingType.TypeKind = TypeKind.Enum + && fs.HasConstantValue + then SymbolKind.EnumMember else SymbolKind.Field @@ -133,7 +145,7 @@ module SymbolKind = module SymbolName = - let fromSymbol (format: SymbolDisplayFormat) (symbol: ISymbol): string = symbol.ToDisplayString(format) + let fromSymbol (format: SymbolDisplayFormat) (symbol: ISymbol) : string = symbol.ToDisplayString format module CallHierarchyItem = @@ -151,12 +163,12 @@ module CallHierarchyItem = miscellaneousOptions = SymbolDisplayMiscellaneousOptions.UseSpecialTypes ) - let fromSymbolAndLocation (symbol: ISymbol) (location: Location): CallHierarchyItem = + let fromSymbolAndLocation (symbol: ISymbol) (location: Location) : CallHierarchyItem = let kind = SymbolKind.fromSymbol symbol let containingType = (symbol.ContainingType :> ISymbol) |> Option.ofObj let containingNamespace = (symbol.ContainingNamespace :> ISymbol) |> Option.ofObj - { Name = symbol.ToDisplayString(displayStyle) + { Name = symbol.ToDisplayString displayStyle Kind = kind Tags = None Detail = @@ -168,7 +180,10 @@ module CallHierarchyItem = SelectionRange = location.Range Data = None } - let fromSymbol (wmResolveSymbolLocations: ISymbol -> Project option -> Async>) (symbol: ISymbol): Async = + let fromSymbol + (wmResolveSymbolLocations: ISymbol -> Project option -> Async>) + (symbol: ISymbol) + : Async = wmResolveSymbolLocations symbol None |> Async.map (List.map (fromSymbolAndLocation symbol)) @@ -187,12 +202,12 @@ module TypeHierarchyItem = miscellaneousOptions = SymbolDisplayMiscellaneousOptions.UseSpecialTypes ) - let fromSymbolAndLocation (symbol: ISymbol) (location: Location): TypeHierarchyItem = + let fromSymbolAndLocation (symbol: ISymbol) (location: Location) : TypeHierarchyItem = let kind = SymbolKind.fromSymbol symbol let containingType = (symbol.ContainingType :> ISymbol) |> Option.ofObj let containingNamespace = (symbol.ContainingNamespace :> ISymbol) |> Option.ofObj - { Name = symbol.ToDisplayString(displayStyle) + { Name = symbol.ToDisplayString displayStyle Kind = kind Tags = None Detail = @@ -204,12 +219,15 @@ module TypeHierarchyItem = SelectionRange = location.Range Data = None } - let fromSymbol (wmResolveSymbolLocations: ISymbol -> Project option -> Async>) (symbol: ISymbol): Async = + let fromSymbol + (wmResolveSymbolLocations: ISymbol -> Project option -> Async>) + (symbol: ISymbol) + : Async = wmResolveSymbolLocations symbol None |> Async.map (List.map (fromSymbolAndLocation symbol)) module SymbolInformation = - let fromSymbol (format: SymbolDisplayFormat) (symbol: ISymbol): SymbolInformation list = + let fromSymbol (format: SymbolDisplayFormat) (symbol: ISymbol) : SymbolInformation list = let toSymbolInformation loc = { Name = SymbolName.fromSymbol format symbol Kind = SymbolKind.fromSymbol symbol @@ -227,7 +245,7 @@ module SymbolInformation = module DiagnosticSeverity = - let fromRoslynDiagnosticSeverity (sev: Microsoft.CodeAnalysis.DiagnosticSeverity): DiagnosticSeverity = + let fromRoslynDiagnosticSeverity (sev: Microsoft.CodeAnalysis.DiagnosticSeverity) : DiagnosticSeverity = match sev with | Microsoft.CodeAnalysis.DiagnosticSeverity.Info -> DiagnosticSeverity.Information | Microsoft.CodeAnalysis.DiagnosticSeverity.Warning -> DiagnosticSeverity.Warning @@ -236,11 +254,12 @@ module DiagnosticSeverity = module Diagnostic = - let fromRoslynDiagnostic (diagnostic: Microsoft.CodeAnalysis.Diagnostic): Diagnostic = + let fromRoslynDiagnostic (diagnostic: Microsoft.CodeAnalysis.Diagnostic) : Diagnostic = let diagnosticCodeUrl = diagnostic.Descriptor.HelpLinkUri |> Option.ofObj + { Range = diagnostic.Location.GetLineSpan().Span |> Range.fromLinePositionSpan - Severity = Some (diagnostic.Severity |> DiagnosticSeverity.fromRoslynDiagnosticSeverity) - Code = Some (U2.C2 diagnostic.Id) + Severity = Some(diagnostic.Severity |> DiagnosticSeverity.fromRoslynDiagnosticSeverity) + Code = Some(U2.C2 diagnostic.Id) CodeDescription = diagnosticCodeUrl |> Option.map (fun x -> { Href = x |> URI }) Source = Some "lsp" Message = diagnostic.GetMessage() @@ -251,16 +270,15 @@ module Diagnostic = module CompletionContext = - let toCompletionTrigger (context: CompletionContext option): Completion.CompletionTrigger = + let toCompletionTrigger (context: CompletionContext option) : CompletionTrigger = context |> Option.bind (fun ctx -> match ctx.TriggerKind with | CompletionTriggerKind.Invoked - | CompletionTriggerKind.TriggerForIncompleteCompletions -> - Some Completion.CompletionTrigger.Invoke + | CompletionTriggerKind.TriggerForIncompleteCompletions -> Some CompletionTrigger.Invoke | CompletionTriggerKind.TriggerCharacter -> ctx.TriggerCharacter |> Option.map Seq.head - |> Option.map Completion.CompletionTrigger.CreateInsertionTrigger + |> Option.map CompletionTrigger.CreateInsertionTrigger | _ -> None) - |> Option.defaultValue (Completion.CompletionTrigger.Invoke) + |> Option.defaultValue CompletionTrigger.Invoke diff --git a/src/CSharpLanguageServer/DocumentationUtil.fs b/src/CSharpLanguageServer/DocumentationUtil.fs index 4a9212fb..38884c64 100644 --- a/src/CSharpLanguageServer/DocumentationUtil.fs +++ b/src/CSharpLanguageServer/DocumentationUtil.fs @@ -16,6 +16,7 @@ module DocumentationUtil = Types: (string * XElement) list Remarks: XElement list OtherLines: XElement list } + static member Default = { Summary = [] Params = [] @@ -26,7 +27,8 @@ module DocumentationUtil = OtherLines = [] } let parseCref (cref: string) = - let parts = cref.Split(':') + let parts = cref.Split ':' + match parts.Length with | 1 -> cref | _ -> String.Join(":", parts |> Seq.skip 1) @@ -34,6 +36,7 @@ module DocumentationUtil = let normalizeWhitespace (s: string) = let mutable modified = s let mutable prevModified = "" + while modified <> prevModified do prevModified <- modified modified <- modified.Replace(" ", " ").Replace("\r\n", " ").Replace("\n", " ") @@ -44,7 +47,7 @@ module DocumentationUtil = let formatSeeElement (e: XElement) = let crefMaybe = - e.Attribute(XName.Get("cref")) + e.Attribute(XName.Get "cref") |> Option.ofObj |> Option.map (fun x -> x.Value) |> Option.map parseCref @@ -52,7 +55,7 @@ module DocumentationUtil = |> Option.toList let langWordMaybe = - e.Attribute(XName.Get("langword")) + e.Attribute(XName.Get "langword") |> Option.ofObj |> Option.map (fun x -> sprintf "``%s``" x.Value) |> Option.toList @@ -66,15 +69,11 @@ module DocumentationUtil = | "c" -> [ sprintf "``%s``" e.Value ] | "see" -> formatSeeElement e | "paramref" -> - e.Attribute(XName.Get("name")) + e.Attribute(XName.Get "name") |> Option.ofObj |> Option.map (fun x -> sprintf "``%s``" x.Value) |> Option.toList - | "para" -> - e.Nodes() - |> Seq.collect formatTextNode - |> Seq.append [ "\n\n" ] - |> List.ofSeq + | "para" -> e.Nodes() |> Seq.collect formatTextNode |> Seq.append [ "\n\n" ] |> List.ofSeq | _ -> [ e.Value ] | :? XText as t -> [ t.Value |> normalizeWhitespace ] | _ -> [] @@ -92,38 +91,45 @@ module DocumentationUtil = { comment with Remarks = newRemarks } | "param" -> - let name = n.Attribute(XName.Get("name")) - |> Option.ofObj - |> Option.map (fun a -> a.Value) - |> Option.defaultValue "(unspecified)" + let name = + n.Attribute(XName.Get "name") + |> Option.ofObj + |> Option.map (fun a -> a.Value) + |> Option.defaultValue "(unspecified)" - { comment with Params = comment.Params |> List.append [ (name, n) ] } + { comment with + Params = comment.Params |> List.append [ (name, n) ] } | "returns" -> - { comment with Returns = comment.Returns |> List.append [ n ] } + { comment with + Returns = comment.Returns |> List.append [ n ] } | "exception" -> - let name = n.Attribute(XName.Get("cref")) - |> Option.ofObj - |> Option.map (fun a -> parseCref a.Value) - |> Option.defaultValue "(unspecified)" + let name = + n.Attribute(XName.Get "cref") + |> Option.ofObj + |> Option.map (fun a -> parseCref a.Value) + |> Option.defaultValue "(unspecified)" { comment with Exceptions = comment.Exceptions |> List.append [ (name, n) ] } | "typeparam" -> - let name = n.Attribute(XName.Get("name")) - |> Option.ofObj - |> Option.map (fun a -> a.Value) - |> Option.defaultValue "(unspecified)" + let name = + n.Attribute(XName.Get "name") + |> Option.ofObj + |> Option.map (fun a -> a.Value) + |> Option.defaultValue "(unspecified)" - { comment with Types = comment.Types |> List.append [ (name, n) ] } + { comment with + Types = comment.Types |> List.append [ (name, n) ] } | _ -> - { comment with OtherLines = comment.OtherLines |> List.append [ n ] } + { comment with + OtherLines = comment.OtherLines |> List.append [ n ] } - let parseComment xmlDocumentation: TripleSlashComment = + let parseComment xmlDocumentation : TripleSlashComment = let doc = XDocument.Parse("" + xmlDocumentation + "") let unwrapDocRoot (root: XElement) = @@ -131,7 +137,7 @@ module DocumentationUtil = el.Elements() |> Seq.map (fun e -> e.Name.LocalName) |> List.ofSeq match elementNames root with - | [ "member" ] -> root.Element(XName.Get("member")) + | [ "member" ] -> root.Element(XName.Get "member") | _ -> root doc.Root @@ -185,10 +191,11 @@ module DocumentationUtil = let comment = parseComment (sym.GetDocumentationCommentXml()) let formattedDocLines = formatComment comment - formattedDocLines |> (fun ss -> String.Join("\n", ss)) + formattedDocLines |> fun ss -> String.Join("\n", ss) let markdownDocForSymbolWithSignature (sym: ISymbol) = - let symbolName = SymbolName.fromSymbol SymbolDisplayFormat.MinimallyQualifiedFormat sym + let symbolName = + SymbolName.fromSymbol SymbolDisplayFormat.MinimallyQualifiedFormat sym let symbolInfoLines = match symbolName with @@ -206,4 +213,4 @@ module DocumentationUtil = [] ) |> Seq.append symbolInfoLines - |> (fun ss -> String.Join("\n", ss)) + |> fun ss -> String.Join("\n", ss) diff --git a/src/CSharpLanguageServer/FormatUtil.fs b/src/CSharpLanguageServer/FormatUtil.fs index 32488dbb..f802b088 100644 --- a/src/CSharpLanguageServer/FormatUtil.fs +++ b/src/CSharpLanguageServer/FormatUtil.fs @@ -32,20 +32,16 @@ module internal FormatUtil = TextSpan.FromBounds(span.Start, span.End + 1) let rec checkSpanLineEndings (newText: string, oldText: SourceText, span: TextSpan, prefix: string) : TextEdit = - if - span.Start > 0 - && newText[0].Equals('\n') - && oldText[span.Start - 1].Equals('\r') - then - checkSpanLineEndings (newText, oldText, padLeft (span), "\r") |> ignore + if span.Start > 0 && newText[0].Equals '\n' && oldText[span.Start - 1].Equals '\r' then + checkSpanLineEndings (newText, oldText, padLeft span, "\r") |> ignore if span.End < oldText.Length - 1 - && newText[newText.Length - 1].Equals('\r') - && oldText[span.End].Equals('\n') + && newText[newText.Length - 1].Equals '\r' + && oldText[span.End].Equals '\n' then - let linePosition = oldText.Lines.GetLinePositionSpan(padRight (span)) - mapToTextEdit (linePosition, (prefix + newText.ToString() + "\n")) + let linePosition = oldText.Lines.GetLinePositionSpan(padRight span) + mapToTextEdit (linePosition, prefix + newText.ToString() + "\n") else let linePosition = oldText.Lines.GetLinePositionSpan span mapToTextEdit (linePosition, newText.ToString()) @@ -60,7 +56,7 @@ module internal FormatUtil = let convert (oldText: SourceText) (changes: TextChange[]) : TextEdit[] = //why doesnt it pick up that TextSpan implements IComparable? //one of life's many mysteries - let comparer (lhs: TextChange) (rhs: TextChange) : int = lhs.Span.CompareTo(rhs.Span) + let comparer (lhs: TextChange) (rhs: TextChange) : int = lhs.Span.CompareTo rhs.Span changes |> Seq.sortWith comparer @@ -70,27 +66,39 @@ module internal FormatUtil = let getChanges (doc: Document) (oldDoc: Document) : Async = async { let! ct = Async.CancellationToken let! changes = doc.GetTextChangesAsync(oldDoc, ct) |> Async.AwaitTask - let! oldText = oldDoc.GetTextAsync(ct) |> Async.AwaitTask + let! oldText = oldDoc.GetTextAsync ct |> Async.AwaitTask return convert oldText (changes |> Seq.toArray) } - let getFormattingOptions (settings: ServerSettings) (doc: Document) (formattingOptions: FormattingOptions) : Async = async { - let! docOptions = doc.GetOptionsAsync() |> Async.AwaitTask + let getFormattingOptions + (settings: ServerSettings) + (doc: Document) + (formattingOptions: FormattingOptions) + : Async = + async { + let! docOptions = doc.GetOptionsAsync() |> Async.AwaitTask - return - match settings.ApplyFormattingOptions with - | false -> - docOptions - | true -> - docOptions - |> _.WithChangedOption(FormattingOptions.IndentationSize, LanguageNames.CSharp, int formattingOptions.TabSize) - |> _.WithChangedOption(FormattingOptions.UseTabs, LanguageNames.CSharp, not formattingOptions.InsertSpaces) - |> match formattingOptions.InsertFinalNewline with - | Some insertFinalNewline -> - _.WithChangedOption(CSharpFormattingOptions.NewLineForFinally, insertFinalNewline) - | None -> id - |> match formattingOptions.TrimFinalNewlines with - | Some trimFinalNewlines -> - _.WithChangedOption(CSharpFormattingOptions.NewLineForFinally, not trimFinalNewlines) - | None -> id - } + return + match settings.ApplyFormattingOptions with + | false -> docOptions + | true -> + docOptions + |> _.WithChangedOption( + FormattingOptions.IndentationSize, + LanguageNames.CSharp, + int formattingOptions.TabSize + ) + |> _.WithChangedOption( + FormattingOptions.UseTabs, + LanguageNames.CSharp, + not formattingOptions.InsertSpaces + ) + |> match formattingOptions.InsertFinalNewline with + | Some insertFinalNewline -> + _.WithChangedOption(CSharpFormattingOptions.NewLineForFinally, insertFinalNewline) + | None -> id + |> match formattingOptions.TrimFinalNewlines with + | Some trimFinalNewlines -> + _.WithChangedOption(CSharpFormattingOptions.NewLineForFinally, not trimFinalNewlines) + | None -> id + } diff --git a/src/CSharpLanguageServer/Handlers/CSharpMetadata.fs b/src/CSharpLanguageServer/Handlers/CSharpMetadata.fs index 46cdbbda..36039bda 100644 --- a/src/CSharpLanguageServer/Handlers/CSharpMetadata.fs +++ b/src/CSharpLanguageServer/Handlers/CSharpMetadata.fs @@ -8,13 +8,17 @@ open CSharpLanguageServer.State [] module CSharpMetadata = - let handle (context: ServerRequestContext) (metadataParams: CSharpMetadataParams): AsyncLspResult = async { - let uri = metadataParams.TextDocument.Uri + let handle + (context: ServerRequestContext) + (metadataParams: CSharpMetadataParams) + : AsyncLspResult = + async { + let uri = metadataParams.TextDocument.Uri - let metadataMaybe = - context.DecompiledMetadata - |> Map.tryFind uri - |> Option.map (fun x -> x.Metadata) + let metadataMaybe = + context.DecompiledMetadata + |> Map.tryFind uri + |> Option.map (fun x -> x.Metadata) - return metadataMaybe |> LspResult.success - } + return metadataMaybe |> LspResult.success + } diff --git a/src/CSharpLanguageServer/Handlers/CallHierarchy.fs b/src/CSharpLanguageServer/Handlers/CallHierarchy.fs index 4e44c92e..30fa9ffd 100644 --- a/src/CSharpLanguageServer/Handlers/CallHierarchy.fs +++ b/src/CSharpLanguageServer/Handlers/CallHierarchy.fs @@ -10,7 +10,7 @@ open CSharpLanguageServer.Conversions [] module CallHierarchy = - let private isCallableSymbol (symbol: ISymbol): bool = + let private isCallableSymbol (symbol: ISymbol) : bool = List.contains symbol.Kind [ Microsoft.CodeAnalysis.SymbolKind.Method @@ -18,60 +18,64 @@ module CallHierarchy = Microsoft.CodeAnalysis.SymbolKind.Event Microsoft.CodeAnalysis.SymbolKind.Property ] - let provider (clientCapabilities: ClientCapabilities) : U3 option = - Some (U3.C1 true) + let provider + (clientCapabilities: ClientCapabilities) + : U3 option = + Some(U3.C1 true) - let prepare (context: ServerRequestContext) (p: CallHierarchyPrepareParams) : AsyncLspResult = async { - match! context.FindSymbol p.TextDocument.Uri p.Position with - | Some symbol when isCallableSymbol symbol -> - let! itemList = CallHierarchyItem.fromSymbol context.ResolveSymbolLocations symbol - return - itemList - |> List.toArray - |> Some - |> LspResult.success - | _ -> return None |> LspResult.success - } + let prepare + (context: ServerRequestContext) + (p: CallHierarchyPrepareParams) + : AsyncLspResult = + async { + match! context.FindSymbol p.TextDocument.Uri p.Position with + | Some symbol when isCallableSymbol symbol -> + let! itemList = CallHierarchyItem.fromSymbol context.ResolveSymbolLocations symbol + return itemList |> List.toArray |> Some |> LspResult.success + | _ -> return None |> LspResult.success + } let incomingCalls (context: ServerRequestContext) (p: CallHierarchyIncomingCallsParams) - : AsyncLspResult = async { - let toCallHierarchyIncomingCalls (info: SymbolCallerInfo) : CallHierarchyIncomingCall seq = - let fromRanges = - info.Locations - |> Seq.map (fun l -> l.GetLineSpan().Span |> Range.fromLinePositionSpan) - |> Seq.toArray + : AsyncLspResult = + async { + let toCallHierarchyIncomingCalls (info: SymbolCallerInfo) : CallHierarchyIncomingCall seq = + let fromRanges = + info.Locations + |> Seq.map (fun l -> l.GetLineSpan().Span |> Range.fromLinePositionSpan) + |> Seq.toArray - info.CallingSymbol.Locations - |> Seq.map Location.fromRoslynLocation - |> Seq.filter _.IsSome - |> Seq.map _.Value - |> Seq.map (fun loc -> - { From = CallHierarchyItem.fromSymbolAndLocation (info.CallingSymbol) loc - FromRanges = fromRanges }) + info.CallingSymbol.Locations + |> Seq.map Location.fromRoslynLocation + |> Seq.filter _.IsSome + |> Seq.map _.Value + |> Seq.map (fun loc -> + { From = CallHierarchyItem.fromSymbolAndLocation (info.CallingSymbol) loc + FromRanges = fromRanges }) - match! context.FindSymbol p.Item.Uri p.Item.Range.Start with - | None -> return None |> LspResult.success - | Some symbol -> - let! callers = context.FindCallers symbol - // TODO: If we remove info.IsDirect, then we will get lots of false positive. But if we keep it, - // we will miss many callers. Maybe it should have some change in LSP protocol. - return - callers - |> Seq.filter (fun info -> info.IsDirect && isCallableSymbol info.CallingSymbol) - |> Seq.collect toCallHierarchyIncomingCalls - |> Seq.distinct - |> Seq.toArray - |> Some - |> LspResult.success - } + match! context.FindSymbol p.Item.Uri p.Item.Range.Start with + | None -> return None |> LspResult.success + | Some symbol -> + let! callers = context.FindCallers symbol + // TODO: If we remove info.IsDirect, then we will get lots of false positive. But if we keep it, + // we will miss many callers. Maybe it should have some change in LSP protocol. + return + callers + |> Seq.filter (fun info -> info.IsDirect && isCallableSymbol info.CallingSymbol) + |> Seq.collect toCallHierarchyIncomingCalls + |> Seq.distinct + |> Seq.toArray + |> Some + |> LspResult.success + } let outgoingCalls (_context: ServerRequestContext) (_: CallHierarchyOutgoingCallsParams) - : AsyncLspResult = async { - // TODO: There is no memthod of SymbolFinder which can find all outgoing calls of a specific symbol. - // Then how can we implement it? Parsing AST manually? - return None |> LspResult.success - } + : AsyncLspResult = + async { + // TODO: There is no memthod of SymbolFinder which can find all outgoing calls of a specific symbol. + // Then how can we implement it? Parsing AST manually? + return None |> LspResult.success + } diff --git a/src/CSharpLanguageServer/Handlers/CodeAction.fs b/src/CSharpLanguageServer/Handlers/CodeAction.fs index 76444e55..4ae65eca 100644 --- a/src/CSharpLanguageServer/Handlers/CodeAction.fs +++ b/src/CSharpLanguageServer/Handlers/CodeAction.fs @@ -36,8 +36,7 @@ module CodeAction = let assemblies = [ "Microsoft.CodeAnalysis.Features" "Microsoft.CodeAnalysis.CSharp.Features" - "Microsoft.CodeAnalysis.Workspaces" - ] + "Microsoft.CodeAnalysis.Workspaces" ] |> Seq.map Assembly.Load |> Array.ofSeq @@ -54,101 +53,103 @@ module CodeAction = let isProviderType (t: Type) = t.IsAssignableTo(typeof<'ProviderType>) - let hasParameterlessConstructor (t: Type) = t.GetConstructor(Array.empty) |> isNull |> not + let hasParameterlessConstructor (t: Type) = + t.GetConstructor(Array.empty) |> isNull |> not types - |> Seq.filter isProviderType - |> Seq.filter hasParameterlessConstructor - |> Seq.filter isValidProvider - |> Seq.map Activator.CreateInstance - |> Seq.filter (fun i -> i <> null) - |> Seq.map (fun i -> i :?> 'ProviderType) - |> Seq.toArray + |> Seq.filter isProviderType + |> Seq.filter hasParameterlessConstructor + |> Seq.filter isValidProvider + |> Seq.map Activator.CreateInstance + |> Seq.filter (fun i -> i <> null) + |> Seq.map (fun i -> i :?> 'ProviderType) + |> Seq.toArray let private refactoringProviderInstances = - instantiateRoslynProviders - (fun t -> ((string t) <> "Microsoft.CodeAnalysis.ChangeSignature.ChangeSignatureCodeRefactoringProvider")) + instantiateRoslynProviders (fun t -> + ((string t) + <> "Microsoft.CodeAnalysis.ChangeSignature.ChangeSignatureCodeRefactoringProvider")) let private codeFixProviderInstances = - instantiateRoslynProviders - (fun _ -> true) + instantiateRoslynProviders (fun _ -> true) // TODO: refactor it. I think a long function in functional language is hard to read :) - let private getRoslynCodeActions (doc: Document) (textSpan: TextSpan) (ct: CancellationToken) - : Async = async { - let roslynCodeActions = List() - let addCodeAction = Action(roslynCodeActions.Add) - let codeActionContext = CodeRefactoringContext(doc, textSpan, addCodeAction, ct) - - for refactoringProvider in refactoringProviderInstances do - try - do! refactoringProvider.ComputeRefactoringsAsync(codeActionContext) |> Async.AwaitTask - with ex -> - logger.LogError( - ex, - "cannot compute refactorings for {provider}", - refactoringProvider) - - // register code fixes - let! semanticModel = doc.GetSemanticModelAsync(ct) |> Async.AwaitTask - - let isDiagnosticsOnTextSpan (diag: Microsoft.CodeAnalysis.Diagnostic) = - diag.Location.SourceSpan.IntersectsWith(textSpan) - - let relatedDiagnostics = - semanticModel.GetDiagnostics(cancellationToken=ct) - |> Seq.filter isDiagnosticsOnTextSpan - |> List.ofSeq - - let diagnosticsBySpan = - relatedDiagnostics - |> Seq.groupBy (fun d -> d.Location.SourceSpan) - - for diagnosticSpan, diagnosticsWithSameSpan in diagnosticsBySpan do - let addCodeFix = - Action>( - fun ca _ -> roslynCodeActions.Add(ca)) - - for codeFixProvider in codeFixProviderInstances do - let refactoringProviderOK (diag: Microsoft.CodeAnalysis.Diagnostic) = - let translatedDiagId diagId = - match diagId with - | "CS8019" -> "RemoveUnnecessaryImportsFixable" - | _ -> "" - - codeFixProvider.FixableDiagnosticIds.Contains(diag.Id) - || codeFixProvider.FixableDiagnosticIds.Contains(translatedDiagId diag.Id) - - let fixableDiagnostics = - diagnosticsWithSameSpan - |> Seq.filter refactoringProviderOK - - if not (Seq.isEmpty fixableDiagnostics) then - let codeFixContext = CodeFixContext(doc, diagnosticSpan, fixableDiagnostics.ToImmutableArray(), addCodeFix, ct) - - try - do! codeFixProvider.RegisterCodeFixesAsync(codeFixContext) |> Async.AwaitTask - with ex -> - logger.LogError(ex, "error in RegisterCodeFixesAsync()") - - let unwrapRoslynCodeAction (ca: Microsoft.CodeAnalysis.CodeActions.CodeAction) = - let nestedCAProp = - ca.GetType().GetProperty("NestedCodeActions", BindingFlags.Instance|||BindingFlags.NonPublic) - |> Option.ofObj - - match nestedCAProp with - | Some nestedCAProp -> - let nestedCAs = nestedCAProp.GetValue(ca, null) :?> ImmutableArray - match nestedCAs.Length with - | 0 -> [ca].ToImmutableArray() - | _ -> nestedCAs - | None -> - [ca].ToImmutableArray() - - return roslynCodeActions - |> Seq.collect unwrapRoslynCodeAction - |> List.ofSeq - } + let private getRoslynCodeActions + (doc: Document) + (textSpan: TextSpan) + (ct: CancellationToken) + : Async = + async { + let roslynCodeActions = List() + let addCodeAction = Action(roslynCodeActions.Add) + let codeActionContext = CodeRefactoringContext(doc, textSpan, addCodeAction, ct) + + for refactoringProvider in refactoringProviderInstances do + try + do! + refactoringProvider.ComputeRefactoringsAsync(codeActionContext) + |> Async.AwaitTask + with ex -> + logger.LogError(ex, "cannot compute refactorings for {provider}", refactoringProvider) + + // register code fixes + let! semanticModel = doc.GetSemanticModelAsync(ct) |> Async.AwaitTask + + let isDiagnosticsOnTextSpan (diag: Microsoft.CodeAnalysis.Diagnostic) = + diag.Location.SourceSpan.IntersectsWith(textSpan) + + let relatedDiagnostics = + semanticModel.GetDiagnostics(cancellationToken = ct) + |> Seq.filter isDiagnosticsOnTextSpan + |> List.ofSeq + + let diagnosticsBySpan = + relatedDiagnostics |> Seq.groupBy (fun d -> d.Location.SourceSpan) + + for diagnosticSpan, diagnosticsWithSameSpan in diagnosticsBySpan do + let addCodeFix = + Action>(fun ca _ -> + roslynCodeActions.Add(ca)) + + for codeFixProvider in codeFixProviderInstances do + let refactoringProviderOK (diag: Microsoft.CodeAnalysis.Diagnostic) = + let translatedDiagId diagId = + match diagId with + | "CS8019" -> "RemoveUnnecessaryImportsFixable" + | _ -> "" + + codeFixProvider.FixableDiagnosticIds.Contains(diag.Id) + || codeFixProvider.FixableDiagnosticIds.Contains(translatedDiagId diag.Id) + + let fixableDiagnostics = diagnosticsWithSameSpan |> Seq.filter refactoringProviderOK + + if not (Seq.isEmpty fixableDiagnostics) then + let codeFixContext = + CodeFixContext(doc, diagnosticSpan, fixableDiagnostics.ToImmutableArray(), addCodeFix, ct) + + try + do! codeFixProvider.RegisterCodeFixesAsync(codeFixContext) |> Async.AwaitTask + with ex -> + logger.LogError(ex, "error in RegisterCodeFixesAsync()") + + let unwrapRoslynCodeAction (ca: Microsoft.CodeAnalysis.CodeActions.CodeAction) = + let nestedCAProp = + ca.GetType().GetProperty("NestedCodeActions", BindingFlags.Instance ||| BindingFlags.NonPublic) + |> Option.ofObj + + match nestedCAProp with + | Some nestedCAProp -> + let nestedCAs = + nestedCAProp.GetValue(ca, null) + :?> ImmutableArray + + match nestedCAs.Length with + | 0 -> [ ca ].ToImmutableArray() + | _ -> nestedCAs + | None -> [ ca ].ToImmutableArray() + + return roslynCodeActions |> Seq.collect unwrapRoslynCodeAction |> List.ofSeq + } let asyncMaybeOnException op = async { try @@ -161,13 +162,15 @@ module CodeAction = let lspCodeActionDetailsFromRoslynCA ca = let typeName = ca.GetType() |> string + if typeName.Contains("CodeAnalysis.AddImport") then Some CodeActionKind.QuickFix, Some true else None, None - let roslynCodeActionToUnresolvedLspCodeAction (ca: CodeActions.CodeAction): CodeAction = + let roslynCodeActionToUnresolvedLspCodeAction (ca: CodeActions.CodeAction) : CodeAction = let caKind, caIsPreferred = lspCodeActionDetailsFromRoslynCA ca + { Title = ca.Title Kind = caKind Diagnostics = None @@ -175,118 +178,137 @@ module CodeAction = Command = None Data = None IsPreferred = caIsPreferred - Disabled = None - } + Disabled = None } let lspDocChangesFromSolutionDiff - originalSolution - (updatedSolution: Solution) - (tryGetDocVersionByUri: string -> int option) - (originatingDoc: Document) - (ct: CancellationToken) - : Async = async { - // make a list of changes - let solutionProjectChanges = updatedSolution.GetChanges(originalSolution).GetProjectChanges() - - let docTextEdits = List() - - let addedDocs = solutionProjectChanges |> Seq.collect (fun pc -> pc.GetAddedDocuments()) - - for docId in addedDocs do - let newDoc = updatedSolution.GetDocument(docId) |> nonNull "updatedSolution.GetDocument(docId)" - let! newDocText = newDoc.GetTextAsync(ct) |> Async.AwaitTask - - let edit: TextEdit = - { Range = { Start = { Line=0u; Character=0u }; End = { Line=0u; Character=0u } } - NewText = newDocText.ToString() } - - let newDocFilePathMaybe = - if String.IsNullOrWhiteSpace(newDoc.FilePath) - || (not <| Path.IsPathRooted(newDoc.FilePath)) then - if String.IsNullOrWhiteSpace(originatingDoc.FilePath) then - None + originalSolution + (updatedSolution: Solution) + (tryGetDocVersionByUri: string -> int option) + (originatingDoc: Document) + (ct: CancellationToken) + : Async = + async { + // make a list of changes + let solutionProjectChanges = + updatedSolution.GetChanges(originalSolution).GetProjectChanges() + + let docTextEdits = List() + + let addedDocs = + solutionProjectChanges |> Seq.collect (fun pc -> pc.GetAddedDocuments()) + + for docId in addedDocs do + let newDoc = + updatedSolution.GetDocument(docId) + |> nonNull "updatedSolution.GetDocument(docId)" + + let! newDocText = newDoc.GetTextAsync(ct) |> Async.AwaitTask + + let edit: TextEdit = + { Range = + { Start = { Line = 0u; Character = 0u } + End = { Line = 0u; Character = 0u } } + NewText = newDocText.ToString() } + + let newDocFilePathMaybe = + if + String.IsNullOrWhiteSpace(newDoc.FilePath) + || (not <| Path.IsPathRooted(newDoc.FilePath)) + then + if String.IsNullOrWhiteSpace(originatingDoc.FilePath) then + None + else + let directory = Path.GetDirectoryName(originatingDoc.FilePath) + Path.Combine(directory, newDoc.Name) |> Some else - let directory = Path.GetDirectoryName(originatingDoc.FilePath) - Path.Combine(directory, newDoc.Name) |> Some - else - Some newDoc.FilePath - - match newDocFilePathMaybe with - | Some newDocFilePath -> - let textEditDocument = { Uri = newDocFilePath |> Path.toUri - Version = newDocFilePath |> Path.toUri |> tryGetDocVersionByUri } - - docTextEdits.Add({ TextDocument = textEditDocument; Edits = [| U2.C1 edit |] }) - | None -> () - - let changedDocs = solutionProjectChanges |> Seq.collect (fun pc -> pc.GetChangedDocuments()) - - for docId in changedDocs do - let originalDoc = originalSolution.GetDocument(docId) |> Option.ofObj - let updatedDoc = updatedSolution.GetDocument(docId) |> Option.ofObj - - match originalDoc, updatedDoc with - | Some originalDoc, Some updatedDoc -> - let! originalDocText = originalDoc.GetTextAsync(ct) |> Async.AwaitTask - let! docChanges = updatedDoc.GetTextChangesAsync(originalDoc, ct) |> Async.AwaitTask - - let diffEdits: U2 array = - docChanges - |> Seq.sortBy (fun c -> c.Span.Start) - |> Seq.map (TextEdit.fromTextChange originalDocText.Lines) - |> Seq.map U2.C1 - |> Array.ofSeq - - let textEditDocument = { Uri = originalDoc.FilePath |> Path.toUri - Version = originalDoc.FilePath |> Path.toUri |> tryGetDocVersionByUri } - - docTextEdits.Add({ TextDocument = textEditDocument; Edits = diffEdits }) - | _, _ -> () - - return docTextEdits |> List.ofSeq - } + Some newDoc.FilePath + + match newDocFilePathMaybe with + | Some newDocFilePath -> + let textEditDocument = + { Uri = newDocFilePath |> Path.toUri + Version = newDocFilePath |> Path.toUri |> tryGetDocVersionByUri } + + docTextEdits.Add( + { TextDocument = textEditDocument + Edits = [| U2.C1 edit |] } + ) + | None -> () + + let changedDocs = + solutionProjectChanges |> Seq.collect (fun pc -> pc.GetChangedDocuments()) + + for docId in changedDocs do + let originalDoc = originalSolution.GetDocument(docId) |> Option.ofObj + let updatedDoc = updatedSolution.GetDocument(docId) |> Option.ofObj + + match originalDoc, updatedDoc with + | Some originalDoc, Some updatedDoc -> + let! originalDocText = originalDoc.GetTextAsync(ct) |> Async.AwaitTask + let! docChanges = updatedDoc.GetTextChangesAsync(originalDoc, ct) |> Async.AwaitTask + + let diffEdits: U2 array = + docChanges + |> Seq.sortBy (fun c -> c.Span.Start) + |> Seq.map (TextEdit.fromTextChange originalDocText.Lines) + |> Seq.map U2.C1 + |> Array.ofSeq + + let textEditDocument = + { Uri = originalDoc.FilePath |> Path.toUri + Version = originalDoc.FilePath |> Path.toUri |> tryGetDocVersionByUri } + + docTextEdits.Add( + { TextDocument = textEditDocument + Edits = diffEdits } + ) + | _, _ -> () + + return docTextEdits |> List.ofSeq + } let roslynCodeActionToResolvedLspCodeAction - originalSolution - tryGetDocVersionByUri - (originatingDoc: Document) - (ct: CancellationToken) - (ca: CodeActions.CodeAction) - : Async = async { - - let! maybeOps = asyncMaybeOnException (fun () -> ca.GetOperationsAsync(ct) |> Async.AwaitTask) - - match maybeOps with - | None -> return None - | Some ops -> - let op = ops |> Seq.map (fun o -> o :?> ApplyChangesOperation) - |> Seq.head - - let! docTextEdit = - lspDocChangesFromSolutionDiff originalSolution - op.ChangedSolution - tryGetDocVersionByUri - originatingDoc - ct - let edit: WorkspaceEdit = { - Changes = None - DocumentChanges = docTextEdit |> Seq.map U4.C1 |> Array.ofSeq |> Some - ChangeAnnotations = None - } - - let caKind, caIsPreferred = lspCodeActionDetailsFromRoslynCA ca - - return Some { - Title = ca.Title - Kind = caKind - Diagnostics = None - Edit = Some edit - Command = None - Data = None - IsPreferred = caIsPreferred - Disabled = None - } - } + originalSolution + tryGetDocVersionByUri + (originatingDoc: Document) + (ct: CancellationToken) + (ca: CodeActions.CodeAction) + : Async = + async { + + let! maybeOps = asyncMaybeOnException (fun () -> ca.GetOperationsAsync(ct) |> Async.AwaitTask) + + match maybeOps with + | None -> return None + | Some ops -> + let op = ops |> Seq.map (fun o -> o :?> ApplyChangesOperation) |> Seq.head + + let! docTextEdit = + lspDocChangesFromSolutionDiff + originalSolution + op.ChangedSolution + tryGetDocVersionByUri + originatingDoc + ct + + let edit: WorkspaceEdit = + { Changes = None + DocumentChanges = docTextEdit |> Seq.map U4.C1 |> Array.ofSeq |> Some + ChangeAnnotations = None } + + let caKind, caIsPreferred = lspCodeActionDetailsFromRoslynCA ca + + return + Some + { Title = ca.Title + Kind = caKind + Diagnostics = None + Edit = Some edit + Command = None + Data = None + IsPreferred = caIsPreferred + Disabled = None } + } let provider (clientCapabilities: ClientCapabilities) : U2 option = let literalSupport = @@ -301,101 +323,98 @@ module CodeAction = WorkDoneProgress = None } |> U2.C2 |> Some - | None -> - true |> U2.C1 |> Some - - let handle (context: ServerRequestContext) - (p: CodeActionParams) - : AsyncLspResult = async { - match context.GetDocument p.TextDocument.Uri with - | None -> return None |> LspResult.success - | Some doc -> - let! ct = Async.CancellationToken - let! docText = doc.GetTextAsync(ct) |> Async.AwaitTask - let textSpan = Range.toTextSpan docText.Lines p.Range - - let! roslynCodeActions = getRoslynCodeActions doc textSpan ct - - let clientSupportsCodeActionEditResolveWithEditAndData = - context.ClientCapabilities.TextDocument - |> Option.bind (fun x -> x.CodeAction) - |> Option.bind (fun x -> x.ResolveSupport) - |> Option.map (fun resolveSupport -> resolveSupport.Properties |> Array.contains "edit") - |> Option.defaultValue false - - let! lspCodeActions = - match clientSupportsCodeActionEditResolveWithEditAndData with - | true -> async { - let toUnresolvedLspCodeAction (ca: Microsoft.CodeAnalysis.CodeActions.CodeAction) = - let resolutionData: CSharpCodeActionResolutionData = - { TextDocumentUri = p.TextDocument.Uri - Range = p.Range } - - (* + | None -> true |> U2.C1 |> Some + + let handle + (context: ServerRequestContext) + (p: CodeActionParams) + : AsyncLspResult = + async { + match context.GetDocument p.TextDocument.Uri with + | None -> return None |> LspResult.success + | Some doc -> + let! ct = Async.CancellationToken + let! docText = doc.GetTextAsync(ct) |> Async.AwaitTask + let textSpan = Range.toTextSpan docText.Lines p.Range + + let! roslynCodeActions = getRoslynCodeActions doc textSpan ct + + let clientSupportsCodeActionEditResolveWithEditAndData = + context.ClientCapabilities.TextDocument + |> Option.bind (fun x -> x.CodeAction) + |> Option.bind (fun x -> x.ResolveSupport) + |> Option.map (fun resolveSupport -> resolveSupport.Properties |> Array.contains "edit") + |> Option.defaultValue false + + let! lspCodeActions = + match clientSupportsCodeActionEditResolveWithEditAndData with + | true -> async { + let toUnresolvedLspCodeAction (ca: Microsoft.CodeAnalysis.CodeActions.CodeAction) = + let resolutionData: CSharpCodeActionResolutionData = + { TextDocumentUri = p.TextDocument.Uri + Range = p.Range } + + (* logger.trace ( Log.setMessage "codeaction data: {data}" >> Log.addContextDestructured "data" resolutionData ) *) - let lspCa = roslynCodeActionToUnresolvedLspCodeAction ca - { lspCa with Data = resolutionData |> serialize |> Some } + let lspCa = roslynCodeActionToUnresolvedLspCodeAction ca - return roslynCodeActions |> Seq.map toUnresolvedLspCodeAction |> Array.ofSeq - } + { lspCa with + Data = resolutionData |> serialize |> Some } - | false -> async { - let results = List() + return roslynCodeActions |> Seq.map toUnresolvedLspCodeAction |> Array.ofSeq + } - for ca in roslynCodeActions do - let! maybeLspCa = - roslynCodeActionToResolvedLspCodeAction - doc.Project.Solution - context.GetDocumentVersion - doc - ct - ca + | false -> async { + let results = List() - if maybeLspCa.IsSome then - results.Add(maybeLspCa.Value) + for ca in roslynCodeActions do + let! maybeLspCa = + roslynCodeActionToResolvedLspCodeAction + doc.Project.Solution + context.GetDocumentVersion + doc + ct + ca - return results |> Array.ofSeq - } + if maybeLspCa.IsSome then + results.Add(maybeLspCa.Value) - return - lspCodeActions - |> Seq.sortByDescending (fun ca -> ca.IsPreferred) - |> Seq.map U2.C2 - |> Array.ofSeq - |> Some - |> LspResult.success - } + return results |> Array.ofSeq + } + + return + lspCodeActions + |> Seq.sortByDescending (fun ca -> ca.IsPreferred) + |> Seq.map U2.C2 + |> Array.ofSeq + |> Some + |> LspResult.success + } let resolve (context: ServerRequestContext) (p: CodeAction) : AsyncLspResult = async { let resolutionData = - p.Data - |> Option.map deserialize + p.Data |> Option.map deserialize match context.GetDocument resolutionData.Value.TextDocumentUri with - | None -> - return raise (Exception(sprintf "no document for uri %s" resolutionData.Value.TextDocumentUri)) + | None -> return raise (Exception(sprintf "no document for uri %s" resolutionData.Value.TextDocumentUri)) | Some doc -> let! ct = Async.CancellationToken let! docText = doc.GetTextAsync(ct) |> Async.AwaitTask let textSpan = Range.toTextSpan docText.Lines resolutionData.Value.Range - let! roslynCodeActions = - getRoslynCodeActions doc textSpan ct + let! roslynCodeActions = getRoslynCodeActions doc textSpan ct - let selectedCodeAction = roslynCodeActions |> Seq.tryFind (fun ca -> ca.Title = p.Title) + let selectedCodeAction = + roslynCodeActions |> Seq.tryFind (fun ca -> ca.Title = p.Title) let toResolvedLspCodeAction = - roslynCodeActionToResolvedLspCodeAction - doc.Project.Solution - context.GetDocumentVersion - doc - ct + roslynCodeActionToResolvedLspCodeAction doc.Project.Solution context.GetDocumentVersion doc ct let! lspCodeAction = match selectedCodeAction with @@ -407,8 +426,7 @@ module CodeAction = return resolvedCA.Value } - | None -> - raise (Exception("no CodeAction resolved")) + | None -> raise (Exception("no CodeAction resolved")) return lspCodeAction |> LspResult.success } diff --git a/src/CSharpLanguageServer/Handlers/CodeLens.fs b/src/CSharpLanguageServer/Handlers/CodeLens.fs index 978def33..11955441 100644 --- a/src/CSharpLanguageServer/Handlers/CodeLens.fs +++ b/src/CSharpLanguageServer/Handlers/CodeLens.fs @@ -18,8 +18,7 @@ type private DocumentSymbolCollectorForCodeLens(semanticModel: SemanticModel) = let collect (node: SyntaxNode) (nameSpan: TextSpan) = match semanticModel.GetDeclaredSymbol(node) |> Option.ofObj with - | Some symbol -> - collectedSymbols <- (symbol, nameSpan) :: collectedSymbols + | Some symbol -> collectedSymbols <- (symbol, nameSpan) :: collectedSymbols | _ -> () member __.GetSymbols() = @@ -29,8 +28,7 @@ type private DocumentSymbolCollectorForCodeLens(semanticModel: SemanticModel) = collect node node.Identifier.Span base.VisitEnumDeclaration(node) - override __.VisitEnumMemberDeclaration(node) = - collect node node.Identifier.Span + override __.VisitEnumMemberDeclaration(node) = collect node node.Identifier.Span override __.VisitClassDeclaration(node) = collect node node.Identifier.Span @@ -48,29 +46,21 @@ type private DocumentSymbolCollectorForCodeLens(semanticModel: SemanticModel) = collect node node.Identifier.Span base.VisitInterfaceDeclaration(node) - override __.VisitDelegateDeclaration(node) = - collect node node.Identifier.Span + override __.VisitDelegateDeclaration(node) = collect node node.Identifier.Span - override __.VisitConstructorDeclaration(node) = - collect node node.Identifier.Span + override __.VisitConstructorDeclaration(node) = collect node node.Identifier.Span - override __.VisitDestructorDeclaration(node) = - collect node node.Identifier.Span + override __.VisitDestructorDeclaration(node) = collect node node.Identifier.Span - override __.VisitOperatorDeclaration(node) = - collect node node.OperatorToken.Span + override __.VisitOperatorDeclaration(node) = collect node node.OperatorToken.Span - override __.VisitIndexerDeclaration(node) = - collect node node.ThisKeyword.Span + override __.VisitIndexerDeclaration(node) = collect node node.ThisKeyword.Span - override __.VisitConversionOperatorDeclaration(node) = - collect node node.Type.Span + override __.VisitConversionOperatorDeclaration(node) = collect node node.Type.Span - override __.VisitMethodDeclaration(node) = - collect node node.Identifier.Span + override __.VisitMethodDeclaration(node) = collect node node.Identifier.Span - override __.VisitPropertyDeclaration(node) = - collect node node.Identifier.Span + override __.VisitPropertyDeclaration(node) = collect node node.Identifier.Span override __.VisitVariableDeclarator(node) = let grandparent = @@ -81,27 +71,28 @@ type private DocumentSymbolCollectorForCodeLens(semanticModel: SemanticModel) = if grandparent.IsSome && grandparent.Value :? FieldDeclarationSyntax then collect node node.Identifier.Span - override __.VisitEventDeclaration(node) = - collect node node.Identifier.Span + override __.VisitEventDeclaration(node) = collect node node.Identifier.Span [] module CodeLens = type CodeLensData = { DocumentUri: string Position: Position } + static member Default = { DocumentUri = "" Position = { Line = 0u; Character = 0u } } let provider (clientCapabilities: ClientCapabilities) : CodeLensOptions option = - Some { ResolveProvider = Some true - WorkDoneProgress = None } + Some + { ResolveProvider = Some true + WorkDoneProgress = None } - let handle (context: ServerRequestContext) (p: CodeLensParams): AsyncLspResult = async { + let handle (context: ServerRequestContext) (p: CodeLensParams) : AsyncLspResult = async { let docMaybe = context.GetDocument p.TextDocument.Uri + match docMaybe with - | None -> - return None |> LspResult.success + | None -> return None |> LspResult.success | Some doc -> let! ct = Async.CancellationToken let! semanticModel = doc.GetSemanticModelAsync(ct) |> Async.AwaitTask @@ -128,9 +119,7 @@ module CodeLens = return codeLens |> Array.ofSeq |> Some |> LspResult.success } - let resolve (context: ServerRequestContext) - (p: CodeLens) - : AsyncLspResult = async { + let resolve (context: ServerRequestContext) (p: CodeLens) : AsyncLspResult = async { let lensData: CodeLensData = p.Data @@ -139,8 +128,7 @@ module CodeLens = |> Option.defaultValue CodeLensData.Default match! context.FindSymbol lensData.DocumentUri lensData.Position with - | None -> - return p |> LspResult.success + | None -> return p |> LspResult.success | Some symbol -> let! locations = context.FindReferences symbol false // FIXME: refNum is wrong. There are lots of false positive even if we distinct locations by @@ -158,6 +146,7 @@ module CodeLens = WorkDoneToken = None PartialResultToken = None Context = { IncludeDeclaration = true } } + let command = { Title = title Command = "textDocument/references" diff --git a/src/CSharpLanguageServer/Handlers/Color.fs b/src/CSharpLanguageServer/Handlers/Color.fs index 4bb70875..f73e3a13 100644 --- a/src/CSharpLanguageServer/Handlers/Color.fs +++ b/src/CSharpLanguageServer/Handlers/Color.fs @@ -7,7 +7,7 @@ open CSharpLanguageServer.State [] module Color = - let provider (clientCapabilities: ClientCapabilities) = None + let provider (clientCapabilities: ClientCapabilities) = None let handle (context: ServerRequestContext) (p: DocumentColorParams) : AsyncLspResult = LspResult.notImplemented |> async.Return diff --git a/src/CSharpLanguageServer/Handlers/Completion.fs b/src/CSharpLanguageServer/Handlers/Completion.fs index 8a417b70..bcea6070 100644 --- a/src/CSharpLanguageServer/Handlers/Completion.fs +++ b/src/CSharpLanguageServer/Handlers/Completion.fs @@ -21,14 +21,17 @@ module Completion = let private completionItemMemoryCacheSet (cacheItemId: string) roslynDoc roslynCompletionItem = completionItemMemoryCache.Set( - key=cacheItemId, - value=(roslynDoc, roslynCompletionItem), - absoluteExpiration=DateTimeOffset.Now.AddMinutes(5) + key = cacheItemId, + value = (roslynDoc, roslynCompletionItem), + absoluteExpiration = DateTimeOffset.Now.AddMinutes(5) ) |> ignore - let private completionItemMemoryCacheGet (cacheItemId: string) : option = + let private completionItemMemoryCacheGet + (cacheItemId: string) + : option = let mutable value = Unchecked.defaultof + if completionItemMemoryCache.TryGetValue(cacheItemId, &value) then Some(value :?> Microsoft.CodeAnalysis.Document * Microsoft.CodeAnalysis.Completion.CompletionItem) else @@ -37,59 +40,73 @@ module Completion = let emptyRoslynOptionSet: Microsoft.CodeAnalysis.Options.OptionSet = let osEmptyOptionSetField = typeof - |> _.GetField("Empty", BindingFlags.Static|||BindingFlags.NonPublic) + |> _.GetField("Empty", BindingFlags.Static ||| BindingFlags.NonPublic) |> nonNull "Microsoft.CodeAnalysis.Options.OptionSet.Empty" osEmptyOptionSetField.GetValue(null) - |> nonNull "Microsoft.CodeAnalysis.Options.OptionSet.Empty" - :?> Microsoft.CodeAnalysis.Options.OptionSet + |> nonNull "Microsoft.CodeAnalysis.Options.OptionSet.Empty" + :?> Microsoft.CodeAnalysis.Options.OptionSet /// the type reflects on internal class Microsoft.CodeAnalysis.Completion.CompletionOptions /// see https://github.com/dotnet/roslyn/blob/main/src/Features/Core/Portable/Completion/CompletionOptions.cs type RoslynCompletionOptions = - { - Object: obj - CompletionOptionsType: Type - } - with - member rco.WithBool(optionName: string, optionValue: bool) = - let cloneCompletionOptionsMI = - rco.CompletionOptionsType.GetMethod("$") - |> nonNull "rco.CompletionOptionsType.GetMethod('$')" + { Object: obj + CompletionOptionsType: Type } + + member rco.WithBool(optionName: string, optionValue: bool) = + let cloneCompletionOptionsMI = + rco.CompletionOptionsType.GetMethod("$") + |> nonNull "rco.CompletionOptionsType.GetMethod('$')" + + let updatedCompletionOptions = cloneCompletionOptionsMI.Invoke(rco.Object, null) - let updatedCompletionOptions = cloneCompletionOptionsMI.Invoke(rco.Object, null) - let newCo = - rco.CompletionOptionsType.GetProperty(optionName) - |> nonNull (sprintf "rco.CompletionOptionsType.GetProperty('%s')" optionName) + let newCo = + rco.CompletionOptionsType.GetProperty(optionName) + |> nonNull (sprintf "rco.CompletionOptionsType.GetProperty('%s')" optionName) - newCo.SetValue(updatedCompletionOptions, optionValue) - { rco with Object = updatedCompletionOptions } + newCo.SetValue(updatedCompletionOptions, optionValue) - static member Default() = - let featuresAssembly = Assembly.Load("Microsoft.CodeAnalysis.Features") - let coType = - featuresAssembly.GetType("Microsoft.CodeAnalysis.Completion.CompletionOptions") - |> nonNull "GetType('Microsoft.CodeAnalysis.Completion.CompletionOptions')" + { rco with + Object = updatedCompletionOptions } - let defaultCo: obj = - coType.GetField("Default") - |> nonNull "Microsoft.CodeAnalysis.Completion.CompletionOptions.Default" - |> _.GetValue() + static member Default() = + let featuresAssembly = Assembly.Load("Microsoft.CodeAnalysis.Features") - { Object = defaultCo; CompletionOptionsType = coType } + let coType = + featuresAssembly.GetType("Microsoft.CodeAnalysis.Completion.CompletionOptions") + |> nonNull "GetType('Microsoft.CodeAnalysis.Completion.CompletionOptions')" + + let defaultCo: obj = + coType.GetField("Default") + |> nonNull "Microsoft.CodeAnalysis.Completion.CompletionOptions.Default" + |> _.GetValue() + + { Object = defaultCo + CompletionOptionsType = coType } type RoslynCompletionServiceWrapper(service: Microsoft.CodeAnalysis.Completion.CompletionService) = - member __.GetCompletionsAsync(doc, position, completionOptions, completionTrigger, ct) : Async = + member __.GetCompletionsAsync + (doc, position, completionOptions, completionTrigger, ct) + : Async = let completionServiceType = service.GetType() let getCompletionsAsync7MI = - completionServiceType.GetMethods(BindingFlags.Instance|||BindingFlags.NonPublic) + completionServiceType.GetMethods(BindingFlags.Instance ||| BindingFlags.NonPublic) |> Seq.filter (fun mi -> mi.Name = "GetCompletionsAsync" && mi.GetParameters().Length = 7) |> Seq.head - let parameters: obj array = [| doc; position; completionOptions.Object; emptyRoslynOptionSet; completionTrigger; null; ct |] + let parameters: obj array = + [| doc + position + completionOptions.Object + emptyRoslynOptionSet + completionTrigger + null + ct |] - let result = getCompletionsAsync7MI.Invoke(service, parameters) |> nonNull "result of getCompletionsAsync7MI" + let result = + getCompletionsAsync7MI.Invoke(service, parameters) + |> nonNull "result of getCompletionsAsync7MI" (result :?> System.Threading.Tasks.Task) |> Async.AwaitTask @@ -101,12 +118,12 @@ module Completion = service.GetDescriptionAsync(doc, item, ct) let provider (clientCapabilities: ClientCapabilities) : CompletionOptions option = - Some { ResolveProvider = Some true - TriggerCharacters = Some ([| "."; "'"; |]) - AllCommitCharacters = None - CompletionItem = None - WorkDoneProgress = None - } + Some + { ResolveProvider = Some true + TriggerCharacters = Some([| "."; "'" |]) + AllCommitCharacters = None + CompletionItem = None + WorkDoneProgress = None } let private roslynTagToLspCompletion tag = match tag with @@ -132,10 +149,16 @@ module Completion = | "TypeParameter" -> CompletionItemKind.TypeParameter | _ -> CompletionItemKind.Property - let parseAndFormatDocumentation (completionDescription: Microsoft.CodeAnalysis.Completion.CompletionDescription) - : (string option * string option) = - let codeBlockStartIndex = completionDescription.TaggedParts |> Seq.tryFindIndex (fun t -> t.Tag = "CodeBlockStart") - let codeBlockEndIndex = completionDescription.TaggedParts |> Seq.tryFindIndex (fun t -> t.Tag = "CodeBlockEnd") + let parseAndFormatDocumentation + (completionDescription: Microsoft.CodeAnalysis.Completion.CompletionDescription) + : (string option * string option) = + let codeBlockStartIndex = + completionDescription.TaggedParts + |> Seq.tryFindIndex (fun t -> t.Tag = "CodeBlockStart") + + let codeBlockEndIndex = + completionDescription.TaggedParts + |> Seq.tryFindIndex (fun t -> t.Tag = "CodeBlockEnd") match codeBlockStartIndex, codeBlockEndIndex with | Some 0, Some codeBlockEndIndex -> @@ -148,80 +171,85 @@ module Completion = let documentationText = completionDescription.TaggedParts - |> Seq.skip (codeBlockEndIndex+1) + |> Seq.skip (codeBlockEndIndex + 1) |> Seq.skipWhile (fun t -> t.Tag = "LineBreak") |> Seq.map _.Text |> String.concat "" |> Option.ofString synopsis, documentationText - | _, _ -> - None, None - - let handle (context: ServerRequestContext) (p: CompletionParams) : Async option>> = async { - match context.GetDocument p.TextDocument.Uri with - | None -> - return None |> LspResult.success - | Some doc -> - let! ct = Async.CancellationToken - let! sourceText = doc.GetTextAsync(ct) |> Async.AwaitTask - - let position = Position.toRoslynPosition sourceText.Lines p.Position - - let completionService = - Microsoft.CodeAnalysis.Completion.CompletionService.GetService(doc) - |> RoslynCompletionServiceWrapper - - let completionOptions = - RoslynCompletionOptions.Default() - |> _.WithBool("ShowItemsFromUnimportedNamespaces", false) - |> _.WithBool("ShowNameSuggestions", false) - - let completionTrigger = CompletionContext.toCompletionTrigger p.Context - - let shouldTriggerCompletion = - p.Context |> Option.exists (fun x -> x.TriggerKind = CompletionTriggerKind.TriggerForIncompleteCompletions) || - completionService.ShouldTriggerCompletion(sourceText, position, completionTrigger) - - let! roslynCompletions = - if shouldTriggerCompletion then - completionService.GetCompletionsAsync(doc, position, completionOptions, completionTrigger, ct) - |> Async.map Option.ofObj - else - async.Return None - - let toLspCompletionItemsWithCacheInfo (completions: Microsoft.CodeAnalysis.Completion.CompletionList) = - completions.ItemsList - |> Seq.map (fun item -> (item, Guid.NewGuid() |> string)) - |> Seq.map (fun (item, cacheItemId) -> - let lspCompletionItem = - { Ionide.LanguageServerProtocol.Types.CompletionItem.Create item.DisplayText with - Kind = item.Tags |> Seq.tryHead |> Option.map roslynTagToLspCompletion - SortText = item.SortText |> Option.ofString - FilterText = item.FilterText |> Option.ofString - InsertText = item.DisplayText |> Option.ofString - Data = cacheItemId |> serialize |> Some - } - - (lspCompletionItem, cacheItemId, doc, item)) - |> Array.ofSeq - - let lspCompletionItemsWithCacheInfo = - roslynCompletions - |> Option.map toLspCompletionItemsWithCacheInfo - - // cache roslyn completion items - for (_, cacheItemId, roslynDoc, roslynItem) - in (lspCompletionItemsWithCacheInfo |> Option.defaultValue Array.empty) do - completionItemMemoryCacheSet cacheItemId roslynDoc roslynItem - - return - lspCompletionItemsWithCacheInfo - |> Option.map (fun itemsWithCacheInfo -> itemsWithCacheInfo |> Array.map (fun (item, _, _, _) -> item)) - |> Option.map (fun items -> { IsIncomplete = true; Items = items; ItemDefaults = None }) - |> Option.map U2.C2 - |> LspResult.success - } + | _, _ -> None, None + + let handle + (context: ServerRequestContext) + (p: CompletionParams) + : Async option>> = + async { + match context.GetDocument p.TextDocument.Uri with + | None -> return None |> LspResult.success + | Some doc -> + let! ct = Async.CancellationToken + let! sourceText = doc.GetTextAsync(ct) |> Async.AwaitTask + + let position = Position.toRoslynPosition sourceText.Lines p.Position + + let completionService = + Microsoft.CodeAnalysis.Completion.CompletionService.GetService(doc) + |> RoslynCompletionServiceWrapper + + let completionOptions = + RoslynCompletionOptions.Default() + |> _.WithBool("ShowItemsFromUnimportedNamespaces", false) + |> _.WithBool("ShowNameSuggestions", false) + + let completionTrigger = CompletionContext.toCompletionTrigger p.Context + + let shouldTriggerCompletion = + p.Context + |> Option.exists (fun x -> x.TriggerKind = CompletionTriggerKind.TriggerForIncompleteCompletions) + || completionService.ShouldTriggerCompletion(sourceText, position, completionTrigger) + + let! roslynCompletions = + if shouldTriggerCompletion then + completionService.GetCompletionsAsync(doc, position, completionOptions, completionTrigger, ct) + |> Async.map Option.ofObj + else + async.Return None + + let toLspCompletionItemsWithCacheInfo (completions: Microsoft.CodeAnalysis.Completion.CompletionList) = + completions.ItemsList + |> Seq.map (fun item -> (item, Guid.NewGuid() |> string)) + |> Seq.map (fun (item, cacheItemId) -> + let lspCompletionItem = + { Ionide.LanguageServerProtocol.Types.CompletionItem.Create item.DisplayText with + Kind = item.Tags |> Seq.tryHead |> Option.map roslynTagToLspCompletion + SortText = item.SortText |> Option.ofString + FilterText = item.FilterText |> Option.ofString + InsertText = item.DisplayText |> Option.ofString + Data = cacheItemId |> serialize |> Some } + + (lspCompletionItem, cacheItemId, doc, item)) + |> Array.ofSeq + + let lspCompletionItemsWithCacheInfo = + roslynCompletions |> Option.map toLspCompletionItemsWithCacheInfo + + // cache roslyn completion items + for (_, cacheItemId, roslynDoc, roslynItem) in + (lspCompletionItemsWithCacheInfo |> Option.defaultValue Array.empty) do + completionItemMemoryCacheSet cacheItemId roslynDoc roslynItem + + return + lspCompletionItemsWithCacheInfo + |> Option.map (fun itemsWithCacheInfo -> + itemsWithCacheInfo |> Array.map (fun (item, _, _, _) -> item)) + |> Option.map (fun items -> + { IsIncomplete = true + Items = items + ItemDefaults = None }) + |> Option.map U2.C2 + |> LspResult.success + } let resolve (_context: ServerRequestContext) (item: CompletionItem) : AsyncLspResult = async { let roslynDocAndItemMaybe = @@ -230,12 +258,13 @@ module Completion = |> Option.bind completionItemMemoryCacheGet match roslynDocAndItemMaybe with - | Some (doc, roslynCompletionItem) -> + | Some(doc, roslynCompletionItem) -> let completionService = Microsoft.CodeAnalysis.Completion.CompletionService.GetService(doc) |> nonNull "Microsoft.CodeAnalysis.Completion.CompletionService.GetService(doc)" let! ct = Async.CancellationToken + let! description = completionService.GetDescriptionAsync(doc, roslynCompletionItem, ct) |> Async.AwaitTask @@ -248,12 +277,16 @@ module Completion = let updatedItemDocumentation = documentation - |> Option.map (fun d -> { Kind = MarkupKind.PlainText; Value = d } |> U2.C2) + |> Option.map (fun d -> + { Kind = MarkupKind.PlainText + Value = d } + |> U2.C2) return - { item with Detail = synopsis; Documentation = updatedItemDocumentation } + { item with + Detail = synopsis + Documentation = updatedItemDocumentation } |> LspResult.success - | None -> - return item |> LspResult.success + | None -> return item |> LspResult.success } diff --git a/src/CSharpLanguageServer/Handlers/Declaration.fs b/src/CSharpLanguageServer/Handlers/Declaration.fs index 89993b12..8c8948ff 100644 --- a/src/CSharpLanguageServer/Handlers/Declaration.fs +++ b/src/CSharpLanguageServer/Handlers/Declaration.fs @@ -9,5 +9,9 @@ open CSharpLanguageServer.State module Declaration = let provider (_cc: ClientCapabilities) : bool option = None - let handle (_context: ServerRequestContext) (_p: DeclarationParams) : AsyncLspResult option> = - LspResult.notImplemented option> |> async.Return + let handle + (_context: ServerRequestContext) + (_p: DeclarationParams) + : AsyncLspResult option> = + LspResult.notImplemented option> + |> async.Return diff --git a/src/CSharpLanguageServer/Handlers/Definition.fs b/src/CSharpLanguageServer/Handlers/Definition.fs index bb50974c..241fd6d9 100644 --- a/src/CSharpLanguageServer/Handlers/Definition.fs +++ b/src/CSharpLanguageServer/Handlers/Definition.fs @@ -7,20 +7,16 @@ open CSharpLanguageServer.State [] module Definition = - let provider (clientCapabilities: ClientCapabilities) : U2 option = - Some (U2.C1 true) + let provider (clientCapabilities: ClientCapabilities) : U2 option = Some(U2.C1 true) - let handle (context: ServerRequestContext) (p: DefinitionParams) : Async option>> = async { - match! context.FindSymbol' p.TextDocument.Uri p.Position with - | None -> - return None |> LspResult.success - | Some (symbol, doc) -> - let! locations = context.ResolveSymbolLocations symbol (Some doc.Project) - return - locations - |> Array.ofList - |> Definition.C2 - |> U2.C1 - |> Some - |> LspResult.success - } + let handle + (context: ServerRequestContext) + (p: DefinitionParams) + : Async option>> = + async { + match! context.FindSymbol' p.TextDocument.Uri p.Position with + | None -> return None |> LspResult.success + | Some(symbol, doc) -> + let! locations = context.ResolveSymbolLocations symbol (Some doc.Project) + return locations |> Array.ofList |> Definition.C2 |> U2.C1 |> Some |> LspResult.success + } diff --git a/src/CSharpLanguageServer/Handlers/Diagnostic.fs b/src/CSharpLanguageServer/Handlers/Diagnostic.fs index fe6c84c6..b52b0abe 100644 --- a/src/CSharpLanguageServer/Handlers/Diagnostic.fs +++ b/src/CSharpLanguageServer/Handlers/Diagnostic.fs @@ -11,50 +11,52 @@ open CSharpLanguageServer.Types [] module Diagnostic = - let provider (clientCapabilities: ClientCapabilities): U2 option = + let provider + (clientCapabilities: ClientCapabilities) + : U2 option = let registrationOptions: DiagnosticRegistrationOptions = { DocumentSelector = Some defaultDocumentSelector WorkDoneProgress = None Identifier = None InterFileDependencies = false WorkspaceDiagnostics = true - Id = None + Id = None } + + Some(U2.C2 registrationOptions) + + let handle + (context: ServerRequestContext) + (p: DocumentDiagnosticParams) + : AsyncLspResult = + async { + let emptyReport: RelatedFullDocumentDiagnosticReport = + { Kind = "full" + ResultId = None + Items = [||] + RelatedDocuments = None } + + match context.GetDocument p.TextDocument.Uri with + | None -> return emptyReport |> U2.C1 |> LspResult.success + + | Some doc -> + let! ct = Async.CancellationToken + let! semanticModelMaybe = doc.GetSemanticModelAsync(ct) |> Async.AwaitTask + + match semanticModelMaybe |> Option.ofObj with + | Some semanticModel -> + let diagnostics = + semanticModel.GetDiagnostics() + |> Seq.map Diagnostic.fromRoslynDiagnostic + |> Array.ofSeq + + return { emptyReport with Items = diagnostics } |> U2.C1 |> LspResult.success + + | None -> return emptyReport |> U2.C1 |> LspResult.success } - Some (U2.C2 registrationOptions) - - let handle (context: ServerRequestContext) (p: DocumentDiagnosticParams) : AsyncLspResult = async { - let emptyReport: RelatedFullDocumentDiagnosticReport = - { - Kind = "full" - ResultId = None - Items = [| |] - RelatedDocuments = None - } - - match context.GetDocument p.TextDocument.Uri with - | None -> - return emptyReport |> U2.C1 |> LspResult.success - - | Some doc -> - let! ct = Async.CancellationToken - let! semanticModelMaybe = doc.GetSemanticModelAsync(ct) |> Async.AwaitTask - match semanticModelMaybe |> Option.ofObj with - | Some semanticModel -> - let diagnostics = - semanticModel.GetDiagnostics() - |> Seq.map Diagnostic.fromRoslynDiagnostic - |> Array.ofSeq - - return { emptyReport with Items = diagnostics } - |> U2.C1 - |> LspResult.success - - | None -> - return emptyReport |> U2.C1 |> LspResult.success - } - - let private getWorkspaceDiagnosticReports (solution: Microsoft.CodeAnalysis.Solution) : AsyncSeq = + let private getWorkspaceDiagnosticReports + (solution: Microsoft.CodeAnalysis.Solution) + : AsyncSeq = asyncSeq { let! ct = Async.CancellationToken @@ -78,59 +80,59 @@ module Diagnostic = for (uri, docDiagnostics) in diagnosticsByDocument do let fullDocumentReport: WorkspaceFullDocumentDiagnosticReport = - { - Kind = "full" - ResultId = None - Uri = uri - Items = docDiagnostics |> Seq.map Diagnostic.fromRoslynDiagnostic |> Array.ofSeq - Version = None - } + { Kind = "full" + ResultId = None + Uri = uri + Items = docDiagnostics |> Seq.map Diagnostic.fromRoslynDiagnostic |> Array.ofSeq + Version = None } - let documentReport: WorkspaceDocumentDiagnosticReport = - U2.C1 fullDocumentReport + let documentReport: WorkspaceDocumentDiagnosticReport = U2.C1 fullDocumentReport yield documentReport } - let handleWorkspaceDiagnostic (context: ServerRequestContext) (p: WorkspaceDiagnosticParams) : AsyncLspResult = async { - let emptyWorkspaceDiagnosticReport: WorkspaceDiagnosticReport = - { Items = Array.empty } + let handleWorkspaceDiagnostic + (context: ServerRequestContext) + (p: WorkspaceDiagnosticParams) + : AsyncLspResult = + async { + let emptyWorkspaceDiagnosticReport: WorkspaceDiagnosticReport = + { Items = Array.empty } - match context.State.Solution, p.PartialResultToken with - | None, _ -> - return emptyWorkspaceDiagnosticReport |> LspResult.success + match context.State.Solution, p.PartialResultToken with + | None, _ -> return emptyWorkspaceDiagnosticReport |> LspResult.success - | Some solution, None -> - let! diagnosticReports = - getWorkspaceDiagnosticReports solution - |> AsyncSeq.toArrayAsync + | Some solution, None -> + let! diagnosticReports = getWorkspaceDiagnosticReports solution |> AsyncSeq.toArrayAsync - let workspaceDiagnosticReport: WorkspaceDiagnosticReport = - { Items = diagnosticReports } + let workspaceDiagnosticReport: WorkspaceDiagnosticReport = + { Items = diagnosticReports } - return workspaceDiagnosticReport |> LspResult.success + return workspaceDiagnosticReport |> LspResult.success - | Some solution, Some partialResultToken -> - let sendWorkspaceDiagnosticReport (documentReport, index) = async { - let progressParams = - if index = 0 then - let report: WorkspaceDiagnosticReport = - { Items = [| documentReport |] } + | Some solution, Some partialResultToken -> + let sendWorkspaceDiagnosticReport (documentReport, index) = async { + let progressParams = + if index = 0 then + let report: WorkspaceDiagnosticReport = { Items = [| documentReport |] } - { Token = partialResultToken; Value = serialize report } - else - let reportPartialResult: WorkspaceDiagnosticReportPartialResult = - { Items = [| documentReport |] } + { Token = partialResultToken + Value = serialize report } + else + let reportPartialResult: WorkspaceDiagnosticReportPartialResult = + { Items = [| documentReport |] } - { Token = partialResultToken; Value = serialize reportPartialResult } + { Token = partialResultToken + Value = serialize reportPartialResult } - let lspClient = context.State.LspClient.Value - do! lspClient.Progress(progressParams) - } + let lspClient = context.State.LspClient.Value + do! lspClient.Progress(progressParams) + } - do! AsyncSeq.ofSeq (Seq.initInfinite id) - |> AsyncSeq.zip (getWorkspaceDiagnosticReports solution) - |> AsyncSeq.iterAsync sendWorkspaceDiagnosticReport + do! + AsyncSeq.ofSeq (Seq.initInfinite id) + |> AsyncSeq.zip (getWorkspaceDiagnosticReports solution) + |> AsyncSeq.iterAsync sendWorkspaceDiagnosticReport - return emptyWorkspaceDiagnosticReport |> LspResult.success - } + return emptyWorkspaceDiagnosticReport |> LspResult.success + } diff --git a/src/CSharpLanguageServer/Handlers/DocumentFormatting.fs b/src/CSharpLanguageServer/Handlers/DocumentFormatting.fs index fed511b4..497625da 100644 --- a/src/CSharpLanguageServer/Handlers/DocumentFormatting.fs +++ b/src/CSharpLanguageServer/Handlers/DocumentFormatting.fs @@ -10,15 +10,15 @@ open CSharpLanguageServer.State [] module DocumentFormatting = let provider (clientCapabilities: ClientCapabilities) : U2 option = - Some (U2.C1 true) + Some(U2.C1 true) - let handle (context: ServerRequestContext) (p: DocumentFormattingParams) : AsyncLspResult = async { + let handle (context: ServerRequestContext) (p: DocumentFormattingParams) : AsyncLspResult = async { match context.GetUserDocument p.TextDocument.Uri with | None -> return None |> LspResult.success | Some doc -> let! ct = Async.CancellationToken let! options = FormatUtil.getFormattingOptions context.State.Settings doc p.Options - let! newDoc = Formatter.FormatAsync(doc, options, cancellationToken=ct) |> Async.AwaitTask + let! newDoc = Formatter.FormatAsync(doc, options, cancellationToken = ct) |> Async.AwaitTask let! textEdits = FormatUtil.getChanges newDoc doc return textEdits |> Some |> LspResult.success } diff --git a/src/CSharpLanguageServer/Handlers/DocumentHighlight.fs b/src/CSharpLanguageServer/Handlers/DocumentHighlight.fs index 2f3e2647..cce8c696 100644 --- a/src/CSharpLanguageServer/Handlers/DocumentHighlight.fs +++ b/src/CSharpLanguageServer/Handlers/DocumentHighlight.fs @@ -12,48 +12,57 @@ open CSharpLanguageServer.Conversions [] module DocumentHighlight = - let provider (_: ClientCapabilities) : U2 option = - Some (U2.C1 true) + let provider (_: ClientCapabilities) : U2 option = Some(U2.C1 true) let private shouldHighlight (symbol: ISymbol) = match symbol with | :? INamespaceSymbol -> false | _ -> true - let handle (context: ServerRequestContext) (p: DocumentHighlightParams) : AsyncLspResult = async { - let! ct = Async.CancellationToken - let filePath = Uri.toPath p.TextDocument.Uri - - // We only need to find references in the file (not the whole workspace), so we don't use - // context.FindSymbol & context.FindReferences here. - let getHighlights (symbol: ISymbol) (doc: Document) = async { - let docSet = ImmutableHashSet.Create(doc) - let! refs = SymbolFinder.FindReferencesAsync(symbol, doc.Project.Solution, docSet, cancellationToken=ct) |> Async.AwaitTask - let! def = SymbolFinder.FindSourceDefinitionAsync(symbol, doc.Project.Solution, cancellationToken=ct) |> Async.AwaitTask - - let locations = - refs - |> Seq.collect (fun r -> r.Locations) - |> Seq.map (fun rl -> rl.Location) - |> Seq.filter (fun l -> l.IsInSource && l.GetMappedLineSpan().Path = filePath) - |> Seq.append (def |> Option.ofObj |> Option.toList |> Seq.collect (fun sym -> sym.Locations)) - - return - locations - |> Seq.map Location.fromRoslynLocation - |> Seq.filter _.IsSome - |> Seq.map _.Value - |> Seq.map (fun l -> - { Range = l.Range - Kind = Some DocumentHighlightKind.Read }) - } + let handle + (context: ServerRequestContext) + (p: DocumentHighlightParams) + : AsyncLspResult = + async { + let! ct = Async.CancellationToken + let filePath = Uri.toPath p.TextDocument.Uri + + // We only need to find references in the file (not the whole workspace), so we don't use + // context.FindSymbol & context.FindReferences here. + let getHighlights (symbol: ISymbol) (doc: Document) = async { + let docSet = ImmutableHashSet.Create(doc) + + let! refs = + SymbolFinder.FindReferencesAsync(symbol, doc.Project.Solution, docSet, cancellationToken = ct) + |> Async.AwaitTask + + let! def = + SymbolFinder.FindSourceDefinitionAsync(symbol, doc.Project.Solution, cancellationToken = ct) + |> Async.AwaitTask - match! context.FindSymbol' p.TextDocument.Uri p.Position with - | None -> return None |> LspResult.success - | Some (symbol, doc) -> - if shouldHighlight symbol then - let! highlights = getHighlights symbol doc - return highlights |> Seq.toArray |> Some |> LspResult.success - else - return None |> LspResult.success - } + let locations = + refs + |> Seq.collect (fun r -> r.Locations) + |> Seq.map (fun rl -> rl.Location) + |> Seq.filter (fun l -> l.IsInSource && l.GetMappedLineSpan().Path = filePath) + |> Seq.append (def |> Option.ofObj |> Option.toList |> Seq.collect (fun sym -> sym.Locations)) + + return + locations + |> Seq.map Location.fromRoslynLocation + |> Seq.filter _.IsSome + |> Seq.map _.Value + |> Seq.map (fun l -> + { Range = l.Range + Kind = Some DocumentHighlightKind.Read }) + } + + match! context.FindSymbol' p.TextDocument.Uri p.Position with + | None -> return None |> LspResult.success + | Some(symbol, doc) -> + if shouldHighlight symbol then + let! highlights = getHighlights symbol doc + return highlights |> Seq.toArray |> Some |> LspResult.success + else + return None |> LspResult.success + } diff --git a/src/CSharpLanguageServer/Handlers/DocumentOnTypeFormatting.fs b/src/CSharpLanguageServer/Handlers/DocumentOnTypeFormatting.fs index 0f5c0c1b..98894a04 100644 --- a/src/CSharpLanguageServer/Handlers/DocumentOnTypeFormatting.fs +++ b/src/CSharpLanguageServer/Handlers/DocumentOnTypeFormatting.fs @@ -14,8 +14,9 @@ open CSharpLanguageServer.Conversions [] module DocumentOnTypeFormatting = let provider (_: ClientCapabilities) : DocumentOnTypeFormattingOptions option = - Some { FirstTriggerCharacter = ";" - MoreTriggerCharacter = Some([| "}"; ")" |]) } + Some + { FirstTriggerCharacter = ";" + MoreTriggerCharacter = Some([| "}"; ")" |]) } let rec getSyntaxNode (token: SyntaxToken) : SyntaxNode option = if token.IsKind(SyntaxKind.EndOfFileToken) then @@ -42,8 +43,7 @@ module DocumentOnTypeFormatting = let handle (context: ServerRequestContext) (p: DocumentOnTypeFormattingParams) : AsyncLspResult = async { match context.GetUserDocument p.TextDocument.Uri with - | None -> - return None |> LspResult.success + | None -> return None |> LspResult.success | Some doc -> let! options = FormatUtil.getFormattingOptions context.State.Settings doc p.Options let! ct = Async.CancellationToken @@ -56,14 +56,20 @@ module DocumentOnTypeFormatting = | ")" -> let! root = doc.GetSyntaxRootAsync(ct) |> Async.AwaitTask let tokenAtPos = root.FindToken pos + match getSyntaxNode tokenAtPos with | None -> return None |> LspResult.success | Some node -> let! newDoc = - Formatter.FormatAsync(doc, TextSpan.FromBounds(node.FullSpan.Start, node.FullSpan.End), options, cancellationToken=ct) + Formatter.FormatAsync( + doc, + TextSpan.FromBounds(node.FullSpan.Start, node.FullSpan.End), + options, + cancellationToken = ct + ) |> Async.AwaitTask + let! textEdits = FormatUtil.getChanges newDoc doc return textEdits |> Some |> LspResult.success - | _ -> - return None |> LspResult.success + | _ -> return None |> LspResult.success } diff --git a/src/CSharpLanguageServer/Handlers/DocumentRangeFormatting.fs b/src/CSharpLanguageServer/Handlers/DocumentRangeFormatting.fs index 49c9394e..5927d1b7 100644 --- a/src/CSharpLanguageServer/Handlers/DocumentRangeFormatting.fs +++ b/src/CSharpLanguageServer/Handlers/DocumentRangeFormatting.fs @@ -11,13 +11,11 @@ open CSharpLanguageServer.Conversions [] module DocumentRangeFormatting = - let provider (_: ClientCapabilities) : U2 option = - Some (U2.C1 true) + let provider (_: ClientCapabilities) : U2 option = Some(U2.C1 true) let handle (context: ServerRequestContext) (p: DocumentRangeFormattingParams) : AsyncLspResult = async { match context.GetUserDocument p.TextDocument.Uri with - | None -> - return None |> LspResult.success + | None -> return None |> LspResult.success | Some doc -> let! ct = Async.CancellationToken let! options = FormatUtil.getFormattingOptions context.State.Settings doc p.Options @@ -26,9 +24,11 @@ module DocumentRangeFormatting = let endPos = Position.toRoslynPosition sourceText.Lines p.Range.End let! syntaxTree = doc.GetSyntaxRootAsync(ct) |> Async.AwaitTask let tokenStart = syntaxTree.FindToken(startPos).FullSpan.Start + let! newDoc = - Formatter.FormatAsync(doc, TextSpan.FromBounds(tokenStart, endPos), options, cancellationToken=ct) + Formatter.FormatAsync(doc, TextSpan.FromBounds(tokenStart, endPos), options, cancellationToken = ct) |> Async.AwaitTask + let! textEdits = FormatUtil.getChanges newDoc doc return textEdits |> Some |> LspResult.success } diff --git a/src/CSharpLanguageServer/Handlers/DocumentSymbol.fs b/src/CSharpLanguageServer/Handlers/DocumentSymbol.fs index e79a80d6..fd751457 100644 --- a/src/CSharpLanguageServer/Handlers/DocumentSymbol.fs +++ b/src/CSharpLanguageServer/Handlers/DocumentSymbol.fs @@ -15,71 +15,59 @@ open CSharpLanguageServer.Util [] module DocumentSymbol = - let private formatSymbol (sym: ISymbol) - showAttributes - (semanticModelMaybe: SemanticModel option) - (posMaybe: int option) = + let private formatSymbol + (sym: ISymbol) + showAttributes + (semanticModelMaybe: SemanticModel option) + (posMaybe: int option) + = match showAttributes, semanticModelMaybe, posMaybe with | true, Some semanticModel, Some pos -> sym.ToMinimalDisplayString(semanticModel, pos) | true, _, _ -> sym.ToDisplayString() | false, _, _ -> sym.Name - let private getSymbolNameAndKind - (semanticModel: SemanticModel option) - (pos: int option) - (symbol: ISymbol) = + let private getSymbolNameAndKind (semanticModel: SemanticModel option) (pos: int option) (symbol: ISymbol) = let showAttributes = true match symbol with - | :? ILocalSymbol as ls -> - (formatSymbol ls showAttributes semanticModel pos, - SymbolKind.Variable) + | :? ILocalSymbol as ls -> (formatSymbol ls showAttributes semanticModel pos, SymbolKind.Variable) - | :? IFieldSymbol as fs -> - (formatSymbol fs showAttributes semanticModel pos, - SymbolKind.Field) + | :? IFieldSymbol as fs -> (formatSymbol fs showAttributes semanticModel pos, SymbolKind.Field) - | :? IPropertySymbol as ps -> - (formatSymbol ps showAttributes semanticModel pos, - SymbolKind.Property) + | :? IPropertySymbol as ps -> (formatSymbol ps showAttributes semanticModel pos, SymbolKind.Property) | :? IMethodSymbol as ms -> (formatSymbol ms showAttributes semanticModel pos, - match ms.MethodKind with - | MethodKind.Constructor -> SymbolKind.Constructor - | MethodKind.StaticConstructor -> SymbolKind.Constructor - | MethodKind.BuiltinOperator -> SymbolKind.Operator - | MethodKind.UserDefinedOperator -> SymbolKind.Operator - | MethodKind.Conversion -> SymbolKind.Operator - | _ -> SymbolKind.Method) + match ms.MethodKind with + | MethodKind.Constructor -> SymbolKind.Constructor + | MethodKind.StaticConstructor -> SymbolKind.Constructor + | MethodKind.BuiltinOperator -> SymbolKind.Operator + | MethodKind.UserDefinedOperator -> SymbolKind.Operator + | MethodKind.Conversion -> SymbolKind.Operator + | _ -> SymbolKind.Method) | :? ITypeSymbol as ts -> (formatSymbol ts showAttributes semanticModel pos, - match ts.TypeKind with - | TypeKind.Class -> SymbolKind.Class - | TypeKind.Enum -> SymbolKind.Enum - | TypeKind.Struct -> SymbolKind.Struct - | TypeKind.Interface -> SymbolKind.Interface - | TypeKind.Delegate -> SymbolKind.Class - | TypeKind.Array -> SymbolKind.Array - | TypeKind.TypeParameter -> SymbolKind.TypeParameter - | _ -> SymbolKind.Class) - - | :? IEventSymbol as es -> - (formatSymbol es showAttributes semanticModel pos, - SymbolKind.Event) + match ts.TypeKind with + | TypeKind.Class -> SymbolKind.Class + | TypeKind.Enum -> SymbolKind.Enum + | TypeKind.Struct -> SymbolKind.Struct + | TypeKind.Interface -> SymbolKind.Interface + | TypeKind.Delegate -> SymbolKind.Class + | TypeKind.Array -> SymbolKind.Array + | TypeKind.TypeParameter -> SymbolKind.TypeParameter + | _ -> SymbolKind.Class) + + | :? IEventSymbol as es -> (formatSymbol es showAttributes semanticModel pos, SymbolKind.Event) | :? INamespaceSymbol as ns -> - (formatSymbol ns showAttributes semanticModel pos, - SymbolKind.Namespace) + (formatSymbol ns showAttributes semanticModel pos, SymbolKind.Namespace) - | _ -> - (symbol.ToString(), SymbolKind.File) + | _ -> (symbol.ToString(), SymbolKind.File) let rec private flattenDocumentSymbol (node: DocumentSymbol) = - let nodeWithNoChildren = - { node with Children = None } + let nodeWithNoChildren = { node with Children = None } let flattenedChildren = match node.Children with @@ -88,18 +76,18 @@ module DocumentSymbol = nodeWithNoChildren :: flattenedChildren - type private DocumentSymbolCollector (docText: SourceText, semanticModel: SemanticModel) = + type private DocumentSymbolCollector(docText: SourceText, semanticModel: SemanticModel) = inherit CSharpSyntaxWalker(SyntaxWalkerDepth.Token) let mutable symbolStack = [] let push (node: SyntaxNode) (nameSpan: TextSpan) = - let symbol = semanticModel.GetDeclaredSymbol(node) |> nonNull "semanticModel.GetDeclaredSymbol(node)" + let symbol = + semanticModel.GetDeclaredSymbol(node) + |> nonNull "semanticModel.GetDeclaredSymbol(node)" let (fullSymbolName, symbolKind) = - getSymbolNameAndKind (Some semanticModel) - (Some nameSpan.Start) - symbol + getSymbolNameAndKind (Some semanticModel) (Some nameSpan.Start) symbol let lspRange = Range.fromTextSpan docText.Lines node.FullSpan @@ -111,23 +99,31 @@ module DocumentSymbol = | SymbolKind.Struct -> None | _ -> Some fullSymbolName - let displayStyle = SymbolDisplayFormat( - typeQualificationStyle = SymbolDisplayTypeQualificationStyle.NameOnly, - genericsOptions = SymbolDisplayGenericsOptions.IncludeTypeParameters, - memberOptions = (SymbolDisplayMemberOptions.IncludeParameters ||| SymbolDisplayMemberOptions.IncludeExplicitInterface), - parameterOptions = (SymbolDisplayParameterOptions.IncludeParamsRefOut ||| SymbolDisplayParameterOptions.IncludeExtensionThis ||| SymbolDisplayParameterOptions.IncludeType ||| SymbolDisplayParameterOptions.IncludeName ||| SymbolDisplayParameterOptions.IncludeDefaultValue), - miscellaneousOptions = SymbolDisplayMiscellaneousOptions.UseSpecialTypes) - - let docSymbol = { - Name = SymbolName.fromSymbol displayStyle symbol - Detail = symbolDetail - Kind = symbolKind - Range = lspRange - SelectionRange = selectionLspRange - Children = None - Tags = None - Deprecated = None - } + let displayStyle = + SymbolDisplayFormat( + typeQualificationStyle = SymbolDisplayTypeQualificationStyle.NameOnly, + genericsOptions = SymbolDisplayGenericsOptions.IncludeTypeParameters, + memberOptions = + (SymbolDisplayMemberOptions.IncludeParameters + ||| SymbolDisplayMemberOptions.IncludeExplicitInterface), + parameterOptions = + (SymbolDisplayParameterOptions.IncludeParamsRefOut + ||| SymbolDisplayParameterOptions.IncludeExtensionThis + ||| SymbolDisplayParameterOptions.IncludeType + ||| SymbolDisplayParameterOptions.IncludeName + ||| SymbolDisplayParameterOptions.IncludeDefaultValue), + miscellaneousOptions = SymbolDisplayMiscellaneousOptions.UseSpecialTypes + ) + + let docSymbol = + { Name = SymbolName.fromSymbol displayStyle symbol + Detail = symbolDetail + Kind = symbolKind + Range = lspRange + SelectionRange = selectionLspRange + Children = None + Tags = None + Deprecated = None } symbolStack <- docSymbol :: symbolStack @@ -135,7 +131,7 @@ module DocumentSymbol = let symbolStack' = match symbolStack with | [] -> failwith "symbolStack is empty" - | [_] -> [] + | [ _ ] -> [] | top :: restPastTop -> match restPastTop with | [] -> failwith "restPastTop is empty" @@ -145,10 +141,11 @@ module DocumentSymbol = parent.Children |> Option.defaultValue Array.empty |> List.ofSeq - |> fun xs -> xs @ [top] + |> fun xs -> xs @ [ top ] |> Array.ofSeq - { parent with Children = Some newChildren } + { parent with + Children = Some newChildren } let poppedSymbolStack = parentWithTopAsChild :: restPastParent @@ -157,26 +154,26 @@ module DocumentSymbol = symbolStack <- symbolStack' member __.Init(moduleName: string) = - let emptyRange = { Start={ Line=0u; Character=0u } - End={ Line=0u; Character=0u } } - - let root: DocumentSymbol = { - Name = moduleName - Detail = None - Kind = SymbolKind.File - Range = emptyRange - SelectionRange = emptyRange - Children = None - Tags = None - Deprecated = None - } - - symbolStack <- [root] - - member __.GetDocumentSymbols (clientSupportsDocSymbolHierarchy: bool) = + let emptyRange = + { Start = { Line = 0u; Character = 0u } + End = { Line = 0u; Character = 0u } } + + let root: DocumentSymbol = + { Name = moduleName + Detail = None + Kind = SymbolKind.File + Range = emptyRange + SelectionRange = emptyRange + Children = None + Tags = None + Deprecated = None } + + symbolStack <- [ root ] + + member __.GetDocumentSymbols(clientSupportsDocSymbolHierarchy: bool) = let root = match symbolStack with - | [root] -> root + | [ root ] -> root | _ -> Exception("symbolStack is not a single node") |> raise if clientSupportsDocSymbolHierarchy then @@ -266,7 +263,8 @@ module DocumentSymbol = override __.VisitVariableDeclarator(node) = let grandparent = - node.Parent |> Option.ofObj + node.Parent + |> Option.ofObj |> Option.bind (fun node -> node.Parent |> Option.ofObj) // Only show field variables and ignore local variables if grandparent.IsSome && grandparent.Value :? FieldDeclarationSyntax then @@ -281,34 +279,36 @@ module DocumentSymbol = base.VisitEventDeclaration(node) pop node - let provider (_: ClientCapabilities) : U2 option = - true |> U2.C1 |> Some - - let handle (context: ServerRequestContext) - (p: DocumentSymbolParams) - : AsyncLspResult option> = async { - let canEmitDocSymbolHierarchy = - context.ClientCapabilities.TextDocument - |> Option.bind (fun cc -> cc.DocumentSymbol) - |> Option.bind (fun cc -> cc.HierarchicalDocumentSymbolSupport) - |> Option.defaultValue false - - match context.GetDocument p.TextDocument.Uri with - | None -> return None |> LspResult.success - | Some doc -> - let! ct = Async.CancellationToken - let! semanticModel = doc.GetSemanticModelAsync(ct) |> Async.AwaitTask - let! docText = doc.GetTextAsync(ct) |> Async.AwaitTask - let! syntaxTree = doc.GetSyntaxTreeAsync(ct) |> Async.AwaitTask - - let collector = DocumentSymbolCollector(docText, semanticModel) - collector.Init(doc.Name) - - let! root = syntaxTree.GetRootAsync(ct) |> Async.AwaitTask - collector.Visit(root) - - return collector.GetDocumentSymbols(canEmitDocSymbolHierarchy) - |> U2.C2 - |> Some - |> LspResult.success - } + let provider (_: ClientCapabilities) : U2 option = true |> U2.C1 |> Some + + let handle + (context: ServerRequestContext) + (p: DocumentSymbolParams) + : AsyncLspResult option> = + async { + let canEmitDocSymbolHierarchy = + context.ClientCapabilities.TextDocument + |> Option.bind (fun cc -> cc.DocumentSymbol) + |> Option.bind (fun cc -> cc.HierarchicalDocumentSymbolSupport) + |> Option.defaultValue false + + match context.GetDocument p.TextDocument.Uri with + | None -> return None |> LspResult.success + | Some doc -> + let! ct = Async.CancellationToken + let! semanticModel = doc.GetSemanticModelAsync(ct) |> Async.AwaitTask + let! docText = doc.GetTextAsync(ct) |> Async.AwaitTask + let! syntaxTree = doc.GetSyntaxTreeAsync(ct) |> Async.AwaitTask + + let collector = DocumentSymbolCollector(docText, semanticModel) + collector.Init(doc.Name) + + let! root = syntaxTree.GetRootAsync(ct) |> Async.AwaitTask + collector.Visit(root) + + return + collector.GetDocumentSymbols(canEmitDocSymbolHierarchy) + |> U2.C2 + |> Some + |> LspResult.success + } diff --git a/src/CSharpLanguageServer/Handlers/Hover.fs b/src/CSharpLanguageServer/Handlers/Hover.fs index 3ddd4d67..f07cf66b 100644 --- a/src/CSharpLanguageServer/Handlers/Hover.fs +++ b/src/CSharpLanguageServer/Handlers/Hover.fs @@ -8,18 +8,21 @@ open CSharpLanguageServer.State [] module Hover = - let provider (_: ClientCapabilities) : U2 option = - Some (U2.C1 true) + let provider (_: ClientCapabilities) : U2 option = Some(U2.C1 true) let handle (context: ServerRequestContext) (p: HoverParams) : AsyncLspResult = async { match! context.FindSymbol' p.TextDocument.Uri p.Position with - | None -> - return None |> LspResult.success - | Some (symbol, _) -> + | None -> return None |> LspResult.success + | Some(symbol, _) -> let content = DocumentationUtil.markdownDocForSymbolWithSignature symbol + let hover = - { Contents = { Kind = MarkupKind.Markdown; Value = content } |> U3.C1 + { Contents = + { Kind = MarkupKind.Markdown + Value = content } + |> U3.C1 // TODO: Support range Range = None } + return hover |> Some |> LspResult.success } diff --git a/src/CSharpLanguageServer/Handlers/Implementation.fs b/src/CSharpLanguageServer/Handlers/Implementation.fs index 93b5e381..5845614a 100644 --- a/src/CSharpLanguageServer/Handlers/Implementation.fs +++ b/src/CSharpLanguageServer/Handlers/Implementation.fs @@ -8,21 +8,25 @@ open CSharpLanguageServer.Util [] module Implementation = - let provider (_: ClientCapabilities) : U3 option = - Some (U3.C1 true) + let provider (_: ClientCapabilities) : U3 option = + Some(U3.C1 true) - let handle (context: ServerRequestContext) (p: ImplementationParams) : Async option>> = async { - match! context.FindSymbol p.TextDocument.Uri p.Position with - | None -> return None |> LspResult.success - | Some symbol -> - let! impls = context.FindImplementations symbol - let! locations = impls |> Seq.map (flip context.ResolveSymbolLocations None) |> Async.Parallel + let handle + (context: ServerRequestContext) + (p: ImplementationParams) + : Async option>> = + async { + match! context.FindSymbol p.TextDocument.Uri p.Position with + | None -> return None |> LspResult.success + | Some symbol -> + let! impls = context.FindImplementations symbol + let! locations = impls |> Seq.map (flip context.ResolveSymbolLocations None) |> Async.Parallel - return - locations - |> Array.collect List.toArray - |> Declaration.C2 - |> U2.C1 - |> Some - |> LspResult.success - } + return + locations + |> Array.collect List.toArray + |> Declaration.C2 + |> U2.C1 + |> Some + |> LspResult.success + } diff --git a/src/CSharpLanguageServer/Handlers/Initialization.fs b/src/CSharpLanguageServer/Handlers/Initialization.fs index 5d90ff55..74e16674 100644 --- a/src/CSharpLanguageServer/Handlers/Initialization.fs +++ b/src/CSharpLanguageServer/Handlers/Initialization.fs @@ -20,94 +20,110 @@ open CSharpLanguageServer.Logging module Initialization = let private logger = Logging.getLoggerByName "Initialization" - let handleInitialize (lspClient: ILspClient) - (setupTimer: unit -> unit) - (serverCapabilities: ServerCapabilities) - (context: ServerRequestContext) - (p: InitializeParams) - : Async> = async { - // context.State.LspClient has not been initialized yet thus context.WindowShowMessage will not work - let windowShowMessage m = lspClient.WindowLogMessage({ Type = MessageType.Info; Message = m }) - - context.Emit(ClientChange (Some lspClient)) - - let serverName = "csharp-ls" - let serverVersion = Assembly.GetExecutingAssembly().GetName().Version |> string - logger.LogInformation("initializing, {name} version {version}", serverName, serverVersion) - logger.LogInformation("server settings: {settings}", context.State.Settings |> string) - - do! windowShowMessage( - sprintf "csharp-ls: initializing, version %s" serverVersion) - - logger.LogInformation( - "{serverName} is released under MIT license and is not affiliated with Microsoft Corp.; see https://github.com/razzmatazz/csharp-language-server", - serverName) - - do! windowShowMessage( - sprintf "csharp-ls: %s is released under MIT license and is not affiliated with Microsoft Corp.; see https://github.com/razzmatazz/csharp-language-server" serverName) - - let vsInstanceQueryOpt = VisualStudioInstanceQueryOptions.Default - let vsInstanceList = MSBuildLocator.QueryVisualStudioInstances(vsInstanceQueryOpt) - if Seq.isEmpty vsInstanceList then - raise (InvalidOperationException("No instances of MSBuild could be detected." + Environment.NewLine + "Try calling RegisterInstance or RegisterMSBuildPath to manually register one.")) - - // do! infoMessage "MSBuildLocator instances found:" - // - // for vsInstance in vsInstanceList do - // do! infoMessage (sprintf "- SDK=\"%s\", Version=%s, MSBuildPath=\"%s\", DiscoveryType=%s" - // vsInstance.Name - // (string vsInstance.Version) - // vsInstance.MSBuildPath - // (string vsInstance.DiscoveryType)) - - let vsInstance = vsInstanceList |> Seq.head - - logger.LogInformation( - "MSBuildLocator: will register \"{vsInstanceName}\", Version={vsInstanceVersion} as default instance", - vsInstance.Name, - (string vsInstance.Version)) - - MSBuildLocator.RegisterInstance(vsInstance) - - logger.LogDebug("handleInitialize: p.Capabilities={caps}", serialize p.Capabilities) - context.Emit(ClientCapabilityChange p.Capabilities) - - // TODO use p.RootUri - let rootPath = Directory.GetCurrentDirectory() - context.Emit(RootPathChange rootPath) - - // setup timer so actors get period ticks - setupTimer() - - let initializeResult = - let assemblyVersion = - Assembly.GetExecutingAssembly().GetName().Version - |> Option.ofObj - |> Option.map string - - { InitializeResult.Default with + let handleInitialize + (lspClient: ILspClient) + (setupTimer: unit -> unit) + (serverCapabilities: ServerCapabilities) + (context: ServerRequestContext) + (p: InitializeParams) + : Async> = + async { + // context.State.LspClient has not been initialized yet thus context.WindowShowMessage will not work + let windowShowMessage m = + lspClient.WindowLogMessage({ Type = MessageType.Info; Message = m }) + + context.Emit(ClientChange(Some lspClient)) + + let serverName = "csharp-ls" + let serverVersion = Assembly.GetExecutingAssembly().GetName().Version |> string + logger.LogInformation("initializing, {name} version {version}", serverName, serverVersion) + logger.LogInformation("server settings: {settings}", context.State.Settings |> string) + + do! windowShowMessage (sprintf "csharp-ls: initializing, version %s" serverVersion) + + logger.LogInformation( + "{serverName} is released under MIT license and is not affiliated with Microsoft Corp.; see https://github.com/razzmatazz/csharp-language-server", + serverName + ) + + do! + windowShowMessage ( + sprintf + "csharp-ls: %s is released under MIT license and is not affiliated with Microsoft Corp.; see https://github.com/razzmatazz/csharp-language-server" + serverName + ) + + let vsInstanceQueryOpt = VisualStudioInstanceQueryOptions.Default + let vsInstanceList = MSBuildLocator.QueryVisualStudioInstances(vsInstanceQueryOpt) + + if Seq.isEmpty vsInstanceList then + raise ( + InvalidOperationException( + "No instances of MSBuild could be detected." + + Environment.NewLine + + "Try calling RegisterInstance or RegisterMSBuildPath to manually register one." + ) + ) + + // do! infoMessage "MSBuildLocator instances found:" + // + // for vsInstance in vsInstanceList do + // do! infoMessage (sprintf "- SDK=\"%s\", Version=%s, MSBuildPath=\"%s\", DiscoveryType=%s" + // vsInstance.Name + // (string vsInstance.Version) + // vsInstance.MSBuildPath + // (string vsInstance.DiscoveryType)) + + let vsInstance = vsInstanceList |> Seq.head + + logger.LogInformation( + "MSBuildLocator: will register \"{vsInstanceName}\", Version={vsInstanceVersion} as default instance", + vsInstance.Name, + (string vsInstance.Version) + ) + + MSBuildLocator.RegisterInstance(vsInstance) + + logger.LogDebug("handleInitialize: p.Capabilities={caps}", serialize p.Capabilities) + context.Emit(ClientCapabilityChange p.Capabilities) + + // TODO use p.RootUri + let rootPath = Directory.GetCurrentDirectory() + context.Emit(RootPathChange rootPath) + + // setup timer so actors get period ticks + setupTimer () + + let initializeResult = + let assemblyVersion = + Assembly.GetExecutingAssembly().GetName().Version + |> Option.ofObj + |> Option.map string + + { InitializeResult.Default with Capabilities = serverCapabilities ServerInfo = - Some - { Name = "csharp-ls" - Version = assemblyVersion }} + Some + { Name = "csharp-ls" + Version = assemblyVersion } } - return initializeResult |> LspResult.success - } + return initializeResult |> LspResult.success + } - let handleInitialized (lspClient: ILspClient) - (stateActor: MailboxProcessor) - (getDynamicRegistrations: ClientCapabilities -> Registration list) - (context: ServerRequestContext) - (_p: unit) - : Async> = + let handleInitialized + (lspClient: ILspClient) + (stateActor: MailboxProcessor) + (getDynamicRegistrations: ClientCapabilities -> Registration list) + (context: ServerRequestContext) + (_p: unit) + : Async> = async { logger.LogDebug("handleInitialized: \"initialized\" notification received from client") logger.LogDebug("handleInitialized: registrationParams..") - let registrationParams = { - Registrations = getDynamicRegistrations context.ClientCapabilities |> List.toArray - } + + let registrationParams = + { Registrations = getDynamicRegistrations context.ClientCapabilities |> List.toArray } logger.LogDebug("handleInitialized: ClientRegisterCapability..") // TODO: Retry on error? @@ -116,8 +132,7 @@ module Initialization = | Ok _ -> () | Error error -> logger.LogWarning("handleInitialized: dynamic cap registration has failed with {error}", error) - with - | ex -> + with ex -> logger.LogWarning("handleInitialized: dynamic cap registration has failed with {error}", string ex) logger.LogDebug("handleInitialized: retrieve csharp settings..") @@ -127,7 +142,10 @@ module Initialization = try let! workspaceCSharpConfig = lspClient.WorkspaceConfiguration( - { Items=[| { Section=Some "csharp"; ScopeUri=None } |] }) + { Items = + [| { Section = Some "csharp" + ScopeUri = None } |] } + ) let csharpConfigTokensMaybe = match workspaceCSharpConfig with @@ -135,28 +153,29 @@ module Initialization = | _ -> None let newSettingsMaybe = - match csharpConfigTokensMaybe with - | Some [| t |] -> - let csharpSettingsMaybe = t |> deserialize + match csharpConfigTokensMaybe with + | Some [| t |] -> + let csharpSettingsMaybe = t |> deserialize - match csharpSettingsMaybe with - | Some csharpSettings -> + match csharpSettingsMaybe with + | Some csharpSettings -> - match csharpSettings.solution with - | Some solutionPath-> Some { context.State.Settings with SolutionPath = Some solutionPath } - | _ -> None + match csharpSettings.solution with + | Some solutionPath -> + Some + { context.State.Settings with + SolutionPath = Some solutionPath } + | _ -> None - | _ -> None - | _ -> None + | _ -> None + | _ -> None // do! logMessage (sprintf "handleInitialized: newSettingsMaybe=%s" (string newSettingsMaybe)) match newSettingsMaybe with - | Some newSettings -> - context.Emit(SettingsChange newSettings) + | Some newSettings -> context.Emit(SettingsChange newSettings) | _ -> () - with - | ex -> + with ex -> logger.LogWarning( "handleInitialized: could not retrieve `csharp` workspace configuration section: {error}", ex |> string @@ -166,7 +185,7 @@ module Initialization = // start loading the solution // logger.LogDebug("handleInitialized: post SolutionReloadRequest") - stateActor.Post(SolutionReloadRequest (TimeSpan.FromMilliseconds(100))) + stateActor.Post(SolutionReloadRequest(TimeSpan.FromMilliseconds(100))) logger.LogDebug("handleInitialized: Ok") diff --git a/src/CSharpLanguageServer/Handlers/InlayHint.fs b/src/CSharpLanguageServer/Handlers/InlayHint.fs index 792c4f7e..bfa7ee56 100644 --- a/src/CSharpLanguageServer/Handlers/InlayHint.fs +++ b/src/CSharpLanguageServer/Handlers/InlayHint.fs @@ -18,16 +18,26 @@ open CSharpLanguageServer.Util module InlayHint = // TODO: Do we keep it now or refactor it to use reflection? let private getBestOrAllSymbols (info: SymbolInfo) = - let best = if isNull info.Symbol then None else Some ([| info.Symbol |]) - let all = if info.CandidateSymbols.IsEmpty then None else Some (info.CandidateSymbols |> Array.ofSeq) + let best = if isNull info.Symbol then None else Some([| info.Symbol |]) + + let all = + if info.CandidateSymbols.IsEmpty then + None + else + Some(info.CandidateSymbols |> Array.ofSeq) + best |> Option.orElse all |> Option.defaultValue Array.empty // rewrite of https://github.com/dotnet/roslyn/blob/main/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ArgumentSyntaxExtensions.cs - let private getParameterForArgumentSyntax (semanticModel: SemanticModel) (argument: ArgumentSyntax) : IParameterSymbol option = + let private getParameterForArgumentSyntax + (semanticModel: SemanticModel) + (argument: ArgumentSyntax) + : IParameterSymbol option = match argument.Parent with | :? BaseArgumentListSyntax as argumentList when not (isNull argumentList.Parent) -> let argumentListParent = argumentList.Parent |> nonNull "argumentList.Parent" let symbols = semanticModel.GetSymbolInfo(argumentListParent) |> getBestOrAllSymbols + match symbols with | [| symbol |] -> let parameters = @@ -40,29 +50,40 @@ module InlayHint = argument.NameColon |> Option.ofObj |> Option.bind (fun anc -> if anc.IsMissing then None else Some anc) - |> Option.bind (fun anc -> parameters |> Seq.tryFind (fun p -> p.Name = anc.Name.Identifier.ValueText)) + |> Option.bind (fun anc -> + parameters |> Seq.tryFind (fun p -> p.Name = anc.Name.Identifier.ValueText)) let positionalParameter = match argumentList.Arguments.IndexOf(argument) with | index when 0 <= index && index < parameters.Length -> let parameter = parameters[index] - if argument.RefOrOutKeyword.Kind() = SyntaxKind.OutKeyword && parameter.RefKind <> RefKind.Out || - argument.RefOrOutKeyword.Kind() = SyntaxKind.RefKeyword && parameter.RefKind <> RefKind.Ref then + + if + argument.RefOrOutKeyword.Kind() = SyntaxKind.OutKeyword + && parameter.RefKind <> RefKind.Out + || argument.RefOrOutKeyword.Kind() = SyntaxKind.RefKeyword + && parameter.RefKind <> RefKind.Ref + then None else Some parameter | _ -> None + namedParameter |> Option.orElse positionalParameter | _ -> None | _ -> None // rewrite of https://github.com/dotnet/roslyn/blob/main/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/AttributeArgumentSyntaxExtensions.cs - let private getParameterForAttributeArgumentSyntax (semanticModel: SemanticModel) (argument: AttributeArgumentSyntax) : IParameterSymbol option = + let private getParameterForAttributeArgumentSyntax + (semanticModel: SemanticModel) + (argument: AttributeArgumentSyntax) + : IParameterSymbol option = match argument.Parent with | :? AttributeArgumentListSyntax as argumentList when not (isNull argument.NameEquals) -> match argumentList.Parent with | :? AttributeSyntax as invocable -> let symbols = semanticModel.GetSymbolInfo(invocable) |> getBestOrAllSymbols + match symbols with | [| symbol |] -> let parameters = @@ -75,18 +96,24 @@ module InlayHint = argument.NameColon |> Option.ofObj |> Option.bind (fun anc -> if anc.IsMissing then None else Some anc) - |> Option.bind (fun anc -> parameters |> Seq.tryFind (fun p -> p.Name = anc.Name.Identifier.ValueText)) + |> Option.bind (fun anc -> + parameters |> Seq.tryFind (fun p -> p.Name = anc.Name.Identifier.ValueText)) let positionalParameter = match argumentList.Arguments.IndexOf(argument) with | index when 0 <= index && index < parameters.Length -> Some parameters[index] | _ -> None + namedParameter |> Option.orElse positionalParameter | _ -> None | _ -> None | _ -> None - let private toInlayHint (semanticModel: SemanticModel) (lines: TextLineCollection) (node: SyntaxNode): InlayHint option = + let private toInlayHint + (semanticModel: SemanticModel) + (lines: TextLineCollection) + (node: SyntaxNode) + : InlayHint option = let validateType (ty: ITypeSymbol | null) = match ty |> Option.ofObj with | None -> None @@ -96,19 +123,24 @@ module InlayHint = else Some ty - let typeDisplayStyle = SymbolDisplayFormat( - genericsOptions = SymbolDisplayGenericsOptions.IncludeTypeParameters, - miscellaneousOptions = (SymbolDisplayMiscellaneousOptions.AllowDefaultLiteral ||| SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier ||| SymbolDisplayMiscellaneousOptions.UseSpecialTypes)) - let toTypeInlayHint (pos: int) (ty: ITypeSymbol): InlayHint = + let typeDisplayStyle = + SymbolDisplayFormat( + genericsOptions = SymbolDisplayGenericsOptions.IncludeTypeParameters, + miscellaneousOptions = + (SymbolDisplayMiscellaneousOptions.AllowDefaultLiteral + ||| SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier + ||| SymbolDisplayMiscellaneousOptions.UseSpecialTypes) + ) + + let toTypeInlayHint (pos: int) (ty: ITypeSymbol) : InlayHint = { Position = pos |> lines.GetLinePosition |> Position.fromLinePosition - Label = U2.C1 (": " + SymbolName.fromSymbol typeDisplayStyle ty) + Label = U2.C1(": " + SymbolName.fromSymbol typeDisplayStyle ty) Kind = Some InlayHintKind.Type TextEdits = None Tooltip = None PaddingLeft = Some false PaddingRight = Some false - Data = None - } + Data = None } let validateParameter (arg: SyntaxNode) (par: IParameterSymbol) = match arg.Parent with @@ -117,18 +149,19 @@ module InlayHint = // Don't show hint if parameter name is empty | _ when String.IsNullOrEmpty(par.Name) -> None // Don't show hint if argument matches parameter name - | _ when String.Equals(arg.GetText().ToString(), par.Name, StringComparison.CurrentCultureIgnoreCase) -> None + | _ when String.Equals(arg.GetText().ToString(), par.Name, StringComparison.CurrentCultureIgnoreCase) -> + None | _ -> Some par - let toParameterInlayHint (pos: int) (par: IParameterSymbol): InlayHint = + + let toParameterInlayHint (pos: int) (par: IParameterSymbol) : InlayHint = { Position = pos |> lines.GetLinePosition |> Position.fromLinePosition - Label = U2.C1 (par.Name + ":") + Label = U2.C1(par.Name + ":") Kind = Some InlayHintKind.Parameter TextEdits = None Tooltip = None PaddingLeft = Some false PaddingRight = Some true - Data = None - } + Data = None } // It's a rewrite of https://github.com/dotnet/roslyn/blob/main/src/Features/CSharp/Portable/InlineHints/CSharpInlineTypeHintsService.cs & // https://github.com/dotnet/roslyn/blob/main/src/Features/CSharp/Portable/InlineHints/CSharpInlineParameterNameHintsService.cs. @@ -136,7 +169,9 @@ module InlayHint = // TODO: Support the configuration whether or not to show some kinds of inlay hints. match node with | :? VariableDeclarationSyntax as var when - var.Type.IsVar && var.Variables.Count = 1 && not var.Variables[0].Identifier.IsMissing + var.Type.IsVar + && var.Variables.Count = 1 + && not var.Variables[0].Identifier.IsMissing -> semanticModel.GetTypeInfo(var.Type).Type |> validateType @@ -145,13 +180,15 @@ module InlayHint = // For example, in `var (x, y) = (0, "")`, we should `int` for `x` and `string` for `y`. // It's redundant to show `(int, string)` for `var` | :? DeclarationExpressionSyntax as dec when - dec.Type.IsVar && not (dec.Designation :? ParenthesizedVariableDesignationSyntax) + dec.Type.IsVar + && not (dec.Designation :? ParenthesizedVariableDesignationSyntax) -> semanticModel.GetTypeInfo(dec.Type).Type |> validateType |> Option.map (toTypeInlayHint dec.Designation.Span.End) | :? SingleVariableDesignationSyntax as var when - not (var.Parent :? DeclarationPatternSyntax) && not (var.Parent :? DeclarationExpressionSyntax) + not (var.Parent :? DeclarationPatternSyntax) + && not (var.Parent :? DeclarationExpressionSyntax) -> semanticModel.GetDeclaredSymbol(var) |> fun sym -> @@ -161,16 +198,13 @@ module InlayHint = |> Option.map (fun local -> local.Type) |> Option.bind validateType |> Option.map (toTypeInlayHint var.Identifier.Span.End) - | :? ForEachStatementSyntax as forEach when - forEach.Type.IsVar - -> + | :? ForEachStatementSyntax as forEach when forEach.Type.IsVar -> semanticModel.GetForEachStatementInfo(forEach).ElementType |> validateType |> Option.map (toTypeInlayHint forEach.Identifier.Span.End) - | :? ParameterSyntax as parameterNode when - isNull parameterNode.Type - -> + | :? ParameterSyntax as parameterNode when isNull parameterNode.Type -> let parameter = semanticModel.GetDeclaredSymbol(parameterNode) + parameter |> fun parameter -> match parameter.ContainingSymbol with @@ -179,22 +213,17 @@ module InlayHint = |> Option.map (fun parameter -> parameter.Type) |> Option.bind validateType |> Option.map (toTypeInlayHint parameterNode.Identifier.Span.End) - | :? ImplicitObjectCreationExpressionSyntax as implicitNew - -> + | :? ImplicitObjectCreationExpressionSyntax as implicitNew -> semanticModel.GetTypeInfo(implicitNew).Type |> validateType |> Option.map (toTypeInlayHint implicitNew.NewKeyword.Span.End) - | :? ArgumentSyntax as argument when - isNull argument.NameColon - -> + | :? ArgumentSyntax as argument when isNull argument.NameColon -> argument |> getParameterForArgumentSyntax semanticModel |> Option.bind (validateParameter node) |> Option.map (toParameterInlayHint argument.Span.Start) - | :? AttributeArgumentSyntax as argument when - isNull argument.NameEquals && isNull argument.NameColon - -> + | :? AttributeArgumentSyntax as argument when isNull argument.NameEquals && isNull argument.NameColon -> argument |> getParameterForAttributeArgumentSyntax semanticModel |> Option.bind (validateParameter node) @@ -205,12 +234,11 @@ module InlayHint = let provider (_: ClientCapabilities) : U3 option = let inlayHintOptions: InlayHintOptions = { ResolveProvider = Some false - WorkDoneProgress = None - } + WorkDoneProgress = None } - Some (U3.C2 inlayHintOptions) + Some(U3.C2 inlayHintOptions) - let handle (context: ServerRequestContext) (p: InlayHintParams): AsyncLspResult = async { + let handle (context: ServerRequestContext) (p: InlayHintParams) : AsyncLspResult = async { match context.GetUserDocument p.TextDocument.Uri with | None -> return None |> LspResult.success | Some doc -> @@ -225,6 +253,7 @@ module InlayHint = |> Seq.map (toInlayHint semanticModel sourceText.Lines) |> Seq.filter Option.isSome |> Seq.map Option.get + return inlayHints |> Seq.toArray |> Some |> LspResult.success } diff --git a/src/CSharpLanguageServer/Handlers/LinkedEditingRange.fs b/src/CSharpLanguageServer/Handlers/LinkedEditingRange.fs index 2446db05..91274623 100644 --- a/src/CSharpLanguageServer/Handlers/LinkedEditingRange.fs +++ b/src/CSharpLanguageServer/Handlers/LinkedEditingRange.fs @@ -9,5 +9,8 @@ open CSharpLanguageServer.State module LinkedEditingRange = let provider (clientCapabilities: ClientCapabilities) = None - let handle (context: ServerRequestContext) (def: LinkedEditingRangeParams) : AsyncLspResult = + let handle + (context: ServerRequestContext) + (def: LinkedEditingRangeParams) + : AsyncLspResult = LspResult.notImplemented |> async.Return diff --git a/src/CSharpLanguageServer/Handlers/References.fs b/src/CSharpLanguageServer/Handlers/References.fs index 553c33aa..d74bb140 100644 --- a/src/CSharpLanguageServer/Handlers/References.fs +++ b/src/CSharpLanguageServer/Handlers/References.fs @@ -8,8 +8,7 @@ open CSharpLanguageServer.Conversions [] module References = - let provider (_: ClientCapabilities) : U2 option = - Some (U2.C1 true) + let provider (_: ClientCapabilities) : U2 option = Some(U2.C1 true) let handle (context: ServerRequestContext) (p: ReferenceParams) : AsyncLspResult = async { match! context.FindSymbol p.TextDocument.Uri p.Position with diff --git a/src/CSharpLanguageServer/Handlers/Rename.fs b/src/CSharpLanguageServer/Handlers/Rename.fs index 7f70f775..c95fbe0c 100644 --- a/src/CSharpLanguageServer/Handlers/Rename.fs +++ b/src/CSharpLanguageServer/Handlers/Rename.fs @@ -29,34 +29,39 @@ module Rename = (originalSolution: Solution) (updatedSolution: Solution) (docId: DocumentId) - : Async = async { - let originalDoc = match originalSolution.GetDocument(docId) with - | null -> failwith "could not originalSolution.GetDocument(docId)" - | x -> x - let! originalDocText = originalDoc.GetTextAsync(ct) |> Async.AwaitTask - - let updatedDoc = match updatedSolution.GetDocument(docId) with - | null -> failwith "could not updatedSolution.GetDocument(docId)" - | x -> x - - let! docChanges = updatedDoc.GetTextChangesAsync(originalDoc, ct) |> Async.AwaitTask - - let diffEdits: U2 array = - docChanges - |> Seq.sortBy (fun c -> c.Span.Start) - |> Seq.map (TextEdit.fromTextChange originalDocText.Lines) - |> Seq.map U2.C1 - |> Array.ofSeq - - let uri = originalDoc.FilePath |> Path.toUri - let textEditDocument = - { Uri = uri - Version = tryGetDocVersionByUri uri } + : Async = + async { + let originalDoc = + match originalSolution.GetDocument(docId) with + | null -> failwith "could not originalSolution.GetDocument(docId)" + | x -> x - return - { TextDocument = textEditDocument - Edits = diffEdits } - } + let! originalDocText = originalDoc.GetTextAsync(ct) |> Async.AwaitTask + + let updatedDoc = + match updatedSolution.GetDocument(docId) with + | null -> failwith "could not updatedSolution.GetDocument(docId)" + | x -> x + + let! docChanges = updatedDoc.GetTextChangesAsync(originalDoc, ct) |> Async.AwaitTask + + let diffEdits: U2 array = + docChanges + |> Seq.sortBy (fun c -> c.Span.Start) + |> Seq.map (TextEdit.fromTextChange originalDocText.Lines) + |> Seq.map U2.C1 + |> Array.ofSeq + + let uri = originalDoc.FilePath |> Path.toUri + + let textEditDocument = + { Uri = uri + Version = tryGetDocVersionByUri uri } + + return + { TextDocument = textEditDocument + Edits = diffEdits } + } updatedSolution.GetChanges(originalSolution).GetProjectChanges() |> Seq.collect (fun projectChange -> projectChange.GetChangedDocuments()) @@ -70,17 +75,19 @@ module Rename = |> Option.bind (fun x -> x.PrepareSupport) |> Option.defaultValue false - let provider (clientCapabilities: ClientCapabilities): U2 option = + let provider (clientCapabilities: ClientCapabilities) : U2 option = match prepareSupport clientCapabilities with - | true -> Some (U2.C2 { PrepareProvider = Some true; WorkDoneProgress = None }) - | false -> Some (U2.C1 true) - - let prepare (context: ServerRequestContext) - (p: PrepareRenameParams) - : AsyncLspResult = async { + | true -> + Some( + U2.C2 + { PrepareProvider = Some true + WorkDoneProgress = None } + ) + | false -> Some(U2.C1 true) + + let prepare (context: ServerRequestContext) (p: PrepareRenameParams) : AsyncLspResult = async { match context.GetUserDocument p.TextDocument.Uri with - | None -> - return None |> LspResult.success + | None -> return None |> LspResult.success | Some doc -> let! ct = Async.CancellationToken let! docSyntaxTree = doc.GetSyntaxTreeAsync(ct) |> Async.AwaitTask @@ -88,6 +95,7 @@ module Rename = let position = Position.toRoslynPosition docText.Lines p.Position let! symbolMaybe = SymbolFinder.FindSymbolAtPositionAsync(doc, position, ct) |> Async.AwaitTask + let symbolIsFromMetadata = symbolMaybe |> Option.ofObj @@ -100,26 +108,24 @@ module Rename = let textSpan = docText.Lines.GetTextSpan(linePositionSpan) let! rootNode = docSyntaxTree.GetRootAsync(ct) |> Async.AwaitTask + let nodeOnPos = rootNode.FindNode(textSpan, findInsideTrivia = false, getInnermostNodeForTie = true) let spanMaybe = match nodeOnPos with - | :? PropertyDeclarationSyntax as propDec -> propDec.Identifier.Span |> Some - | :? MethodDeclarationSyntax as methodDec -> methodDec.Identifier.Span |> Some - | :? BaseTypeDeclarationSyntax as typeDec -> typeDec.Identifier.Span |> Some - | :? VariableDeclaratorSyntax as varDec -> varDec.Identifier.Span |> Some - | :? EnumMemberDeclarationSyntax as enumMemDec -> enumMemDec.Identifier.Span |> Some - | :? ParameterSyntax as paramSyn -> paramSyn.Identifier.Span |> Some - | :? NameSyntax as nameSyn -> nameSyn.Span |> Some + | :? PropertyDeclarationSyntax as propDec -> propDec.Identifier.Span |> Some + | :? MethodDeclarationSyntax as methodDec -> methodDec.Identifier.Span |> Some + | :? BaseTypeDeclarationSyntax as typeDec -> typeDec.Identifier.Span |> Some + | :? VariableDeclaratorSyntax as varDec -> varDec.Identifier.Span |> Some + | :? EnumMemberDeclarationSyntax as enumMemDec -> enumMemDec.Identifier.Span |> Some + | :? ParameterSyntax as paramSyn -> paramSyn.Identifier.Span |> Some + | :? NameSyntax as nameSyn -> nameSyn.Span |> Some | :? SingleVariableDesignationSyntax as designationSyn -> designationSyn.Identifier.Span |> Some - | :? ForEachStatementSyntax as forEachSyn -> forEachSyn.Identifier.Span |> Some - | :? LocalFunctionStatementSyntax as localFunStSyn -> localFunStSyn.Identifier.Span |> Some + | :? ForEachStatementSyntax as forEachSyn -> forEachSyn.Identifier.Span |> Some + | :? LocalFunctionStatementSyntax as localFunStSyn -> localFunStSyn.Identifier.Span |> Some | node -> - logger.LogDebug( - "textDocument/prepareRename: unhandled Type={type}", - (node.GetType().Name) - ) + logger.LogDebug("textDocument/prepareRename: unhandled Type={type}", (node.GetType().Name)) None let rangeWithPlaceholderMaybe: PrepareRenameResult option = @@ -135,14 +141,10 @@ module Rename = return rangeWithPlaceholderMaybe |> LspResult.success } - let handle - (context: ServerRequestContext) - (p: RenameParams) - : AsyncLspResult = async { + let handle (context: ServerRequestContext) (p: RenameParams) : AsyncLspResult = async { match! context.FindSymbol' p.TextDocument.Uri p.Position with - | None -> - return None |> LspResult.success - | Some (symbol, doc) -> + | None -> return None |> LspResult.success + | Some(symbol, doc) -> let! ct = Async.CancellationToken let originalSolution = doc.Project.Solution @@ -157,11 +159,11 @@ module Rename = |> Async.AwaitTask let! docTextEdit = - lspDocChangesFromSolutionDiff - ct - originalSolution - updatedSolution - (fun uri -> context.OpenDocs.TryFind uri |> Option.map _.Version) + lspDocChangesFromSolutionDiff ct originalSolution updatedSolution (fun uri -> + context.OpenDocs.TryFind uri |> Option.map _.Version) - return WorkspaceEdit.Create(docTextEdit, context.ClientCapabilities) |> Some |> LspResult.success + return + WorkspaceEdit.Create(docTextEdit, context.ClientCapabilities) + |> Some + |> LspResult.success } diff --git a/src/CSharpLanguageServer/Handlers/SemanticTokens.fs b/src/CSharpLanguageServer/Handlers/SemanticTokens.fs index 83303730..75b51153 100644 --- a/src/CSharpLanguageServer/Handlers/SemanticTokens.fs +++ b/src/CSharpLanguageServer/Handlers/SemanticTokens.fs @@ -13,41 +13,40 @@ open CSharpLanguageServer.Conversions [] module SemanticTokens = - let private classificationTypeMap = Map [ - (ClassificationTypeNames.ClassName, "class"); - (ClassificationTypeNames.Comment, "comment"); - (ClassificationTypeNames.ConstantName, "property"); - (ClassificationTypeNames.ControlKeyword, "keyword"); - (ClassificationTypeNames.DelegateName, "class"); - (ClassificationTypeNames.EnumMemberName, "enumMember"); - (ClassificationTypeNames.EnumName, "enum"); - (ClassificationTypeNames.EventName, "event"); - (ClassificationTypeNames.ExtensionMethodName, "method"); - (ClassificationTypeNames.FieldName, "property"); - (ClassificationTypeNames.Identifier, "variable"); - (ClassificationTypeNames.InterfaceName, "interface"); - (ClassificationTypeNames.LabelName, "variable"); - (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"); - (ClassificationTypeNames.VerbatimStringLiteral, "string") - ] - - let private classificationModifierMap = Map [ - (ClassificationTypeNames.StaticSymbol, "static") - ] + let private classificationTypeMap = + Map + [ (ClassificationTypeNames.ClassName, "class") + (ClassificationTypeNames.Comment, "comment") + (ClassificationTypeNames.ConstantName, "property") + (ClassificationTypeNames.ControlKeyword, "keyword") + (ClassificationTypeNames.DelegateName, "class") + (ClassificationTypeNames.EnumMemberName, "enumMember") + (ClassificationTypeNames.EnumName, "enum") + (ClassificationTypeNames.EventName, "event") + (ClassificationTypeNames.ExtensionMethodName, "method") + (ClassificationTypeNames.FieldName, "property") + (ClassificationTypeNames.Identifier, "variable") + (ClassificationTypeNames.InterfaceName, "interface") + (ClassificationTypeNames.LabelName, "variable") + (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") + (ClassificationTypeNames.VerbatimStringLiteral, "string") ] + + let private classificationModifierMap = + Map [ (ClassificationTypeNames.StaticSymbol, "static") ] let private semanticTokenTypeMap = classificationTypeMap @@ -89,68 +88,84 @@ module SemanticTokens = let private 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) + |> 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 textSpan.Length, typeId, modifiers) - let private computePosition (((pLine, pChar, _, _, _), (cLine, cChar, cLen, cToken, cModifiers)): ((uint32 * uint32 * uint32 * uint32 * uint32) * (uint32 * uint32 * uint32 * uint32 * uint32))) = + let private 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 + let deltaChar = if deltaLine = 0u then cChar - pChar else cChar (deltaLine, deltaChar, cLen, cToken, cModifiers) - let private getSemanticTokensRange (context: ServerRequestContext) (uri: string) (range: Range option): AsyncLspResult = async { - let docMaybe = context.GetUserDocument uri - match docMaybe with - | None -> - return None |> LspResult.success - | Some doc -> - let! ct = Async.CancellationToken - let! sourceText = doc.GetTextAsync(ct) |> Async.AwaitTask - let textSpan = - range - |> Option.map (Range.toTextSpan sourceText.Lines) - |> Option.defaultValue (TextSpan(0, sourceText.Length)) - let! spans = Classifier.GetClassifiedSpansAsync(doc, textSpan, ct) |> 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 |> LspResult.success - } + let private getSemanticTokensRange + (context: ServerRequestContext) + (uri: string) + (range: Range option) + : AsyncLspResult = + async { + let docMaybe = context.GetUserDocument uri + + match docMaybe with + | None -> return None |> LspResult.success + | Some doc -> + let! ct = Async.CancellationToken + let! sourceText = doc.GetTextAsync(ct) |> Async.AwaitTask + + let textSpan = + range + |> Option.map (Range.toTextSpan sourceText.Lines) + |> Option.defaultValue (TextSpan(0, sourceText.Length)) + + let! spans = Classifier.GetClassifiedSpansAsync(doc, textSpan, ct) |> 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 |> LspResult.success + } let provider (_: ClientCapabilities) : U2 option = let semanticTokensOptions: SemanticTokensOptions = - { Legend = { TokenTypes = semanticTokenTypes |> Seq.toArray - TokenModifiers = semanticTokenModifiers |> Seq.toArray } - Range = Some (U2.C1 true) - Full = Some (U2.C1 true) - WorkDoneProgress = None - } + { Legend = + { TokenTypes = semanticTokenTypes |> Seq.toArray + TokenModifiers = semanticTokenModifiers |> Seq.toArray } + Range = Some(U2.C1 true) + Full = Some(U2.C1 true) + WorkDoneProgress = None } - Some (U2.C1 semanticTokensOptions) + Some(U2.C1 semanticTokensOptions) // TODO: Everytime the server will re-compute semantic tokens, is it possible to cache the result? - let handleFull (context: ServerRequestContext) (p: SemanticTokensParams): AsyncLspResult = + let handleFull (context: ServerRequestContext) (p: SemanticTokensParams) : AsyncLspResult = getSemanticTokensRange context p.TextDocument.Uri None let handleFullDelta @@ -160,5 +175,8 @@ module SemanticTokens = LspResult.notImplemented option> |> async.Return - let handleRange (context: ServerRequestContext) (p: SemanticTokensRangeParams): AsyncLspResult = + let handleRange + (context: ServerRequestContext) + (p: SemanticTokensRangeParams) + : AsyncLspResult = getSemanticTokensRange context p.TextDocument.Uri (Some p.Range) diff --git a/src/CSharpLanguageServer/Handlers/SignatureHelp.fs b/src/CSharpLanguageServer/Handlers/SignatureHelp.fs index 3052e9cf..1f02964c 100644 --- a/src/CSharpLanguageServer/Handlers/SignatureHelp.fs +++ b/src/CSharpLanguageServer/Handlers/SignatureHelp.fs @@ -29,9 +29,9 @@ module SignatureInformation = Value = DocumentationUtil.markdownDocForSymbol m } |> U2.C2 - { Label = SymbolName.fromSymbol SymbolDisplayFormat.MinimallyQualifiedFormat m - Documentation = Some documentation - Parameters = Some parameters + { Label = SymbolName.fromSymbol SymbolDisplayFormat.MinimallyQualifiedFormat m + Documentation = Some documentation + Parameters = Some parameters ActiveParameter = None } [] @@ -62,8 +62,9 @@ module SignatureHelp = RetriggerCharacters = None } |> Some - let handle (context: ServerRequestContext) (p: SignatureHelpParams): AsyncLspResult = async { + let handle (context: ServerRequestContext) (p: SignatureHelpParams) : AsyncLspResult = async { let docMaybe = context.GetUserDocument p.TextDocument.Uri + match docMaybe with | None -> return None |> LspResult.success | Some doc -> @@ -76,12 +77,16 @@ module SignatureHelp = let! syntaxTree = doc.GetSyntaxTreeAsync(ct) |> Async.AwaitTask let! root = syntaxTree.GetRootAsync(ct) |> Async.AwaitTask - let rec findInvocationContext (node: SyntaxNode): InvocationContext option = + let rec findInvocationContext (node: SyntaxNode) : InvocationContext option = match node with | :? InvocationExpressionSyntax as invocation when invocation.ArgumentList.Span.Contains(position) -> - Some { Receiver = invocation.Expression - ArgumentTypes = invocation.ArgumentList.Arguments |> Seq.map (fun a -> semanticModel.GetTypeInfo(a.Expression)) |> List.ofSeq - Separators = invocation.ArgumentList.Arguments.GetSeparators() |> List.ofSeq } + Some + { Receiver = invocation.Expression + ArgumentTypes = + invocation.ArgumentList.Arguments + |> Seq.map (fun a -> semanticModel.GetTypeInfo(a.Expression)) + |> List.ofSeq + Separators = invocation.ArgumentList.Arguments.GetSeparators() |> List.ofSeq } | :? BaseObjectCreationExpressionSyntax as objectCreation when objectCreation.ArgumentList @@ -91,14 +96,17 @@ module SignatureHelp = -> let argumentList = objectCreation.ArgumentList |> Option.ofObj - Some { Receiver = objectCreation - ArgumentTypes = argumentList - |> Option.map (fun al -> al.Arguments |> List.ofSeq) - |> Option.defaultValue [] - |> List.map (fun a -> semanticModel.GetTypeInfo(a.Expression)) - Separators = argumentList - |> Option.map (fun al -> al.Arguments.GetSeparators() |> List.ofSeq) - |> Option.defaultValue [] } + Some + { Receiver = objectCreation + ArgumentTypes = + argumentList + |> Option.map (fun al -> al.Arguments |> List.ofSeq) + |> Option.defaultValue [] + |> List.map (fun a -> semanticModel.GetTypeInfo(a.Expression)) + Separators = + argumentList + |> Option.map (fun al -> al.Arguments.GetSeparators() |> List.ofSeq) + |> Option.defaultValue [] } | :? AttributeSyntax as attributeSyntax when attributeSyntax.ArgumentList @@ -108,14 +116,17 @@ module SignatureHelp = -> let argumentList = attributeSyntax.ArgumentList |> Option.ofObj - Some { Receiver = attributeSyntax - ArgumentTypes = argumentList - |> Option.map (fun al -> al.Arguments |> List.ofSeq) - |> Option.defaultValue [] - |> List.map (fun a -> semanticModel.GetTypeInfo(a.Expression)) - Separators = argumentList - |> Option.map (fun al -> al.Arguments.GetSeparators() |> List.ofSeq) - |> Option.defaultValue [] } + Some + { Receiver = attributeSyntax + ArgumentTypes = + argumentList + |> Option.map (fun al -> al.Arguments |> List.ofSeq) + |> Option.defaultValue [] + |> List.map (fun a -> semanticModel.GetTypeInfo(a.Expression)) + Separators = + argumentList + |> Option.map (fun al -> al.Arguments.GetSeparators() |> List.ofSeq) + |> Option.defaultValue [] } | _ -> node @@ -143,7 +154,9 @@ module SignatureHelp = let signatureHelpResult = { Signatures = methodGroup |> Seq.map SignatureInformation.fromMethod |> Array.ofSeq - ActiveSignature = matchingMethodMaybe |> Option.map (fun m -> List.findIndex ((=) m) methodGroup |> uint32) + ActiveSignature = + matchingMethodMaybe + |> Option.map (fun m -> List.findIndex ((=) m) methodGroup |> uint32) ActiveParameter = activeParameterMaybe } return Some signatureHelpResult |> LspResult.success diff --git a/src/CSharpLanguageServer/Handlers/TextDocumentSync.fs b/src/CSharpLanguageServer/Handlers/TextDocumentSync.fs index f1f33d07..1e7685d2 100644 --- a/src/CSharpLanguageServer/Handlers/TextDocumentSync.fs +++ b/src/CSharpLanguageServer/Handlers/TextDocumentSync.fs @@ -18,8 +18,9 @@ module TextDocumentSync = let private logger = Logging.getLoggerByName "TextDocumentSync" let private applyLspContentChangesOnRoslynSourceText - (changes: TextDocumentContentChangeEvent[]) - (initialSourceText: SourceText) = + (changes: TextDocumentContentChangeEvent[]) + (initialSourceText: SourceText) + = let applyLspContentChangeOnRoslynSourceText (sourceText: SourceText) (change: TextDocumentContentChangeEvent) = match change with @@ -30,25 +31,21 @@ module TextDocumentSync = |> sourceText.Lines.GetTextSpan TextChange(changeTextSpan, change.Text) |> sourceText.WithChanges - | U2.C2 changeWoRange -> - SourceText.From(changeWoRange.Text) + | U2.C2 changeWoRange -> SourceText.From(changeWoRange.Text) changes |> Seq.fold applyLspContentChangeOnRoslynSourceText initialSourceText let provider (_: ClientCapabilities) : TextDocumentSyncOptions option = { TextDocumentSyncOptions.Default with OpenClose = Some true - Save = Some (U2.C2 { IncludeText = Some true }) - Change = Some TextDocumentSyncKind.Incremental - } + Save = Some(U2.C2 { IncludeText = Some true }) + Change = Some TextDocumentSyncKind.Incremental } |> Some - let didOpen (context: ServerRequestContext) - (openParams: DidOpenTextDocumentParams) - : Async> = + let didOpen (context: ServerRequestContext) (openParams: DidOpenTextDocumentParams) : Async> = match context.GetDocumentForUriOfType AnyDocument openParams.TextDocument.Uri with - | Some (doc, docType) -> + | Some(doc, docType) -> match docType with | UserDocument -> // we want to load the document in case it has been changed since we have the solution loaded @@ -56,13 +53,12 @@ module TextDocumentSync = // went out of sync with editor let updatedDoc = SourceText.From(openParams.TextDocument.Text) |> doc.WithText - context.Emit(OpenDocAdd (openParams.TextDocument.Uri, openParams.TextDocument.Version, DateTime.Now)) + context.Emit(OpenDocAdd(openParams.TextDocument.Uri, openParams.TextDocument.Version, DateTime.Now)) context.Emit(SolutionChange updatedDoc.Project.Solution) Ok() |> async.Return - | _ -> - Ok() |> async.Return + | _ -> Ok() |> async.Return | None -> let docFilePathMaybe = Util.tryParseFileUri openParams.TextDocument.Uri @@ -70,16 +66,11 @@ module TextDocumentSync = match docFilePathMaybe with | Some docFilePath -> async { // ok, this document is not in solution, register a new document - let! newDocMaybe = - tryAddDocument - logger - docFilePath - openParams.TextDocument.Text - context.Solution + let! newDocMaybe = tryAddDocument logger docFilePath openParams.TextDocument.Text context.Solution match newDocMaybe with | Some newDoc -> - context.Emit(OpenDocAdd (openParams.TextDocument.Uri, openParams.TextDocument.Version, DateTime.Now)) + context.Emit(OpenDocAdd(openParams.TextDocument.Uri, openParams.TextDocument.Version, DateTime.Now)) context.Emit(SolutionChange newDoc.Project.Solution) | None -> () @@ -87,71 +78,63 @@ module TextDocumentSync = return Ok() } - | None -> - Ok() |> async.Return + | None -> Ok() |> async.Return - let didChange (context: ServerRequestContext) - (changeParams: DidChangeTextDocumentParams) - : Async> = - async { + let didChange (context: ServerRequestContext) (changeParams: DidChangeTextDocumentParams) : Async> = async { let docMaybe = context.GetUserDocument changeParams.TextDocument.Uri + match docMaybe with | None -> () | Some doc -> - let! ct = Async.CancellationToken - let! sourceText = doc.GetTextAsync(ct) |> Async.AwaitTask - //logMessage (sprintf "TextDocumentDidChange: changeParams: %s" (string changeParams)) - //logMessage (sprintf "TextDocumentDidChange: sourceText: %s" (string sourceText)) + let! ct = Async.CancellationToken + let! sourceText = doc.GetTextAsync(ct) |> Async.AwaitTask + //logMessage (sprintf "TextDocumentDidChange: changeParams: %s" (string changeParams)) + //logMessage (sprintf "TextDocumentDidChange: sourceText: %s" (string sourceText)) - let updatedSourceText = sourceText |> applyLspContentChangesOnRoslynSourceText changeParams.ContentChanges - let updatedDoc = doc.WithText(updatedSourceText) + let updatedSourceText = + sourceText + |> applyLspContentChangesOnRoslynSourceText changeParams.ContentChanges - //logMessage (sprintf "TextDocumentDidChange: newSourceText: %s" (string updatedSourceText)) + let updatedDoc = doc.WithText(updatedSourceText) - let updatedSolution = updatedDoc.Project.Solution + //logMessage (sprintf "TextDocumentDidChange: newSourceText: %s" (string updatedSourceText)) - context.Emit(SolutionChange updatedSolution) - context.Emit(OpenDocAdd (changeParams.TextDocument.Uri, changeParams.TextDocument.Version, DateTime.Now)) + let updatedSolution = updatedDoc.Project.Solution + + context.Emit(SolutionChange updatedSolution) + context.Emit(OpenDocAdd(changeParams.TextDocument.Uri, changeParams.TextDocument.Version, DateTime.Now)) return Ok() } - let didClose (context: ServerRequestContext) - (closeParams: DidCloseTextDocumentParams) - : Async> = + let didClose (context: ServerRequestContext) (closeParams: DidCloseTextDocumentParams) : Async> = context.Emit(OpenDocRemove closeParams.TextDocument.Uri) Ok() |> async.Return - let willSave (_context: ServerRequestContext) (_p: WillSaveTextDocumentParams): Async> = async { - return Ok () + let willSave (_context: ServerRequestContext) (_p: WillSaveTextDocumentParams) : Async> = async { + return Ok() } - let willSaveWaitUntil (_context: ServerRequestContext) (_p: WillSaveTextDocumentParams): AsyncLspResult = async { - return LspResult.notImplemented - } + let willSaveWaitUntil + (_context: ServerRequestContext) + (_p: WillSaveTextDocumentParams) + : AsyncLspResult = + async { return LspResult.notImplemented } - let didSave (context: ServerRequestContext) - (saveParams: DidSaveTextDocumentParams) - : Async> = + let didSave (context: ServerRequestContext) (saveParams: DidSaveTextDocumentParams) : Async> = // we need to add this file to solution if not already let doc = context.GetDocument saveParams.TextDocument.Uri match doc with - | Some _ -> - Ok() |> async.Return + | Some _ -> Ok() |> async.Return | None -> async { let docFilePath = Util.parseFileUri saveParams.TextDocument.Uri - let! newDocMaybe = - tryAddDocument - logger - docFilePath - saveParams.Text.Value - context.Solution + let! newDocMaybe = tryAddDocument logger docFilePath saveParams.Text.Value context.Solution match newDocMaybe with | Some newDoc -> - context.Emit(OpenDocTouch (saveParams.TextDocument.Uri, DateTime.Now)) + context.Emit(OpenDocTouch(saveParams.TextDocument.Uri, DateTime.Now)) context.Emit(SolutionChange newDoc.Project.Solution) | None -> () diff --git a/src/CSharpLanguageServer/Handlers/TypeDefinition.fs b/src/CSharpLanguageServer/Handlers/TypeDefinition.fs index 88e00b37..9241c863 100644 --- a/src/CSharpLanguageServer/Handlers/TypeDefinition.fs +++ b/src/CSharpLanguageServer/Handlers/TypeDefinition.fs @@ -9,30 +9,30 @@ open CSharpLanguageServer.Util [] module TypeDefinition = - let provider (_: ClientCapabilities) : U3 option = - Some (U3.C1 true) + let provider (_: ClientCapabilities) : U3 option = + Some(U3.C1 true) - let handle (context: ServerRequestContext) (p: TypeDefinitionParams) : Async option>> = async { - match! context.FindSymbol' p.TextDocument.Uri p.Position with - | None -> - return None |> LspResult.success - | Some (symbol, doc) -> - let typeSymbol = - match symbol with - | :? ILocalSymbol as localSymbol -> [localSymbol.Type] - | :? IFieldSymbol as fieldSymbol -> [fieldSymbol.Type] - | :? IPropertySymbol as propertySymbol -> [propertySymbol.Type] - | :? IParameterSymbol as parameterSymbol -> [parameterSymbol.Type] - | _ -> [] - let! locations = - typeSymbol - |> Seq.map (flip context.ResolveSymbolLocations (Some doc.Project)) - |> Async.Parallel - |> Async.map (Seq.collect id >> Seq.toArray) - return - locations - |> Declaration.C2 - |> U2.C1 - |> Some - |> LspResult.success - } + let handle + (context: ServerRequestContext) + (p: TypeDefinitionParams) + : Async option>> = + async { + match! context.FindSymbol' p.TextDocument.Uri p.Position with + | None -> return None |> LspResult.success + | Some(symbol, doc) -> + let typeSymbol = + match symbol with + | :? ILocalSymbol as localSymbol -> [ localSymbol.Type ] + | :? IFieldSymbol as fieldSymbol -> [ fieldSymbol.Type ] + | :? IPropertySymbol as propertySymbol -> [ propertySymbol.Type ] + | :? IParameterSymbol as parameterSymbol -> [ parameterSymbol.Type ] + | _ -> [] + + let! locations = + typeSymbol + |> Seq.map (flip context.ResolveSymbolLocations (Some doc.Project)) + |> Async.Parallel + |> Async.map (Seq.collect id >> Seq.toArray) + + return locations |> Declaration.C2 |> U2.C1 |> Some |> LspResult.success + } diff --git a/src/CSharpLanguageServer/Handlers/TypeHierarchy.fs b/src/CSharpLanguageServer/Handlers/TypeHierarchy.fs index 94980c18..9a387f76 100644 --- a/src/CSharpLanguageServer/Handlers/TypeHierarchy.fs +++ b/src/CSharpLanguageServer/Handlers/TypeHierarchy.fs @@ -16,50 +16,69 @@ module TypeHierarchy = | _ -> false let provider (_: ClientCapabilities) : U3 option = - Some (U3.C1 true) + Some(U3.C1 true) - let prepare (context: ServerRequestContext) (p: TypeHierarchyPrepareParams) : AsyncLspResult = async { - match! context.FindSymbol p.TextDocument.Uri p.Position with - | Some symbol when isTypeSymbol symbol -> - let! itemList = TypeHierarchyItem.fromSymbol context.ResolveSymbolLocations symbol - return itemList |> List.toArray |> Some |> LspResult.success - | _ -> - return None |> LspResult.success - } + let prepare + (context: ServerRequestContext) + (p: TypeHierarchyPrepareParams) + : AsyncLspResult = + async { + match! context.FindSymbol p.TextDocument.Uri p.Position with + | Some symbol when isTypeSymbol symbol -> + let! itemList = TypeHierarchyItem.fromSymbol context.ResolveSymbolLocations symbol + return itemList |> List.toArray |> Some |> LspResult.success + | _ -> return None |> LspResult.success + } let supertypes - (context: ServerRequestContext) - (p: TypeHierarchySupertypesParams) - : AsyncLspResult = async { - match! context.FindSymbol p.Item.Uri p.Item.Range.Start with - | Some symbol when isTypeSymbol symbol -> - let typeSymbol = symbol :?> INamedTypeSymbol - let baseType = - typeSymbol.BaseType - |> Option.ofObj - |> Option.filter (fun sym -> sym.SpecialType = SpecialType.None) - |> Option.toList - let interfaces = Seq.toList typeSymbol.Interfaces - let supertypes = baseType @ interfaces - let! items = supertypes |> Seq.map (TypeHierarchyItem.fromSymbol context.ResolveSymbolLocations) |> Async.Parallel - return items |> Seq.collect id |> Seq.toArray |> Some |> LspResult.success - | _ -> return None |> LspResult.success - } + (context: ServerRequestContext) + (p: TypeHierarchySupertypesParams) + : AsyncLspResult = + async { + match! context.FindSymbol p.Item.Uri p.Item.Range.Start with + | Some symbol when isTypeSymbol symbol -> + let typeSymbol = symbol :?> INamedTypeSymbol - let subtypes (context: ServerRequestContext) (p: TypeHierarchySubtypesParams) : AsyncLspResult = async { - match! context.FindSymbol p.Item.Uri p.Item.Range.Start with - | Some symbol when isTypeSymbol symbol -> - let typeSymbol = symbol :?> INamedTypeSymbol - // We only want immediately derived classes/interfaces/implementations here (we only need - // subclasses not subclasses' subclasses) - let! subtypes = - [ context.FindDerivedClasses' typeSymbol false - context.FindDerivedInterfaces' typeSymbol false - context.FindImplementations' typeSymbol false ] - |> Async.Parallel - |> Async.map (Seq.collect id >> Seq.toList) - let! items = subtypes |> Seq.map (TypeHierarchyItem.fromSymbol context.ResolveSymbolLocations) |> Async.Parallel - return items |> Seq.collect id |> Seq.toArray |> Some |> LspResult.success - | _ -> - return None |> LspResult.success - } + let baseType = + typeSymbol.BaseType + |> Option.ofObj + |> Option.filter (fun sym -> sym.SpecialType = SpecialType.None) + |> Option.toList + + let interfaces = Seq.toList typeSymbol.Interfaces + let supertypes = baseType @ interfaces + + let! items = + supertypes + |> Seq.map (TypeHierarchyItem.fromSymbol context.ResolveSymbolLocations) + |> Async.Parallel + + return items |> Seq.collect id |> Seq.toArray |> Some |> LspResult.success + | _ -> return None |> LspResult.success + } + + let subtypes + (context: ServerRequestContext) + (p: TypeHierarchySubtypesParams) + : AsyncLspResult = + async { + match! context.FindSymbol p.Item.Uri p.Item.Range.Start with + | Some symbol when isTypeSymbol symbol -> + let typeSymbol = symbol :?> INamedTypeSymbol + // We only want immediately derived classes/interfaces/implementations here (we only need + // subclasses not subclasses' subclasses) + let! subtypes = + [ context.FindDerivedClasses' typeSymbol false + context.FindDerivedInterfaces' typeSymbol false + context.FindImplementations' typeSymbol false ] + |> Async.Parallel + |> Async.map (Seq.collect id >> Seq.toList) + + let! items = + subtypes + |> Seq.map (TypeHierarchyItem.fromSymbol context.ResolveSymbolLocations) + |> Async.Parallel + + return items |> Seq.collect id |> Seq.toArray |> Some |> LspResult.success + | _ -> return None |> LspResult.success + } diff --git a/src/CSharpLanguageServer/Handlers/Workspace.fs b/src/CSharpLanguageServer/Handlers/Workspace.fs index d1e5d45d..35335771 100644 --- a/src/CSharpLanguageServer/Handlers/Workspace.fs +++ b/src/CSharpLanguageServer/Handlers/Workspace.fs @@ -21,8 +21,7 @@ module Workspace = let provider (_: ClientCapabilities) : ServerCapabilitiesWorkspace option = { WorkspaceFolders = None - FileOperations = None - } + FileOperations = None } |> Some let dynamicRegistrationForDidChangeWatchedFiles (clientCapabilities: ClientCapabilities) = @@ -31,13 +30,13 @@ module Workspace = |> Option.bind (fun x -> x.DynamicRegistration) |> Option.defaultValue false - let didChangeWatchedFilesRegistration (clientCapabilities: ClientCapabilities): Registration option = + let didChangeWatchedFilesRegistration (clientCapabilities: ClientCapabilities) : Registration option = match dynamicRegistrationForDidChangeWatchedFiles clientCapabilities with | false -> None | true -> let fileSystemWatcher = { GlobPattern = U2.C1 "**/*.{cs,csproj,sln,slnx}" - Kind = Some (WatchKind.Create ||| WatchKind.Change ||| WatchKind.Delete) } + Kind = Some(WatchKind.Create ||| WatchKind.Change ||| WatchKind.Delete) } let registerOptions: DidChangeWatchedFilesRegistrationOptions = { Watchers = [| fileSystemWatcher |] } @@ -57,17 +56,15 @@ module Workspace = | None -> let docFilePathMaybe = uri |> Util.tryParseFileUri + match docFilePathMaybe with | Some docFilePath -> // ok, this document is not on solution, register a new one let fileText = docFilePath |> File.ReadAllText - let! newDocMaybe = tryAddDocument logger - docFilePath - fileText - context.Solution + let! newDocMaybe = tryAddDocument logger docFilePath fileText context.Solution + match newDocMaybe with - | Some newDoc -> - context.Emit(SolutionChange newDoc.Project.Solution) + | Some newDoc -> context.Emit(SolutionChange newDoc.Project.Solution) | None -> () | None -> () } @@ -81,52 +78,52 @@ module Workspace = context.Emit(OpenDocRemove uri) | None -> () - let didChangeWatchedFiles (context: ServerRequestContext) - (p: DidChangeWatchedFilesParams) - : Async> = async { - for change in p.Changes do - match Path.GetExtension(change.Uri) with - | ".csproj" -> - do! context.WindowShowMessage "change to .csproj detected, will reload solution" - context.Emit(SolutionReloadRequest (TimeSpan.FromSeconds(5:int64))) - - | ".sln" | ".slnx" -> - do! context.WindowShowMessage "change to .sln detected, will reload solution" - context.Emit(SolutionReloadRequest (TimeSpan.FromSeconds(5:int64))) - - | ".cs" -> - match change.Type with - | FileChangeType.Created -> - do! tryReloadDocumentOnUri logger context change.Uri - | FileChangeType.Changed -> - do! tryReloadDocumentOnUri logger context change.Uri - | FileChangeType.Deleted -> - do removeDocument context change.Uri + let didChangeWatchedFiles + (context: ServerRequestContext) + (p: DidChangeWatchedFilesParams) + : Async> = + async { + for change in p.Changes do + match Path.GetExtension(change.Uri) with + | ".csproj" -> + do! context.WindowShowMessage "change to .csproj detected, will reload solution" + context.Emit(SolutionReloadRequest(TimeSpan.FromSeconds(5: int64))) + + | ".sln" + | ".slnx" -> + do! context.WindowShowMessage "change to .sln detected, will reload solution" + context.Emit(SolutionReloadRequest(TimeSpan.FromSeconds(5: int64))) + + | ".cs" -> + match change.Type with + | FileChangeType.Created -> do! tryReloadDocumentOnUri logger context change.Uri + | FileChangeType.Changed -> do! tryReloadDocumentOnUri logger context change.Uri + | FileChangeType.Deleted -> do removeDocument context change.Uri + | _ -> () + | _ -> () - | _ -> () + return Ok() + } + + let didChangeConfiguration + (context: ServerRequestContext) + (configParams: DidChangeConfigurationParams) + : Async> = + async { - return Ok() - } + let csharpSettings = + configParams.Settings + |> deserialize + |> (fun x -> x.csharp) + |> Option.defaultValue ServerSettingsCSharpDto.Default - let didChangeConfiguration (context: ServerRequestContext) - (configParams: DidChangeConfigurationParams) - : Async> = async { - - let csharpSettings = - configParams.Settings - |> deserialize - |> (fun x -> x.csharp) - |> Option.defaultValue ServerSettingsCSharpDto.Default - - let newServerSettings = { - context.State.Settings with - SolutionPath = csharpSettings.solution - ApplyFormattingOptions = csharpSettings.applyFormattingOptions - |> Option.defaultValue false - } + let newServerSettings = + { context.State.Settings with + SolutionPath = csharpSettings.solution + ApplyFormattingOptions = csharpSettings.applyFormattingOptions |> Option.defaultValue false } - context.Emit(SettingsChange newServerSettings) + context.Emit(SettingsChange newServerSettings) - return Ok() - } + return Ok() + } diff --git a/src/CSharpLanguageServer/Handlers/WorkspaceSymbol.fs b/src/CSharpLanguageServer/Handlers/WorkspaceSymbol.fs index 58c6f6eb..9bfd63a2 100644 --- a/src/CSharpLanguageServer/Handlers/WorkspaceSymbol.fs +++ b/src/CSharpLanguageServer/Handlers/WorkspaceSymbol.fs @@ -11,28 +11,28 @@ open CSharpLanguageServer.Conversions [] module WorkspaceSymbol = - let provider (_: ClientCapabilities) : U2 option = - Some (U2.C1 true) - - let handle (context: ServerRequestContext) (p: WorkspaceSymbolParams) : AsyncLspResult option> = async { - let pattern = - if String.IsNullOrEmpty(p.Query) then - None - else - Some p.Query - - let! symbols = context.FindSymbols pattern - return - symbols - |> Seq.map (SymbolInformation.fromSymbol SymbolDisplayFormat.MinimallyQualifiedFormat) - |> Seq.collect id - // TODO: make 100 configurable? - |> Seq.truncate 100 - |> Seq.toArray - |> U2.C1 - |> Some - |> LspResult.success - } + let provider (_: ClientCapabilities) : U2 option = Some(U2.C1 true) + + let handle + (context: ServerRequestContext) + (p: WorkspaceSymbolParams) + : AsyncLspResult option> = + async { + let pattern = if String.IsNullOrEmpty(p.Query) then None else Some p.Query + + let! symbols = context.FindSymbols pattern + + return + symbols + |> Seq.map (SymbolInformation.fromSymbol SymbolDisplayFormat.MinimallyQualifiedFormat) + |> Seq.collect id + // TODO: make 100 configurable? + |> Seq.truncate 100 + |> Seq.toArray + |> U2.C1 + |> Some + |> LspResult.success + } let resolve (_context: ServerRequestContext) (_p: WorkspaceSymbol) : AsyncLspResult = LspResult.notImplemented |> async.Return diff --git a/src/CSharpLanguageServer/Logging.fs b/src/CSharpLanguageServer/Logging.fs index 4dbb04d0..b8975a50 100644 --- a/src/CSharpLanguageServer/Logging.fs +++ b/src/CSharpLanguageServer/Logging.fs @@ -16,12 +16,10 @@ module Logging = let configureConsoleLogger (opts: ConsoleLoggerOptions) = opts.LogToStandardErrorThreshold <- LogLevel.Trace // send everything to stderr - builder - .AddSimpleConsole(configureSimpleConsole) - .AddConsole(configureConsoleLogger) - .SetMinimumLevel(minimumLevel) - |> ignore + builder.AddSimpleConsole(configureSimpleConsole).AddConsole(configureConsoleLogger).SetMinimumLevel + minimumLevel + |> ignore - loggerFactory <- LoggerFactory.Create(createConsoleLogger) |> Some + loggerFactory <- LoggerFactory.Create createConsoleLogger |> Some let getLoggerByName name = loggerFactory.Value.CreateLogger name diff --git a/src/CSharpLanguageServer/Lsp/Client.fs b/src/CSharpLanguageServer/Lsp/Client.fs index 2b307937..5b2bdcea 100644 --- a/src/CSharpLanguageServer/Lsp/Client.fs +++ b/src/CSharpLanguageServer/Lsp/Client.fs @@ -6,43 +6,43 @@ open Ionide.LanguageServerProtocol.Server type CSharpLspClient(sendServerNotification: ClientNotificationSender, sendServerRequest: ClientRequestSender) = inherit LspClient() - override __.WindowShowMessage(p) = + override __.WindowShowMessage p = sendServerNotification "window/showMessage" (box p) |> Async.Ignore // TODO: Send notifications / requests to client only if client support it - override __.WindowShowMessageRequest(p) = + override __.WindowShowMessageRequest p = sendServerRequest.Send "window/showMessageRequest" (box p) - override __.WindowLogMessage(p) = + override __.WindowLogMessage p = sendServerNotification "window/logMessage" (box p) |> Async.Ignore - override __.TelemetryEvent(p) = + override __.TelemetryEvent p = sendServerNotification "telemetry/event" (box p) |> Async.Ignore - override __.ClientRegisterCapability(p) = + override __.ClientRegisterCapability p = sendServerRequest.Send "client/registerCapability" (box p) - override __.ClientUnregisterCapability(p) = + override __.ClientUnregisterCapability p = sendServerRequest.Send "client/unregisterCapability" (box p) override __.WorkspaceWorkspaceFolders() = sendServerRequest.Send "workspace/workspaceFolders" () - override __.WorkspaceConfiguration(p) = + override __.WorkspaceConfiguration p = sendServerRequest.Send "workspace/configuration" (box p) - override __.WorkspaceApplyEdit(p) = + override __.WorkspaceApplyEdit p = sendServerRequest.Send "workspace/applyEdit" (box p) override __.WorkspaceSemanticTokensRefresh() = sendServerNotification "workspace/semanticTokens/refresh" () - override __.TextDocumentPublishDiagnostics(p) = + override __.TextDocumentPublishDiagnostics p = sendServerNotification "textDocument/publishDiagnostics" (box p) |> Async.Ignore - override __.WindowWorkDoneProgressCreate(createParams) = + override __.WindowWorkDoneProgressCreate createParams = sendServerRequest.Send "window/workDoneProgress/create" (box createParams) - override __.Progress(progressParams) = + override __.Progress progressParams = sendServerNotification "$/progress" (box progressParams) |> Async.Ignore diff --git a/src/CSharpLanguageServer/Lsp/Server.fs b/src/CSharpLanguageServer/Lsp/Server.fs index a398ef3c..7be56891 100644 --- a/src/CSharpLanguageServer/Lsp/Server.fs +++ b/src/CSharpLanguageServer/Lsp/Server.fs @@ -28,202 +28,363 @@ module LspUtils = open LspUtils -type CSharpLspServer( - lspClient: CSharpLspClient, - settings: ServerSettings - ) = +type CSharpLspServer(lspClient: CSharpLspClient, settings: ServerSettings) = let _logger = Logging.getLoggerByName "Server" - let stateActor = MailboxProcessor.Start( - serverEventLoop - { ServerState.Empty with Settings = settings }) + let stateActor = + MailboxProcessor.Start( + serverEventLoop + { ServerState.Empty with + Settings = settings } + ) let getDocumentForUriFromCurrentState docType uri = - stateActor.PostAndAsyncReply(fun rc -> GetDocumentOfTypeForUri (docType, uri, rc)) + stateActor.PostAndAsyncReply(fun rc -> GetDocumentOfTypeForUri(docType, uri, rc)) - let mutable timer: System.Threading.Timer option = None + let mutable timer: Threading.Timer option = None let setupTimer () = - timer <- Some (new System.Threading.Timer( - System.Threading.TimerCallback( - fun _ -> do stateActor.Post(PeriodicTimerTick)), - null, dueTime=100, period=250)) + timer <- + Some( + new Threading.Timer( + Threading.TimerCallback(fun _ -> do stateActor.Post PeriodicTimerTick), + null, + dueTime = 100, + period = 250 + ) + ) let mutable _workspaceFolders: WorkspaceFolder list = [] - let withContext - requestName - requestMode - (handlerFn: ServerRequestContext -> 'a -> Async>) - param = + let withContext requestName requestMode (handlerFn: ServerRequestContext -> 'a -> Async>) param = // we want to be careful and lock solution for change immediately w/o entering async/returing an `async` workflow // // StreamJsonRpc lib we're using in Ionide.LanguageServerProtocol guarantees that it will not call another // handler until previous one returns a Task (in our case -- F# `async` object.) - let startRequest rc = StartRequest (requestName, requestMode, 0, rc) - let requestId, semaphore = stateActor.PostAndReply(startRequest) + let startRequest rc = + StartRequest(requestName, requestMode, 0, rc) + + let requestId, semaphore = stateActor.PostAndReply startRequest let stateAcquisitionAndHandlerInvocation = async { do! semaphore.WaitAsync() |> Async.AwaitTask - let! state = stateActor.PostAndAsyncReply(GetState) + let! state = stateActor.PostAndAsyncReply GetState let context = ServerRequestContext(requestId, state, stateActor.Post) return! handlerFn context param } - let wrapExceptionAsLspResult op = - async { - let! resultOrExn = op |> Async.Catch - - return - match resultOrExn with - | Choice1Of2 result -> result - | Choice2Of2 exn -> - match exn with - | :? TaskCanceledException -> LspResult.requestCancelled - | :? OperationCanceledException -> LspResult.requestCancelled - | _ -> LspResult.internalError (string exn) - } + let wrapExceptionAsLspResult op = async { + let! resultOrExn = op |> Async.Catch + + return + match resultOrExn with + | Choice1Of2 result -> result + | Choice2Of2 exn -> + match exn with + | :? TaskCanceledException -> LspResult.requestCancelled + | :? OperationCanceledException -> LspResult.requestCancelled + | _ -> LspResult.internalError (string exn) + } stateAcquisitionAndHandlerInvocation |> wrapExceptionAsLspResult |> unwindProtect (fun () -> stateActor.Post(FinishRequest requestId)) - let withReadOnlyContext requestName handlerFn = withContext requestName ReadOnly handlerFn - let withReadWriteContext requestName handlerFn = withContext requestName ReadWrite handlerFn + let withReadOnlyContext requestName handlerFn = + withContext requestName ReadOnly handlerFn + + let withReadWriteContext requestName handlerFn = + withContext requestName ReadWrite handlerFn let ignoreResult handlerFn = async { let! _ = handlerFn return () } - let getDynamicRegistrations (clientCapabilities: ClientCapabilities): Registration list = + let getDynamicRegistrations (clientCapabilities: ClientCapabilities) : Registration list = [ Workspace.didChangeWatchedFilesRegistration ] |> List.choose (fun regFunc -> regFunc clientCapabilities) - let getServerCapabilities - (lspClient: InitializeParams) = - { ServerCapabilities.Default with - TextDocumentSync = TextDocumentSync.provider lspClient.Capabilities |> Option.map U2.C1 - CompletionProvider = Completion.provider lspClient.Capabilities - HoverProvider = Hover.provider lspClient.Capabilities - SignatureHelpProvider = SignatureHelp.provider lspClient.Capabilities - // DeclarationProvider = Declaration.provider lspClient.Capabilities - DefinitionProvider = Definition.provider lspClient.Capabilities - TypeDefinitionProvider = TypeDefinition.provider lspClient.Capabilities - ImplementationProvider = Implementation.provider lspClient.Capabilities - ReferencesProvider = References.provider lspClient.Capabilities - DocumentHighlightProvider = DocumentHighlight.provider lspClient.Capabilities - DocumentSymbolProvider = DocumentSymbol.provider lspClient.Capabilities - CodeActionProvider = CodeAction.provider lspClient.Capabilities - CodeLensProvider = CodeLens.provider lspClient.Capabilities - // DocumentLinkProvider = DocumentLink.provider lspClient.Capabilities - // ColorProvider = Color.provider lspClient.Capabilities - DocumentFormattingProvider = DocumentFormatting.provider lspClient.Capabilities - DocumentRangeFormattingProvider = DocumentRangeFormatting.provider lspClient.Capabilities - DocumentOnTypeFormattingProvider = DocumentOnTypeFormatting.provider lspClient.Capabilities - RenameProvider = Rename.provider lspClient.Capabilities - // FoldingRangeProvider = FoldingRange.provider lspClient.Capabilities - ExecuteCommandProvider = ExecuteCommand.provider lspClient.Capabilities - // SelectionRangeProvider = SelectionRange.provider lspClient.Capabilities - // LinkedEditingRangeProvider = LinkedEditingRange.provider lspClient.Capabilities - CallHierarchyProvider = CallHierarchy.provider lspClient.Capabilities - SemanticTokensProvider = SemanticTokens.provider lspClient.Capabilities - // MonikerProvider = Moniker.provider lspClient.Capabilities - TypeHierarchyProvider = TypeHierarchy.provider lspClient.Capabilities - // InlineValueProvider = InlineValue.provider lspClient.Capabilities - InlayHintProvider = InlayHint.provider lspClient.Capabilities - DiagnosticProvider = Diagnostic.provider lspClient.Capabilities - WorkspaceSymbolProvider = WorkspaceSymbol.provider lspClient.Capabilities - Workspace = Workspace.provider lspClient.Capabilities - } + let getServerCapabilities (lspClient: InitializeParams) = + { ServerCapabilities.Default with + TextDocumentSync = TextDocumentSync.provider lspClient.Capabilities |> Option.map U2.C1 + CompletionProvider = Completion.provider lspClient.Capabilities + HoverProvider = Hover.provider lspClient.Capabilities + SignatureHelpProvider = SignatureHelp.provider lspClient.Capabilities + // DeclarationProvider = Declaration.provider lspClient.Capabilities + DefinitionProvider = Definition.provider lspClient.Capabilities + TypeDefinitionProvider = TypeDefinition.provider lspClient.Capabilities + ImplementationProvider = Implementation.provider lspClient.Capabilities + ReferencesProvider = References.provider lspClient.Capabilities + DocumentHighlightProvider = DocumentHighlight.provider lspClient.Capabilities + DocumentSymbolProvider = DocumentSymbol.provider lspClient.Capabilities + CodeActionProvider = CodeAction.provider lspClient.Capabilities + CodeLensProvider = CodeLens.provider lspClient.Capabilities + // DocumentLinkProvider = DocumentLink.provider lspClient.Capabilities + // ColorProvider = Color.provider lspClient.Capabilities + DocumentFormattingProvider = DocumentFormatting.provider lspClient.Capabilities + DocumentRangeFormattingProvider = DocumentRangeFormatting.provider lspClient.Capabilities + DocumentOnTypeFormattingProvider = DocumentOnTypeFormatting.provider lspClient.Capabilities + RenameProvider = Rename.provider lspClient.Capabilities + // FoldingRangeProvider = FoldingRange.provider lspClient.Capabilities + ExecuteCommandProvider = ExecuteCommand.provider lspClient.Capabilities + // SelectionRangeProvider = SelectionRange.provider lspClient.Capabilities + // LinkedEditingRangeProvider = LinkedEditingRange.provider lspClient.Capabilities + CallHierarchyProvider = CallHierarchy.provider lspClient.Capabilities + SemanticTokensProvider = SemanticTokens.provider lspClient.Capabilities + // MonikerProvider = Moniker.provider lspClient.Capabilities + TypeHierarchyProvider = TypeHierarchy.provider lspClient.Capabilities + // InlineValueProvider = InlineValue.provider lspClient.Capabilities + InlayHintProvider = InlayHint.provider lspClient.Capabilities + DiagnosticProvider = Diagnostic.provider lspClient.Capabilities + WorkspaceSymbolProvider = WorkspaceSymbol.provider lspClient.Capabilities + Workspace = Workspace.provider lspClient.Capabilities } interface ICSharpLspServer with override __.Dispose() = () - override __.Initialize(p) = + override __.Initialize p = let serverCapabilities = getServerCapabilities p - p |> withReadWriteContext "initialize" (Initialization.handleInitialize lspClient setupTimer serverCapabilities) - override __.Initialized(_) = - () |> withReadWriteContext "initialized" (Initialization.handleInitialized lspClient stateActor getDynamicRegistrations) - |> ignoreResult + p + |> withReadWriteContext + "initialize" + (Initialization.handleInitialize lspClient setupTimer serverCapabilities) + + override __.Initialized _ = + () + |> withReadWriteContext + "initialized" + (Initialization.handleInitialized lspClient stateActor getDynamicRegistrations) + |> ignoreResult + + override __.Shutdown() = + () |> withReadWriteContext "shutdown" Initialization.handleShutdown - override __.Shutdown() = () |> withReadWriteContext "shutdown" Initialization.handleShutdown override __.Exit() = ignoreNotification - override __.TextDocumentHover(p) = p |> withReadOnlyContext "textDocument/hover" Hover.handle - override __.TextDocumentDidOpen(p) = p |> withReadOnlyContext "textDocument/didOpen" TextDocumentSync.didOpen |> ignoreResult - override __.TextDocumentDidChange(p) = p |> withReadWriteContext "textDocument/didChange" TextDocumentSync.didChange |> ignoreResult - override __.TextDocumentDidClose(p) = p |> withReadWriteContext "textDocument/didClose" TextDocumentSync.didClose |> ignoreResult - override __.TextDocumentWillSave(p) = p |> withReadWriteContext "textDocument/willSave" TextDocumentSync.willSave |> ignoreResult - override __.TextDocumentWillSaveWaitUntil(p) = p |> withReadWriteContext "textDocument/willSaveWaitUntil" TextDocumentSync.willSaveWaitUntil - override __.TextDocumentDidSave(p) = p |> withReadWriteContext "textDocument/didSave" TextDocumentSync.didSave |> ignoreResult - override __.TextDocumentCompletion(p) = p |> withReadOnlyContext "textDocument/completion" Completion.handle - override __.CompletionItemResolve(p) = p |> withReadOnlyContext "completionItem/resolve" Completion.resolve - override __.TextDocumentPrepareRename(p) = p |> withReadOnlyContext "textDocument/prepareRename" Rename.prepare - override __.TextDocumentRename(p) = p |> withReadOnlyContext "textDocument/rename" Rename.handle - override __.TextDocumentDefinition(p) = p |> withReadOnlyContext "textDocument/definition" Definition.handle - override __.TextDocumentReferences(p) = p |> withReadOnlyContext "textDocument/references" References.handle - override __.TextDocumentDocumentHighlight(p) = p |> withReadOnlyContext "textDocument/documentHighlight" DocumentHighlight.handle - override __.TextDocumentDocumentLink(p) = p |> withReadOnlyContext "textDocument/documentLink" DocumentLink.handle - override __.DocumentLinkResolve(p) = p |> withReadOnlyContext "documentLink/resolve" DocumentLink.resolve - override __.TextDocumentTypeDefinition(p) = p |> withReadOnlyContext "textDocument/typeDefinition" TypeDefinition.handle - override __.TextDocumentImplementation(p) = p |> withReadOnlyContext "textDocument/implementation" Implementation.handle - override __.TextDocumentCodeAction(p) = p |> withReadOnlyContext "textDocument/codeAction" CodeAction.handle - override __.CodeActionResolve(p) = p |> withReadOnlyContext "codeAction/resolve" CodeAction.resolve - override __.TextDocumentCodeLens(p) = p |> withReadOnlyContext "textDocument/codeLens" CodeLens.handle - override __.CodeLensResolve(p) = p |> withReadOnlyContext "codeLens/resolve" CodeLens.resolve - override __.TextDocumentSignatureHelp(p) = p |> withReadOnlyContext "textDocument/signatureHelp" SignatureHelp.handle - override __.TextDocumentDocumentColor(p) = p |> withReadOnlyContext "textDocument/documentColor" Color.handle - override __.TextDocumentColorPresentation(p) = p |> withReadOnlyContext "textDocument/colorPresentation" Color.present - override __.TextDocumentFormatting(p) = p |> withReadOnlyContext "textDocument/formatting" DocumentFormatting.handle - override __.TextDocumentRangeFormatting(p) = p |> withReadOnlyContext "textDocument/rangeFormatting" DocumentRangeFormatting.handle - override __.TextDocumentOnTypeFormatting(p) = p |> withReadOnlyContext "textDocument/onTypeFormatting" DocumentOnTypeFormatting.handle - override __.TextDocumentDocumentSymbol(p) = p |> withReadOnlyContext "textDocument/documentSymbol" DocumentSymbol.handle - override __.WorkspaceDidChangeWatchedFiles(p) = p |> withReadWriteContext "workspace/didChangeWatchedFiles" Workspace.didChangeWatchedFiles |> ignoreResult - override __.WorkspaceDidChangeWorkspaceFolders(_p) = ignoreNotification - override __.WorkspaceDidChangeConfiguration(p) = p |> withReadWriteContext "workspace/didChangeConfiguration" Workspace.didChangeConfiguration |> ignoreResult - override __.WorkspaceWillCreateFiles(_) = () |> withReadOnlyContext "workspace/willCreateFiles" (fun _ _ -> notImplemented) - override __.WorkspaceDidCreateFiles(_) = ignoreNotification - override __.WorkspaceWillRenameFiles(_) = () |> withReadOnlyContext "workspace/willRenameFiles" (fun _ _ -> notImplemented) - override __.WorkspaceDidRenameFiles(_) = ignoreNotification - override __.WorkspaceWillDeleteFiles(_) = () |> withReadOnlyContext "workspace/willDeleteFiles" (fun _ _ -> notImplemented) - override __.WorkspaceDidDeleteFiles(_) = ignoreNotification - override __.WorkspaceSymbol(p) = p |> withReadOnlyContext "workspace/symbol" WorkspaceSymbol.handle - override __.WorkspaceExecuteCommand(p) = p |> withReadOnlyContext "workspace/executeCommand" ExecuteCommand.handle - override __.TextDocumentFoldingRange(p) = p |> withReadOnlyContext "textDocument/foldingRange" FoldingRange.handle - override __.TextDocumentSelectionRange(p) = p |> withReadOnlyContext "textDocument/selectionRange" SelectionRange.handle - override __.TextDocumentSemanticTokensFull(p) = p |> withReadOnlyContext "textDocument/semanticTokens/full" SemanticTokens.handleFull - override __.TextDocumentSemanticTokensFullDelta(p) = p |> withReadOnlyContext "textDocument/semanticTokens/full/delta" SemanticTokens.handleFullDelta - override __.TextDocumentSemanticTokensRange(p) = p |> withReadOnlyContext "textDocument/semanticTokens/range" SemanticTokens.handleRange - override __.TextDocumentInlayHint(p) = p |> withReadOnlyContext "textDocument/inlayHint" InlayHint.handle - override __.InlayHintResolve(p) = p |> withReadOnlyContext "inlayHint/resolve" InlayHint.resolve - override __.WindowWorkDoneProgressCancel (_) = raise (System.NotImplementedException()) - override __.TextDocumentInlineValue(_) = notImplemented - override __.TextDocumentPrepareCallHierarchy(p) = p |> withReadOnlyContext "textDocument/prepareCallHierarchy" CallHierarchy.prepare - override __.CallHierarchyIncomingCalls(p) = p |> withReadOnlyContext "callHierarchy/incomingCalls" CallHierarchy.incomingCalls - override __.CallHierarchyOutgoingCalls(p) = p |> withReadOnlyContext "callHierarchy/outgoingCalls" CallHierarchy.outgoingCalls - override __.TextDocumentPrepareTypeHierarchy(p) = p |> withReadOnlyContext "textDocument/prepareTypeHierarchy" TypeHierarchy.prepare - override __.TypeHierarchySupertypes(p) = p |> withReadOnlyContext "typeHierarchy/supertypes" TypeHierarchy.supertypes - override __.TypeHierarchySubtypes(p) = p |> withReadOnlyContext "typeHierarchy/subtypes" TypeHierarchy.subtypes - override __.TextDocumentDeclaration(p) = p |> withReadOnlyContext "textDocument/declaration" Declaration.handle - override __.WorkspaceDiagnostic(p) = p |> withReadOnlyContext "workspace/diagnostic" Diagnostic.handleWorkspaceDiagnostic - override __.CancelRequest(_) = ignoreNotification - override __.NotebookDocumentDidChange(_) = ignoreNotification - override __.NotebookDocumentDidClose(_) = ignoreNotification - override __.NotebookDocumentDidOpen(_) = ignoreNotification - override __.NotebookDocumentDidSave(_) = ignoreNotification - override __.WorkspaceSymbolResolve(p) = p |> withReadOnlyContext "workspaceSymbol/resolve" WorkspaceSymbol.resolve - override __.TextDocumentDiagnostic(p) = p |> withReadOnlyContext "textDocument/diagnostic" Diagnostic.handle - override __.TextDocumentLinkedEditingRange(p) = p |> withReadOnlyContext "textDocument/linkedEditingRange" LinkedEditingRange.handle - override __.TextDocumentMoniker(p) = p |> withReadOnlyContext "textDocument/moniker" Moniker.handle - override __.Progress(_) = ignoreNotification - override __.SetTrace(_) = ignoreNotification - override __.CSharpMetadata(p) = p |> withReadOnlyContext "csharp/metadata" CSharpMetadata.handle + + override __.TextDocumentHover p = + p |> withReadOnlyContext "textDocument/hover" Hover.handle + + override __.TextDocumentDidOpen p = + p + |> withReadOnlyContext "textDocument/didOpen" TextDocumentSync.didOpen + |> ignoreResult + + override __.TextDocumentDidChange p = + p + |> withReadWriteContext "textDocument/didChange" TextDocumentSync.didChange + |> ignoreResult + + override __.TextDocumentDidClose p = + p + |> withReadWriteContext "textDocument/didClose" TextDocumentSync.didClose + |> ignoreResult + + override __.TextDocumentWillSave p = + p + |> withReadWriteContext "textDocument/willSave" TextDocumentSync.willSave + |> ignoreResult + + override __.TextDocumentWillSaveWaitUntil p = + p + |> withReadWriteContext "textDocument/willSaveWaitUntil" TextDocumentSync.willSaveWaitUntil + + override __.TextDocumentDidSave p = + p + |> withReadWriteContext "textDocument/didSave" TextDocumentSync.didSave + |> ignoreResult + + override __.TextDocumentCompletion p = + p |> withReadOnlyContext "textDocument/completion" Completion.handle + + override __.CompletionItemResolve p = + p |> withReadOnlyContext "completionItem/resolve" Completion.resolve + + override __.TextDocumentPrepareRename p = + p |> withReadOnlyContext "textDocument/prepareRename" Rename.prepare + + override __.TextDocumentRename p = + p |> withReadOnlyContext "textDocument/rename" Rename.handle + + override __.TextDocumentDefinition p = + p |> withReadOnlyContext "textDocument/definition" Definition.handle + + override __.TextDocumentReferences p = + p |> withReadOnlyContext "textDocument/references" References.handle + + override __.TextDocumentDocumentHighlight p = + p + |> withReadOnlyContext "textDocument/documentHighlight" DocumentHighlight.handle + + override __.TextDocumentDocumentLink p = + p |> withReadOnlyContext "textDocument/documentLink" DocumentLink.handle + + override __.DocumentLinkResolve p = + p |> withReadOnlyContext "documentLink/resolve" DocumentLink.resolve + + override __.TextDocumentTypeDefinition p = + p |> withReadOnlyContext "textDocument/typeDefinition" TypeDefinition.handle + + override __.TextDocumentImplementation p = + p |> withReadOnlyContext "textDocument/implementation" Implementation.handle + + override __.TextDocumentCodeAction p = + p |> withReadOnlyContext "textDocument/codeAction" CodeAction.handle + + override __.CodeActionResolve p = + p |> withReadOnlyContext "codeAction/resolve" CodeAction.resolve + + override __.TextDocumentCodeLens p = + p |> withReadOnlyContext "textDocument/codeLens" CodeLens.handle + + override __.CodeLensResolve p = + p |> withReadOnlyContext "codeLens/resolve" CodeLens.resolve + + override __.TextDocumentSignatureHelp p = + p |> withReadOnlyContext "textDocument/signatureHelp" SignatureHelp.handle + + override __.TextDocumentDocumentColor p = + p |> withReadOnlyContext "textDocument/documentColor" Color.handle + + override __.TextDocumentColorPresentation p = + p |> withReadOnlyContext "textDocument/colorPresentation" Color.present + + override __.TextDocumentFormatting p = + p |> withReadOnlyContext "textDocument/formatting" DocumentFormatting.handle + + override __.TextDocumentRangeFormatting p = + p + |> withReadOnlyContext "textDocument/rangeFormatting" DocumentRangeFormatting.handle + + override __.TextDocumentOnTypeFormatting p = + p + |> withReadOnlyContext "textDocument/onTypeFormatting" DocumentOnTypeFormatting.handle + + override __.TextDocumentDocumentSymbol p = + p |> withReadOnlyContext "textDocument/documentSymbol" DocumentSymbol.handle + + override __.WorkspaceDidChangeWatchedFiles p = + p + |> withReadWriteContext "workspace/didChangeWatchedFiles" Workspace.didChangeWatchedFiles + |> ignoreResult + + override __.WorkspaceDidChangeWorkspaceFolders _p = ignoreNotification + + override __.WorkspaceDidChangeConfiguration p = + p + |> withReadWriteContext "workspace/didChangeConfiguration" Workspace.didChangeConfiguration + |> ignoreResult + + override __.WorkspaceWillCreateFiles _ = + () + |> withReadOnlyContext "workspace/willCreateFiles" (fun _ _ -> notImplemented) + + override __.WorkspaceDidCreateFiles _ = ignoreNotification + + override __.WorkspaceWillRenameFiles _ = + () + |> withReadOnlyContext "workspace/willRenameFiles" (fun _ _ -> notImplemented) + + override __.WorkspaceDidRenameFiles _ = ignoreNotification + + override __.WorkspaceWillDeleteFiles _ = + () + |> withReadOnlyContext "workspace/willDeleteFiles" (fun _ _ -> notImplemented) + + override __.WorkspaceDidDeleteFiles _ = ignoreNotification + + override __.WorkspaceSymbol p = + p |> withReadOnlyContext "workspace/symbol" WorkspaceSymbol.handle + + override __.WorkspaceExecuteCommand p = + p |> withReadOnlyContext "workspace/executeCommand" ExecuteCommand.handle + + override __.TextDocumentFoldingRange p = + p |> withReadOnlyContext "textDocument/foldingRange" FoldingRange.handle + + override __.TextDocumentSelectionRange p = + p |> withReadOnlyContext "textDocument/selectionRange" SelectionRange.handle + + override __.TextDocumentSemanticTokensFull p = + p + |> withReadOnlyContext "textDocument/semanticTokens/full" SemanticTokens.handleFull + + override __.TextDocumentSemanticTokensFullDelta p = + p + |> withReadOnlyContext "textDocument/semanticTokens/full/delta" SemanticTokens.handleFullDelta + + override __.TextDocumentSemanticTokensRange p = + p + |> withReadOnlyContext "textDocument/semanticTokens/range" SemanticTokens.handleRange + + override __.TextDocumentInlayHint p = + p |> withReadOnlyContext "textDocument/inlayHint" InlayHint.handle + + override __.InlayHintResolve p = + p |> withReadOnlyContext "inlayHint/resolve" InlayHint.resolve + + override __.WindowWorkDoneProgressCancel _ = raise (NotImplementedException()) + override __.TextDocumentInlineValue _ = notImplemented + + override __.TextDocumentPrepareCallHierarchy p = + p + |> withReadOnlyContext "textDocument/prepareCallHierarchy" CallHierarchy.prepare + + override __.CallHierarchyIncomingCalls p = + p + |> withReadOnlyContext "callHierarchy/incomingCalls" CallHierarchy.incomingCalls + + override __.CallHierarchyOutgoingCalls p = + p + |> withReadOnlyContext "callHierarchy/outgoingCalls" CallHierarchy.outgoingCalls + + override __.TextDocumentPrepareTypeHierarchy p = + p + |> withReadOnlyContext "textDocument/prepareTypeHierarchy" TypeHierarchy.prepare + + override __.TypeHierarchySupertypes p = + p |> withReadOnlyContext "typeHierarchy/supertypes" TypeHierarchy.supertypes + + override __.TypeHierarchySubtypes p = + p |> withReadOnlyContext "typeHierarchy/subtypes" TypeHierarchy.subtypes + + override __.TextDocumentDeclaration p = + p |> withReadOnlyContext "textDocument/declaration" Declaration.handle + + override __.WorkspaceDiagnostic p = + p + |> withReadOnlyContext "workspace/diagnostic" Diagnostic.handleWorkspaceDiagnostic + + override __.CancelRequest _ = ignoreNotification + override __.NotebookDocumentDidChange _ = ignoreNotification + override __.NotebookDocumentDidClose _ = ignoreNotification + override __.NotebookDocumentDidOpen _ = ignoreNotification + override __.NotebookDocumentDidSave _ = ignoreNotification + + override __.WorkspaceSymbolResolve p = + p |> withReadOnlyContext "workspaceSymbol/resolve" WorkspaceSymbol.resolve + + override __.TextDocumentDiagnostic p = + p |> withReadOnlyContext "textDocument/diagnostic" Diagnostic.handle + + override __.TextDocumentLinkedEditingRange p = + p + |> withReadOnlyContext "textDocument/linkedEditingRange" LinkedEditingRange.handle + + override __.TextDocumentMoniker p = + p |> withReadOnlyContext "textDocument/moniker" Moniker.handle + + override __.Progress _ = ignoreNotification + override __.SetTrace _ = ignoreNotification + + override __.CSharpMetadata p = + p |> withReadOnlyContext "csharp/metadata" CSharpMetadata.handle module Server = let logger = Logging.getLoggerByName "LSP" @@ -234,7 +395,7 @@ module Server = | :? LocalRpcException -> Some() | :? TaskCanceledException -> Some() | :? OperationCanceledException -> Some() - | :? System.AggregateException as aex -> + | :? AggregateException as aex -> if aex.InnerExceptions.Count = 1 then (|HandleableException|_|) aex.InnerException else @@ -256,7 +417,7 @@ module Server = : ServerRequestHandling = serverRequestHandling run - [ "csharp/metadata", requestHandling (fun s p -> s.CSharpMetadata(p)) ] + [ "csharp/metadata", requestHandling (fun s p -> s.CSharpMetadata p) ] |> Map.ofList // TODO: @@ -272,21 +433,14 @@ module Server = let serverCreator client = new CSharpLspServer(client, settings) :> ICSharpLspServer - let clientCreator = - fun (a, b) -> new CSharpLspClient(a, b) + let clientCreator = fun (a, b) -> new CSharpLspClient(a, b) - Ionide.LanguageServerProtocol.Server.start - requestHandlings - input - output - clientCreator - serverCreator - createRpc + start requestHandlings input output clientCreator serverCreator createRpc let start options = try let result = startCore options int result with ex -> - logger.LogError("{name} crashed", ex, (Process.GetCurrentProcess().ProcessName)) + logger.LogError("{name} crashed", ex, Process.GetCurrentProcess().ProcessName) 3 diff --git a/src/CSharpLanguageServer/Program.fs b/src/CSharpLanguageServer/Program.fs index 208447cb..99e0b8a3 100644 --- a/src/CSharpLanguageServer/Program.fs +++ b/src/CSharpLanguageServer/Program.fs @@ -1,6 +1,5 @@ module CSharpLanguageServer.Program -open System open System.Reflection open Argu @@ -12,17 +11,17 @@ open CSharpLanguageServer.Logging type CLIArguments = | [] Version - | [] LogLevel of level:string - | [] Solution of solution:string + | [] LogLevel of level: string + | [] Solution of solution: string | Debug - with - interface IArgParserTemplate with - member s.Usage = - match s with - | Version -> "display versioning information" - | Solution _ -> ".sln file to load (relative to CWD)" - | LogLevel _ -> "log level, ; default is `info`" - | Debug -> "enable debug mode" + + interface IArgParserTemplate with + member s.Usage = + match s with + | Version -> "display versioning information" + | Solution _ -> ".sln file to load (relative to CWD)" + | LogLevel _ -> "log level, ; default is `info`" + | Debug -> "enable debug mode" [] @@ -32,15 +31,16 @@ let entry args = let serverArgs = argParser.Parse args let printVersion () = - printfn "csharp-ls, %s" - (Assembly.GetExecutingAssembly().GetName().Version |> string) + printfn "csharp-ls, %s" (Assembly.GetExecutingAssembly().GetName().Version |> string) - serverArgs.TryGetResult(<@ CLIArguments.Version @>) - |> Option.iter (fun _ -> printVersion (); exit 0) + serverArgs.TryGetResult <@ Version @> + |> Option.iter (fun _ -> + printVersion () + exit 0) - let debugMode: bool = serverArgs.Contains Debug + let debugMode: bool = serverArgs.Contains Debug - let logLevelArg = serverArgs.TryGetResult(<@ CLIArguments.LogLevel @>) + let logLevelArg = serverArgs.TryGetResult <@ LogLevel @> let logLevel = match logLevelArg with @@ -51,12 +51,12 @@ let entry args = | Some "trace" -> LogLevel.Trace | _ -> if debugMode then LogLevel.Debug else LogLevel.Information - let settings = { - ServerSettings.Default with - SolutionPath = serverArgs.TryGetResult(<@ CLIArguments.Solution @>) + let settings = + { ServerSettings.Default with + SolutionPath = serverArgs.TryGetResult <@ Solution @> LogLevel = logLevel DebugMode = debugMode - } + ApplyFormattingOptions = failwith "Not Implemented" } Logging.setupLogging settings.LogLevel @@ -67,7 +67,7 @@ let entry args = match ex.ErrorCode with | ErrorCode.HelpText -> 0 - | _ -> 1 // Unrecognised arguments + | _ -> 1 // Unrecognised arguments | e -> eprintfn "Server crashing error - %s \n %s" e.Message e.StackTrace diff --git a/src/CSharpLanguageServer/ProgressReporter.fs b/src/CSharpLanguageServer/ProgressReporter.fs index 4c55edad..dffcd54a 100644 --- a/src/CSharpLanguageServer/ProgressReporter.fs +++ b/src/CSharpLanguageServer/ProgressReporter.fs @@ -10,40 +10,48 @@ type ProgressReporter(client: ILspClient) = let mutable endSent = false - member val Token = ProgressToken.C2 (Guid.NewGuid().ToString()) + member val Token = ProgressToken.C2(Guid.NewGuid().ToString()) member this.Begin(title, ?cancellable, ?message, ?percentage) = async { - let! progressCreateResult = client.WindowWorkDoneProgressCreate({ Token = this.Token }) + let! progressCreateResult = client.WindowWorkDoneProgressCreate { Token = this.Token } match progressCreateResult with - | Error _ -> - canReport <- false + | Error _ -> canReport <- false | Ok() -> canReport <- true - let param = WorkDoneProgressBegin.Create( - title = title, - ?cancellable = cancellable, - ?message = message, - ?percentage = percentage - ) - do! client.Progress({ Token = this.Token; Value = serialize param }) + + let param = + WorkDoneProgressBegin.Create( + title = title, + ?cancellable = cancellable, + ?message = message, + ?percentage = percentage + ) + + do! + client.Progress + { Token = this.Token + Value = serialize param } } member this.Report(?cancellable, ?message, ?percentage) = async { if canReport && not endSent then - let param = WorkDoneProgressReport.Create( - ?cancellable = cancellable, - ?message = message, - ?percentage = percentage - ) - do! client.Progress({ Token = this.Token; Value = serialize param }) + let param = + WorkDoneProgressReport.Create(?cancellable = cancellable, ?message = message, ?percentage = percentage) + + do! + client.Progress + { Token = this.Token + Value = serialize param } } member this.End(?message) = async { if canReport && not endSent then endSent <- true - let param = WorkDoneProgressEnd.Create( - ?message = message - ) - do! client.Progress({ Token = this.Token; Value = serialize param }) + let param = WorkDoneProgressEnd.Create(?message = message) + + do! + client.Progress + { Token = this.Token + Value = serialize param } } diff --git a/src/CSharpLanguageServer/RoslynHelpers.fs b/src/CSharpLanguageServer/RoslynHelpers.fs index 8307bbcc..9508909c 100644 --- a/src/CSharpLanguageServer/RoslynHelpers.fs +++ b/src/CSharpLanguageServer/RoslynHelpers.fs @@ -31,8 +31,7 @@ open CSharpLanguageServer.Conversions open CSharpLanguageServer.Logging open CSharpLanguageServer.Util -type DocumentSymbolCollectorForMatchingSymbolName - (documentUri, sym: ISymbol) = +type DocumentSymbolCollectorForMatchingSymbolName(documentUri, sym: ISymbol) = inherit CSharpSyntaxWalker(SyntaxWalkerDepth.Token) let mutable collectedLocations = [] @@ -41,8 +40,7 @@ type DocumentSymbolCollectorForMatchingSymbolName let collectIdentifier (identifier: SyntaxToken) exactMatch = let location: Types.Location = { Uri = documentUri - Range = identifier.GetLocation().GetLineSpan().Span - |> Range.fromLinePositionSpan } + Range = identifier.GetLocation().GetLineSpan().Span |> Range.fromLinePositionSpan } if exactMatch then collectedLocations <- location :: collectedLocations @@ -68,82 +66,87 @@ type DocumentSymbolCollectorForMatchingSymbolName symMethod.Parameters.Length = nodeMethodDecl.ParameterList.Parameters.Count collectIdentifier nodeMethodDecl.Identifier methodArityMatches - else - if node :? TypeDeclarationSyntax then - let typeDecl = node :?> TypeDeclarationSyntax - if typeDecl.Identifier.ValueText = sym.Name then - collectIdentifier typeDecl.Identifier false + else if node :? TypeDeclarationSyntax then + let typeDecl = node :?> TypeDeclarationSyntax + + if typeDecl.Identifier.ValueText = sym.Name then + collectIdentifier typeDecl.Identifier false + + else if node :? PropertyDeclarationSyntax then + let propertyDecl = node :?> PropertyDeclarationSyntax - else if node :? PropertyDeclarationSyntax then - let propertyDecl = node :?> PropertyDeclarationSyntax - if propertyDecl.Identifier.ValueText = sym.Name then - collectIdentifier propertyDecl.Identifier false + if propertyDecl.Identifier.ValueText = sym.Name then + collectIdentifier propertyDecl.Identifier false - else if node :? EventDeclarationSyntax then - let eventDecl = node :?> EventDeclarationSyntax - if eventDecl.Identifier.ValueText = sym.Name then - collectIdentifier eventDecl.Identifier false + else if node :? EventDeclarationSyntax then + let eventDecl = node :?> EventDeclarationSyntax - // TODO: collect other type of syntax nodes too + if eventDecl.Identifier.ValueText = sym.Name then + collectIdentifier eventDecl.Identifier false - base.Visit(node) + // TODO: collect other type of syntax nodes too -type CleanCodeGenerationOptionsProviderInterceptor (_logMessage) = + base.Visit node + +type CleanCodeGenerationOptionsProviderInterceptor(_logMessage) = interface IInterceptor with member __.Intercept(invocation: IInvocation) = match invocation.Method.Name with - "GetCleanCodeGenerationOptionsAsync" -> - let workspacesAssembly = Assembly.Load("Microsoft.CodeAnalysis.Workspaces") + | "GetCleanCodeGenerationOptionsAsync" -> + let workspacesAssembly = Assembly.Load "Microsoft.CodeAnalysis.Workspaces" let cleanCodeGenOptionsType = - workspacesAssembly.GetType("Microsoft.CodeAnalysis.CodeGeneration.CleanCodeGenerationOptions") - |> nonNull "workspacesAssembly.GetType('Microsoft.CodeAnalysis.CodeGeneration.CleanCodeGenerationOptions')" + workspacesAssembly.GetType "Microsoft.CodeAnalysis.CodeGeneration.CleanCodeGenerationOptions" + |> nonNull + "workspacesAssembly.GetType('Microsoft.CodeAnalysis.CodeGeneration.CleanCodeGenerationOptions')" - let getDefaultMI = cleanCodeGenOptionsType.GetMethod("GetDefault") |> nonNull "cleanCodeGenOptionsType.GetMethod('GetDefault')" + let getDefaultMI = + cleanCodeGenOptionsType.GetMethod "GetDefault" + |> nonNull "cleanCodeGenOptionsType.GetMethod('GetDefault')" let argLanguageServices = invocation.Arguments[0] - let defaultCleanCodeGenOptions = getDefaultMI.Invoke(null, [| argLanguageServices |]) + + let defaultCleanCodeGenOptions = + getDefaultMI.Invoke(null, [| argLanguageServices |]) let valueTaskType = typedefof> - let valueTaskTypeForCleanCodeGenOptions = valueTaskType.MakeGenericType([| cleanCodeGenOptionsType |]) + + let valueTaskTypeForCleanCodeGenOptions = + valueTaskType.MakeGenericType [| cleanCodeGenOptionsType |] invocation.ReturnValue <- Activator.CreateInstance(valueTaskTypeForCleanCodeGenOptions, defaultCleanCodeGenOptions) - | _ -> - NotImplementedException(string invocation.Method) |> raise + | _ -> NotImplementedException(string invocation.Method) |> raise -type LegacyWorkspaceOptionServiceInterceptor (logMessage) = +type LegacyWorkspaceOptionServiceInterceptor(logMessage) = interface IInterceptor with member __.Intercept(invocation: IInvocation) = //logMessage (sprintf "LegacyWorkspaceOptionServiceInterceptor: %s" (string invocation.Method)) match invocation.Method.Name with - | "RegisterWorkspace" -> - () - | "GetGenerateEqualsAndGetHashCodeFromMembersGenerateOperators" -> - invocation.ReturnValue <- box true - | "GetGenerateEqualsAndGetHashCodeFromMembersImplementIEquatable" -> - invocation.ReturnValue <- box true - | "GetGenerateConstructorFromMembersOptionsAddNullChecks" -> - invocation.ReturnValue <- box true - | "get_GenerateOverrides" -> - invocation.ReturnValue <- box true + | "RegisterWorkspace" -> () + | "GetGenerateEqualsAndGetHashCodeFromMembersGenerateOperators" -> invocation.ReturnValue <- box true + | "GetGenerateEqualsAndGetHashCodeFromMembersImplementIEquatable" -> invocation.ReturnValue <- box true + | "GetGenerateConstructorFromMembersOptionsAddNullChecks" -> invocation.ReturnValue <- box true + | "get_GenerateOverrides" -> invocation.ReturnValue <- box true | "get_CleanCodeGenerationOptionsProvider" -> - let workspacesAssembly = Assembly.Load("Microsoft.CodeAnalysis.Workspaces") - let cleanCodeGenOptionsProvType = workspacesAssembly.GetType("Microsoft.CodeAnalysis.CodeGeneration.AbstractCleanCodeGenerationOptionsProvider") + let workspacesAssembly = Assembly.Load "Microsoft.CodeAnalysis.Workspaces" + + let cleanCodeGenOptionsProvType = + workspacesAssembly.GetType + "Microsoft.CodeAnalysis.CodeGeneration.AbstractCleanCodeGenerationOptionsProvider" let generator = ProxyGenerator() - let interceptor = CleanCodeGenerationOptionsProviderInterceptor(logMessage) + let interceptor = CleanCodeGenerationOptionsProviderInterceptor logMessage let proxy = generator.CreateClassProxy(cleanCodeGenOptionsProvType, interceptor) invocation.ReturnValue <- proxy - | _ -> - NotImplementedException(string invocation.Method) |> raise + | _ -> NotImplementedException(string invocation.Method) |> raise -type PickMembersServiceInterceptor (_logMessage) = +type PickMembersServiceInterceptor(_logMessage) = interface IInterceptor with - member __.Intercept(invocation: IInvocation) = + member __.Intercept(invocation: IInvocation) = match invocation.Method.Name with | "PickMembers" -> @@ -155,13 +158,12 @@ type PickMembersServiceInterceptor (_logMessage) = invocation.ReturnValue <- Activator.CreateInstance(pickMembersResultType, argMembers, argOptions, box true) - | _ -> - NotImplementedException(string invocation.Method) |> raise + | _ -> NotImplementedException(string invocation.Method) |> raise -type ExtractClassOptionsServiceInterceptor (_logMessage) = +type ExtractClassOptionsServiceInterceptor(_logMessage) = - let getExtractClassOptionsImpl(argOriginalType: INamedTypeSymbol): Object = - let featuresAssembly = Assembly.Load("Microsoft.CodeAnalysis.Features") + let getExtractClassOptionsImpl (argOriginalType: INamedTypeSymbol) : Object = + let featuresAssembly = Assembly.Load "Microsoft.CodeAnalysis.Features" let typeName = "Base" + argOriginalType.Name let fileName = typeName + ".cs" @@ -170,11 +172,14 @@ type ExtractClassOptionsServiceInterceptor (_logMessage) = let immArrayType = typeof let extractClassMemberAnalysisResultType = - featuresAssembly.GetType("Microsoft.CodeAnalysis.ExtractClass.ExtractClassMemberAnalysisResult") - |> nonNull "featuresAssembly.GetType('Microsoft.CodeAnalysis.ExtractClass.ExtractClassMemberAnalysisResult')" + featuresAssembly.GetType "Microsoft.CodeAnalysis.ExtractClass.ExtractClassMemberAnalysisResult" + |> nonNull + "featuresAssembly.GetType('Microsoft.CodeAnalysis.ExtractClass.ExtractClassMemberAnalysisResult')" + + let resultListType = + typedefof>.MakeGenericType extractClassMemberAnalysisResultType - let resultListType = typedefof>.MakeGenericType(extractClassMemberAnalysisResultType) - let resultList = Activator.CreateInstance(resultListType) + let resultList = Activator.CreateInstance resultListType let memberFilter (m: ISymbol) = match m with @@ -182,20 +187,21 @@ type ExtractClassOptionsServiceInterceptor (_logMessage) = | :? IFieldSymbol as fs -> not fs.IsImplicitlyDeclared | _ -> m.Kind = SymbolKind.Property || m.Kind = SymbolKind.Event - let selectedMembersToAdd = - argOriginalType.GetMembers() - |> Seq.filter memberFilter + let selectedMembersToAdd = argOriginalType.GetMembers() |> Seq.filter memberFilter for memberToAdd in selectedMembersToAdd do let memberAnalysisResult = Activator.CreateInstance(extractClassMemberAnalysisResultType, memberToAdd, false) - let resultListAddMI = resultListType.GetMethod("Add") |> nonNull "resultListType.GetMethod('Add')" + let resultListAddMI = + resultListType.GetMethod "Add" |> nonNull "resultListType.GetMethod('Add')" - resultListAddMI.Invoke(resultList, [| memberAnalysisResult |]) - |> ignore + resultListAddMI.Invoke(resultList, [| memberAnalysisResult |]) |> ignore + + let resultListToArrayMI = + resultListType.GetMethod "ToArray" + |> nonNull "resultListType.GetMethod('ToArray')" - let resultListToArrayMI = resultListType.GetMethod("ToArray") |> nonNull "resultListType.GetMethod('ToArray')" let resultListAsArray = resultListToArrayMI.Invoke(resultList, null) let immArrayCreateFromArrayMI = @@ -204,15 +210,16 @@ type ExtractClassOptionsServiceInterceptor (_logMessage) = |> Seq.head let emptyMemberAnalysisResults = - immArrayCreateFromArrayMI.MakeGenericMethod([| extractClassMemberAnalysisResultType |]).Invoke(null, [| resultListAsArray |]) + immArrayCreateFromArrayMI + .MakeGenericMethod([| extractClassMemberAnalysisResultType |]) + .Invoke(null, [| resultListAsArray |]) |> nonNull "MakeGenericMethod()" let extractClassOptionsType = - featuresAssembly.GetType("Microsoft.CodeAnalysis.ExtractClass.ExtractClassOptions") + featuresAssembly.GetType "Microsoft.CodeAnalysis.ExtractClass.ExtractClassOptions" |> nonNull "featuresAssembly.GetType('Microsoft.CodeAnalysis.ExtractClass.ExtractClassOptions')" - Activator.CreateInstance( - extractClassOptionsType, fileName, typeName, sameFile, emptyMemberAnalysisResults) + Activator.CreateInstance(extractClassOptionsType, fileName, typeName, sameFile, emptyMemberAnalysisResults) |> nonNull (sprintf "could not Activator.CreateInstance(%s,..)" (string extractClassOptionsType)) interface IInterceptor with @@ -221,24 +228,24 @@ type ExtractClassOptionsServiceInterceptor (_logMessage) = match invocation.Method.Name with | "GetExtractClassOptionsAsync" -> let argOriginalType = invocation.Arguments[1] :?> INamedTypeSymbol - let extractClassOptionsValue = getExtractClassOptionsImpl(argOriginalType) + let extractClassOptionsValue = getExtractClassOptionsImpl argOriginalType let fromResultMethod = - typeof.GetMethod("FromResult") + typeof.GetMethod "FromResult" |> nonNull (sprintf "%s.FromResult()" (string typeof)) - let typedFromResultMethod = fromResultMethod.MakeGenericMethod([| extractClassOptionsValue.GetType() |]) + let typedFromResultMethod = + fromResultMethod.MakeGenericMethod [| extractClassOptionsValue.GetType() |] invocation.ReturnValue <- typedFromResultMethod.Invoke(null, [| extractClassOptionsValue |]) | "GetExtractClassOptions" -> let argOriginalType = invocation.Arguments[1] :?> INamedTypeSymbol - invocation.ReturnValue <- getExtractClassOptionsImpl(argOriginalType) + invocation.ReturnValue <- getExtractClassOptionsImpl argOriginalType - | _ -> - NotImplementedException(string invocation.Method) |> raise + | _ -> NotImplementedException(string invocation.Method) |> raise -type ExtractInterfaceOptionsServiceInterceptor (logMessage) = +type ExtractInterfaceOptionsServiceInterceptor(logMessage) = interface IInterceptor with member __.Intercept(invocation: IInvocation) = @@ -249,25 +256,30 @@ type ExtractInterfaceOptionsServiceInterceptor (logMessage) = let fileName = sprintf "%s.cs" argDefaultInterfaceName - let featuresAssembly = Assembly.Load("Microsoft.CodeAnalysis.Features") + let featuresAssembly = Assembly.Load "Microsoft.CodeAnalysis.Features" let extractInterfaceOptionsResultType = - featuresAssembly.GetType("Microsoft.CodeAnalysis.ExtractInterface.ExtractInterfaceOptionsResult") - |> nonNull "featuresAssembly.GetType('Microsoft.CodeAnalysis.ExtractInterface.ExtractInterfaceOptionsResult')" + featuresAssembly.GetType "Microsoft.CodeAnalysis.ExtractInterface.ExtractInterfaceOptionsResult" + |> nonNull + "featuresAssembly.GetType('Microsoft.CodeAnalysis.ExtractInterface.ExtractInterfaceOptionsResult')" let locationEnumType = - extractInterfaceOptionsResultType.GetNestedType("ExtractLocation") + extractInterfaceOptionsResultType.GetNestedType "ExtractLocation" |> nonNull "extractInterfaceOptionsResultType.GetNestedType('ExtractLocation')" let location = Enum.Parse(locationEnumType, "NewFile") // or "SameFile" - let workspacesAssembly = Assembly.Load("Microsoft.CodeAnalysis.Workspaces") - let cleanCodeGenOptionsProvType = workspacesAssembly.GetType("Microsoft.CodeAnalysis.CodeGeneration.AbstractCleanCodeGenerationOptionsProvider") + let workspacesAssembly = Assembly.Load "Microsoft.CodeAnalysis.Workspaces" + + let cleanCodeGenOptionsProvType = + workspacesAssembly.GetType + "Microsoft.CodeAnalysis.CodeGeneration.AbstractCleanCodeGenerationOptionsProvider" let generator = ProxyGenerator() - let interceptor = CleanCodeGenerationOptionsProviderInterceptor(logMessage) + let interceptor = CleanCodeGenerationOptionsProviderInterceptor logMessage + let cleanCodeGenerationOptionsProvider = - generator.CreateClassProxy(cleanCodeGenOptionsProvType, interceptor) + generator.CreateClassProxy(cleanCodeGenOptionsProvType, interceptor) let extractInterfaceOptionsResultValue = Activator.CreateInstance( @@ -277,23 +289,23 @@ type ExtractInterfaceOptionsServiceInterceptor (logMessage) = argDefaultInterfaceName, fileName, location, - cleanCodeGenerationOptionsProvider) + cleanCodeGenerationOptionsProvider + ) let fromResultMethod = - typeof.GetMethod("FromResult") + typeof.GetMethod "FromResult" |> nonNull (sprintf "%s.FromResult()" (string typeof)) - let typedFromResultMethod = fromResultMethod.MakeGenericMethod([| extractInterfaceOptionsResultType |]) + let typedFromResultMethod = + fromResultMethod.MakeGenericMethod [| extractInterfaceOptionsResultType |] - invocation.ReturnValue <- - typedFromResultMethod.Invoke(null, [| extractInterfaceOptionsResultValue |]) + invocation.ReturnValue <- typedFromResultMethod.Invoke(null, [| extractInterfaceOptionsResultValue |]) - | _ -> - NotImplementedException(string invocation.Method.Name) |> raise + | _ -> NotImplementedException(string invocation.Method.Name) |> raise -type MoveStaticMembersOptionsServiceInterceptor (_logMessage) = +type MoveStaticMembersOptionsServiceInterceptor(_logMessage) = interface IInterceptor with - member __.Intercept(invocation: IInvocation) = + member __.Intercept(invocation: IInvocation) = match invocation.Method.Name with | "GetMoveMembersToTypeOptions" -> @@ -301,9 +313,10 @@ type MoveStaticMembersOptionsServiceInterceptor (_logMessage) = let _argOriginalType = invocation.Arguments[1] :?> INamedTypeSymbol let argSelectedMembers = invocation.Arguments[2] :?> ImmutableArray - let featuresAssembly = Assembly.Load("Microsoft.CodeAnalysis.Features") + let featuresAssembly = Assembly.Load "Microsoft.CodeAnalysis.Features" + let msmOptionsType = - featuresAssembly.GetType("Microsoft.CodeAnalysis.MoveStaticMembers.MoveStaticMembersOptions") + featuresAssembly.GetType "Microsoft.CodeAnalysis.MoveStaticMembers.MoveStaticMembersOptions" |> nonNull "typeof" let newStaticClassName = "NewStaticClass" @@ -314,36 +327,36 @@ type MoveStaticMembersOptionsServiceInterceptor (_logMessage) = newStaticClassName + ".cs", newStaticClassName, argSelectedMembers, - false |> box) + false |> box + ) invocation.ReturnValue <- msmOptions - | _ -> - NotImplementedException(string invocation.Method) |> raise + | _ -> NotImplementedException(string invocation.Method) |> raise -type RemoteHostClientProviderInterceptor (_logMessage) = +type RemoteHostClientProviderInterceptor(_logMessage) = interface IInterceptor with - member __.Intercept(invocation: IInvocation) = + member __.Intercept(invocation: IInvocation) = match invocation.Method.Name with | "TryGetRemoteHostClientAsync" -> - let workspacesAssembly = Assembly.Load("Microsoft.CodeAnalysis.Workspaces") + let workspacesAssembly = Assembly.Load "Microsoft.CodeAnalysis.Workspaces" + let remoteHostClientType = - workspacesAssembly.GetType("Microsoft.CodeAnalysis.Remote.RemoteHostClient") + workspacesAssembly.GetType "Microsoft.CodeAnalysis.Remote.RemoteHostClient" |> nonNull "GetType(Microsoft.CodeAnalysis.Remote.RemoteHostClient)" let fromResultMI = typeof.GetMethod("FromResult", BindingFlags.Static ||| BindingFlags.Public) |> nonNull (sprintf "%s.FromResult()" (string typeof)) - let genericMethod = fromResultMI.MakeGenericMethod(remoteHostClientType) + let genericMethod = fromResultMI.MakeGenericMethod remoteHostClientType let nullResultTask = genericMethod.Invoke(null, [| null |]) invocation.ReturnValue <- nullResultTask - | _ -> - NotImplementedException(string invocation.Method) |> raise + | _ -> NotImplementedException(string invocation.Method) |> raise -type WorkspaceServicesInterceptor () = +type WorkspaceServicesInterceptor() = let logger = Logging.getLoggerByName "WorkspaceServicesInterceptor" interface IInterceptor with @@ -392,17 +405,19 @@ type WorkspaceServicesInterceptor () = invocation.ReturnValue <- updatedReturnValue -type CSharpLspHostServices () = +type CSharpLspHostServices() = inherit HostServices() - member private this.hostServices = MSBuildMefHostServices.DefaultServices + member private _.hostServices = MSBuildMefHostServices.DefaultServices - override this.CreateWorkspaceServices (workspace: Workspace) = + override this.CreateWorkspaceServices(workspace: Workspace) = // Ugly but we can't: // 1. use Castle since there is no default constructor of MefHostServices. // 2. call this.hostServices.CreateWorkspaceServices directly since it's internal. let createWorkspaceServicesMI = - this.hostServices.GetType().GetMethod("CreateWorkspaceServices", BindingFlags.Instance|||BindingFlags.NonPublic) + this.hostServices + .GetType() + .GetMethod("CreateWorkspaceServices", BindingFlags.Instance ||| BindingFlags.NonPublic) |> nonNull (sprintf "no %s.CreateWorkspaceServices()" (this.hostServices.GetType() |> string)) let services = @@ -415,13 +430,14 @@ type CSharpLspHostServices () = let loadProjectFilenamesFromSolution (solutionPath: string) = - assert Path.IsPathRooted(solutionPath) + assert Path.IsPathRooted solutionPath let projectFilenames = new List() - let solutionFile = Microsoft.Build.Construction.SolutionFile.Parse(solutionPath) + let solutionFile = SolutionFile.Parse solutionPath + for project in solutionFile.ProjectsInOrder do - if project.ProjectType = Microsoft.Build.Construction.SolutionProjectType.KnownToBeMSBuildFormat then - projectFilenames.Add(project.AbsolutePath) + if project.ProjectType = SolutionProjectType.KnownToBeMSBuildFormat then + projectFilenames.Add project.AbsolutePath projectFilenames |> Set.ofSeq @@ -436,19 +452,19 @@ type TfmCategory = let selectLatestTfm (tfms: string seq) : string option = let parseTfm (tfm: string) : TfmCategory = - let patterns = [ - @"^net(?\d)(?\d)?(?\d)?$", NetFramework - @"^netstandard(?\d+)\.(?\d+)$", NetStandard - @"^netcoreapp(?\d+)\.(?\d+)$", NetCoreApp - @"^net(?\d+)\.(?\d+)$", Net - ] + let patterns = + [ @"^net(?\d)(?\d)?(?\d)?$", NetFramework + @"^netstandard(?\d+)\.(?\d+)$", NetStandard + @"^netcoreapp(?\d+)\.(?\d+)$", NetCoreApp + @"^net(?\d+)\.(?\d+)$", Net ] let matchingTfmCategory (pat, categoryCtor) = let m = Regex.Match(tfm.ToLowerInvariant(), pat) + if m.Success then let readVersionNum (groupName: string) = let group = m.Groups.[groupName] - if group.Success then (int group.Value) else 0 + if group.Success then int group.Value else 0 Version(readVersionNum "major", readVersionNum "minor", readVersionNum "build") |> categoryCtor @@ -456,55 +472,54 @@ let selectLatestTfm (tfms: string seq) : string option = else None - patterns - |> List.tryPick matchingTfmCategory - |> Option.defaultValue Unknown + patterns |> List.tryPick matchingTfmCategory |> Option.defaultValue Unknown - let rankTfm = function + let rankTfm = + function | Net v -> 3000 + v.Major * 10 + v.Minor | NetCoreApp v -> 2000 + v.Major * 10 + v.Minor | NetStandard v -> 1000 + v.Major * 10 + v.Minor | NetFramework v -> 0 + v.Major * 10 + v.Minor | Unknown -> -1 - tfms - |> Seq.sortByDescending (parseTfm >> rankTfm) - |> Seq.tryHead + tfms |> Seq.sortByDescending (parseTfm >> rankTfm) |> Seq.tryHead let loadProjectTfms (logger: ILogger) (projs: string seq) : Map> = let mutable projectTfms = Map.empty for projectFilename in projs do - let projectCollection = new Microsoft.Build.Evaluation.ProjectCollection(); - let props = new Dictionary(); + let projectCollection = new Microsoft.Build.Evaluation.ProjectCollection() + let props = new Dictionary() try - let buildProject = projectCollection.LoadProject(projectFilename, props, toolsVersion=null) + let buildProject = + projectCollection.LoadProject(projectFilename, props, toolsVersion = null) let noneIfEmpty s = - s |> Option.ofObj - |> Option.bind (fun s -> if String.IsNullOrEmpty(s) then None else Some s) + s + |> Option.ofObj + |> Option.bind (fun s -> if String.IsNullOrEmpty s then None else Some s) let targetFramework = - match buildProject.GetPropertyValue("TargetFramework") |> noneIfEmpty with - | Some tfm -> [tfm.Trim()] + match buildProject.GetPropertyValue "TargetFramework" |> noneIfEmpty with + | Some tfm -> [ tfm.Trim() ] | None -> [] let targetFrameworks = - match buildProject.GetPropertyValue("TargetFrameworks") |> noneIfEmpty with - | Some tfms -> tfms.Split(";") |> Array.map (fun s -> s.Trim()) |> List.ofArray + match buildProject.GetPropertyValue "TargetFrameworks" |> noneIfEmpty with + | Some tfms -> tfms.Split ";" |> Array.map (fun s -> s.Trim()) |> List.ofArray | None -> [] projectTfms <- projectTfms |> Map.add projectFilename (targetFramework @ targetFrameworks) - projectCollection.UnloadProject(buildProject) - with - | :? InvalidProjectFileException as ipfe -> + projectCollection.UnloadProject buildProject + with :? InvalidProjectFileException as ipfe -> logger.LogDebug( "loadProjectTfms: failed to load {projectFilename}: {ex}", projectFilename, - ipfe.GetType() |> string) + ipfe.GetType() |> string + ) projectTfms @@ -525,33 +540,27 @@ let applyWorkspaceTargetFrameworkProp (tfmsPerProject: Map> let resolveDefaultWorkspaceProps (logger: ILogger) projs : Map = let tfmsPerProject = loadProjectTfms logger projs - Map.empty - |> applyWorkspaceTargetFrameworkProp tfmsPerProject + Map.empty |> applyWorkspaceTargetFrameworkProp tfmsPerProject -let tryLoadSolutionOnPath - (lspClient: ILspClient) - (logger: ILogger) - (solutionPath: string) = - assert Path.IsPathRooted(solutionPath) - let progress = ProgressReporter(lspClient) +let tryLoadSolutionOnPath (lspClient: ILspClient) (logger: ILogger) (solutionPath: string) = + assert Path.IsPathRooted solutionPath + let progress = ProgressReporter lspClient let logMessage m = - lspClient.WindowLogMessage({ - Type = MessageType.Info - Message = sprintf "csharp-ls: %s" m - }) + lspClient.WindowLogMessage + { Type = MessageType.Info + Message = sprintf "csharp-ls: %s" m } let showMessage m = - lspClient.WindowShowMessage({ - Type = MessageType.Info - Message = sprintf "csharp-ls: %s" m - }) + lspClient.WindowShowMessage + { Type = MessageType.Info + Message = sprintf "csharp-ls: %s" m } async { try let beginMessage = sprintf "Loading solution \"%s\"..." solutionPath - do! progress.Begin(beginMessage) + do! progress.Begin beginMessage do! logMessage beginMessage let projs = loadProjectFilenamesFromSolution solutionPath @@ -560,10 +569,12 @@ let tryLoadSolutionOnPath if workspaceProps.Count > 0 then logger.LogInformation("Will use these MSBuild props: {workspaceProps}", string workspaceProps) - let msbuildWorkspace = MSBuildWorkspace.Create(workspaceProps, CSharpLspHostServices()) + let msbuildWorkspace = + MSBuildWorkspace.Create(workspaceProps, CSharpLspHostServices()) + msbuildWorkspace.LoadMetadataForReferencedProjects <- true - let! solution = msbuildWorkspace.OpenSolutionAsync(solutionPath) |> Async.AwaitTask + let! solution = msbuildWorkspace.OpenSolutionAsync solutionPath |> Async.AwaitTask for diag in msbuildWorkspace.Diagnostics do logger.LogInformation("msbuildWorkspace.Diagnostics: {message}", diag.ToString()) @@ -575,20 +586,22 @@ let tryLoadSolutionOnPath do! logMessage endMessage return Some solution - with - | ex -> - let errorMessage = sprintf "Solution \"%s\" could not be loaded: %s" solutionPath (ex.ToString()) + with ex -> + let errorMessage = + sprintf "Solution \"%s\" could not be loaded: %s" solutionPath (ex.ToString()) + do! progress.End errorMessage do! showMessage errorMessage return None } let tryLoadSolutionFromProjectFiles - (lspClient: ILspClient) - (logger: ILogger) - (logMessage: string -> Async) - (projs: string list) = - let progress = ProgressReporter(lspClient) + (lspClient: ILspClient) + (logger: ILogger) + (logMessage: string -> Async) + (projs: string list) + = + let progress = ProgressReporter lspClient async { do! progress.Begin($"Loading {projs.Length} project(s)...", false, $"0/{projs.Length}", 0u) @@ -597,41 +610,42 @@ let tryLoadSolutionFromProjectFiles let workspaceProps = resolveDefaultWorkspaceProps logger projs if workspaceProps.Count > 0 then - logger.LogDebug( - "Will use these MSBuild props: {workspaceProps}", - string workspaceProps) + logger.LogDebug("Will use these MSBuild props: {workspaceProps}", string workspaceProps) + + let msbuildWorkspace = + MSBuildWorkspace.Create(workspaceProps, CSharpLspHostServices()) - let msbuildWorkspace = MSBuildWorkspace.Create(workspaceProps, CSharpLspHostServices()) msbuildWorkspace.LoadMetadataForReferencedProjects <- true for file in projs do if projs.Length < 10 then - do! logMessage (sprintf "loading project \"%s\".." file) + do! logMessage (sprintf "loading project \"%s\".." file) + try - do! msbuildWorkspace.OpenProjectAsync(file) |> Async.AwaitTask |> Async.Ignore + do! msbuildWorkspace.OpenProjectAsync file |> Async.AwaitTask |> Async.Ignore with ex -> - logger.LogError("could not OpenProjectAsync('{file}'): {exception}", file, (string ex)) + logger.LogError("could not OpenProjectAsync('{file}'): {exception}", file, string ex) let projectFile = new FileInfo(file) let projName = projectFile.Name - let loaded = Interlocked.Increment(loadedProj) + let loaded = Interlocked.Increment loadedProj let percent = 100 * loaded / projs.Length |> uint do! progress.Report(false, $"{projName} {loaded}/{projs.Length}", percent) for diag in msbuildWorkspace.Diagnostics do logger.LogTrace("msbuildWorkspace.Diagnostics: {message}", diag.ToString()) - do! progress.End (sprintf "OK, %d project file(s) loaded" projs.Length) + do! progress.End(sprintf "OK, %d project file(s) loaded" projs.Length) //workspace <- Some(msbuildWorkspace :> Workspace) return Some msbuildWorkspace.CurrentSolution } -let selectPreferredSolution (slnFiles: string list): option = +let selectPreferredSolution (slnFiles: string list) : option = let getProjectCount (slnPath: string) = try - let sln = SolutionFile.Parse(slnPath) - Some (sln.ProjectsInOrder.Count, slnPath) + let sln = SolutionFile.Parse slnPath + Some(sln.ProjectsInOrder.Count, slnPath) with _ -> None @@ -644,65 +658,65 @@ let selectPreferredSolution (slnFiles: string list): option = |> Seq.map snd |> Seq.tryHead -let findAndLoadSolutionOnDir - (lspClient: ILspClient) - (logger: ILogger) - dir = - async { - let fileNotOnNodeModules (filename: string) = - filename.Split(Path.DirectorySeparatorChar) - |> Seq.contains "node_modules" - |> not - - let solutionFiles = - [ "*.sln"; "*.slnx" ] - |> List.collect(fun p -> Directory.GetFiles(dir, p, SearchOption.AllDirectories) |> List.ofArray) - |> Seq.filter fileNotOnNodeModules - |> Seq.toList +let findAndLoadSolutionOnDir (lspClient: ILspClient) (logger: ILogger) dir = async { + let fileNotOnNodeModules (filename: string) = + filename.Split Path.DirectorySeparatorChar |> Seq.contains "node_modules" |> not - let logMessage m = - lspClient.WindowLogMessage({ - Type = MessageType.Info - Message = sprintf "csharp-ls: %s" m - }) + let solutionFiles = + [ "*.sln"; "*.slnx" ] + |> List.collect (fun p -> Directory.GetFiles(dir, p, SearchOption.AllDirectories) |> List.ofArray) + |> Seq.filter fileNotOnNodeModules + |> Seq.toList - do! logMessage (sprintf "%d solution(s) found: [%s]" solutionFiles.Length (String.Join(", ", solutionFiles)) ) + let logMessage m = + lspClient.WindowLogMessage + { Type = MessageType.Info + Message = sprintf "csharp-ls: %s" m } - let preferredSlnFile = solutionFiles |> selectPreferredSolution + do! logMessage (sprintf "%d solution(s) found: [%s]" solutionFiles.Length (String.Join(", ", solutionFiles))) - match preferredSlnFile with - | None -> - do! logMessage ("no single preferred .sln/.slnx file found on " + dir + "; fill load project files manually") - do! logMessage ("looking for .csproj/fsproj files on " + dir + "..") + let preferredSlnFile = solutionFiles |> selectPreferredSolution - let projFiles = - let csprojFiles = Directory.GetFiles(dir, "*.csproj", SearchOption.AllDirectories) - let fsprojFiles = Directory.GetFiles(dir, "*.fsproj", SearchOption.AllDirectories) + match preferredSlnFile with + | None -> + do! + logMessage ( + "no single preferred .sln/.slnx file found on " + + dir + + "; fill load project files manually" + ) - [ csprojFiles; fsprojFiles ] |> Seq.concat - |> Seq.filter fileNotOnNodeModules - |> Seq.toList + do! logMessage ("looking for .csproj/fsproj files on " + dir + "..") - if projFiles.Length = 0 then - let message = "no or .csproj/.fsproj or sln files found on " + dir - do! logMessage message - Exception message |> raise + let projFiles = + let csprojFiles = Directory.GetFiles(dir, "*.csproj", SearchOption.AllDirectories) + let fsprojFiles = Directory.GetFiles(dir, "*.fsproj", SearchOption.AllDirectories) - return! tryLoadSolutionFromProjectFiles lspClient logger logMessage projFiles + [ csprojFiles; fsprojFiles ] + |> Seq.concat + |> Seq.filter fileNotOnNodeModules + |> Seq.toList - | Some solutionPath -> - return! tryLoadSolutionOnPath lspClient logger solutionPath - } + if projFiles.Length = 0 then + let message = "no or .csproj/.fsproj or sln files found on " + dir + do! logMessage message + Exception message |> raise + + return! tryLoadSolutionFromProjectFiles lspClient logger logMessage projFiles + + | Some solutionPath -> return! tryLoadSolutionOnPath lspClient logger solutionPath +} let loadSolutionOnSolutionPathOrDir - (lspClient: ILspClient) - (logger: ILogger) - (solutionPathMaybe: string option) - (rootPath: string) = + (lspClient: ILspClient) + (logger: ILogger) + (solutionPathMaybe: string option) + (rootPath: string) + = match solutionPathMaybe with | Some solutionPath -> async { let rootedSolutionPath = - match (Path.IsPathRooted(solutionPath)) with + match Path.IsPathRooted solutionPath with | true -> solutionPath | false -> Path.Combine(rootPath, solutionPath) @@ -710,81 +724,85 @@ let loadSolutionOnSolutionPathOrDir } | None -> async { - let logMessage: LogMessageParams = { - Type = MessageType.Info - Message = sprintf "csharp-ls: attempting to find and load solution based on root path (\"%s\").." rootPath - } + let logMessage: LogMessageParams = + { Type = MessageType.Info + Message = sprintf "csharp-ls: attempting to find and load solution based on root path (\"%s\").." rootPath } - do! lspClient.WindowLogMessage(logMessage) + do! lspClient.WindowLogMessage logMessage return! findAndLoadSolutionOnDir lspClient logger rootPath } -let getContainingTypeOrThis (symbol: ISymbol): INamedTypeSymbol = - if (symbol :? INamedTypeSymbol) then +let getContainingTypeOrThis (symbol: ISymbol) : INamedTypeSymbol = + if symbol :? INamedTypeSymbol then symbol :?> INamedTypeSymbol else symbol.ContainingType let getFullReflectionName (containingType: INamedTypeSymbol) = - let stack = Stack(); - stack.Push(containingType.MetadataName); - let mutable ns = containingType.ContainingNamespace; + let stack = Stack() + stack.Push containingType.MetadataName + let mutable ns = containingType.ContainingNamespace let mutable doContinue = true + while doContinue do - stack.Push(ns.Name); + stack.Push ns.Name ns <- ns.ContainingNamespace doContinue <- ns <> null && not ns.IsGlobalNamespace String.Join(".", stack) -let tryAddDocument (logger: ILogger) - (docFilePath: string) - (text: string) - (solution: Solution) - : Async = - async { - let docDir = Path.GetDirectoryName(docFilePath) - //logMessage (sprintf "TextDocumentDidOpen: docFilename=%s docDir=%s" docFilename docDir) +let tryAddDocument + (logger: ILogger) + (docFilePath: string) + (text: string) + (solution: Solution) + : Async = + async { + let docDir = Path.GetDirectoryName docFilePath + //logMessage (sprintf "TextDocumentDidOpen: docFilename=%s docDir=%s" docFilename docDir) - let fileOnProjectDir (p: Project) = - let projectDir = Path.GetDirectoryName(p.FilePath) - let projectDirWithDirSepChar = projectDir + (string Path.DirectorySeparatorChar) + let fileOnProjectDir (p: Project) = + let projectDir = Path.GetDirectoryName p.FilePath + let projectDirWithDirSepChar = projectDir + string Path.DirectorySeparatorChar - (docDir = projectDir) || docDir.StartsWith(projectDirWithDirSepChar) + docDir = projectDir || docDir.StartsWith projectDirWithDirSepChar - let projectOnPath = - solution.Projects - |> Seq.filter fileOnProjectDir - |> Seq.tryHead + let projectOnPath = solution.Projects |> Seq.filter fileOnProjectDir |> Seq.tryHead - let! newDocumentMaybe = - match projectOnPath with - | Some proj -> - let projectBaseDir = Path.GetDirectoryName(proj.FilePath) - let docName = docFilePath.Substring(projectBaseDir.Length+1) + let! newDocumentMaybe = + match projectOnPath with + | Some proj -> + let projectBaseDir = Path.GetDirectoryName proj.FilePath + let docName = docFilePath.Substring(projectBaseDir.Length + 1) - //logMessage (sprintf "Adding \"%s\" (\"%s\") to project %s" docName docFilePath proj.FilePath) + //logMessage (sprintf "Adding \"%s\" (\"%s\") to project %s" docName docFilePath proj.FilePath) - let newDoc = proj.AddDocument(name=docName, text=SourceText.From(text), folders=null, filePath=docFilePath) - Some newDoc |> async.Return + let newDoc = + proj.AddDocument( + name = docName, + text = SourceText.From text, + folders = null, + filePath = docFilePath + ) - | None -> async { - logger.LogTrace( - "No parent project could be resolved to add file \"{file}\" to workspace", - docFilePath) - return None - } + Some newDoc |> async.Return + + | None -> async { + logger.LogTrace("No parent project could be resolved to add file \"{file}\" to workspace", docFilePath) + return None + } - return newDocumentMaybe - } + return newDocumentMaybe + } let makeDocumentFromMetadata - (compilation: Microsoft.CodeAnalysis.Compilation) - (project: Microsoft.CodeAnalysis.Project) - (l: Microsoft.CodeAnalysis.Location) - (fullName: string) = + (compilation: Microsoft.CodeAnalysis.Compilation) + (project: Microsoft.CodeAnalysis.Project) + (l: Microsoft.CodeAnalysis.Location) + (fullName: string) + = let mdLocation = l let containingAssembly = @@ -793,11 +811,13 @@ let makeDocumentFromMetadata |> _.ContainingAssembly let reference = - compilation.GetMetadataReference(containingAssembly) + compilation.GetMetadataReference containingAssembly |> nonNull "compilation.GetMetadataReference(containingAssembly)" let peReference = reference :?> PortableExecutableReference |> Option.ofObj - let assemblyLocation = peReference |> Option.map (fun r -> r.FilePath) |> Option.defaultValue "???" + + let assemblyLocation = + peReference |> Option.map (fun r -> r.FilePath) |> Option.defaultValue "???" let decompilerSettings = DecompilerSettings() decompilerSettings.ThrowOnAssemblyResolveErrors <- false // this shouldn't be a showstopper for us @@ -808,12 +828,14 @@ let makeDocumentFromMetadata // (This happens for example, when there is compiler-generated code that is not yet recognized/transformed by the decompiler.) decompiler.AstTransforms.Add(EscapeInvalidIdentifiers()) - let fullTypeName = ICSharpCode.Decompiler.TypeSystem.FullTypeName(fullName) + let fullTypeName = TypeSystem.FullTypeName fullName + + let text = decompiler.DecompileTypeAsString fullTypeName - let text = decompiler.DecompileTypeAsString(fullTypeName) + let mdDocumentFilename = + $"$metadata$/projects/{project.Name}/assemblies/{containingAssembly.Name}/symbols/{fullName}.cs" - let mdDocumentFilename = $"$metadata$/projects/{project.Name}/assemblies/{containingAssembly.Name}/symbols/{fullName}.cs" let mdDocumentEmpty = project.AddDocument(mdDocumentFilename, String.Empty) - let mdDocument = SourceText.From(text) |> mdDocumentEmpty.WithText - (mdDocument, text) + let mdDocument = SourceText.From text |> mdDocumentEmpty.WithText + mdDocument, text diff --git a/src/CSharpLanguageServer/Types.fs b/src/CSharpLanguageServer/Types.fs index 24ac2180..b0654016 100644 --- a/src/CSharpLanguageServer/Types.fs +++ b/src/CSharpLanguageServer/Types.fs @@ -9,14 +9,13 @@ type ServerSettings = { SolutionPath: string option LogLevel: LogLevel ApplyFormattingOptions: bool - DebugMode: bool - } + DebugMode: bool } + static member Default: ServerSettings = { SolutionPath = None LogLevel = LogLevel.Information ApplyFormattingOptions = false - DebugMode = false - } + DebugMode = false } type CSharpMetadataInformation = { ProjectName: string @@ -42,22 +41,17 @@ type ICSharpLspClient = abstract member Capabilities: ClientCapabilities option with get, set let defaultDocumentFilter: TextDocumentFilter = - { Language = None - Scheme = Some "file" - Pattern = Some "**/*.cs" } + { Language = None + Scheme = Some "file" + Pattern = Some "**/*.cs" } // Type abbreviations cannot have augmentations, extensions -let defaultDocumentSelector: DocumentSelector = - [| - defaultDocumentFilter |> U2.C1 - |] +let defaultDocumentSelector: DocumentSelector = [| defaultDocumentFilter |> U2.C1 |] let emptyClientCapabilities: ClientCapabilities = - { - Workspace = None - TextDocument = None - NotebookDocument = None - Window = None - General = None - Experimental = None - } + { Workspace = None + TextDocument = None + NotebookDocument = None + Window = None + General = None + Experimental = None } diff --git a/src/CSharpLanguageServer/Util.fs b/src/CSharpLanguageServer/Util.fs index 88262e23..b3b4fb4e 100644 --- a/src/CSharpLanguageServer/Util.fs +++ b/src/CSharpLanguageServer/Util.fs @@ -4,39 +4,37 @@ open System open System.Runtime.InteropServices open System.IO -let nonNull name (value: 'T when 'T : null) : 'T = +let nonNull name (value: 'T when 'T: null) : 'T = if Object.ReferenceEquals(value, null) then raise (new Exception(sprintf "A non-null value was expected: %s" name)) else value -let parseFileUri s: string = - Uri(s).LocalPath +let parseFileUri s : string = Uri(s).LocalPath -let tryParseFileUri s: string option = +let tryParseFileUri s : string option = try - let uri = Uri(s) + let uri = Uri s Some uri.LocalPath with _ex -> None -let makeFileUri (path: string): string = - let fullPath = Path.GetFullPath(path) +let makeFileUri (path: string) : string = + let fullPath = Path.GetFullPath path - match RuntimeInformation.IsOSPlatform(OSPlatform.Windows) with + match RuntimeInformation.IsOSPlatform OSPlatform.Windows with | true -> "file:///" + fullPath | false -> "file://" + fullPath -let unwindProtect cleanupFn op = - async { - try - return! op - finally - cleanupFn () - } +let unwindProtect cleanupFn op = async { + try + return! op + finally + cleanupFn () +} // TPL Task's wrap exceptions in AggregateException, -- this fn unpacks them -let rec unpackException (exn : Exception) = +let rec unpackException (exn: Exception) = match exn with | :? AggregateException as agg -> match Seq.tryExactlyOne agg.InnerExceptions with @@ -59,30 +57,25 @@ let formatInColumns (data: list>) : string = let numCols = data |> List.map List.length |> List.max let columnWidths = - [0 .. numCols - 1] + [ 0 .. numCols - 1 ] |> List.map (fun colIdx -> data - |> List.map (fun row -> - if colIdx < row.Length then row.[colIdx].Length - else 0 - ) + |> List.map (fun row -> if colIdx < row.Length then row.[colIdx].Length else 0) |> List.max) data |> List.map (fun row -> - [0 .. numCols - 1] + [ 0 .. numCols - 1 ] |> List.map (fun colIdx -> let value = if colIdx < row.Length then row.[colIdx] else "" let width = columnWidths.[colIdx] - value.PadRight(width) - ) - |> String.concat " " - ) - |> String.concat System.Environment.NewLine + value.PadRight width) + |> String.concat " ") + |> String.concat Environment.NewLine module Seq = - let inline tryMaxBy (projection: 'T -> 'U) (source: 'T seq): 'T option = + let inline tryMaxBy (projection: 'T -> 'U) (source: 'T seq) : 'T option = if isNull source || Seq.isEmpty source then None else @@ -90,7 +83,7 @@ module Seq = module Option = let inline ofString (value: string) = - match String.IsNullOrWhiteSpace(value) with + match String.IsNullOrWhiteSpace value with | true -> None | false -> Some value From f5333ad366560b87cb878355ddb30da14e5fcc28 Mon Sep 17 00:00:00 2001 From: alsi-lawr Date: Thu, 18 Sep 2025 20:49:49 +0100 Subject: [PATCH 2/2] fix: undo random failwith on formatting options in server startup --- src/CSharpLanguageServer/Program.fs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/CSharpLanguageServer/Program.fs b/src/CSharpLanguageServer/Program.fs index 99e0b8a3..b9cd3771 100644 --- a/src/CSharpLanguageServer/Program.fs +++ b/src/CSharpLanguageServer/Program.fs @@ -55,8 +55,7 @@ let entry args = { ServerSettings.Default with SolutionPath = serverArgs.TryGetResult <@ Solution @> LogLevel = logLevel - DebugMode = debugMode - ApplyFormattingOptions = failwith "Not Implemented" } + DebugMode = debugMode } Logging.setupLogging settings.LogLevel