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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [Unreleased]
* Add `--diagnose` command option to run basic diagnostics interactively
- By @razzmatazz in https://github.com/razzmatazz/csharp-language-server/pull/224
* Fix `textDocument/codeAction` for extracting an interface
- By @alsi-lawr in https://github.com/razzmatazz/csharp-language-server/pull/267
* Will use static server capability registration, unless required (i.e. for workspace/didChangeWatchedFiles).
Expand Down
1 change: 1 addition & 0 deletions src/CSharpLanguageServer/CSharpLanguageServer.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<Compile Include="ProgressReporter.fs" />
<Compile Include="RoslynHelpers.fs" />
<Compile Include="DocumentationUtil.fs" />
<Compile Include="Diagnostics.fs" />
<Compile Include="Lsp/Client.fs" />
<Compile Include="State/ServerState.fs" />
<Compile Include="State/ServerRequestContext.fs" />
Expand Down
73 changes: 73 additions & 0 deletions src/CSharpLanguageServer/Diagnostics.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
namespace CSharpLanguageServer

open System.IO
open Microsoft.Extensions.Logging
open Ionide.LanguageServerProtocol
open Ionide.LanguageServerProtocol.Types
open Ionide.LanguageServerProtocol.JsonRpc

open CSharpLanguageServer.Types
open CSharpLanguageServer.Logging
open CSharpLanguageServer.RoslynHelpers

module Diagnostics =
let private logger = Logging.getLoggerByName "Diagnostics"

type LspClientStub() =
interface System.IDisposable with
member _.Dispose() = ()

interface ILspClient with
member _.WindowShowMessage(p: ShowMessageParams) = async {
logger.LogDebug("WindowShowMessage: {message}", p.Message)
return ()
}

member _.WindowLogMessage(p: LogMessageParams) = async {
logger.LogDebug("WindowLogMessage: {message}", p.Message)
return ()
}

member _.TelemetryEvent(_: LSPAny) = async { return () }
member _.TextDocumentPublishDiagnostics(_: PublishDiagnosticsParams) = async { return () }
member _.LogTrace(_: LogTraceParams) = async { return () }
member _.CancelRequest(_: CancelParams) = async { return () }
member _.Progress(_: ProgressParams) = async { return () }
member _.WorkspaceWorkspaceFolders() = async { return LspResult.Ok None }
member _.WorkspaceConfiguration(_: ConfigurationParams) = async { return LspResult.Ok [||] }
member _.WindowWorkDoneProgressCreate(_: WorkDoneProgressCreateParams) = async { return LspResult.Ok() }
member _.WorkspaceSemanticTokensRefresh() = async { return LspResult.Ok() }
member _.WindowShowDocument(_: ShowDocumentParams) = async { return LspResult.Ok { Success = false } }
member _.WorkspaceInlineValueRefresh() = async { return LspResult.Ok() }
member _.WorkspaceInlayHintRefresh() = async { return LspResult.Ok() }
member _.WorkspaceDiagnosticRefresh() = async { return LspResult.Ok() }
member _.ClientRegisterCapability(_: RegistrationParams) = async { return LspResult.Ok() }
member _.ClientUnregisterCapability(_: UnregistrationParams) = async { return LspResult.Ok() }
member _.WindowShowMessageRequest(_: ShowMessageRequestParams) = async { return LspResult.Ok None }
member _.WorkspaceCodeLensRefresh() = async { return LspResult.Ok() }

member _.WorkspaceApplyEdit(_: ApplyWorkspaceEditParams) = async {
return
LspResult.Ok
{ Applied = false
FailureReason = None
FailedChange = None }
}


let diagnose (settings: ServerSettings) : Async<int> = async {
logger.LogDebug("diagnose: settings={settings}", settings)

initializeMSBuild logger

logger.LogDebug("diagnose: loading solution..")

let lspClient = new LspClientStub()
let cwd = string (Directory.GetCurrentDirectory())
let! _sln = loadSolutionOnSolutionPathOrDir lspClient logger None cwd

logger.LogDebug("diagnose: done")

let exitCode = 0
return exitCode
}
33 changes: 2 additions & 31 deletions src/CSharpLanguageServer/Handlers/Initialization.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ open System
open System.IO
open System.Reflection

open Microsoft.Build.Locator
open Ionide.LanguageServerProtocol
open Ionide.LanguageServerProtocol.Types
open Ionide.LanguageServerProtocol.Server
Expand All @@ -15,6 +14,7 @@ open CSharpLanguageServer.State
open CSharpLanguageServer.State.ServerState
open CSharpLanguageServer.Types
open CSharpLanguageServer.Logging
open CSharpLanguageServer.RoslynHelpers

[<RequireQualifiedAccess>]
module Initialization =
Expand Down Expand Up @@ -53,36 +53,7 @@ module Initialization =
serverName
)

let vsInstanceQueryOpt = VisualStudioInstanceQueryOptions.Default
let vsInstanceList = MSBuildLocator.QueryVisualStudioInstances(vsInstanceQueryOpt)

if Seq.isEmpty vsInstanceList then
raise (
InvalidOperationException(
"No instances of MSBuild could be detected."
+ Environment.NewLine
+ "Try calling RegisterInstance or RegisterMSBuildPath to manually register one."
)
)

// do! infoMessage "MSBuildLocator instances found:"
//
// for vsInstance in vsInstanceList do
// do! infoMessage (sprintf "- SDK=\"%s\", Version=%s, MSBuildPath=\"%s\", DiscoveryType=%s"
// vsInstance.Name
// (string vsInstance.Version)
// vsInstance.MSBuildPath
// (string vsInstance.DiscoveryType))

let vsInstance = vsInstanceList |> Seq.head

logger.LogInformation(
"MSBuildLocator: will register \"{vsInstanceName}\", Version={vsInstanceVersion} as default instance",
vsInstance.Name,
(string vsInstance.Version)
)

MSBuildLocator.RegisterInstance(vsInstance)
initializeMSBuild logger

logger.LogDebug("handleInitialize: p.Capabilities={caps}", serialize p.Capabilities)
context.Emit(ClientCapabilityChange p.Capabilities)
Expand Down
54 changes: 33 additions & 21 deletions src/CSharpLanguageServer/Program.fs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module CSharpLanguageServer.Program

open System
open System.Reflection

open Argu
Expand All @@ -8,20 +9,34 @@ open Microsoft.Extensions.Logging
open CSharpLanguageServer.Types
open CSharpLanguageServer.Lsp
open CSharpLanguageServer.Logging
open CSharpLanguageServer.Diagnostics


type CLIArguments =
| [<AltCommandLine("-v")>] Version
| [<AltCommandLine("-l")>] LogLevel of level: string
| [<AltCommandLine("-s")>] Solution of solution: string
| Debug
| Diagnose

interface IArgParserTemplate with
member s.Usage =
match s with
| Version -> "display versioning information"
| Solution _ -> ".sln file to load (relative to CWD)"
| LogLevel _ -> "log level, <trace|debug|info|warning|error>; default is `info`"
| Debug -> "enable debug mode"
| Version -> "Display versioning information"
| Solution _ -> "Specify .sln file to load (relative to CWD)"
| LogLevel _ -> "Set log level, <trace|debug|info|warning|error>; default is `info`"
| Debug -> "Enable debug mode"
| Diagnose -> "Run diagnostics"


let parseLogLevel (debugMode: bool) (logLevelArg: string option) =
match logLevelArg with
| Some "error" -> LogLevel.Error
| Some "warning" -> LogLevel.Warning
| Some "info" -> LogLevel.Information
| Some "debug" -> LogLevel.Debug
| Some "trace" -> LogLevel.Trace
| _ -> if debugMode then LogLevel.Debug else LogLevel.Information


[<EntryPoint>]
Expand All @@ -39,27 +54,24 @@ let entry args =
exit 0)

let debugMode: bool = serverArgs.Contains Debug

let logLevelArg = serverArgs.TryGetResult <@ LogLevel @>

let logLevel =
match logLevelArg with
| Some "error" -> LogLevel.Error
| Some "warning" -> LogLevel.Warning
| Some "info" -> LogLevel.Information
| Some "debug" -> LogLevel.Debug
| Some "trace" -> LogLevel.Trace
| _ -> if debugMode then LogLevel.Debug else LogLevel.Information
let logLevel = serverArgs.TryGetResult(<@ LogLevel @>) |> parseLogLevel debugMode

let settings =
{ ServerSettings.Default with
DebugMode = debugMode
SolutionPath = serverArgs.TryGetResult <@ Solution @>
LogLevel = logLevel
DebugMode = debugMode }

Logging.setupLogging settings.LogLevel

Server.start settings
LogLevel = logLevel }

let exitCode =
match serverArgs.TryGetResult <@ Diagnose @> with
| Some _ ->
Logging.setupLogging LogLevel.Trace
diagnose settings |> Async.RunSynchronously
| _ ->
Logging.setupLogging settings.LogLevel
Server.start settings

exitCode
with
| :? ArguParseException as ex ->
eprintfn "%s" ex.Message
Expand Down
38 changes: 38 additions & 0 deletions src/CSharpLanguageServer/RoslynHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ open System.Threading
open System.Threading.Tasks
open System.Collections.Immutable
open System.Text.RegularExpressions

open Microsoft.Build.Locator
open Castle.DynamicProxy
open ICSharpCode.Decompiler
open ICSharpCode.Decompiler.CSharp
Expand Down Expand Up @@ -834,3 +836,39 @@ let makeDocumentFromMetadata

let mdDocument = SourceText.From text |> mdDocumentEmpty.WithText
mdDocument, text


let initializeMSBuild (logger: ILogger) : unit =
let vsInstanceQueryOpt = VisualStudioInstanceQueryOptions.Default
let vsInstanceList = MSBuildLocator.QueryVisualStudioInstances(vsInstanceQueryOpt)

if Seq.isEmpty vsInstanceList then
raise (
InvalidOperationException(
"No instances of MSBuild could be detected."
+ Environment.NewLine
+ "Try calling RegisterInstance or RegisterMSBuildPath to manually register one."
)
)

logger.LogTrace("MSBuildLocator instances found:")

for vsInstance in vsInstanceList do
logger.LogTrace(
sprintf
"- SDK=\"%s\", Version=%s, MSBuildPath=\"%s\", DiscoveryType=%s"
vsInstance.Name
(string vsInstance.Version)
vsInstance.MSBuildPath
(string vsInstance.DiscoveryType)
)

let vsInstance = vsInstanceList |> Seq.head

logger.LogInformation(
"MSBuildLocator: will register \"{vsInstanceName}\", Version={vsInstanceVersion} as default instance",
vsInstance.Name,
(string vsInstance.Version)
)

MSBuildLocator.RegisterInstance(vsInstance)
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<Compile Include="CompletionTests.fs" />
<Compile Include="WorkspaceSymbolTests.fs" />
<Compile Include="SemanticTokenTests.fs" />
<Compile Include="DiagnoseCommandTests.fs" />
</ItemGroup>

<ItemGroup>
Expand Down
52 changes: 52 additions & 0 deletions tests/CSharpLanguageServer.Tests/DiagnoseCommandTests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
module CSharpLanguageServer.Tests.DiagnoseCommandTests

open System.IO
open System.Reflection
open System.Diagnostics
open System.Threading.Tasks

open NUnit.Framework

open CSharpLanguageServer.Tests.Tooling


[<TestCase>]
let testDiagnoseCommandWorks () =
let testDataDirName = "testDiagnoseCommandWorks"

let testAssemblyLocationDir =
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)

let actualTestDataDir =
DirectoryInfo(Path.Combine(testAssemblyLocationDir, "..", "..", "..", "TestData", testDataDirName))
|> _.FullName

let processStartInfo = makeServerProcessInfo actualTestDataDir
processStartInfo.Arguments <- "--diagnose"

let p = new Process()
p.StartInfo <- processStartInfo

let startResult = p.Start()

if not startResult then
failwith "Failed to start server process."

let stdoutTask = p.StandardOutput.ReadToEndAsync()
let stderrTask = p.StandardError.ReadToEndAsync()

p.WaitForExit(1000 * 10) |> ignore

if not p.HasExited then
p.Kill()
p.WaitForExit()

Task.WaitAll(stdoutTask, stderrTask)

let stdout: string = stdoutTask.Result
Assert.IsEmpty(stdout)

let stderr: string = stderrTask.Result
Assert.IsTrue(stderr.Contains("diagnose: loading solution.."))
Assert.IsTrue(stderr.Contains("csharp-ls: loading project"))
Assert.IsTrue(stderr.Contains("diagnose: done"))
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class Class
{
public void MethodA(string arg)
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>
</Project>
Loading