Skip to content
Open
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
3 changes: 3 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,17 @@
<NoWarn>$(NoWarn);NU1902;NU1903</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="FSharp.Core" Version="10.0.101" />
<PackageVersion Include="Argu" Version="6.2.5" />
<PackageVersion Include="Fabulous.AST" Version="[1.2.0]" />
<PackageVersion Include="Castle.Core" Version="5.2.1" />
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
<PackageVersion Include="DotNet.ReproducibleBuilds" Version="1.2.25" />
<PackageVersion Include="FSharp.Control.AsyncSeq" Version="3.2.1" />
<PackageVersion Include="ICSharpCode.Decompiler" Version="9.1.0.7988" />
<PackageVersion Include="Ionide.KeepAChangelog.Tasks" Version="0.1.8" />
<PackageVersion Include="Ionide.LanguageServerProtocol" Version="0.7.0" />
<PackageVersion Include="StreamJsonRpc" Version="2.16.36" />
<PackageVersion Include="Microsoft.Build" Version="$(MSBuildPackageVersion)" />
<PackageVersion Include="Microsoft.Build.Framework" Version="$(MSBuildPackageVersion)" />
<PackageVersion Include="Microsoft.Build.Locator" Version="$(MSBuildLocatorPackageVersion)" />
Expand Down
900 changes: 579 additions & 321 deletions plans/system-text-json-migration.md

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions src/CSharpLanguageServer/CSharpLanguageServer.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
<PackageReadmeFile>README.md</PackageReadmeFile>
<ChangelogFile>CHANGELOG.md</ChangelogFile>
<Nullable>enable</Nullable>
<MSBuildTreatWarningsAsErrors>true</MSBuildTreatWarningsAsErrors>
<NoWarn>$(NoWarn);NU1902;NU1903</NoWarn>
<NoWarn>$(NoWarn);NU1902;NU1903;FS3261;FS3262;FS3264;FS0043</NoWarn>
</PropertyGroup>

<ItemGroup>
Expand Down Expand Up @@ -87,12 +86,15 @@
<None Include="..\..\README.md" Pack="true" PackagePath="\" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="../Ionide.LanguageServerProtocol/src/Ionide.LanguageServerProtocol.fsproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Argu" />
<PackageReference Include="Castle.Core" />
<PackageReference Include="FSharp.Control.AsyncSeq" />
<PackageReference Include="ICSharpCode.Decompiler" />
<PackageReference Include="Ionide.LanguageServerProtocol" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" />
<PackageReference Include="Microsoft.Extensions.Logging" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
Expand Down
3 changes: 1 addition & 2 deletions src/CSharpLanguageServer/Handlers/CodeLens.fs
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,7 @@ module CodeLens =

let lensData: CodeLensData =
p.Data
|> Option.map _.ToObject<CodeLensData>()
|> Option.bind Option.ofObj
|> Option.map (fun je -> deserialize<CodeLensData> je)
|> Option.defaultValue CodeLensData.Default

let! wf, solution = lensData.DocumentUri |> context.LoadWorkspaceFolder
Expand Down
2 changes: 1 addition & 1 deletion src/CSharpLanguageServer/Handlers/Completion.fs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ module Completion =
completionOptions.Object
emptyRoslynOptionSet
completionTrigger
null
(null: obj)
ct |]

let result =
Expand Down
5 changes: 2 additions & 3 deletions src/CSharpLanguageServer/Handlers/Debug.fs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
namespace CSharpLanguageServer.Handlers

open Newtonsoft.Json.Linq

open System.Text.Json
open Ionide.LanguageServerProtocol.JsonRpc

open CSharpLanguageServer.Runtime.DebugInfo
Expand All @@ -17,7 +16,7 @@ module Debug =
let handle
(getDebugInfo: unit -> Async<DebugInfo option>)
(_ctx: RequestContext)
(_params: JObject)
(_params: JsonElement)
: Async<LspResult<DebugInfo option> * LspWorkspaceUpdate> =
async {
let! info = getDebugInfo ()
Expand Down
16 changes: 11 additions & 5 deletions src/CSharpLanguageServer/Handlers/Workspace.fs
Original file line number Diff line number Diff line change
Expand Up @@ -192,11 +192,17 @@ module Workspace =
: Async<LspResult<unit> * LspWorkspaceUpdate> =
async {
let pushedConfig =
configParams.Settings
|> Option.ofObj
|> Option.bind deserialize<DidChangeConfigurationSettingsDto option>
|> Option.map _.csharp
|> Option.bind id // flatten option option, also guards against null from Newtonsoft
// Settings is LSPAny (JsonElement, a struct) — check it's an object before deserializing
let settingsOpt =
if
configParams.Settings.ValueKind = System.Text.Json.JsonValueKind.Null
|| configParams.Settings.ValueKind = System.Text.Json.JsonValueKind.Undefined
then
None
else
deserialize<DidChangeConfigurationSettingsDto option> configParams.Settings

settingsOpt |> Option.map _.csharp |> Option.bind id // flatten option option

let configurationSupported =
context.ClientCapabilities.Workspace
Expand Down
62 changes: 29 additions & 33 deletions src/CSharpLanguageServer/Lsp/Client.fs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
module CSharpLanguageServer.Lsp.Client

open System.Text.Json
open Newtonsoft.Json.Linq
open Microsoft.Extensions.Logging

open Ionide.LanguageServerProtocol
Expand All @@ -26,28 +25,25 @@ type CSharpLspClient

return
match result with
| Result.Ok je -> je.GetRawText() |> JToken.Parse |> deserialize |> Result.Ok
| Result.Ok je -> je |> deserialize |> Result.Ok
| Result.Error errEl ->
let errToken = JToken.Parse(errEl.GetRawText())

let code =
errToken.SelectToken("code")
|> Option.ofObj
|> Option.bind (fun t ->
t.ToString()
|> System.Int32.TryParse
|> function
| true, v -> Some v
| _ -> None)
|> Option.defaultValue -32603 // -32603 = JSON-RPC "Internal error"
match errEl.TryGetProperty("code") with
| true, v ->
match v.TryGetInt32() with
| true, i -> i
| _ -> -32603
| _ -> -32603

let message =
errToken.SelectToken("message")
|> Option.ofObj
|> Option.map _.ToObject<string>()
|> Option.defaultValue "Unknown error"
match errEl.TryGetProperty("message") with
| true, v -> v.GetString()
| _ -> "Unknown error"

let data = errToken.SelectToken("data") |> Option.ofObj
let data =
match errEl.TryGetProperty("data") with
| true, v -> Some v
| _ -> None

Result.Error
{ Code = code
Expand All @@ -56,35 +52,35 @@ type CSharpLspClient
}

override __.WindowShowMessage p =
sendServerNotification "window/showMessage" (serialize p |> jtokenToJe)
sendServerNotification "window/showMessage" (serialize p)

// Note: CSharpLspClient is a pure transport adapter. It does not gate calls on
// ClientCapabilities — that is the responsibility of callers, who have access to
// capabilities via RequestContext. See ProgressReporter for the reference pattern.

override __.WindowShowMessageRequest p : AsyncLspResult<Types.MessageActionItem option> =
sendServerRequest "window/showMessageRequest" (serialize p |> jtokenToJe)
sendServerRequest "window/showMessageRequest" (serialize p)

override __.WindowLogMessage p =
sendServerNotification "window/logMessage" (serialize p |> jtokenToJe)
sendServerNotification "window/logMessage" (serialize p)

override __.TelemetryEvent p =
sendServerNotification "telemetry/event" (serialize p |> jtokenToJe)
sendServerNotification "telemetry/event" (p)

override __.ClientRegisterCapability p =
sendServerRequest "client/registerCapability" (serialize p |> jtokenToJe)
sendServerRequest "client/registerCapability" (serialize p)

override __.ClientUnregisterCapability p =
sendServerRequest "client/unregisterCapability" (serialize p |> jtokenToJe)
sendServerRequest "client/unregisterCapability" (serialize p)

override __.WorkspaceWorkspaceFolders() : AsyncLspResult<Types.WorkspaceFolder[] option> =
sendServerRequest "workspace/workspaceFolders" nullJE

override __.WorkspaceConfiguration p : AsyncLspResult<JToken[]> =
sendServerRequest "workspace/configuration" (serialize p |> jtokenToJe)
override __.WorkspaceConfiguration p : AsyncLspResult<JsonElement[]> =
sendServerRequest "workspace/configuration" (serialize p)

override __.WorkspaceApplyEdit p : AsyncLspResult<Types.ApplyWorkspaceEditResult> =
sendServerRequest "workspace/applyEdit" (serialize p |> jtokenToJe)
sendServerRequest "workspace/applyEdit" (serialize p)

override __.WorkspaceSemanticTokensRefresh() =
sendServerRequest "workspace/semanticTokens/refresh" nullJE
Expand All @@ -93,22 +89,22 @@ type CSharpLspClient
sendServerRequest "workspace/diagnostic/refresh" nullJE

override __.TextDocumentPublishDiagnostics p =
sendServerNotification "textDocument/publishDiagnostics" (serialize p |> jtokenToJe)
sendServerNotification "textDocument/publishDiagnostics" (serialize p)

override __.WindowWorkDoneProgressCreate createParams =
sendServerRequest "window/workDoneProgress/create" (serialize createParams |> jtokenToJe)
sendServerRequest "window/workDoneProgress/create" (serialize createParams)

override __.LogTrace p =
sendServerNotification "$/logTrace" (serialize p |> jtokenToJe)
sendServerNotification "$/logTrace" (serialize p)

override __.Progress progressParams =
sendServerNotification "$/progress" (serialize progressParams |> jtokenToJe)
sendServerNotification "$/progress" (serialize progressParams)

/// Query the client for the `csharp` workspace configuration section.
/// Returns `None` when the call fails or the response cannot be deserialized.
static member TryPullCSharpConfig(lspClient: ILspClient) : Async<CSharpConfiguration option> = async {
try
let! (result: Result<JToken[], _>) =
let! (result: Result<JsonElement[], _>) =
lspClient.WorkspaceConfiguration(
{ Items =
[| { Section = Some "csharp"
Expand All @@ -119,7 +115,7 @@ type CSharpLspClient
result
|> Option.fromResult
|> Option.bind Seq.tryHead
|> Option.bind deserialize<CSharpConfiguration option>
|> Option.bind (fun je -> deserialize<CSharpConfiguration option> je)
with ex ->
logger.LogWarning("could not retrieve `csharp` workspace configuration section: {error}", ex |> string)

Expand Down
13 changes: 3 additions & 10 deletions src/CSharpLanguageServer/Lsp/Server.fs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ open StreamJsonRpc
open Microsoft.Extensions.Logging
open System.Text.Json
open System.Text.Json.Nodes
open Newtonsoft.Json.Linq

open CSharpLanguageServer.Types
open CSharpLanguageServer.Handlers
Expand Down Expand Up @@ -135,7 +134,7 @@ let configureRpcTransport

let fnParams =
try
rawParams |> sanitize |> jeToJToken |> deserialize
rawParams |> sanitize |> deserialize
with ex ->
logger.LogError(
ex,
Expand All @@ -154,16 +153,10 @@ let configureRpcTransport
stateActor.Post(LeaveRequestContext(jsonRpcCtx.RequestOrdinal, wsUpdate))
}

let serializeNullable value =
if isNull (box value) then
Newtonsoft.Json.Linq.JValue.CreateNull() :> Newtonsoft.Json.Linq.JToken
else
serialize value

let unwrapResult result =
match result with
| Ok value -> value |> serializeNullable |> jtokenToJe |> Ok
| Error error -> error |> serialize |> jtokenToJe |> Error
| Ok value -> value |> serialize |> Ok
| Error error -> error |> serialize |> Error

let callHandler requestMode fn : JsonRpcCallHandler =
wrapHandler unwrapResult requestMode id fn
Expand Down
24 changes: 17 additions & 7 deletions src/CSharpLanguageServer/Runtime/ServerStateLoop.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ open System.Threading
open Ionide.LanguageServerProtocol.Types
open Ionide.LanguageServerProtocol
open Microsoft.Extensions.Logging
open Newtonsoft.Json.Linq


open CSharpLanguageServer.Logging
open CSharpLanguageServer.Lsp.Workspace
Expand Down Expand Up @@ -400,13 +400,23 @@ let processServerEvent state postServerEvent (inbox: MailboxProcessor<ServerEven
ShutdownReceived = true }

| ClientCapabilityChange cc ->
let experimentalCapsBoolValue boolPropName =
let experimentalCapsBoolValue (dotPath: string) =
// Navigate a dot-separated path (e.g. "csharp.metadataUris") through JsonElement
let rec navigate (parts: string list) (je: System.Text.Json.JsonElement) =
match parts with
| [] -> Some je
| head :: tail ->
match je.TryGetProperty(head) with
| true, child -> navigate tail child
| _ -> None

cc.Experimental
|> Option.map _.SelectToken(boolPropName)
|> Option.bind Option.ofObj
|> Option.map (fun t ->
let v = t :?> JValue
v.Value :?> bool)
|> Option.bind (fun je -> navigate (dotPath.Split('.') |> List.ofArray) je)
|> Option.bind (fun je ->
match je.ValueKind with
| System.Text.Json.JsonValueKind.True -> Some true
| System.Text.Json.JsonValueKind.False -> Some false
| _ -> None)

let newConfig =
{ state.Config with
Expand Down
8 changes: 0 additions & 8 deletions src/CSharpLanguageServer/Util.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ open System.Threading.Tasks

open System.Text.Json
open System.Text.Json.Nodes
open Newtonsoft.Json.Linq

let noneIfEmpty (s: string) : string option =
s
Expand Down Expand Up @@ -102,13 +101,6 @@ module Option =
| Some v -> [ v ]
| None -> []

let jeToJToken (je: JsonElement) : JToken = JToken.Parse(je.GetRawText())

let jtokenToJe (token: JToken) : JsonElement =
let json = token.ToString(Newtonsoft.Json.Formatting.None)
use doc = JsonDocument.Parse(json)
doc.RootElement.Clone()

let nullJE =
use doc = JsonDocument.Parse("null")
doc.RootElement.Clone()
Expand Down
Loading
Loading