diff --git a/src/CSharpLanguageServer/CSharpLanguageServer.fsproj b/src/CSharpLanguageServer/CSharpLanguageServer.fsproj
index 8e2c76d7..79a1fb5d 100644
--- a/src/CSharpLanguageServer/CSharpLanguageServer.fsproj
+++ b/src/CSharpLanguageServer/CSharpLanguageServer.fsproj
@@ -33,6 +33,7 @@
+
diff --git a/src/CSharpLanguageServer/Handlers/CSharpMetadata.fs b/src/CSharpLanguageServer/Handlers/CSharpMetadata.fs
index 36039bda..1b96250d 100644
--- a/src/CSharpLanguageServer/Handlers/CSharpMetadata.fs
+++ b/src/CSharpLanguageServer/Handlers/CSharpMetadata.fs
@@ -5,20 +5,19 @@ open Ionide.LanguageServerProtocol.JsonRpc
open CSharpLanguageServer.Types
open CSharpLanguageServer.State
+open CSharpLanguageServer.Lsp.Workspace
[]
module CSharpMetadata =
let handle
(context: ServerRequestContext)
- (metadataParams: CSharpMetadataParams)
+ (p: CSharpMetadataParams)
: AsyncLspResult =
- async {
- let uri = metadataParams.TextDocument.Uri
- let metadataMaybe =
- context.DecompiledMetadata
- |> Map.tryFind uri
- |> Option.map (fun x -> x.Metadata)
-
- return metadataMaybe |> LspResult.success
- }
+ p.TextDocument.Uri
+ |> workspaceFolder context.Workspace
+ |> Option.map _.DecompiledMetadata
+ |> Option.bind (Map.tryFind p.TextDocument.Uri)
+ |> Option.map _.Metadata
+ |> LspResult.success
+ |> async.Return
diff --git a/src/CSharpLanguageServer/Handlers/CallHierarchy.fs b/src/CSharpLanguageServer/Handlers/CallHierarchy.fs
index b833e544..d51036b9 100644
--- a/src/CSharpLanguageServer/Handlers/CallHierarchy.fs
+++ b/src/CSharpLanguageServer/Handlers/CallHierarchy.fs
@@ -6,7 +6,9 @@ open Ionide.LanguageServerProtocol.Types
open Ionide.LanguageServerProtocol.JsonRpc
open CSharpLanguageServer.State
+open CSharpLanguageServer.State.ServerState
open CSharpLanguageServer.Roslyn.Conversions
+open CSharpLanguageServer.Lsp.Workspace
[]
module CallHierarchy =
@@ -18,9 +20,7 @@ module CallHierarchy =
Microsoft.CodeAnalysis.SymbolKind.Event
Microsoft.CodeAnalysis.SymbolKind.Property ]
- let provider
- (clientCapabilities: ClientCapabilities)
- : U3 option =
+ let provider (_cc: ClientCapabilities) : U3 option =
Some(U3.C1 true)
let prepare
@@ -28,10 +28,19 @@ module CallHierarchy =
(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
+ match! workspaceDocumentSymbol context.Workspace AnyDocument p.TextDocument.Uri p.Position with
+ | Some wf, Some(symbol, project, _) when isCallableSymbol symbol ->
+ let! locations, updatedWf = workspaceFolderSymbolLocations symbol project wf
+
+ context.Emit(WorkspaceFolderChange updatedWf)
+
+ return
+ locations
+ |> Seq.map (CallHierarchyItem.fromSymbolAndLocation symbol)
+ |> Seq.toArray
+ |> Some
+ |> LspResult.success
+
| _ -> return None |> LspResult.success
}
@@ -40,6 +49,8 @@ module CallHierarchy =
(p: CallHierarchyIncomingCallsParams)
: AsyncLspResult =
async {
+ let! ct = Async.CancellationToken
+
let toCallHierarchyIncomingCalls (info: SymbolCallerInfo) : CallHierarchyIncomingCall seq =
let fromRanges =
info.Locations
@@ -49,13 +60,15 @@ module CallHierarchy =
info.CallingSymbol.Locations
|> Seq.choose Location.fromRoslynLocation
|> Seq.map (fun loc ->
- { From = CallHierarchyItem.fromSymbolAndLocation (info.CallingSymbol) 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
+ match! workspaceDocumentSymbol context.Workspace AnyDocument p.Item.Uri p.Item.Range.Start with
+ | Some wf, Some(symbol, _, _) ->
+ let! callers =
+ SymbolFinder.FindCallersAsync(symbol, wf.Solution.Value, cancellationToken = ct)
+ |> Async.AwaitTask
+
// 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
@@ -66,6 +79,8 @@ module CallHierarchy =
|> Seq.toArray
|> Some
|> LspResult.success
+
+ | _, _ -> return None |> LspResult.success
}
let outgoingCalls
diff --git a/src/CSharpLanguageServer/Handlers/CodeAction.fs b/src/CSharpLanguageServer/Handlers/CodeAction.fs
index 407fc3fb..c8b68404 100644
--- a/src/CSharpLanguageServer/Handlers/CodeAction.fs
+++ b/src/CSharpLanguageServer/Handlers/CodeAction.fs
@@ -21,6 +21,7 @@ open CSharpLanguageServer.Logging
open CSharpLanguageServer.Roslyn.Conversions
open CSharpLanguageServer.State
open CSharpLanguageServer.Util
+open CSharpLanguageServer.Lsp.Workspace
type CSharpCodeActionResolutionData =
@@ -330,7 +331,10 @@ module CodeAction =
(p: CodeActionParams)
: AsyncLspResult =
async {
- match context.GetDocument p.TextDocument.Uri with
+ let wf, docForUri =
+ p.TextDocument.Uri |> workspaceDocument context.Workspace AnyDocument
+
+ match docForUri with
| None -> return None |> LspResult.success
| Some doc ->
let! ct = Async.CancellationToken
@@ -376,7 +380,7 @@ module CodeAction =
let! maybeLspCa =
roslynCodeActionToResolvedLspCodeAction
doc.Project.Solution
- context.GetDocumentVersion
+ (Uri.unescape >> workspaceDocumentVersion context.Workspace)
doc
ct
ca
@@ -400,7 +404,11 @@ module CodeAction =
let resolutionData =
p.Data |> Option.map deserialize
- match context.GetDocument resolutionData.Value.TextDocumentUri with
+ let wf, docForUri =
+ resolutionData.Value.TextDocumentUri
+ |> workspaceDocument context.Workspace AnyDocument
+
+ match docForUri with
| None -> return raise (Exception(sprintf "no document for uri %s" resolutionData.Value.TextDocumentUri))
| Some doc ->
let! ct = Async.CancellationToken
@@ -414,7 +422,11 @@ module CodeAction =
roslynCodeActions |> Seq.tryFind (fun ca -> ca.Title = p.Title)
let toResolvedLspCodeAction =
- roslynCodeActionToResolvedLspCodeAction doc.Project.Solution context.GetDocumentVersion doc ct
+ roslynCodeActionToResolvedLspCodeAction
+ doc.Project.Solution
+ (Uri.unescape >> workspaceDocumentVersion context.Workspace)
+ doc
+ ct
let! lspCodeAction =
match selectedCodeAction with
diff --git a/src/CSharpLanguageServer/Handlers/CodeLens.fs b/src/CSharpLanguageServer/Handlers/CodeLens.fs
index 6ec0fb1c..bb7c73e4 100644
--- a/src/CSharpLanguageServer/Handlers/CodeLens.fs
+++ b/src/CSharpLanguageServer/Handlers/CodeLens.fs
@@ -4,12 +4,14 @@ open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.CSharp
open Microsoft.CodeAnalysis.CSharp.Syntax
open Microsoft.CodeAnalysis.Text
+open Microsoft.CodeAnalysis.FindSymbols
open Ionide.LanguageServerProtocol.Server
open Ionide.LanguageServerProtocol.Types
open Ionide.LanguageServerProtocol.JsonRpc
open CSharpLanguageServer.State
open CSharpLanguageServer.Roslyn.Conversions
+open CSharpLanguageServer.Lsp.Workspace
type private DocumentSymbolCollectorForCodeLens(semanticModel: SemanticModel) =
inherit CSharpSyntaxWalker(SyntaxWalkerDepth.Token)
@@ -89,9 +91,10 @@ module CodeLens =
WorkDoneProgress = None }
let handle (context: ServerRequestContext) (p: CodeLensParams) : AsyncLspResult = async {
- let docMaybe = context.GetDocument p.TextDocument.Uri
+ let wf, docForUri =
+ p.TextDocument.Uri |> workspaceDocument context.Workspace AnyDocument
- match docMaybe with
+ match docForUri with
| None -> return None |> LspResult.success
| Some doc ->
let! ct = Async.CancellationToken
@@ -120,6 +123,7 @@ module CodeLens =
}
let resolve (context: ServerRequestContext) (p: CodeLens) : AsyncLspResult = async {
+ let! ct = Async.CancellationToken
let lensData: CodeLensData =
p.Data
@@ -127,14 +131,18 @@ module CodeLens =
|> Option.bind Option.ofObj
|> Option.defaultValue CodeLensData.Default
- match! context.FindSymbol lensData.DocumentUri lensData.Position with
- | None -> return p |> LspResult.success
- | Some symbol ->
- let! locations = context.FindReferences symbol false
+ match! workspaceDocumentSymbol context.Workspace AnyDocument lensData.DocumentUri lensData.Position with
+ | Some wf, Some(symbol, _, _) ->
+ let! refs =
+ SymbolFinder.FindReferencesAsync(symbol, wf.Solution.Value, cancellationToken = ct)
+ |> Async.AwaitTask
+
// FIXME: refNum is wrong. There are lots of false positive even if we distinct locations by
// (l.SourceTree.FilePath, l.SourceSpan)
let refNum =
- locations
+ refs
+ |> Seq.collect _.Locations
+ |> Seq.map _.Location
|> Seq.distinctBy (fun l -> (l.GetMappedLineSpan().Path, l.SourceSpan))
|> Seq.length
@@ -153,4 +161,6 @@ module CodeLens =
Arguments = Some [| arg |> serialize |] }
return { p with Command = Some command } |> LspResult.success
+
+ | _, _ -> return p |> LspResult.success
}
diff --git a/src/CSharpLanguageServer/Handlers/Completion.fs b/src/CSharpLanguageServer/Handlers/Completion.fs
index f26e697a..899a374e 100644
--- a/src/CSharpLanguageServer/Handlers/Completion.fs
+++ b/src/CSharpLanguageServer/Handlers/Completion.fs
@@ -12,6 +12,7 @@ open CSharpLanguageServer.State
open CSharpLanguageServer.Util
open CSharpLanguageServer.Roslyn.Conversions
open CSharpLanguageServer.Logging
+open CSharpLanguageServer.Lsp.Workspace
[]
module Completion =
@@ -185,7 +186,10 @@ module Completion =
(p: CompletionParams)
: Async option>> =
async {
- match context.GetDocument p.TextDocument.Uri with
+ let wf, docForUri =
+ p.TextDocument.Uri |> workspaceDocument context.Workspace AnyDocument
+
+ match docForUri with
| None -> return None |> LspResult.success
| Some doc ->
let! ct = Async.CancellationToken
diff --git a/src/CSharpLanguageServer/Handlers/Definition.fs b/src/CSharpLanguageServer/Handlers/Definition.fs
index 64a7dc2a..e8e05016 100644
--- a/src/CSharpLanguageServer/Handlers/Definition.fs
+++ b/src/CSharpLanguageServer/Handlers/Definition.fs
@@ -4,6 +4,8 @@ open Ionide.LanguageServerProtocol.Types
open Ionide.LanguageServerProtocol.JsonRpc
open CSharpLanguageServer.State
+open CSharpLanguageServer.State.ServerState
+open CSharpLanguageServer.Lsp.Workspace
[]
module Definition =
@@ -14,9 +16,13 @@ module Definition =
(p: DefinitionParams)
: Async option>> =
async {
- match! context.FindSymbol' p.TextDocument.Uri p.Position with
- | None -> return None |> LspResult.success
- | Some(symbol, project, _) ->
- let! locations = context.ResolveSymbolLocations symbol (Some project)
+ match! workspaceDocumentSymbol context.Workspace AnyDocument p.TextDocument.Uri p.Position with
+ | Some wf, Some(symbol, project, _) ->
+ let! locations, updatedWf = workspaceFolderSymbolLocations symbol project wf
+
+ context.Emit(WorkspaceFolderChange updatedWf)
+
return locations |> Array.ofList |> Definition.C2 |> U2.C1 |> Some |> LspResult.success
+
+ | _, _ -> return None |> LspResult.success
}
diff --git a/src/CSharpLanguageServer/Handlers/Diagnostic.fs b/src/CSharpLanguageServer/Handlers/Diagnostic.fs
index ef33f4d0..621b81d5 100644
--- a/src/CSharpLanguageServer/Handlers/Diagnostic.fs
+++ b/src/CSharpLanguageServer/Handlers/Diagnostic.fs
@@ -9,6 +9,8 @@ open CSharpLanguageServer.Roslyn.Conversions
open CSharpLanguageServer.State
open CSharpLanguageServer.Types
open CSharpLanguageServer.Util
+open CSharpLanguageServer.Lsp.Workspace
+
[]
module Diagnostic =
@@ -36,7 +38,10 @@ module Diagnostic =
Items = [||]
RelatedDocuments = None }
- match context.GetDocument p.TextDocument.Uri with
+ let wf, docForUri =
+ p.TextDocument.Uri |> workspaceDocument context.Workspace AnyDocument
+
+ match docForUri with
| None -> return emptyReport |> U2.C1 |> LspResult.success
| Some doc ->
@@ -107,7 +112,7 @@ module Diagnostic =
let emptyWorkspaceDiagnosticReport: WorkspaceDiagnosticReport =
{ Items = Array.empty }
- match context.State.Solution, p.PartialResultToken with
+ match context.Workspace.SingletonFolder.Solution, p.PartialResultToken with
| None, _ -> return emptyWorkspaceDiagnosticReport |> LspResult.success
| Some solution, None ->
diff --git a/src/CSharpLanguageServer/Handlers/DocumentFormatting.fs b/src/CSharpLanguageServer/Handlers/DocumentFormatting.fs
index 46667f4a..4323c495 100644
--- a/src/CSharpLanguageServer/Handlers/DocumentFormatting.fs
+++ b/src/CSharpLanguageServer/Handlers/DocumentFormatting.fs
@@ -7,9 +7,10 @@ open Ionide.LanguageServerProtocol.JsonRpc
open CSharpLanguageServer.State
open CSharpLanguageServer.Util
open CSharpLanguageServer.Roslyn.Document
+open CSharpLanguageServer.Lsp.Workspace
-[]
+[]
module DocumentFormatting =
let provider (_cc: ClientCapabilities) : U2 option = Some(U2.C1 true)
@@ -22,12 +23,12 @@ module DocumentFormatting =
}
let handle (context: ServerRequestContext) (p: DocumentFormattingParams) : AsyncLspResult =
- let formatDocument =
- p.Options
- |> context.State.Settings.GetEffectiveFormattingOptions
- |> formatDocument
+ let lspFormattingOptions =
+ p.Options |> context.State.Settings.GetEffectiveFormattingOptions
+
+ let wf, doc = p.TextDocument.Uri |> workspaceDocument context.Workspace UserDocument
- context.GetUserDocument p.TextDocument.Uri
+ doc
|> async.Return
- |> Async.bindOption formatDocument
+ |> Async.bindOption (formatDocument lspFormattingOptions)
|> Async.map LspResult.success
diff --git a/src/CSharpLanguageServer/Handlers/DocumentHighlight.fs b/src/CSharpLanguageServer/Handlers/DocumentHighlight.fs
index 87486b08..73393d61 100644
--- a/src/CSharpLanguageServer/Handlers/DocumentHighlight.fs
+++ b/src/CSharpLanguageServer/Handlers/DocumentHighlight.fs
@@ -10,10 +10,11 @@ open Ionide.LanguageServerProtocol.JsonRpc
open CSharpLanguageServer.State
open CSharpLanguageServer.Roslyn.Conversions
open CSharpLanguageServer.Util
+open CSharpLanguageServer.Lsp.Workspace
[]
module DocumentHighlight =
- let provider (_: ClientCapabilities) : U2 option = Some(U2.C1 true)
+ let provider (_cc: ClientCapabilities) : U2 option = Some(U2.C1 true)
let private shouldHighlight (symbol: ISymbol) =
match symbol with
@@ -56,13 +57,13 @@ module DocumentHighlight =
Kind = Some DocumentHighlightKind.Read })
}
- match! context.FindSymbol' p.TextDocument.Uri p.Position with
- | Some(symbol, _, Some doc) ->
+ match! workspaceDocumentSymbol context.Workspace AnyDocument p.TextDocument.Uri p.Position with
+ | Some wf, 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
+ | _, _ -> return None |> LspResult.success
}
diff --git a/src/CSharpLanguageServer/Handlers/DocumentOnTypeFormatting.fs b/src/CSharpLanguageServer/Handlers/DocumentOnTypeFormatting.fs
index c5b91b8e..6ea4fb4d 100644
--- a/src/CSharpLanguageServer/Handlers/DocumentOnTypeFormatting.fs
+++ b/src/CSharpLanguageServer/Handlers/DocumentOnTypeFormatting.fs
@@ -10,6 +10,8 @@ open Ionide.LanguageServerProtocol.JsonRpc
open CSharpLanguageServer.State
open CSharpLanguageServer.Roslyn.Conversions
open CSharpLanguageServer.Roslyn.Document
+open CSharpLanguageServer.Lsp.Workspace
+
[]
module DocumentOnTypeFormatting =
@@ -43,12 +45,12 @@ module DocumentOnTypeFormatting =
let handle (context: ServerRequestContext) (p: DocumentOnTypeFormattingParams) : AsyncLspResult = async {
let lspFormattingOptions =
- if context.State.Settings.ApplyFormattingOptions then
- Some p.Options
- else
- None
+ p.Options |> context.State.Settings.GetEffectiveFormattingOptions
+
+ let wf, docForUri =
+ p.TextDocument.Uri |> workspaceDocument context.Workspace UserDocument
- match context.GetUserDocument p.TextDocument.Uri with
+ match docForUri with
| None -> return None |> LspResult.success
| Some doc ->
let! options = getDocumentFormattingOptionSet doc lspFormattingOptions
diff --git a/src/CSharpLanguageServer/Handlers/DocumentRangeFormatting.fs b/src/CSharpLanguageServer/Handlers/DocumentRangeFormatting.fs
index a42be2c0..92629e96 100644
--- a/src/CSharpLanguageServer/Handlers/DocumentRangeFormatting.fs
+++ b/src/CSharpLanguageServer/Handlers/DocumentRangeFormatting.fs
@@ -8,6 +8,7 @@ open Ionide.LanguageServerProtocol.JsonRpc
open CSharpLanguageServer.State
open CSharpLanguageServer.Roslyn.Conversions
open CSharpLanguageServer.Roslyn.Document
+open CSharpLanguageServer.Lsp.Workspace
[]
module DocumentRangeFormatting =
@@ -15,12 +16,12 @@ module DocumentRangeFormatting =
let handle (context: ServerRequestContext) (p: DocumentRangeFormattingParams) : AsyncLspResult = async {
let lspFormattingOptions =
- if context.State.Settings.ApplyFormattingOptions then
- Some p.Options
- else
- None
+ p.Options |> context.State.Settings.GetEffectiveFormattingOptions
- match context.GetUserDocument p.TextDocument.Uri with
+ let wf, docForUri =
+ p.TextDocument.Uri |> workspaceDocument context.Workspace UserDocument
+
+ match docForUri with
| None -> return None |> LspResult.success
| Some doc ->
let! ct = Async.CancellationToken
diff --git a/src/CSharpLanguageServer/Handlers/DocumentSymbol.fs b/src/CSharpLanguageServer/Handlers/DocumentSymbol.fs
index d1f796b5..f19b9220 100644
--- a/src/CSharpLanguageServer/Handlers/DocumentSymbol.fs
+++ b/src/CSharpLanguageServer/Handlers/DocumentSymbol.fs
@@ -12,6 +12,7 @@ open Ionide.LanguageServerProtocol.JsonRpc
open CSharpLanguageServer.State
open CSharpLanguageServer.Roslyn.Conversions
open CSharpLanguageServer.Util
+open CSharpLanguageServer.Lsp.Workspace
[]
module DocumentSymbol =
@@ -292,7 +293,10 @@ module DocumentSymbol =
|> Option.bind _.HierarchicalDocumentSymbolSupport
|> Option.defaultValue false
- match context.GetDocument p.TextDocument.Uri with
+ let wf, docForUri =
+ p.TextDocument.Uri |> workspaceDocument context.Workspace AnyDocument
+
+ match docForUri with
| None -> return None |> LspResult.success
| Some doc ->
let! ct = Async.CancellationToken
diff --git a/src/CSharpLanguageServer/Handlers/Hover.fs b/src/CSharpLanguageServer/Handlers/Hover.fs
index 35d59b61..912b133e 100644
--- a/src/CSharpLanguageServer/Handlers/Hover.fs
+++ b/src/CSharpLanguageServer/Handlers/Hover.fs
@@ -5,6 +5,7 @@ open Ionide.LanguageServerProtocol.JsonRpc
open CSharpLanguageServer
open CSharpLanguageServer.State
+open CSharpLanguageServer.Lsp.Workspace
open CSharpLanguageServer.Util
[]
@@ -23,7 +24,10 @@ module Hover =
hover |> Some
- let handle (context: ServerRequestContext) (p: HoverParams) : AsyncLspResult =
- context.FindSymbol p.TextDocument.Uri p.Position
- |> Async.bindOption (makeHoverForSymbol >> async.Return)
- |> Async.map LspResult.success
+ let handle (context: ServerRequestContext) (p: HoverParams) : AsyncLspResult = async {
+ let! wf, symInfo = workspaceDocumentSymbol context.Workspace AnyDocument p.TextDocument.Uri p.Position
+
+ match symInfo with
+ | Some(sym, _, _) -> return makeHoverForSymbol sym |> LspResult.success
+ | None -> return None |> LspResult.success
+ }
diff --git a/src/CSharpLanguageServer/Handlers/Implementation.fs b/src/CSharpLanguageServer/Handlers/Implementation.fs
index ba7981f9..2662f023 100644
--- a/src/CSharpLanguageServer/Handlers/Implementation.fs
+++ b/src/CSharpLanguageServer/Handlers/Implementation.fs
@@ -1,27 +1,53 @@
namespace CSharpLanguageServer.Handlers
+open Microsoft.CodeAnalysis
+open Microsoft.CodeAnalysis.FindSymbols
open Ionide.LanguageServerProtocol.Types
open Ionide.LanguageServerProtocol.JsonRpc
open CSharpLanguageServer.State
+open CSharpLanguageServer.State.ServerState
open CSharpLanguageServer.Util
+open CSharpLanguageServer.Lsp.Workspace
[]
module Implementation =
- let provider (_: ClientCapabilities) : U3 option =
+ let provider (_cc: ClientCapabilities) : U3 option =
Some(U3.C1 true)
- let findImplementationsOfSymbol (context: ServerRequestContext) sym = async {
- let! impls = context.FindImplementations sym
- let! locations = impls |> Seq.map (flip context.ResolveSymbolLocations None) |> Async.Parallel
+ let findImplLocationsOfSymbol wf project (sym: ISymbol) = async {
+ let! ct = Async.CancellationToken
- return locations |> Array.collect List.toArray |> Declaration.C2 |> U2.C1 |> Some
+ let! impls =
+ SymbolFinder.FindImplementationsAsync(sym, wf.Solution.Value, cancellationToken = ct)
+ |> Async.AwaitTask
+
+ let mutable updatedWf = wf
+
+ let locations = System.Collections.Generic.List()
+
+ for i in impls do
+ let! implLocations, wf = workspaceFolderSymbolLocations i project updatedWf
+
+ locations.AddRange(implLocations)
+ updatedWf <- wf
+
+ return locations |> Seq.toArray, updatedWf
}
let handle
(context: ServerRequestContext)
(p: ImplementationParams)
: Async option>> =
- context.FindSymbol p.TextDocument.Uri p.Position
- |> Async.bindOption (findImplementationsOfSymbol context)
- |> Async.map LspResult.success
+ async {
+ let! wf, symInfo = workspaceDocumentSymbol context.Workspace AnyDocument p.TextDocument.Uri p.Position
+
+ match wf, symInfo with
+ | Some wf, Some(sym, project, _) ->
+ let! impls, updatedWf = findImplLocationsOfSymbol wf project sym
+ context.Emit(WorkspaceFolderChange updatedWf)
+
+ return impls |> Declaration.C2 |> U2.C1 |> Some |> LspResult.success
+
+ | _, _ -> return None |> LspResult.success
+ }
diff --git a/src/CSharpLanguageServer/Handlers/Initialization.fs b/src/CSharpLanguageServer/Handlers/Initialization.fs
index c8d8c8ce..e2979fa6 100644
--- a/src/CSharpLanguageServer/Handlers/Initialization.fs
+++ b/src/CSharpLanguageServer/Handlers/Initialization.fs
@@ -69,28 +69,22 @@ module Initialization =
p.WorkspaceFolders
)
- // TODO use p.WorkspaceFolders
- let rootPath, rootPathSource =
+ let workspaceFoldersFallbackUri: DocumentUri =
p.RootUri
- |> Option.map (fun rootUri -> (Uri.toPath rootUri, "InitializeParams.rootUri"))
- |> Option.orElse (
- p.RootPath
- |> Option.map (fun rootPath -> (rootPath, "InitializeParams.rootPath"))
- )
- |> Option.defaultValue (Directory.GetCurrentDirectory(), "Process CWD")
+ |> Option.orElse (p.RootPath |> Option.map Uri.fromPath)
+ |> Option.defaultValue (Directory.GetCurrentDirectory() |> Uri.fromPath)
- do!
- windowShowMessage (
- sprintf "csharp-ls: will use \"%s\" (%s) as workspace root path" rootPath rootPathSource
- )
+ let workspaceFolders =
+ p.WorkspaceFolders
+ |> Option.defaultValue Array.empty
+ |> Seq.append
+ [ { Uri = workspaceFoldersFallbackUri
+ Name = "root" } ]
+ |> List.ofSeq
- logger.LogDebug(
- "handleInitialize: using rootPath \"{rootPath}\" from {rootPathSource}",
- rootPath,
- rootPathSource
- )
+ logger.LogInformation("handleInitialize: using workspaceFolders: {folders}", serialize workspaceFolders)
- context.Emit(RootPathChange rootPath)
+ context.Emit(WorkspaceConfigurationChanged workspaceFolders)
// setup timer so actors get period ticks
setupTimer ()
@@ -183,10 +177,9 @@ module Initialization =
)
//
- // start loading the solution
+ // start loading workspace
//
- logger.LogDebug("handleInitialized: post SolutionReloadRequest")
- stateActor.Post(SolutionReloadRequest(TimeSpan.FromMilliseconds(100)))
+ stateActor.Post(WorkspaceReloadRequested(TimeSpan.FromMilliseconds(100)))
logger.LogDebug("handleInitialized: Ok")
diff --git a/src/CSharpLanguageServer/Handlers/InlayHint.fs b/src/CSharpLanguageServer/Handlers/InlayHint.fs
index 356ece62..b32e3d87 100644
--- a/src/CSharpLanguageServer/Handlers/InlayHint.fs
+++ b/src/CSharpLanguageServer/Handlers/InlayHint.fs
@@ -13,6 +13,8 @@ open Ionide.LanguageServerProtocol.JsonRpc
open CSharpLanguageServer.State
open CSharpLanguageServer.Roslyn.Conversions
open CSharpLanguageServer.Util
+open CSharpLanguageServer.Lsp.Workspace
+
[]
module InlayHint =
@@ -239,7 +241,10 @@ module InlayHint =
Some(U3.C2 inlayHintOptions)
let handle (context: ServerRequestContext) (p: InlayHintParams) : AsyncLspResult = async {
- match context.GetUserDocument p.TextDocument.Uri with
+ let wf, docForUri =
+ p.TextDocument.Uri |> workspaceDocument context.Workspace UserDocument
+
+ match docForUri with
| None -> return None |> LspResult.success
| Some doc ->
let! ct = Async.CancellationToken
diff --git a/src/CSharpLanguageServer/Handlers/References.fs b/src/CSharpLanguageServer/Handlers/References.fs
index 49d1d50d..ba332aa7 100644
--- a/src/CSharpLanguageServer/Handlers/References.fs
+++ b/src/CSharpLanguageServer/Handlers/References.fs
@@ -1,29 +1,42 @@
namespace CSharpLanguageServer.Handlers
+open Microsoft.CodeAnalysis
+open Microsoft.CodeAnalysis.FindSymbols
open Ionide.LanguageServerProtocol.Types
open Ionide.LanguageServerProtocol.JsonRpc
open CSharpLanguageServer.State
+open CSharpLanguageServer.Lsp.Workspace
open CSharpLanguageServer.Roslyn.Conversions
open CSharpLanguageServer.Logging
[]
module References =
- let private logger = Logging.getLoggerByName "References"
-
- let provider (_: ClientCapabilities) : U2 option = Some(U2.C1 true)
+ let provider (_cc: ClientCapabilities) : U2 option = Some(U2.C1 true)
let handle (context: ServerRequestContext) (p: ReferenceParams) : AsyncLspResult = async {
- match! context.FindSymbol p.TextDocument.Uri p.Position with
- | None -> return None |> LspResult.success
- | Some symbol ->
- let! locations = context.FindReferences symbol p.Context.IncludeDeclaration
+ let! ct = Async.CancellationToken
+
+ match! workspaceDocumentSymbol context.Workspace AnyDocument p.TextDocument.Uri p.Position with
+ | Some wf, Some(symbol, _, _) ->
+ let! refs =
+ SymbolFinder.FindReferencesAsync(symbol, wf.Solution.Value, cancellationToken = ct)
+ |> Async.AwaitTask
+
+ let locationsFromReferencedSym (r: ReferencedSymbol) =
+ let locations = r.Locations |> Seq.map _.Location
+
+ match p.Context.IncludeDeclaration with
+ | true -> locations |> Seq.append r.Definition.Locations
+ | false -> locations
return
- locations
+ refs
+ |> Seq.collect locationsFromReferencedSym
|> Seq.choose Location.fromRoslynLocation
|> Seq.distinct
|> Seq.toArray
|> Some
|> LspResult.success
+ | _, _ -> return None |> LspResult.success
}
diff --git a/src/CSharpLanguageServer/Handlers/Rename.fs b/src/CSharpLanguageServer/Handlers/Rename.fs
index 32b85dfc..1ebfcb60 100644
--- a/src/CSharpLanguageServer/Handlers/Rename.fs
+++ b/src/CSharpLanguageServer/Handlers/Rename.fs
@@ -14,6 +14,8 @@ open CSharpLanguageServer.State
open CSharpLanguageServer.Logging
open CSharpLanguageServer.Roslyn.Conversions
open CSharpLanguageServer.Util
+open CSharpLanguageServer.Lsp.Workspace
+
[]
module Rename =
@@ -86,7 +88,11 @@ module Rename =
| false -> Some(U2.C1 true)
let prepare (context: ServerRequestContext) (p: PrepareRenameParams) : AsyncLspResult = async {
- match context.GetUserDocument p.TextDocument.Uri with
+
+ let wf, docForUri =
+ p.TextDocument.Uri |> workspaceDocument context.Workspace UserDocument
+
+ match docForUri with
| None -> return None |> LspResult.success
| Some doc ->
let! ct = Async.CancellationToken
@@ -142,9 +148,8 @@ module Rename =
}
let handle (context: ServerRequestContext) (p: RenameParams) : AsyncLspResult = async {
- match! context.FindSymbol' p.TextDocument.Uri p.Position with
- | None -> return None |> LspResult.success
- | Some(symbol, project, _) ->
+ match! workspaceDocumentSymbol context.Workspace AnyDocument p.TextDocument.Uri p.Position with
+ | Some wf, Some(symbol, project, _) ->
let! ct = Async.CancellationToken
let originalSolution = project.Solution
@@ -158,12 +163,18 @@ module Rename =
)
|> Async.AwaitTask
+
let! docTextEdit =
- lspDocChangesFromSolutionDiff ct originalSolution updatedSolution (fun uri ->
- context.OpenDocs.TryFind uri |> Option.map _.Version)
+ lspDocChangesFromSolutionDiff
+ ct
+ originalSolution
+ updatedSolution
+ (workspaceDocumentVersion context.Workspace)
return
WorkspaceEdit.Create(docTextEdit, context.ClientCapabilities)
|> Some
|> LspResult.success
+
+ | _, _ -> return None |> LspResult.success
}
diff --git a/src/CSharpLanguageServer/Handlers/SemanticTokens.fs b/src/CSharpLanguageServer/Handlers/SemanticTokens.fs
index 9afc6107..256d71b8 100644
--- a/src/CSharpLanguageServer/Handlers/SemanticTokens.fs
+++ b/src/CSharpLanguageServer/Handlers/SemanticTokens.fs
@@ -10,6 +10,8 @@ open Microsoft.CodeAnalysis.Text
open CSharpLanguageServer.State
open CSharpLanguageServer.Util
open CSharpLanguageServer.Roslyn.Conversions
+open CSharpLanguageServer.Lsp.Workspace
+
[]
module SemanticTokens =
@@ -52,14 +54,14 @@ module SemanticTokens =
classificationTypeMap
|> Map.values
|> Seq.distinct
- |> flip Seq.zip (Seq.initInfinite uint32)
+ |> (fun x -> Seq.zip x (Seq.initInfinite uint32))
|> Map.ofSeq
let private semanticTokenModifierMap =
classificationModifierMap
|> Map.values
|> Seq.distinct
- |> flip Seq.zip (Seq.initInfinite uint32)
+ |> (fun x -> Seq.zip x (Seq.initInfinite uint32))
|> Map.ofSeq
let private semanticTokenTypes =
@@ -75,12 +77,12 @@ module SemanticTokens =
let private getSemanticTokenIdFromClassification (classification: string) =
classificationTypeMap
|> Map.tryFind classification
- |> Option.bind (flip Map.tryFind semanticTokenTypeMap)
+ |> Option.bind (fun x -> Map.tryFind x semanticTokenTypeMap)
let private getSemanticTokenModifierFlagFromClassification (classification: string) =
classificationModifierMap
|> Map.tryFind classification
- |> Option.bind (flip Map.tryFind semanticTokenModifierMap)
+ |> Option.bind (fun x -> Map.tryFind x semanticTokenModifierMap)
|> Option.defaultValue 0u
|> int32
|> (<<<) 1u
@@ -113,7 +115,7 @@ module SemanticTokens =
(range: Range option)
: AsyncLspResult =
async {
- let docMaybe = context.GetUserDocument uri
+ let wf, docMaybe = uri |> workspaceDocument context.Workspace UserDocument
match docMaybe with
| None -> return None |> LspResult.success
diff --git a/src/CSharpLanguageServer/Handlers/SignatureHelp.fs b/src/CSharpLanguageServer/Handlers/SignatureHelp.fs
index 6eb32b21..079d0386 100644
--- a/src/CSharpLanguageServer/Handlers/SignatureHelp.fs
+++ b/src/CSharpLanguageServer/Handlers/SignatureHelp.fs
@@ -1,11 +1,8 @@
namespace CSharpLanguageServer.Handlers
-open System
-
open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.CSharp
open Microsoft.CodeAnalysis.CSharp.Syntax
-open Ionide.LanguageServerProtocol.Server
open Ionide.LanguageServerProtocol.Types
open Ionide.LanguageServerProtocol.JsonRpc
@@ -13,7 +10,7 @@ open CSharpLanguageServer
open CSharpLanguageServer.State
open CSharpLanguageServer.Util
open CSharpLanguageServer.Roslyn.Conversions
-open CSharpLanguageServer.Types
+open CSharpLanguageServer.Lsp.Workspace
module SignatureInformation =
let internal fromMethod (m: IMethodSymbol) =
@@ -43,7 +40,7 @@ module SignatureHelp =
// Algorithm from omnisharp-roslyn (https://github.com/OmniSharp/omnisharp-roslyn/blob/2d582b05839dbd23baf6e78fa2279163723a824c/src/OmniSharp.Roslyn.CSharp/Services/Signatures/SignatureHelpService.cs#L139C1-L166C10)
let private methodScore (types: TypeInfo list) (m: IMethodSymbol) =
- let score (invocation: TypeInfo) (definition: IParameterSymbol) =
+ let score (invocation: TypeInfo, definition: IParameterSymbol) =
if isNull invocation.ConvertedType then
1
else if SymbolEqualityComparer.Default.Equals(invocation.ConvertedType, definition.Type) then
@@ -54,16 +51,17 @@ module SignatureHelp =
if m.Parameters.Length < types.Length then
Microsoft.FSharp.Core.int.MinValue
else
- Seq.zip types m.Parameters |> Seq.map (uncurry score) |> Seq.sum
+ Seq.zip types m.Parameters |> Seq.map score |> Seq.sum
- let provider (_: ClientCapabilities) : SignatureHelpOptions option =
+ let provider (_cc: ClientCapabilities) : SignatureHelpOptions option =
{ TriggerCharacters = Some([| "("; ","; "<"; "{"; "[" |])
WorkDoneProgress = None
RetriggerCharacters = None }
|> Some
let handle (context: ServerRequestContext) (p: SignatureHelpParams) : AsyncLspResult = async {
- let docMaybe = context.GetUserDocument p.TextDocument.Uri
+ let wf, docMaybe =
+ p.TextDocument.Uri |> workspaceDocument context.Workspace UserDocument
match docMaybe with
| None -> return None |> LspResult.success
@@ -72,7 +70,7 @@ module SignatureHelp =
let! sourceText = doc.GetTextAsync(ct) |> Async.AwaitTask
let! semanticModel = doc.GetSemanticModelAsync(ct) |> Async.AwaitTask
- let position = Position.toRoslynPosition sourceText.Lines p.Position
+ let position = p.Position |> Position.toRoslynPosition sourceText.Lines
let! syntaxTree = doc.GetSyntaxTreeAsync(ct) |> Async.AwaitTask
let! root = syntaxTree.GetRootAsync(ct) |> Async.AwaitTask
diff --git a/src/CSharpLanguageServer/Handlers/TextDocumentSync.fs b/src/CSharpLanguageServer/Handlers/TextDocumentSync.fs
index 31498973..c113dcc3 100644
--- a/src/CSharpLanguageServer/Handlers/TextDocumentSync.fs
+++ b/src/CSharpLanguageServer/Handlers/TextDocumentSync.fs
@@ -10,9 +10,11 @@ open CSharpLanguageServer
open CSharpLanguageServer.Roslyn.Conversions
open CSharpLanguageServer.State
open CSharpLanguageServer.State.ServerState
-open CSharpLanguageServer.Roslyn.Symbol
open CSharpLanguageServer.Roslyn.Solution
+open CSharpLanguageServer.Lsp.Workspace
open CSharpLanguageServer.Logging
+open CSharpLanguageServer.Lsp.Workspace
+
[]
module TextDocumentSync =
@@ -44,35 +46,48 @@ module TextDocumentSync =
|> Some
- let didOpen (context: ServerRequestContext) (openParams: DidOpenTextDocumentParams) : Async> =
- match context.GetDocumentForUriOfType AnyDocument openParams.TextDocument.Uri with
- | Some(doc, docType) ->
+ let didOpen (context: ServerRequestContext) (p: DidOpenTextDocumentParams) : Async> =
+ let wf, docAndDocTypeForUri =
+ p.TextDocument.Uri |> workspaceDocumentDetails context.Workspace AnyDocument
+
+ match wf, docAndDocTypeForUri with
+ | Some(wf), 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
// also, as a bonus we can recover from corrupted document view in case document in roslyn solution
// went out of sync with editor
- let updatedDoc = SourceText.From(openParams.TextDocument.Text) |> doc.WithText
+ let updatedDoc = SourceText.From(p.TextDocument.Text) |> doc.WithText
+
+ context.Emit(DocumentOpened(p.TextDocument.Uri, p.TextDocument.Version, DateTime.Now))
- context.Emit(OpenDocAdd(openParams.TextDocument.Uri, openParams.TextDocument.Version, DateTime.Now))
- context.Emit(SolutionChange updatedDoc.Project.Solution)
+ context.Emit(
+ WorkspaceFolderChange
+ { wf with
+ Solution = Some updatedDoc.Project.Solution }
+ )
Ok() |> async.Return
| _ -> Ok() |> async.Return
- | None ->
- let docFilePathMaybe = Util.tryParseFileUri openParams.TextDocument.Uri
+ | Some wf, None ->
+ let docFilePathMaybe = Util.tryParseFileUri p.TextDocument.Uri
match docFilePathMaybe with
| Some docFilePath -> async {
// ok, this document is not in solution, register a new document
- let! newDocMaybe = solutionTryAddDocument docFilePath openParams.TextDocument.Text context.Solution
+ let! newDocMaybe = solutionTryAddDocument docFilePath p.TextDocument.Text wf.Solution.Value
match newDocMaybe with
| Some newDoc ->
- context.Emit(OpenDocAdd(openParams.TextDocument.Uri, openParams.TextDocument.Version, DateTime.Now))
- context.Emit(SolutionChange newDoc.Project.Solution)
+ context.Emit(DocumentOpened(p.TextDocument.Uri, p.TextDocument.Version, DateTime.Now))
+
+ context.Emit(
+ WorkspaceFolderChange
+ { wf with
+ Solution = Some newDoc.Project.Solution }
+ )
| None -> ()
@@ -81,35 +96,40 @@ module TextDocumentSync =
| None -> Ok() |> async.Return
- let didChange (context: ServerRequestContext) (changeParams: DidChangeTextDocumentParams) : Async> = async {
- let docMaybe = context.GetUserDocument changeParams.TextDocument.Uri
+ | _, _ -> Ok() |> async.Return
+
+ let didChange (context: ServerRequestContext) (p: DidChangeTextDocumentParams) : Async> = async {
+ let wf, docMaybe =
+ p.TextDocument.Uri |> workspaceDocument context.Workspace UserDocument
- match docMaybe with
- | None -> ()
- | Some doc ->
+ match wf, docMaybe with
+ | Some wf, 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 updatedSourceText =
- sourceText
- |> applyLspContentChangesOnRoslynSourceText changeParams.ContentChanges
+ sourceText |> applyLspContentChangesOnRoslynSourceText p.ContentChanges
let updatedDoc = doc.WithText(updatedSourceText)
//logMessage (sprintf "TextDocumentDidChange: newSourceText: %s" (string updatedSourceText))
- let updatedSolution = updatedDoc.Project.Solution
+ let updatedWf =
+ { wf with
+ Solution = Some updatedDoc.Project.Solution }
- context.Emit(SolutionChange updatedSolution)
- context.Emit(OpenDocAdd(changeParams.TextDocument.Uri, changeParams.TextDocument.Version, DateTime.Now))
+ context.Emit(WorkspaceFolderChange updatedWf)
+ context.Emit(DocumentOpened(p.TextDocument.Uri, p.TextDocument.Version, DateTime.Now))
+
+ | _, _ -> ()
return Ok()
}
- let didClose (context: ServerRequestContext) (closeParams: DidCloseTextDocumentParams) : Async> =
- context.Emit(OpenDocRemove closeParams.TextDocument.Uri)
+ let didClose (context: ServerRequestContext) (p: DidCloseTextDocumentParams) : Async> =
+ context.Emit(DocumentClosed p.TextDocument.Uri)
Ok() |> async.Return
let willSave (_context: ServerRequestContext) (_p: WillSaveTextDocumentParams) : Async> = async {
@@ -122,23 +142,30 @@ module TextDocumentSync =
: AsyncLspResult =
async { return LspResult.notImplemented }
- 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
+ let didSave (context: ServerRequestContext) (p: DidSaveTextDocumentParams) : Async> =
+ let wf, doc = p.TextDocument.Uri |> workspaceDocument context.Workspace AnyDocument
+
+ match wf, doc with
+ | Some _, Some doc -> Ok() |> async.Return
- match doc with
- | Some _ -> Ok() |> async.Return
+ | Some wf, None -> async {
+ let docFilePath = Util.parseFileUri p.TextDocument.Uri
- | None -> async {
- let docFilePath = Util.parseFileUri saveParams.TextDocument.Uri
- let! newDocMaybe = solutionTryAddDocument docFilePath saveParams.Text.Value context.Solution
+ // we need to add this file to solution if not already
+ let! newDocMaybe = solutionTryAddDocument docFilePath p.Text.Value wf.Solution.Value
match newDocMaybe with
| Some newDoc ->
- context.Emit(OpenDocTouch(saveParams.TextDocument.Uri, DateTime.Now))
- context.Emit(SolutionChange newDoc.Project.Solution)
+ let updatedWf =
+ { wf with
+ Solution = Some newDoc.Project.Solution }
+
+ context.Emit(DocumentTouched(p.TextDocument.Uri, DateTime.Now))
+ context.Emit(WorkspaceFolderChange updatedWf)
| None -> ()
return Ok()
}
+
+ | _, _ -> Ok() |> async.Return
diff --git a/src/CSharpLanguageServer/Handlers/TypeDefinition.fs b/src/CSharpLanguageServer/Handlers/TypeDefinition.fs
index b01005a5..f684613d 100644
--- a/src/CSharpLanguageServer/Handlers/TypeDefinition.fs
+++ b/src/CSharpLanguageServer/Handlers/TypeDefinition.fs
@@ -5,34 +5,42 @@ open Ionide.LanguageServerProtocol.Types
open Ionide.LanguageServerProtocol.JsonRpc
open CSharpLanguageServer.State
+open CSharpLanguageServer.State.ServerState
open CSharpLanguageServer.Util
+open CSharpLanguageServer.Lsp.Workspace
[]
module TypeDefinition =
- let provider (_: ClientCapabilities) : U3 option =
+ let provider (_cc: 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, project, _) ->
+ match! workspaceDocumentSymbol context.Workspace AnyDocument p.TextDocument.Uri p.Position with
+ | Some wf, Some(symbol, project, _) ->
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 project))
- |> Async.Parallel
- |> Async.map (Seq.collect id >> Seq.toArray)
-
- return locations |> Declaration.C2 |> U2.C1 |> Some |> LspResult.success
+ | :? ILocalSymbol as localSymbol -> Some localSymbol.Type
+ | :? IFieldSymbol as fieldSymbol -> Some fieldSymbol.Type
+ | :? IPropertySymbol as propertySymbol -> Some propertySymbol.Type
+ | :? IParameterSymbol as parameterSymbol -> Some parameterSymbol.Type
+ | _ -> None
+
+ let! locations, wf =
+ match typeSymbol with
+ | None -> async.Return([], wf)
+ | Some symbol -> async {
+ let! aggregatedLspLocations, updatedWf = workspaceFolderSymbolLocations symbol project wf
+
+ context.Emit(WorkspaceFolderChange updatedWf)
+ return (aggregatedLspLocations, updatedWf)
+ }
+
+ return locations |> Seq.toArray |> Declaration.C2 |> U2.C1 |> Some |> LspResult.success
+
+ | _, _ -> return None |> LspResult.success
}
diff --git a/src/CSharpLanguageServer/Handlers/TypeHierarchy.fs b/src/CSharpLanguageServer/Handlers/TypeHierarchy.fs
index 7fbae774..dcd8d84c 100644
--- a/src/CSharpLanguageServer/Handlers/TypeHierarchy.fs
+++ b/src/CSharpLanguageServer/Handlers/TypeHierarchy.fs
@@ -1,12 +1,15 @@
namespace CSharpLanguageServer.Handlers
open Microsoft.CodeAnalysis
+open Microsoft.CodeAnalysis.FindSymbols
open Ionide.LanguageServerProtocol.Types
open Ionide.LanguageServerProtocol.JsonRpc
open CSharpLanguageServer.State
+open CSharpLanguageServer.State.ServerState
open CSharpLanguageServer.Roslyn.Conversions
open CSharpLanguageServer.Util
+open CSharpLanguageServer.Lsp.Workspace
[]
module TypeHierarchy =
@@ -23,11 +26,20 @@ module TypeHierarchy =
(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
+ match! workspaceDocumentSymbol context.Workspace AnyDocument p.TextDocument.Uri p.Position with
+ | Some wf, Some(symbol, project, _) when isTypeSymbol symbol ->
+ let! symLocations, updatedWf = workspaceFolderSymbolLocations symbol project wf
+
+ context.Emit(WorkspaceFolderChange updatedWf)
+
+ return
+ symLocations
+ |> Seq.map (TypeHierarchyItem.fromSymbolAndLocation symbol)
+ |> Seq.toArray
+ |> Some
+ |> LspResult.success
+
+ | _, _ -> return None |> LspResult.success
}
let supertypes
@@ -35,8 +47,8 @@ module TypeHierarchy =
(p: TypeHierarchySupertypesParams)
: AsyncLspResult =
async {
- match! context.FindSymbol p.Item.Uri p.Item.Range.Start with
- | Some symbol when isTypeSymbol symbol ->
+ match! workspaceDocumentSymbol context.Workspace AnyDocument p.Item.Uri p.Item.Range.Start with
+ | Some wf, Some(symbol, project, _) when isTypeSymbol symbol ->
let typeSymbol = symbol :?> INamedTypeSymbol
let baseType =
@@ -48,13 +60,24 @@ module TypeHierarchy =
let interfaces = Seq.toList typeSymbol.Interfaces
let supertypes = baseType @ interfaces
- let! items =
- supertypes
- |> Seq.map (TypeHierarchyItem.fromSymbol context.ResolveSymbolLocations)
- |> Async.Parallel
+ let items = System.Collections.Generic.List()
+ let mutable updatedWf = wf
+
+ for typeSym in supertypes do
+ let! locations, wf = workspaceFolderSymbolLocations typeSym project updatedWf
+
+ let typeSymItems =
+ locations |> Seq.map (TypeHierarchyItem.fromSymbolAndLocation typeSym)
- return items |> Seq.collect id |> Seq.toArray |> Some |> LspResult.success
- | _ -> return None |> LspResult.success
+ items.AddRange(typeSymItems)
+
+ updatedWf <- wf
+
+ context.Emit(WorkspaceFolderChange updatedWf)
+
+ return items |> Seq.toArray |> Some |> LspResult.success
+
+ | _, _ -> return None |> LspResult.success
}
let subtypes
@@ -62,23 +85,53 @@ module TypeHierarchy =
(p: TypeHierarchySubtypesParams)
: AsyncLspResult =
async {
- match! context.FindSymbol p.Item.Uri p.Item.Range.Start with
- | Some symbol when isTypeSymbol symbol ->
+ let! ct = Async.CancellationToken
+
+ match! workspaceDocumentSymbol context.Workspace AnyDocument p.Item.Uri p.Item.Range.Start with
+ | Some wf, Some(symbol, project, _) when isTypeSymbol symbol ->
let typeSymbol = symbol :?> INamedTypeSymbol
// We only want immediately derived classes/interfaces/implementations here (we only need
// subclasses not subclasses' subclasses)
+ let findDerivedClasses' (symbol: INamedTypeSymbol) (transitive: bool) : Async =
+ SymbolFinder.FindDerivedClassesAsync(symbol, wf.Solution.Value, transitive, cancellationToken = ct)
+ |> Async.AwaitTask
+
+ let findDerivedInterfaces' (symbol: INamedTypeSymbol) (transitive: bool) : Async =
+ SymbolFinder.FindDerivedInterfacesAsync(
+ symbol,
+ wf.Solution.Value,
+ transitive,
+ cancellationToken = ct
+ )
+ |> Async.AwaitTask
+
+ let findImplementations' (symbol: INamedTypeSymbol) (transitive: bool) : Async =
+ SymbolFinder.FindImplementationsAsync(symbol, wf.Solution.Value, transitive, cancellationToken = ct)
+ |> Async.AwaitTask
+
let! subtypes =
- [ context.FindDerivedClasses' typeSymbol false
- context.FindDerivedInterfaces' typeSymbol false
- context.FindImplementations' typeSymbol false ]
+ [ findDerivedClasses' typeSymbol false
+ findDerivedInterfaces' typeSymbol false
+ findImplementations' typeSymbol false ]
|> Async.Parallel
|> Async.map (Seq.collect id >> Seq.toList)
- let! items =
- subtypes
- |> Seq.map (TypeHierarchyItem.fromSymbol context.ResolveSymbolLocations)
- |> Async.Parallel
+ let items = System.Collections.Generic.List()
+ let mutable updatedWf = wf
+
+ for typeSym in subtypes do
+ let! locations, wf = workspaceFolderSymbolLocations typeSym project updatedWf
+
+ let typeSymItems =
+ locations |> Seq.map (TypeHierarchyItem.fromSymbolAndLocation typeSym)
+
+ items.AddRange(typeSymItems)
+
+ updatedWf <- wf
+
+ context.Emit(WorkspaceFolderChange updatedWf)
+
+ return items |> Seq.toArray |> Some |> LspResult.success
- return items |> Seq.collect id |> Seq.toArray |> Some |> LspResult.success
- | _ -> return None |> LspResult.success
+ | _, _ -> return None |> LspResult.success
}
diff --git a/src/CSharpLanguageServer/Handlers/Workspace.fs b/src/CSharpLanguageServer/Handlers/Workspace.fs
index d7a5f9f0..dc6e4c19 100644
--- a/src/CSharpLanguageServer/Handlers/Workspace.fs
+++ b/src/CSharpLanguageServer/Handlers/Workspace.fs
@@ -11,10 +11,11 @@ open Microsoft.CodeAnalysis.Text
open CSharpLanguageServer
open CSharpLanguageServer.State
open CSharpLanguageServer.State.ServerState
-open CSharpLanguageServer.Roslyn.Symbol
open CSharpLanguageServer.Roslyn.Solution
open CSharpLanguageServer.Logging
open CSharpLanguageServer.Types
+open CSharpLanguageServer.Lsp.Workspace
+
[]
module Workspace =
@@ -25,12 +26,14 @@ module Workspace =
FileOperations = None }
|> Some
+
let dynamicRegistrationForDidChangeWatchedFiles (clientCapabilities: ClientCapabilities) =
clientCapabilities.Workspace
|> Option.bind _.DidChangeWatchedFiles
|> Option.bind _.DynamicRegistration
|> Option.defaultValue false
+
let didChangeWatchedFilesRegistration (clientCapabilities: ClientCapabilities) : Registration option =
match dynamicRegistrationForDidChangeWatchedFiles clientCapabilities with
| false -> None
@@ -47,53 +50,86 @@ module Workspace =
Method = "workspace/didChangeWatchedFiles"
RegisterOptions = registerOptions |> serialize |> Some }
+
let private tryReloadDocumentOnUri logger (context: ServerRequestContext) uri = async {
- match context.GetUserDocument uri with
- | Some doc ->
+ let wf, doc = uri |> workspaceDocument context.Workspace UserDocument
+
+ match wf, doc with
+ | Some wf, Some doc ->
let fileText = uri |> Util.parseFileUri |> File.ReadAllText
let updatedDoc = SourceText.From(fileText) |> doc.WithText
- context.Emit(SolutionChange updatedDoc.Project.Solution)
+ let updatedWf =
+ { wf with
+ Solution = Some updatedDoc.Project.Solution }
+
+ context.Emit(WorkspaceFolderChange updatedWf)
- | None ->
+ | Some wf, 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 = solutionTryAddDocument docFilePath fileText context.Solution
+ let! newDocMaybe = solutionTryAddDocument docFilePath fileText wf.Solution.Value
match newDocMaybe with
- | Some newDoc -> context.Emit(SolutionChange newDoc.Project.Solution)
+ | Some newDoc ->
+ let updatedWf =
+ { wf with
+ Solution = Some newDoc.Project.Solution }
+
+ context.Emit(WorkspaceFolderChange updatedWf)
| None -> ()
| None -> ()
+
+ | _, _ -> ()
}
+
let private removeDocument (context: ServerRequestContext) uri =
- match context.GetUserDocument uri with
- | Some existingDoc ->
+ let wf, doc = uri |> workspaceDocument context.Workspace UserDocument
+
+ match wf, doc with
+ | Some wf, Some existingDoc ->
let updatedProject = existingDoc.Project.RemoveDocument(existingDoc.Id)
- context.Emit(SolutionChange updatedProject.Solution)
- context.Emit(OpenDocRemove uri)
- | None -> ()
+ let updatedWf =
+ { wf with
+ Solution = Some updatedProject.Solution }
+
+ context.Emit(WorkspaceFolderChange updatedWf)
+ context.Emit(DocumentClosed uri)
+
+ | _, _ -> ()
+
let didChangeWatchedFiles
(context: ServerRequestContext)
(p: DidChangeWatchedFilesParams)
: Async> =
+
+ let windowShowMessage (m: string) =
+ match context.State.LspClient with
+ | Some lspClient ->
+ lspClient.WindowShowMessage(
+ { Type = MessageType.Info
+ Message = sprintf "csharp-ls: %s" m }
+ )
+ | None -> async.Return()
+
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)))
+ do! windowShowMessage "change to .csproj detected, will reload solution"
+ context.Emit(WorkspaceReloadRequested(TimeSpan.FromSeconds(5: int64)))
| ".sln"
| ".slnx" ->
- do! context.WindowShowMessage "change to .sln detected, will reload solution"
- context.Emit(SolutionReloadRequest(TimeSpan.FromSeconds(5: int64)))
+ do! windowShowMessage "change to .sln detected, will reload solution"
+ context.Emit(WorkspaceReloadRequested(TimeSpan.FromSeconds(5: int64)))
| ".cs" ->
match change.Type with
@@ -107,6 +143,7 @@ module Workspace =
return Ok()
}
+
let didChangeConfiguration
(context: ServerRequestContext)
(configParams: DidChangeConfigurationParams)
diff --git a/src/CSharpLanguageServer/Handlers/WorkspaceSymbol.fs b/src/CSharpLanguageServer/Handlers/WorkspaceSymbol.fs
index 5defb325..487f8176 100644
--- a/src/CSharpLanguageServer/Handlers/WorkspaceSymbol.fs
+++ b/src/CSharpLanguageServer/Handlers/WorkspaceSymbol.fs
@@ -3,6 +3,7 @@ namespace CSharpLanguageServer.Handlers
open System
open Microsoft.CodeAnalysis
+open Microsoft.CodeAnalysis.FindSymbols
open Ionide.LanguageServerProtocol.Types
open Ionide.LanguageServerProtocol.JsonRpc
@@ -11,16 +12,40 @@ open CSharpLanguageServer.Roslyn.Conversions
[]
module WorkspaceSymbol =
- let provider (_: ClientCapabilities) : U2 option = Some(U2.C1 true)
+ let provider (_cc: ClientCapabilities) : U2 option = Some(U2.C1 true)
let handle
(context: ServerRequestContext)
(p: WorkspaceSymbolParams)
: AsyncLspResult option> =
async {
+ let! ct = Async.CancellationToken
+
let pattern = if String.IsNullOrEmpty(p.Query) then None else Some p.Query
- let! symbols = context.FindSymbols pattern
+ let! symbols =
+ match context.Workspace.SingletonFolder.Solution with
+ | None -> async.Return Seq.empty
+ | Some solution ->
+ match pattern with
+ | Some pat ->
+ SymbolFinder.FindSourceDeclarationsWithPatternAsync(
+ solution,
+ pat,
+ SymbolFilter.TypeAndMember,
+ cancellationToken = ct
+ )
+ |> Async.AwaitTask
+ | None ->
+ let true' = System.Func(fun _ -> true)
+
+ SymbolFinder.FindSourceDeclarationsAsync(
+ solution,
+ true',
+ SymbolFilter.TypeAndMember,
+ cancellationToken = ct
+ )
+ |> Async.AwaitTask
return
symbols
diff --git a/src/CSharpLanguageServer/Lsp/Server.fs b/src/CSharpLanguageServer/Lsp/Server.fs
index 7be56891..180305a9 100644
--- a/src/CSharpLanguageServer/Lsp/Server.fs
+++ b/src/CSharpLanguageServer/Lsp/Server.fs
@@ -39,9 +39,6 @@ type CSharpLspServer(lspClient: CSharpLspClient, settings: ServerSettings) =
Settings = settings }
)
- let getDocumentForUriFromCurrentState docType uri =
- stateActor.PostAndAsyncReply(fun rc -> GetDocumentOfTypeForUri(docType, uri, rc))
-
let mutable timer: Threading.Timer option = None
let setupTimer () =
@@ -73,7 +70,7 @@ type CSharpLspServer(lspClient: CSharpLspClient, settings: ServerSettings) =
let! state = stateActor.PostAndAsyncReply GetState
- let context = ServerRequestContext(requestId, state, stateActor.Post)
+ let context = ServerRequestContext(state, stateActor.Post)
return! handlerFn context param
}
diff --git a/src/CSharpLanguageServer/Lsp/Workspace.fs b/src/CSharpLanguageServer/Lsp/Workspace.fs
new file mode 100644
index 00000000..0c3f50b5
--- /dev/null
+++ b/src/CSharpLanguageServer/Lsp/Workspace.fs
@@ -0,0 +1,235 @@
+module CSharpLanguageServer.Lsp.Workspace
+
+open System
+open System.IO
+
+open Microsoft.CodeAnalysis
+open Microsoft.CodeAnalysis.FindSymbols
+open Ionide.LanguageServerProtocol.Types
+open Microsoft.Extensions.Logging
+
+open CSharpLanguageServer.Util
+open CSharpLanguageServer.Types
+open CSharpLanguageServer.Logging
+open CSharpLanguageServer.Roslyn.Document
+open CSharpLanguageServer.Roslyn.Symbol
+open CSharpLanguageServer.Roslyn.Conversions
+
+let logger = Logging.getLoggerByName "Lsp.Workspace"
+
+type LspWorkspaceDecompiledMetadataDocument =
+ { Metadata: CSharpMetadataInformation
+ Document: Document }
+
+type LspWorkspaceFolder =
+ { Uri: string
+ Name: string
+ RoslynWorkspace: Workspace option
+ Solution: Solution option
+ DecompiledMetadata: Map }
+
+ static member Empty =
+ { Uri = Directory.GetCurrentDirectory() |> Uri.fromPath
+ Name = "(no name)"
+ RoslynWorkspace = None
+ Solution = None
+ DecompiledMetadata = Map.empty }
+
+type LspWorkspaceDocumentType =
+ | UserDocument // user Document from solution, on disk
+ | DecompiledDocument // Document decompiled from metadata, readonly
+ | AnyDocument
+
+let workspaceFolderResolveSymbolLocation
+ (project: Microsoft.CodeAnalysis.Project)
+ (symbol: Microsoft.CodeAnalysis.ISymbol)
+ (l: Microsoft.CodeAnalysis.Location)
+ (folder: LspWorkspaceFolder)
+ =
+ async {
+ match l.IsInMetadata, l.IsInSource with
+ | true, _ ->
+ let! ct = Async.CancellationToken
+ let! compilation = project.GetCompilationAsync(ct) |> Async.AwaitTask
+
+ let fullName =
+ symbol |> symbolGetContainingTypeOrThis |> symbolGetFullReflectionName
+
+ let containingAssemblyName =
+ l.MetadataModule |> nonNull "l.MetadataModule" |> _.ContainingAssembly.Name
+
+ let uri =
+ $"csharp:/metadata/projects/{project.Name}/assemblies/{containingAssemblyName}/symbols/{fullName}.cs"
+
+ let mdDocument, folder =
+ match Map.tryFind uri folder.DecompiledMetadata with
+ | Some value -> (value.Document, folder)
+ | None ->
+ let (documentFromMd, text) = documentFromMetadata compilation project l fullName
+
+ let csharpMetadata =
+ { ProjectName = project.Name
+ AssemblyName = containingAssemblyName
+ SymbolName = fullName
+ Source = text }
+
+ let md =
+ { Metadata = csharpMetadata
+ Document = documentFromMd }
+
+ let updatedFolder =
+ { folder with
+ DecompiledMetadata = Map.add uri md folder.DecompiledMetadata }
+
+ (documentFromMd, updatedFolder)
+
+ // figure out location on the document (approx implementation)
+ let! syntaxTree = mdDocument.GetSyntaxTreeAsync(ct) |> Async.AwaitTask
+
+ let collector = DocumentSymbolCollectorForMatchingSymbolName(uri, symbol)
+ let! root = syntaxTree.GetRootAsync(ct) |> Async.AwaitTask
+ collector.Visit(root)
+
+ let fallbackLocationInMetadata =
+ { Uri = uri
+ Range =
+ { Start = { Line = 0u; Character = 0u }
+ End = { Line = 0u; Character = 1u } } }
+
+ return
+ match collector.GetLocations() with
+ | [] -> [ fallbackLocationInMetadata ], folder
+ | ls -> ls, folder
+
+ | false, true ->
+ return
+ match (Location.fromRoslynLocation l) with
+ | Some loc -> [ loc ], folder
+ | None -> [], folder
+
+ | _, _ -> return [], folder
+ }
+
+/// The process of retrieving locations may update LspWorkspaceFolder itself,
+/// thus return value is a pair of symbol location list * LspWorkspaceFolder
+let workspaceFolderSymbolLocations
+ (symbol: Microsoft.CodeAnalysis.ISymbol)
+ (project: Microsoft.CodeAnalysis.Project)
+ folder
+ =
+ async {
+ let mutable wf = folder
+ let mutable aggregatedLspLocations = []
+
+ for l in symbol.Locations do
+ let! symLspLocations, updatedWf = workspaceFolderResolveSymbolLocation project symbol l wf
+
+ aggregatedLspLocations <- aggregatedLspLocations @ symLspLocations
+ wf <- updatedWf
+
+ return aggregatedLspLocations, wf
+ }
+
+type LspWorkspaceOpenDocInfo = { Version: int; Touched: DateTime }
+
+type LspWorkspace =
+ { Folders: LspWorkspaceFolder list
+ OpenDocs: Map }
+
+ static member Empty = { Folders = []; OpenDocs = Map.empty }
+
+ member this.SingletonFolder = this.Folders |> Seq.exactlyOne
+
+ member this.WithSingletonFolderUpdated(update: LspWorkspaceFolder -> LspWorkspaceFolder) =
+ let updatedFolders =
+ this.Folders
+ |> Seq.tryExactlyOne
+ |> Option.defaultValue LspWorkspaceFolder.Empty
+ |> update
+ |> List.singleton
+
+ { this with Folders = updatedFolders }
+
+ member this.WithSolution(solution: Solution option) =
+ this.WithSingletonFolderUpdated(fun f -> { f with Solution = solution })
+
+let workspaceFrom (workspaceFolders: WorkspaceFolder list) =
+ // TODO: currently only the first workspace folder is taken into account (see Seq.take 1)
+ match workspaceFolders.Length with
+ | 0 -> failwith "workspaceFrom: at least 1 workspace folder must be provided!"
+ | 1 -> ()
+ | _ -> logger.LogWarning("workspaceFrom: only the first WorkspaceFolder will be loaded!")
+
+ let folders =
+ workspaceFolders
+ |> Seq.take 1
+ |> Seq.map (fun f ->
+ { LspWorkspaceFolder.Empty with
+ Uri = f.Uri
+ Name = f.Name })
+ |> List.ofSeq
+
+ { LspWorkspace.Empty with
+ Folders = folders }
+
+let workspaceFolder (workspace: LspWorkspace) _uri = Some workspace.SingletonFolder
+
+let workspaceDocumentDetails (workspace: LspWorkspace) docType (u: string) =
+ let uri = Uri(u.Replace("%3A", ":", true, null))
+
+ let wf = workspaceFolder workspace uri
+ let solution = wf |> Option.bind _.Solution
+
+ let docAndDocType =
+ match wf, solution with
+ | Some wf, Some solution ->
+ let matchingUserDocuments =
+ solution.Projects
+ |> Seq.collect (fun p -> p.Documents)
+ |> Seq.filter (fun d -> Uri(d.FilePath, UriKind.Absolute) = uri)
+ |> List.ofSeq
+
+ let matchingUserDocumentMaybe =
+ match matchingUserDocuments with
+ | [ d ] -> Some(d, UserDocument)
+ | _ -> None
+
+ let matchingDecompiledDocumentMaybe =
+ wf.DecompiledMetadata
+ |> Map.tryFind u
+ |> Option.map (fun x -> (x.Document, DecompiledDocument))
+
+ match docType with
+ | UserDocument -> matchingUserDocumentMaybe
+ | DecompiledDocument -> matchingDecompiledDocumentMaybe
+ | AnyDocument -> matchingUserDocumentMaybe |> Option.orElse matchingDecompiledDocumentMaybe
+
+ | _, _ -> None
+
+ wf, docAndDocType
+
+let workspaceDocument workspace docType (u: string) =
+ let wf, docAndType = workspaceDocumentDetails workspace docType u
+ let doc = docAndType |> Option.map fst
+ wf, doc
+
+let workspaceDocumentSymbol workspace docType (uri: DocumentUri) (pos: Ionide.LanguageServerProtocol.Types.Position) = async {
+ let wf, docForUri = uri |> workspaceDocument workspace AnyDocument
+
+ match wf, docForUri with
+ | Some wf, 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
+
+ let symbolInfo =
+ symbol |> Option.ofObj |> Option.map (fun sym -> sym, doc.Project, Some doc)
+
+ return Some wf, symbolInfo
+
+ | wf, _ -> return (wf, None)
+}
+
+let workspaceDocumentVersion workspace uri =
+ uri |> workspace.OpenDocs.TryFind |> Option.map _.Version
diff --git a/src/CSharpLanguageServer/Roslyn/Conversions.fs b/src/CSharpLanguageServer/Roslyn/Conversions.fs
index fe61bf7d..ba6211e5 100644
--- a/src/CSharpLanguageServer/Roslyn/Conversions.fs
+++ b/src/CSharpLanguageServer/Roslyn/Conversions.fs
@@ -149,13 +149,6 @@ module CallHierarchyItem =
SelectionRange = location.Range
Data = None }
- let fromSymbol
- (wmResolveSymbolLocations: ISymbol -> Project option -> Async>)
- (symbol: ISymbol)
- : Async =
- wmResolveSymbolLocations symbol None
- |> Async.map (List.map (fromSymbolAndLocation symbol))
-
module TypeHierarchyItem =
let private displayStyle =
SymbolDisplayFormat(
@@ -188,13 +181,6 @@ module TypeHierarchyItem =
SelectionRange = location.Range
Data = None }
- 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 toSymbolInformation loc =
diff --git a/src/CSharpLanguageServer/State/ServerRequestContext.fs b/src/CSharpLanguageServer/State/ServerRequestContext.fs
index 6c6ed43c..9337c938 100644
--- a/src/CSharpLanguageServer/State/ServerRequestContext.fs
+++ b/src/CSharpLanguageServer/State/ServerRequestContext.fs
@@ -1,289 +1,9 @@
namespace CSharpLanguageServer.State
-open Microsoft.CodeAnalysis
-open Microsoft.CodeAnalysis.FindSymbols
-open Ionide.LanguageServerProtocol.Types
-
open CSharpLanguageServer.State.ServerState
-open CSharpLanguageServer.Types
-open CSharpLanguageServer.Roslyn.Document
-open CSharpLanguageServer.Roslyn.Symbol
-open CSharpLanguageServer.Roslyn.Solution
-open CSharpLanguageServer.Roslyn.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
+type ServerRequestContext(state: ServerState, emit: ServerStateEvent -> unit) =
member _.State = state
+ member _.Workspace = state.Workspace
member _.ClientCapabilities = state.ClientCapabilities
- member _.Solution = solutionMaybe.Value
- member _.OpenDocs = state.OpenDocs
- member _.DecompiledMetadata = state.DecompiledMetadata
-
- member _.WindowShowMessage(m: string) =
- match state.LspClient with
- | Some lspClient ->
- lspClient.WindowShowMessage(
- { Type = MessageType.Info
- Message = sprintf "csharp-ls: %s" m }
- )
- | None -> async.Return()
-
- member this.GetDocumentForUriOfType = getDocumentForUriOfType this.State
-
- member this.GetUserDocument(u: string) =
- this.GetDocumentForUriOfType UserDocument u |> Option.map fst
-
- member this.GetDocument(u: string) =
- this.GetDocumentForUriOfType AnyDocument u |> Option.map fst
-
- member _.Emit ev =
- match ev with
- | SolutionChange newSolution -> solutionMaybe <- Some newSolution
- | _ -> ()
-
- emitServerEvent ev
-
- member this.EmitMany es =
- for e in es do
- this.Emit e
-
- member private this.ResolveSymbolLocation
- (project: Microsoft.CodeAnalysis.Project option)
- sym
- (l: Microsoft.CodeAnalysis.Location)
- =
- async {
- match l.IsInMetadata, l.IsInSource, project with
- | true, _, Some project ->
- let! ct = Async.CancellationToken
- let! compilation = project.GetCompilationAsync(ct) |> Async.AwaitTask
-
- let fullName = sym |> symbolGetContainingTypeOrThis |> symbolGetFullReflectionName
-
- let containingAssemblyName =
- l.MetadataModule |> nonNull "l.MetadataModule" |> _.ContainingAssembly.Name
-
- let uri =
- $"csharp:/metadata/projects/{project.Name}/assemblies/{containingAssemblyName}/symbols/{fullName}.cs"
-
- let mdDocument, stateChanges =
- match Map.tryFind uri state.DecompiledMetadata with
- | Some value -> (value.Document, [])
- | None ->
- let (documentFromMd, text) = documentFromMetadata compilation project l fullName
-
- let csharpMetadata =
- { ProjectName = project.Name
- AssemblyName = containingAssemblyName
- SymbolName = fullName
- Source = text }
-
- (documentFromMd,
- [ DecompiledMetadataAdd(
- uri,
- { Metadata = csharpMetadata
- Document = documentFromMd }
- ) ])
-
- this.EmitMany stateChanges
-
- // figure out location on the document (approx implementation)
- let! syntaxTree = mdDocument.GetSyntaxTreeAsync(ct) |> Async.AwaitTask
-
- let collector = DocumentSymbolCollectorForMatchingSymbolName(uri, sym)
- let! root = syntaxTree.GetRootAsync(ct) |> Async.AwaitTask
- collector.Visit(root)
-
- let fallbackLocationInMetadata =
- { Uri = uri
- Range =
- { Start = { Line = 0u; Character = 0u }
- End = { Line = 0u; Character = 1u } } }
-
- return
- match collector.GetLocations() with
- | [] -> [ fallbackLocationInMetadata ]
- | ls -> ls
-
- | false, true, _ ->
- return
- match (Location.fromRoslynLocation l) with
- | Some loc -> [ loc ]
- | None -> []
-
- | _, _, _ -> return []
- }
-
- member this.ResolveSymbolLocations
- (symbol: Microsoft.CodeAnalysis.ISymbol)
- (project: Microsoft.CodeAnalysis.Project option)
- =
- async {
- let mutable aggregatedLspLocations = []
-
- for l in symbol.Locations do
- let! symLspLocations = this.ResolveSymbolLocation project symbol l
-
- aggregatedLspLocations <- aggregatedLspLocations @ symLspLocations
-
- return aggregatedLspLocations
- }
-
- 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.Project, Some doc)
- }
-
- member this.FindSymbol (uri: DocumentUri) (pos: Position) : Async =
- this.FindSymbol' uri pos |> Async.map (Option.map (fun (sym, _, _) -> sym))
-
- member private __._FindDerivedClasses (symbol: INamedTypeSymbol) (transitive: bool) : Async = async {
- match state.Solution with
- | None -> return []
- | Some currentSolution ->
- let! ct = Async.CancellationToken
-
- return!
- SymbolFinder.FindDerivedClassesAsync(symbol, currentSolution, transitive, cancellationToken = ct)
- |> Async.AwaitTask
- }
-
- member private __._FindDerivedInterfaces
- (symbol: INamedTypeSymbol)
- (transitive: bool)
- : Async =
- async {
- match state.Solution with
- | None -> return []
- | Some currentSolution ->
- let! ct = Async.CancellationToken
-
- return!
- SymbolFinder.FindDerivedInterfacesAsync(symbol, currentSolution, transitive, cancellationToken = ct)
- |> Async.AwaitTask
- }
-
- member __.FindImplementations(symbol: ISymbol) : Async = async {
- match state.Solution with
- | None -> return []
- | Some currentSolution ->
- let! ct = Async.CancellationToken
-
- return!
- SymbolFinder.FindImplementationsAsync(symbol, currentSolution, cancellationToken = ct)
- |> Async.AwaitTask
- }
-
- member __.FindImplementations' (symbol: INamedTypeSymbol) (transitive: bool) : Async = async {
- match state.Solution with
- | None -> return []
- | Some currentSolution ->
- let! ct = Async.CancellationToken
-
- return!
- SymbolFinder.FindImplementationsAsync(symbol, currentSolution, transitive, cancellationToken = ct)
- |> Async.AwaitTask
- }
-
- member this.FindDerivedClasses(symbol: INamedTypeSymbol) : Async =
- this._FindDerivedClasses symbol true
-
- member this.FindDerivedClasses' (symbol: INamedTypeSymbol) (transitive: bool) : Async =
- this._FindDerivedClasses symbol transitive
-
- member this.FindDerivedInterfaces(symbol: INamedTypeSymbol) : Async =
- this._FindDerivedInterfaces symbol true
-
- member this.FindDerivedInterfaces' (symbol: INamedTypeSymbol) (transitive: bool) : Async =
- this._FindDerivedInterfaces symbol transitive
-
- member __.FindCallers(symbol: ISymbol) : Async = async {
- match state.Solution with
- | None -> return []
- | Some currentSolution ->
- let! ct = Async.CancellationToken
-
- return!
- SymbolFinder.FindCallersAsync(symbol, currentSolution, cancellationToken = ct)
- |> Async.AwaitTask
- }
-
- member this.ResolveTypeSymbolLocations
- (project: Microsoft.CodeAnalysis.Project)
- (symbols: Microsoft.CodeAnalysis.ITypeSymbol list)
- =
- async {
- let mutable aggregatedLspLocations = []
-
- for sym in symbols do
- for l in sym.Locations do
- let! symLspLocations = this.ResolveSymbolLocation (Some project) sym l
-
- aggregatedLspLocations <- aggregatedLspLocations @ symLspLocations
-
- return aggregatedLspLocations
- }
-
- member this.FindSymbols(pattern: string option) : Async = async {
- let findTask ct =
- match pattern with
- | Some pat ->
- fun (sln: Solution) ->
- SymbolFinder.FindSourceDeclarationsWithPatternAsync(
- sln,
- pat,
- SymbolFilter.TypeAndMember,
- cancellationToken = ct
- )
- | None ->
- let true' = System.Func(fun _ -> true)
-
- fun (sln: Solution) ->
- SymbolFinder.FindSourceDeclarationsAsync(
- sln,
- true',
- SymbolFilter.TypeAndMember,
- cancellationToken = ct
- )
-
- match this.State.Solution with
- | None -> return []
- | Some solution ->
- let! ct = Async.CancellationToken
- return! findTask ct solution |> Async.AwaitTask
- }
-
- member this.FindReferences (symbol: ISymbol) (withDefinition: bool) : Async = async {
- match this.State.Solution with
- | None -> return []
- | Some solution ->
- let! ct = Async.CancellationToken
-
- let locationsFromReferencedSym (r: ReferencedSymbol) =
- let locations = r.Locations |> Seq.map _.Location
-
- match withDefinition with
- | true -> locations |> Seq.append r.Definition.Locations
- | false -> locations
-
- let! refs =
- SymbolFinder.FindReferencesAsync(symbol, solution, cancellationToken = ct)
- |> Async.AwaitTask
-
- return refs |> Seq.collect locationsFromReferencedSym
- }
-
- member this.GetDocumentVersion(uri: DocumentUri) : int option =
- Uri.unescape uri |> this.OpenDocs.TryFind |> Option.map _.Version
+ member _.Emit = emit
diff --git a/src/CSharpLanguageServer/State/ServerState.fs b/src/CSharpLanguageServer/State/ServerState.fs
index d41e6734..33398fd1 100644
--- a/src/CSharpLanguageServer/State/ServerState.fs
+++ b/src/CSharpLanguageServer/State/ServerState.fs
@@ -1,7 +1,6 @@
module CSharpLanguageServer.State.ServerState
open System
-open System.IO
open System.Threading
open System.Threading.Tasks
@@ -13,20 +12,14 @@ open Microsoft.Extensions.Logging
open CSharpLanguageServer.Logging
open CSharpLanguageServer.Roslyn.Conversions
open CSharpLanguageServer.Roslyn.Solution
-open CSharpLanguageServer.Roslyn.Symbol
+open CSharpLanguageServer.Lsp.Workspace
open CSharpLanguageServer.Types
open CSharpLanguageServer.Util
-type DecompiledMetadataDocument =
- { Metadata: CSharpMetadataInformation
- Document: Document }
-
type ServerRequestMode =
| ReadOnly
| ReadWrite
-type ServerOpenDocInfo = { Version: int; Touched: DateTime }
-
type RequestMetrics =
{ Count: int
TotalDuration: TimeSpan
@@ -53,16 +46,13 @@ type ServerRequest =
and ServerState =
{ Settings: ServerSettings
- RootPath: string
LspClient: ILspClient option
ClientCapabilities: ClientCapabilities
- Solution: Solution option
- OpenDocs: Map
- DecompiledMetadata: Map
+ Workspace: LspWorkspace
LastRequestId: int
PendingRequests: ServerRequest list
RunningRequests: Map
- SolutionReloadPending: DateTime option
+ WorkspaceReloadPending: DateTime option
PushDiagnosticsDocumentBacklog: string list
PushDiagnosticsCurrentDocTask: (string * Task) option
RequestStats: Map
@@ -70,22 +60,18 @@ and ServerState =
static member Empty =
{ Settings = ServerSettings.Default
- RootPath = Directory.GetCurrentDirectory()
LspClient = None
ClientCapabilities = emptyClientCapabilities
- Solution = None
- OpenDocs = Map.empty
- DecompiledMetadata = Map.empty
+ Workspace = LspWorkspace.Empty
LastRequestId = 0
PendingRequests = []
RunningRequests = Map.empty
- SolutionReloadPending = None
+ WorkspaceReloadPending = None
PushDiagnosticsDocumentBacklog = []
PushDiagnosticsCurrentDocTask = None
RequestStats = Map.empty
LastStatsDumpTime = DateTime.MinValue }
-
let pullFirstRequestMaybe requestQueue =
match requestQueue with
| [] -> (None, [])
@@ -115,61 +101,25 @@ let pullNextRequestMaybe requestQueue =
(Some nextRequest, queueRemainder)
-type ServerDocumentType =
- | UserDocument // user Document from solution, on disk
- | DecompiledDocument // Document decompiled from metadata, readonly
- | AnyDocument
-
-
type ServerStateEvent =
- | SettingsChange of ServerSettings
- | RootPathChange of string
- | ClientChange of ILspClient option
| ClientCapabilityChange of ClientCapabilities
- | SolutionChange of Solution
- | DecompiledMetadataAdd of string * DecompiledMetadataDocument
- | OpenDocAdd of string * int * DateTime
- | OpenDocRemove of string
- | OpenDocTouch of string * DateTime
- | GetState of AsyncReplyChannel
- | GetDocumentOfTypeForUri of ServerDocumentType * string * AsyncReplyChannel
- | StartRequest of string * ServerRequestMode * int * AsyncReplyChannel
+ | ClientChange of ILspClient option
+ | DocumentClosed of string
+ | DocumentOpened of string * int * DateTime
+ | DocumentTouched of string * DateTime
+ | DumpAndResetRequestStats
| FinishRequest of int
+ | GetState of AsyncReplyChannel
+ | PeriodicTimerTick
| ProcessRequestQueue
- | SolutionReloadRequest of TimeSpan
| PushDiagnosticsDocumentBacklogUpdate
- | PushDiagnosticsProcessPendingDocuments
| PushDiagnosticsDocumentDiagnosticsResolution of Result<(string * int option * Diagnostic array), Exception>
- | PeriodicTimerTick
- | DumpAndResetRequestStats
-
-
-let getDocumentForUriOfType state docType (u: string) =
- let uri = Uri(u.Replace("%3A", ":", true, null))
-
- match state.Solution with
- | Some solution ->
- let matchingUserDocuments =
- solution.Projects
- |> Seq.collect (fun p -> p.Documents)
- |> Seq.filter (fun d -> Uri(d.FilePath, UriKind.Absolute) = uri)
- |> List.ofSeq
-
- let matchingUserDocumentMaybe =
- match matchingUserDocuments with
- | [ d ] -> Some(d, UserDocument)
- | _ -> None
-
- let matchingDecompiledDocumentMaybe =
- Map.tryFind u state.DecompiledMetadata
- |> Option.map (fun x -> (x.Document, DecompiledDocument))
-
- match docType with
- | UserDocument -> matchingUserDocumentMaybe
- | DecompiledDocument -> matchingDecompiledDocumentMaybe
- | AnyDocument -> matchingUserDocumentMaybe |> Option.orElse matchingDecompiledDocumentMaybe
- | None -> None
-
+ | PushDiagnosticsProcessPendingDocuments
+ | SettingsChange of ServerSettings
+ | StartRequest of string * ServerRequestMode * int * AsyncReplyChannel
+ | WorkspaceConfigurationChanged of WorkspaceFolder list
+ | WorkspaceFolderChange of LspWorkspaceFolder
+ | WorkspaceReloadRequested of TimeSpan
let processFinishRequest postSelf state request =
request.Semaphore.Dispose()
@@ -227,7 +177,6 @@ let processFinishRequest postSelf state request =
postSelf ProcessRequestQueue
newState
-
let processDumpAndResetRequestStats (logger: ILogger) state =
let formatStats stats =
let calculateRequestStatsMetrics (name, metrics) =
@@ -277,7 +226,6 @@ let processDumpAndResetRequestStats (logger: ILogger) state =
RequestStats = Map.empty
LastStatsDumpTime = DateTime.Now }
-
let processServerEvent (logger: ILogger) state postSelf msg : Async = async {
match msg with
| SettingsChange newSettings ->
@@ -287,7 +235,7 @@ let processServerEvent (logger: ILogger) state postSelf msg : Async
not (state.Settings.SolutionPath = newState.Settings.SolutionPath)
if solutionChanged then
- postSelf (SolutionReloadRequest(TimeSpan.FromMilliseconds(250)))
+ postSelf (WorkspaceReloadRequested(TimeSpan.FromMilliseconds(250)))
return newState
@@ -295,12 +243,6 @@ let processServerEvent (logger: ILogger) state postSelf msg : Async
replyChannel.Reply(state)
return state
- | GetDocumentOfTypeForUri(docType, uri, replyChannel) ->
- let documentAndTypeMaybe = getDocumentForUriOfType state docType uri
- replyChannel.Reply(documentAndTypeMaybe |> Option.map fst)
-
- return state
-
| StartRequest(name, requestMode, requestPriority, replyChannel) ->
postSelf ProcessRequestQueue
@@ -371,61 +313,64 @@ let processServerEvent (logger: ILogger) state postSelf msg : Async
newState
- | RootPathChange rootPath -> return { state with RootPath = rootPath }
+ | WorkspaceConfigurationChanged workspaceFolders ->
+ let newWorkspace = workspaceFrom workspaceFolders
+ return { state with Workspace = newWorkspace }
| ClientChange lspClient -> return { state with LspClient = lspClient }
| ClientCapabilityChange cc -> return { state with ClientCapabilities = cc }
- | SolutionChange s ->
- postSelf PushDiagnosticsDocumentBacklogUpdate
- return { state with Solution = Some s }
-
- | DecompiledMetadataAdd(uri, md) ->
- let newDecompiledMd = Map.add uri md state.DecompiledMetadata
+ | WorkspaceFolderChange updatedWf ->
+ let updatedWorkspaceFolderList =
+ state.Workspace.Folders
+ |> List.map (fun wf -> if wf.Uri = updatedWf.Uri then updatedWf else wf)
return
{ state with
- DecompiledMetadata = newDecompiledMd }
+ Workspace.Folders = updatedWorkspaceFolderList }
- | OpenDocAdd(doc, ver, timestamp) ->
+ | DocumentOpened(uri, ver, timestamp) ->
postSelf PushDiagnosticsDocumentBacklogUpdate
let openDocInfo = { Version = ver; Touched = timestamp }
- let newOpenDocs = state.OpenDocs |> Map.add doc openDocInfo
- return { state with OpenDocs = newOpenDocs }
+ let newOpenDocs = state.Workspace.OpenDocs |> Map.add uri openDocInfo
- | OpenDocRemove uri ->
+ return
+ { state with
+ Workspace.OpenDocs = newOpenDocs }
+
+ | DocumentClosed uri ->
postSelf PushDiagnosticsDocumentBacklogUpdate
- let newOpenDocVersions = state.OpenDocs |> Map.remove uri
+ let newOpenDocVersions = state.Workspace.OpenDocs |> Map.remove uri
return
{ state with
- OpenDocs = newOpenDocVersions }
+ Workspace.OpenDocs = newOpenDocVersions }
- | OpenDocTouch(uri, timestamp) ->
+ | DocumentTouched(uri, timestamp) ->
postSelf PushDiagnosticsDocumentBacklogUpdate
- let openDocInfo = state.OpenDocs |> Map.tryFind uri
+ let openDocInfo = state.Workspace.OpenDocs |> Map.tryFind uri
match openDocInfo with
| None -> return state
| Some openDocInfo ->
let updatedOpenDocInfo = { openDocInfo with Touched = timestamp }
- let newOpenDocVersions = state.OpenDocs |> Map.add uri updatedOpenDocInfo
+ let newOpenDocVersions = state.Workspace.OpenDocs |> Map.add uri updatedOpenDocInfo
return
{ state with
- OpenDocs = newOpenDocVersions }
+ Workspace.OpenDocs = newOpenDocVersions }
- | SolutionReloadRequest reloadNoLaterThanIn ->
+ | WorkspaceReloadRequested reloadNoLaterThanIn ->
// we need to wait a bit before starting this so we
// can buffer many incoming requests at once
let newSolutionReloadDeadline =
let suggestedDeadline = DateTime.Now + reloadNoLaterThanIn
- match state.SolutionReloadPending with
+ match state.WorkspaceReloadPending with
| Some currentDeadline ->
if (suggestedDeadline < currentDeadline) then
suggestedDeadline
@@ -435,14 +380,14 @@ let processServerEvent (logger: ILogger) state postSelf msg : Async
return
{ state with
- SolutionReloadPending = newSolutionReloadDeadline |> Some }
+ WorkspaceReloadPending = newSolutionReloadDeadline |> Some }
| PushDiagnosticsDocumentBacklogUpdate ->
// here we build new backlog for background diagnostics processing
// which will consider documents by their last modification date
// for processing first
let newBacklog =
- state.OpenDocs
+ state.Workspace.OpenDocs
|> Seq.sortByDescending (fun kv -> kv.Value.Touched)
|> Seq.map (fun kv -> kv.Key)
|> List.ofSeq
@@ -477,9 +422,9 @@ let processServerEvent (logger: ILogger) state postSelf msg : Async
{ state with
PushDiagnosticsDocumentBacklog = newBacklog }
- let docAndTypeMaybe = docUri |> getDocumentForUriOfType state AnyDocument
+ let wf, docForUri = docUri |> workspaceDocument state.Workspace AnyDocument
- match docAndTypeMaybe with
+ match docForUri with
| None ->
// could not find document for this enqueued uri
logger.LogDebug(
@@ -489,7 +434,7 @@ let processServerEvent (logger: ILogger) state postSelf msg : Async
return newState
- | Some(doc, _docType) ->
+ | Some doc ->
let resolveDocumentDiagnostics () : Task = task {
let! semanticModelMaybe = doc.GetSemanticModelAsync()
@@ -558,17 +503,22 @@ let processServerEvent (logger: ILogger) state postSelf msg : Async
postSelf DumpAndResetRequestStats
let solutionReloadDeadline =
- state.SolutionReloadPending |> Option.defaultValue (DateTime.Now.AddDays(1))
+ state.WorkspaceReloadPending |> Option.defaultValue (DateTime.Now.AddDays(1))
match solutionReloadDeadline < DateTime.Now with
| true ->
+ let workspaceFolder = state.Workspace.SingletonFolder
+
let! newSolution =
- solutionLoadSolutionWithPathOrOnCwd state.LspClient.Value state.Settings.SolutionPath state.RootPath
+ solutionLoadSolutionWithPathOrOnCwd
+ state.LspClient.Value
+ state.Settings.SolutionPath
+ (workspaceFolder.Uri |> Uri.toPath)
return
{ state with
- Solution = newSolution
- SolutionReloadPending = None }
+ Workspace = state.Workspace.WithSolution(newSolution)
+ WorkspaceReloadPending = None }
| false -> return state
diff --git a/src/CSharpLanguageServer/Util.fs b/src/CSharpLanguageServer/Util.fs
index 5310acfa..c701a5c7 100644
--- a/src/CSharpLanguageServer/Util.fs
+++ b/src/CSharpLanguageServer/Util.fs
@@ -62,13 +62,6 @@ let rec unpackException (exn: Exception) =
| None -> exn
| _ -> exn
-// flip f takes its (first) two arguments in the reverse order of f, just like
-// the function with the same name in Haskell.
-let flip f x y = f y x
-
-let curry f x y = f (x, y)
-let uncurry f (x, y) = f x y
-
let formatInColumns (data: list>) : string =
if List.isEmpty data then
diff --git a/tests/CSharpLanguageServer.Tests/CSharpLanguageServer.Tests.fsproj b/tests/CSharpLanguageServer.Tests/CSharpLanguageServer.Tests.fsproj
index 97bf2ef4..62c149c2 100644
--- a/tests/CSharpLanguageServer.Tests/CSharpLanguageServer.Tests.fsproj
+++ b/tests/CSharpLanguageServer.Tests/CSharpLanguageServer.Tests.fsproj
@@ -23,6 +23,9 @@
+
+
+
diff --git a/tests/CSharpLanguageServer.Tests/ImplementationTests.fs b/tests/CSharpLanguageServer.Tests/ImplementationTests.fs
new file mode 100644
index 00000000..6b29ae19
--- /dev/null
+++ b/tests/CSharpLanguageServer.Tests/ImplementationTests.fs
@@ -0,0 +1,25 @@
+module CSharpLanguageServer.Tests.ImplementationTests
+
+open System
+
+open NUnit.Framework
+open Ionide.LanguageServerProtocol.Types
+
+open CSharpLanguageServer.Tests.Tooling
+
+[]
+let ``test textDocument/implementation works`` () =
+ use client = activateFixture "genericProject"
+ use classFile = client.Open "Project/Class.cs"
+
+ let implementationParams0: ImplementationParams =
+ { TextDocument = { Uri = classFile.Uri }
+ Position = { Line = 10u; Character = 8u }
+ WorkDoneToken = None
+ PartialResultToken = None }
+
+ let implementation0: U2 option =
+ client.Request("textDocument/implementation", implementationParams0)
+
+ // TODO: fix this test
+ ()
diff --git a/tests/CSharpLanguageServer.Tests/ReferenceTests.fs b/tests/CSharpLanguageServer.Tests/ReferenceTests.fs
index 7bab5060..07e7bba6 100644
--- a/tests/CSharpLanguageServer.Tests/ReferenceTests.fs
+++ b/tests/CSharpLanguageServer.Tests/ReferenceTests.fs
@@ -165,17 +165,21 @@ let testReferenceWorksToAspNetRazorPageReferencedValue () =
Assert.AreEqual(2, locations0.Value.Length)
let expectedLocations0: Location array =
- [| { Uri = testControllerCsFile.Uri
+ [| { Uri = viewsTestIndexCshtmlFile.Uri
Range =
- { Start = { Line = 11u; Character = 12u }
- End = { Line = 11u; Character = 18u } } }
+ { Start = { Line = 1u; Character = 7u }
+ End = { Line = 1u; Character = 13u } } }
- { Uri = viewsTestIndexCshtmlFile.Uri
+ { Uri = testControllerCsFile.Uri
Range =
- { Start = { Line = 1u; Character = 7u }
- End = { Line = 1u; Character = 13u } } } |]
+ { Start = { Line = 11u; Character = 12u }
+ End = { Line = 11u; Character = 18u } } } |]
+
+ let sortedLocations0 =
+ locations0.Value
+ |> Array.sortBy (fun f -> (f.Range.Start.Line, f.Range.Start.Character))
- Assert.AreEqual(expectedLocations0, locations0.Value)
+ Assert.AreEqual(expectedLocations0, sortedLocations0)
//
// do same but with IncludeDeclaration=true
diff --git a/tests/CSharpLanguageServer.Tests/SignatureHelpTests.fs b/tests/CSharpLanguageServer.Tests/SignatureHelpTests.fs
new file mode 100644
index 00000000..84cf0583
--- /dev/null
+++ b/tests/CSharpLanguageServer.Tests/SignatureHelpTests.fs
@@ -0,0 +1,44 @@
+module CSharpLanguageServer.Tests.SignatureHelpTests
+
+open NUnit.Framework
+open Ionide.LanguageServerProtocol.Types
+
+open CSharpLanguageServer.Tests.Tooling
+
+[]
+let ``test textDocument/signatureHelp works`` () =
+ use client = activateFixture "genericProject"
+ use classFile = client.Open "Project/Class.cs"
+
+ let signatureHelpParams0: SignatureHelpParams =
+ { TextDocument = { Uri = classFile.Uri }
+ Position = { Line = 9u; Character = 16u }
+ WorkDoneToken = None
+ Context = None }
+
+ let signatureHelp0: SignatureHelp option =
+ client.Request("textDocument/signatureHelp", signatureHelpParams0)
+
+ match signatureHelp0 with
+ | None -> failwith "Some SignatureHelp was expected"
+ | Some sh ->
+ Assert.AreEqual(1, sh.Signatures.Length)
+
+ let expectedSignature0 =
+ { Label = "void Class.MethodA(string arg)"
+ Documentation =
+ Some(
+ U2.C2
+ { Kind = MarkupKind.Markdown
+ Value = "" }
+ )
+ Parameters =
+ Some
+ [| { Label = U2.C1 "string arg"
+ Documentation = None } |]
+ ActiveParameter = None }
+
+ Assert.AreEqual(expectedSignature0, sh.Signatures[0])
+
+ Assert.AreEqual(Some 0u, sh.ActiveSignature)
+ Assert.AreEqual(None, sh.ActiveParameter)
diff --git a/tests/CSharpLanguageServer.Tests/TypeDefinitionTests.fs b/tests/CSharpLanguageServer.Tests/TypeDefinitionTests.fs
new file mode 100644
index 00000000..763fce82
--- /dev/null
+++ b/tests/CSharpLanguageServer.Tests/TypeDefinitionTests.fs
@@ -0,0 +1,38 @@
+module CSharpLanguageServer.Tests.TypeDefinitionTests
+
+open System
+
+open NUnit.Framework
+open Ionide.LanguageServerProtocol.Types
+
+open CSharpLanguageServer.Tests.Tooling
+
+[]
+let ``test textDocument/typeDefinition works`` () =
+ use client = activateFixture "genericProject"
+ use classFile = client.Open "Project/Class.cs"
+
+ let typeDefinitionParams0: TypeDefinitionParams =
+ { TextDocument = { Uri = classFile.Uri }
+ Position = { Line = 9u; Character = 16u }
+ WorkDoneToken = None
+ PartialResultToken = None }
+
+ let typeDefinition0: U2 option =
+ client.Request("textDocument/typeDefinition", typeDefinitionParams0)
+
+ match typeDefinition0 with
+ | Some(U2.C1(U2.C2 ls)) ->
+ Assert.AreEqual(1, ls.Length)
+
+ let expectedTypeDefLocationsForStringArg =
+ [| { Uri = "csharp:/metadata/projects/Project/assemblies/System.Runtime/symbols/System.String.cs"
+ Range =
+ { Start = { Line = 12u; Character = 20u }
+ End = { Line = 12u; Character = 26u } } } |]
+
+ Assert.AreEqual(expectedTypeDefLocationsForStringArg, ls)
+
+ | _ -> failwith "Some U2.C1 (U2.C2) was expected"
+
+ ()