Skip to content
41 changes: 18 additions & 23 deletions src/CSharpLanguageServer/Conversions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,6 @@ module Uri =
Name = Uri.UnescapeDataString(Uri(unescape uri).Segments |> Array.last) }


module Path =
let toUri = Uri.fromPath

let fromUri = Uri.toPath

let toWorkspaceFolder = toUri >> Uri.toWorkspaceFolder


module Position =
let fromLinePosition (pos: LinePosition) : Position =
{ Line = uint32 pos.Line
Expand Down Expand Up @@ -74,7 +66,7 @@ module Range =
module Location =
let fromRoslynLocation (loc: Microsoft.CodeAnalysis.Location) : option<Location> =
let toLspLocation (path: string) span : Location =
{ Uri = path |> Path.toUri
{ Uri = path |> Uri.fromPath
Range = span |> Range.fromLinePositionSpan }

match loc.Kind with
Expand Down Expand Up @@ -237,9 +229,7 @@ module SymbolInformation =
Tags = None }

symbol.Locations
|> Seq.map Location.fromRoslynLocation
|> Seq.filter _.IsSome
|> Seq.map _.Value
|> Seq.choose Location.fromRoslynLocation
|> Seq.map toSymbolInformation
|> Seq.toList

Expand All @@ -254,19 +244,24 @@ module DiagnosticSeverity =


module Diagnostic =
let fromRoslynDiagnostic (diagnostic: Microsoft.CodeAnalysis.Diagnostic) : Diagnostic =
let fromRoslynDiagnostic (diagnostic: Microsoft.CodeAnalysis.Diagnostic) : Diagnostic * string =
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)
CodeDescription = diagnosticCodeUrl |> Option.map (fun x -> { Href = x |> URI })
Source = Some "lsp"
Message = diagnostic.GetMessage()
RelatedInformation = None
// TODO: Convert diagnostic.Descriptor.CustomTags to Tags
Tags = None
Data = None }
let mappedLineSpan = diagnostic.Location.GetMappedLineSpan()

let diagnostic =
{ Range = mappedLineSpan.Span |> Range.fromLinePositionSpan
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()
RelatedInformation = None
// TODO: Convert diagnostic.Descriptor.CustomTags to Tags
Tags = None
Data = None }

(diagnostic, mappedLineSpan.Path |> Uri.fromPath)


module CompletionContext =
Expand Down
4 changes: 1 addition & 3 deletions src/CSharpLanguageServer/Handlers/CallHierarchy.fs
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,7 @@ module CallHierarchy =
|> Seq.toArray

info.CallingSymbol.Locations
|> Seq.map Location.fromRoslynLocation
|> Seq.filter _.IsSome
|> Seq.map _.Value
|> Seq.choose Location.fromRoslynLocation
|> Seq.map (fun loc ->
{ From = CallHierarchyItem.fromSymbolAndLocation (info.CallingSymbol) loc
FromRanges = fromRanges })
Expand Down
8 changes: 4 additions & 4 deletions src/CSharpLanguageServer/Handlers/CodeAction.fs
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,8 @@ module CodeAction =
match newDocFilePathMaybe with
| Some newDocFilePath ->
let textEditDocument =
{ Uri = newDocFilePath |> Path.toUri
Version = newDocFilePath |> Path.toUri |> tryGetDocVersionByUri }
{ Uri = Uri.fromPath newDocFilePath
Version = newDocFilePath |> Uri.fromPath |> tryGetDocVersionByUri }

docTextEdits.Add(
{ TextDocument = textEditDocument
Expand Down Expand Up @@ -255,8 +255,8 @@ module CodeAction =
|> Array.ofSeq

let textEditDocument =
{ Uri = originalDoc.FilePath |> Path.toUri
Version = originalDoc.FilePath |> Path.toUri |> tryGetDocVersionByUri }
{ Uri = originalDoc.FilePath |> Uri.fromPath
Version = originalDoc.FilePath |> Uri.fromPath |> tryGetDocVersionByUri }

docTextEdits.Add(
{ TextDocument = textEditDocument
Expand Down
6 changes: 3 additions & 3 deletions src/CSharpLanguageServer/Handlers/Definition.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ open CSharpLanguageServer.State

[<RequireQualifiedAccess>]
module Definition =
let provider (clientCapabilities: ClientCapabilities) : U2<bool, DefinitionOptions> option = Some(U2.C1 true)
let provider (_cc: ClientCapabilities) : U2<bool, DefinitionOptions> option = Some(U2.C1 true)

let handle
(context: ServerRequestContext)
Expand All @@ -16,7 +16,7 @@ module Definition =
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)
| Some(symbol, project, _) ->
let! locations = context.ResolveSymbolLocations symbol (Some project)
return locations |> Array.ofList |> Definition.C2 |> U2.C1 |> Some |> LspResult.success
}
11 changes: 9 additions & 2 deletions src/CSharpLanguageServer/Handlers/Diagnostic.fs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ module Diagnostic =
let diagnostics =
semanticModel.GetDiagnostics()
|> Seq.map Diagnostic.fromRoslynDiagnostic
|> Seq.map fst
|> Array.ofSeq

return { emptyReport with Items = diagnostics } |> U2.C1 |> LspResult.success
Expand All @@ -70,7 +71,7 @@ module Diagnostic =
d.Location.SourceTree
|> Option.ofObj
|> Option.map _.FilePath
|> Option.map Path.toUri
|> Option.map Uri.fromPath

let diagnosticsByDocument =
compilation.GetDiagnostics(ct)
Expand All @@ -79,11 +80,17 @@ module Diagnostic =
|> Seq.map (fun (uri, ds) -> (uri.Value, ds))

for (uri, docDiagnostics) in diagnosticsByDocument do
let items =
docDiagnostics
|> Seq.map Diagnostic.fromRoslynDiagnostic
|> Seq.map fst
|> Array.ofSeq

let fullDocumentReport: WorkspaceFullDocumentDiagnosticReport =
{ Kind = "full"
ResultId = None
Uri = uri
Items = docDiagnostics |> Seq.map Diagnostic.fromRoslynDiagnostic |> Array.ofSeq
Items = items
Version = None }

let documentReport: WorkspaceDocumentDiagnosticReport = U2.C1 fullDocumentReport
Expand Down
9 changes: 4 additions & 5 deletions src/CSharpLanguageServer/Handlers/DocumentHighlight.fs
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,19 @@ module DocumentHighlight =

return
locations
|> Seq.map Location.fromRoslynLocation
|> Seq.filter _.IsSome
|> Seq.map _.Value
|> Seq.choose Location.fromRoslynLocation
|> 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) ->
| Some(symbol, _, Some doc) ->
if shouldHighlight symbol then
let! highlights = getHighlights symbol doc
return highlights |> Seq.toArray |> Some |> LspResult.success
else
return None |> LspResult.success

| _ -> return None |> LspResult.success
}
2 changes: 1 addition & 1 deletion src/CSharpLanguageServer/Handlers/Hover.fs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module Hover =
let handle (context: ServerRequestContext) (p: HoverParams) : AsyncLspResult<Hover option> = async {
match! context.FindSymbol' p.TextDocument.Uri p.Position with
| None -> return None |> LspResult.success
| Some(symbol, _) ->
| Some(symbol, _, _) ->
let content = DocumentationUtil.markdownDocForSymbolWithSignature symbol

let hover =
Expand Down
4 changes: 1 addition & 3 deletions src/CSharpLanguageServer/Handlers/InlayHint.fs
Original file line number Diff line number Diff line change
Expand Up @@ -250,9 +250,7 @@ module InlayHint =

let inlayHints =
root.DescendantNodes(textSpan, fun node -> node.Span.IntersectsWith(textSpan))
|> Seq.map (toInlayHint semanticModel sourceText.Lines)
|> Seq.filter Option.isSome
|> Seq.map Option.get
|> Seq.choose (toInlayHint semanticModel sourceText.Lines)

return inlayHints |> Seq.toArray |> Some |> LspResult.success
}
Expand Down
4 changes: 1 addition & 3 deletions src/CSharpLanguageServer/Handlers/References.fs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ module References =

return
locations
|> Seq.map Location.fromRoslynLocation
|> Seq.filter _.IsSome
|> Seq.map _.Value
|> Seq.choose Location.fromRoslynLocation
|> Seq.distinct
|> Seq.toArray
|> Some
Expand Down
8 changes: 4 additions & 4 deletions src/CSharpLanguageServer/Handlers/Rename.fs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ module Rename =
|> Seq.map U2.C1
|> Array.ofSeq

let uri = originalDoc.FilePath |> Path.toUri
let uri = originalDoc.FilePath |> Uri.fromPath

let textEditDocument =
{ Uri = uri
Expand Down Expand Up @@ -144,13 +144,13 @@ module Rename =
let handle (context: ServerRequestContext) (p: RenameParams) : AsyncLspResult<WorkspaceEdit option> = async {
match! context.FindSymbol' p.TextDocument.Uri p.Position with
| None -> return None |> LspResult.success
| Some(symbol, doc) ->
| Some(symbol, project, _) ->
let! ct = Async.CancellationToken
let originalSolution = doc.Project.Solution
let originalSolution = project.Solution

let! updatedSolution =
Renamer.RenameSymbolAsync(
doc.Project.Solution,
project.Solution,
symbol,
SymbolRenameOptions(RenameOverloads = true, RenameFile = true),
p.NewName,
Expand Down
4 changes: 2 additions & 2 deletions src/CSharpLanguageServer/Handlers/TypeDefinition.fs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ module TypeDefinition =
async {
match! context.FindSymbol' p.TextDocument.Uri p.Position with
| None -> return None |> LspResult.success
| Some(symbol, doc) ->
| Some(symbol, project, _) ->
let typeSymbol =
match symbol with
| :? ILocalSymbol as localSymbol -> [ localSymbol.Type ]
Expand All @@ -30,7 +30,7 @@ module TypeDefinition =

let! locations =
typeSymbol
|> Seq.map (flip context.ResolveSymbolLocations (Some doc.Project))
|> Seq.map (flip context.ResolveSymbolLocations (Some project))
|> Async.Parallel
|> Async.map (Seq.collect id >> Seq.toArray)

Expand Down
24 changes: 12 additions & 12 deletions src/CSharpLanguageServer/RoslynHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -750,32 +750,32 @@ let getFullReflectionName (containingType: INamedTypeSymbol) =

String.Join(".", stack)

let getProjectForPathOnSolution (solution: Solution) (filePath: string) : Project option =
let docDir = Path.GetDirectoryName filePath

let fileOnProjectDir (p: Project) =
let projectDir = Path.GetDirectoryName p.FilePath
let projectDirWithDirSepChar = projectDir + string Path.DirectorySeparatorChar

docDir = projectDir || docDir.StartsWith projectDirWithDirSepChar

solution.Projects |> Seq.filter fileOnProjectDir |> Seq.tryHead

let tryAddDocument
(logger: ILogger)
(docFilePath: string)
(text: string)
(solution: Solution)
: Async<Document option> =
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

docDir = projectDir || docDir.StartsWith projectDirWithDirSepChar

let projectOnPath = solution.Projects |> Seq.filter fileOnProjectDir |> Seq.tryHead
let projectOnPath = getProjectForPathOnSolution solution docFilePath

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)

let newDoc =
proj.AddDocument(
name = docName,
Expand Down
12 changes: 8 additions & 4 deletions src/CSharpLanguageServer/State/ServerRequestContext.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,20 @@ namespace CSharpLanguageServer.State
open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.FindSymbols
open Ionide.LanguageServerProtocol.Types
open Microsoft.Extensions.Logging

open CSharpLanguageServer.State.ServerState
open CSharpLanguageServer.Types
open CSharpLanguageServer.RoslynHelpers
open CSharpLanguageServer.Conversions
open CSharpLanguageServer.Util
open CSharpLanguageServer.Logging

type ServerRequestContext(requestId: int, state: ServerState, emitServerEvent) =
let mutable solutionMaybe = state.Solution

let logger = Logging.getLoggerByName "ServerRequestContext"

member _.RequestId = requestId
member _.State = state
member _.ClientCapabilities = state.ClientCapabilities
Expand Down Expand Up @@ -130,19 +134,19 @@ type ServerRequestContext(requestId: int, state: ServerState, emitServerEvent) =
return aggregatedLspLocations
}

member this.FindSymbol' (uri: DocumentUri) (pos: Position) : Async<(ISymbol * Document) option> = async {
member this.FindSymbol' (uri: DocumentUri) (pos: Position) : Async<(ISymbol * Project * Document option) option> = async {
match this.GetDocument uri with
| None -> return None
| Some doc ->
let! ct = Async.CancellationToken
let! sourceText = doc.GetTextAsync(ct) |> Async.AwaitTask
let position = Position.toRoslynPosition sourceText.Lines pos
let! symbol = SymbolFinder.FindSymbolAtPositionAsync(doc, position, ct) |> Async.AwaitTask
return symbol |> Option.ofObj |> Option.map (fun sym -> sym, doc)
return symbol |> Option.ofObj |> Option.map (fun sym -> sym, doc.Project, Some doc)
}

member this.FindSymbol (uri: DocumentUri) (pos: Position) : Async<ISymbol option> =
this.FindSymbol' uri pos |> Async.map (Option.map fst)
this.FindSymbol' uri pos |> Async.map (Option.map (fun (sym, _, _) -> sym))

member private __._FindDerivedClasses (symbol: INamedTypeSymbol) (transitive: bool) : Async<INamedTypeSymbol seq> = async {
match state.Solution with
Expand Down Expand Up @@ -267,7 +271,7 @@ type ServerRequestContext(requestId: int, state: ServerState, emitServerEvent) =
let! ct = Async.CancellationToken

let locationsFromReferencedSym (r: ReferencedSymbol) =
let locations = r.Locations |> Seq.map (fun rl -> rl.Location)
let locations = r.Locations |> Seq.map _.Location

match withDefinition with
| true -> locations |> Seq.append r.Definition.Locations
Expand Down
1 change: 1 addition & 0 deletions src/CSharpLanguageServer/State/ServerState.fs
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,7 @@ let processServerEvent (logger: ILogger) state postSelf msg : Async<ServerState>
let diagnostics =
semanticModel.GetDiagnostics()
|> Seq.map Diagnostic.fromRoslynDiagnostic
|> Seq.map fst
|> Array.ofSeq

Ok(docUri, None, diagnostics)
Expand Down
12 changes: 8 additions & 4 deletions src/CSharpLanguageServer/Types.fs
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,17 @@ type ICSharpLspClient =
// But ClientCapabilities is a complex type, write it again will be a huge work.
abstract member Capabilities: ClientCapabilities option with get, set

let defaultDocumentFilter: TextDocumentFilter =
{ Language = None
let csharpDocumentFilter: TextDocumentFilter =
{ Language = Some "csharp"
Scheme = Some "file"
Pattern = Some "**/*.cs" }

// Type abbreviations cannot have augmentations, extensions
let defaultDocumentSelector: DocumentSelector = [| defaultDocumentFilter |> U2.C1 |]
let razorCsharpDocumentFilter: TextDocumentFilter =
{ Language = Some "razor-csharp"
Scheme = Some "file"
Pattern = Some "**/*.cshtml" }

let defaultDocumentSelector: DocumentSelector = [| csharpDocumentFilter |> U2.C1 |]

let emptyClientCapabilities: ClientCapabilities =
{ Workspace = None
Expand Down
2 changes: 1 addition & 1 deletion tests/CSharpLanguageServer.Tests/InitializationTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ let testServerRegistersCapabilitiesWithTheClient () =
{ DocumentSelector =
Some
[| U2.C1
{ Language = None
{ Language = Some "csharp"
Scheme = Some "file"
Pattern = Some "**/*.cs" } |]
WorkDoneProgress = None
Expand Down
Loading