From 677e4dbab49721b13eaa89ee44bdd202503b2f03 Mon Sep 17 00:00:00 2001 From: Joey Robichaud Date: Mon, 4 May 2026 10:49:48 -0700 Subject: [PATCH 1/7] Update lsp.config to invoke roslyn-language-server directly Due to the behavior of dotnet SDK resolution when running in repos which use a global.json, we are not gaurenteed that the choosen SDK will be new enough to support the `dotnet dnx` command, which we were using to install and run the roslyn-language-server. Instead, we make having roslyn-language-server installed a prerequisite so that we can invoke it directly. --- plugins/dotnet/README.md | 8 ++++++-- plugins/dotnet/lsp.json | 9 ++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/plugins/dotnet/README.md b/plugins/dotnet/README.md index 1b25dd5b54..4035c8c1fe 100644 --- a/plugins/dotnet/README.md +++ b/plugins/dotnet/README.md @@ -12,8 +12,12 @@ Core .NET and C# skills for coding agents. This plugin declares a C# LSP server that is launched through the .NET CLI. Prerequisites: -- .NET SDK installed -- `dotnet` available on PATH +- .NET 10 SDK installed +- The `roslyn-language-server` tool installed globally + ``` + dotnet tool install --global --prerelease roslyn-language-server + ``` +- `roslyn-language-server` available on PATH ## Skills diff --git a/plugins/dotnet/lsp.json b/plugins/dotnet/lsp.json index 38259e22b3..c4db658dec 100644 --- a/plugins/dotnet/lsp.json +++ b/plugins/dotnet/lsp.json @@ -1,13 +1,8 @@ { "lspServers": { "csharp": { - "command": "dotnet", + "command": "roslyn-language-server", "args": [ - "dnx", - "roslyn-language-server", - "--yes", - "--prerelease", - "--", "--stdio", "--autoLoadProjects" ], @@ -16,4 +11,4 @@ } } } -} +} \ No newline at end of file From ed30214fccf8a855e175c348bd41ab3a18a04d8b Mon Sep 17 00:00:00 2001 From: Joey Robichaud Date: Mon, 4 May 2026 11:22:13 -0700 Subject: [PATCH 2/7] Apply suggestion from @JoeRobich --- plugins/dotnet/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/dotnet/README.md b/plugins/dotnet/README.md index 4035c8c1fe..bdf564eb15 100644 --- a/plugins/dotnet/README.md +++ b/plugins/dotnet/README.md @@ -9,7 +9,7 @@ Core .NET and C# skills for coding agents. ## LSP -This plugin declares a C# LSP server that is launched through the .NET CLI. +This plugin declares a C# LSP server that is installed through the .NET CLI. Prerequisites: - .NET 10 SDK installed From b8c3c6f22b321e81661bf18a08146a46dadbcb0b Mon Sep 17 00:00:00 2001 From: Joey Robichaud Date: Mon, 4 May 2026 11:27:12 -0700 Subject: [PATCH 3/7] Apply suggestion from @JoeRobich --- plugins/dotnet/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/dotnet/README.md b/plugins/dotnet/README.md index bdf564eb15..a256b855d8 100644 --- a/plugins/dotnet/README.md +++ b/plugins/dotnet/README.md @@ -13,7 +13,7 @@ This plugin declares a C# LSP server that is installed through the .NET CLI. Prerequisites: - .NET 10 SDK installed -- The `roslyn-language-server` tool installed globally +- The `roslyn-language-server` tool installed globally. [Click here](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-tool-install#global-tools) to learn more about dotnet global tools. ``` dotnet tool install --global --prerelease roslyn-language-server ``` From fee84cd7f053247140dd6863f6a97a2eb57ff37b Mon Sep 17 00:00:00 2001 From: Joey Robichaud Date: Tue, 5 May 2026 13:38:07 -0700 Subject: [PATCH 4/7] Add a sessionStart hook to install the roslyn language server. --- plugins/dotnet/README.md | 6 +- plugins/dotnet/hooks.json | 13 ++++ plugins/dotnet/scripts/session-start.ps1 | 84 ++++++++++++++++++++++++ plugins/dotnet/scripts/session-start.sh | 80 ++++++++++++++++++++++ 4 files changed, 178 insertions(+), 5 deletions(-) create mode 100644 plugins/dotnet/hooks.json create mode 100644 plugins/dotnet/scripts/session-start.ps1 create mode 100644 plugins/dotnet/scripts/session-start.sh diff --git a/plugins/dotnet/README.md b/plugins/dotnet/README.md index a256b855d8..e434f7c546 100644 --- a/plugins/dotnet/README.md +++ b/plugins/dotnet/README.md @@ -13,11 +13,7 @@ This plugin declares a C# LSP server that is installed through the .NET CLI. Prerequisites: - .NET 10 SDK installed -- The `roslyn-language-server` tool installed globally. [Click here](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-tool-install#global-tools) to learn more about dotnet global tools. - ``` - dotnet tool install --global --prerelease roslyn-language-server - ``` -- `roslyn-language-server` available on PATH +- `dotnet` available on PATH ## Skills diff --git a/plugins/dotnet/hooks.json b/plugins/dotnet/hooks.json new file mode 100644 index 0000000000..379cde7602 --- /dev/null +++ b/plugins/dotnet/hooks.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "hooks": { + "sessionStart": [ + { + "type": "command", + "bash": "./scripts/session-start.sh", + "powershell": "./scripts/session-start.ps1", + "timeoutSec": 30 + } + ] + } +} \ No newline at end of file diff --git a/plugins/dotnet/scripts/session-start.ps1 b/plugins/dotnet/scripts/session-start.ps1 new file mode 100644 index 0000000000..60b8e23cf8 --- /dev/null +++ b/plugins/dotnet/scripts/session-start.ps1 @@ -0,0 +1,84 @@ +#Requires -Version 5.1 +<# +.SYNOPSIS + Initializes the development session by ensuring the roslyn-language-server tool is installed and up to date. +.DESCRIPTION + Creates a temporary working directory for the session and installs or updates the + roslyn-language-server .NET global tool to the latest prerelease version. +#> +[CmdletBinding()] +param() + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$ToolName = 'roslyn-language-server' +$NuGetSource = 'https://api.nuget.org/v3/index.json' + +function New-SessionDirectory { + $dir = New-Item -ItemType Directory -Path (Join-Path ([System.IO.Path]::GetTempPath()) ([System.Guid]::NewGuid().ToString())) + Write-Verbose "[$ToolName] Created temp directory: $($dir.FullName)" + Set-Location -Path $dir.FullName + return $dir +} + +function Write-GlobalJson { + param([string]$Directory) + + @{ + sdk = @{ + version = '10.0.100' + rollForward = 'latestMajor' + } + } | ConvertTo-Json -Depth 3 | Set-Content -Path (Join-Path $Directory 'global.json') -Encoding utf8 + Write-Verbose "[$ToolName] Wrote global.json pinning SDK to 10.0.100 (rollForward: latestMajor)" +} + +function Get-InstalledToolVersion { + param([string]$Name) + + $toolList = dotnet tool list --global + $installedEntry = $toolList | Select-String -Pattern $Name + if ($installedEntry) { ($installedEntry -split '\s+')[1] } else { $null } +} + +function Get-LatestToolVersion { + param([string]$Name, [string]$Source) + + $searchJson = dotnet package search $Name --source $Source --exact-match --prerelease --format json | ConvertFrom-Json + $packages = $searchJson.searchResult | ForEach-Object { $_.packages } | Where-Object { $_.id -eq $Name } + if ($packages) { ($packages | Select-Object -Last 1).version } else { $null } +} + +function Install-OrUpdateTool { + $currentVersion = Get-InstalledToolVersion -Name $ToolName + + if ($currentVersion) { + Write-Host "[$ToolName] Installed version: $currentVersion" + + $latestVersion = Get-LatestToolVersion -Name $ToolName -Source $NuGetSource + + if (-not $latestVersion) { + Write-Warning "[$ToolName] Unable to determine the latest version. Skipping update." + return + } + + Write-Host "[$ToolName] Latest available version: $latestVersion" + + if ($currentVersion -ne $latestVersion) { + Write-Host "[$ToolName] Updating from $currentVersion to $latestVersion..." + dotnet tool update --global --prerelease $ToolName --add-source $NuGetSource + Write-Host "[$ToolName] Update complete." + } else { + Write-Host "[$ToolName] Already up to date." + } + } else { + Write-Host "[$ToolName] Not found. Installing latest prerelease..." + dotnet tool install --global --prerelease $ToolName --add-source $NuGetSource + Write-Host "[$ToolName] Installation complete." + } +} + +$tempDir = New-SessionDirectory +Write-GlobalJson -Directory $tempDir.FullName +Install-OrUpdateTool diff --git a/plugins/dotnet/scripts/session-start.sh b/plugins/dotnet/scripts/session-start.sh new file mode 100644 index 0000000000..e02fdd7981 --- /dev/null +++ b/plugins/dotnet/scripts/session-start.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# +# Initializes the development session by ensuring the roslyn-language-server +# tool is installed and up to date. + +set -euo pipefail + +TOOL_NAME="roslyn-language-server" +NUGET_SOURCE="https://api.nuget.org/v3/index.json" + +create_session_directory() { + local dir + dir="$(mktemp -d)" + echo "[$TOOL_NAME] Created temp directory: $dir" >&2 + cd "$dir" + echo "$dir" +} + +write_global_json() { + local dir="$1" + cat > "$dir/global.json" <<'EOF' +{ + "sdk": { + "version": "10.0.100", + "rollForward": "latestMajor" + } +} +EOF + echo "[$TOOL_NAME] Wrote global.json pinning SDK to 10.0.100 (rollForward: latestMajor)" >&2 +} + +get_installed_version() { + local name="$1" + dotnet tool list --global | grep -i "$name" | awk '{print $2}' || true +} + +get_latest_version() { + local name="$1" + local source="$2" + local search_json + search_json=$(dotnet package search "$name" --source "$source" --exact-match --prerelease --format json) + echo "$search_json" | jq -r \ + --arg name "$name" \ + '.searchResult[].packages[] | select(.id == $name) | .version' | tail -1 +} + +install_or_update_tool() { + local current_version + current_version=$(get_installed_version "$TOOL_NAME") + + if [[ -n "$current_version" ]]; then + echo "[$TOOL_NAME] Installed version: $current_version" + + local latest_version + latest_version=$(get_latest_version "$TOOL_NAME" "$NUGET_SOURCE") + + if [[ -z "$latest_version" ]]; then + echo "[$TOOL_NAME] WARNING: Unable to determine the latest version. Skipping update." >&2 + return + fi + + echo "[$TOOL_NAME] Latest available version: $latest_version" + + if [[ "$current_version" != "$latest_version" ]]; then + echo "[$TOOL_NAME] Updating from $current_version to $latest_version..." + dotnet tool update --global --prerelease "$TOOL_NAME" --add-source "$NUGET_SOURCE" + echo "[$TOOL_NAME] Update complete." + else + echo "[$TOOL_NAME] Already up to date." + fi + else + echo "[$TOOL_NAME] Not found. Installing latest prerelease..." + dotnet tool install --global --prerelease "$TOOL_NAME" --add-source "$NUGET_SOURCE" + echo "[$TOOL_NAME] Installation complete." + fi +} + +temp_dir=$(create_session_directory) +write_global_json "$temp_dir" +install_or_update_tool From c89c2273b33247fe514f8e5624c4cfdca1e443f8 Mon Sep 17 00:00:00 2001 From: Joey Robichaud Date: Fri, 5 Jun 2026 13:59:07 -0700 Subject: [PATCH 5/7] Use the new lsp config options to set CWD --- plugins/dotnet/global.json | 7 ++ plugins/dotnet/hooks.json | 13 ---- plugins/dotnet/lsp.json | 14 +++- plugins/dotnet/scripts/session-start.ps1 | 84 ------------------------ plugins/dotnet/scripts/session-start.sh | 80 ---------------------- 5 files changed, 18 insertions(+), 180 deletions(-) create mode 100644 plugins/dotnet/global.json delete mode 100644 plugins/dotnet/hooks.json delete mode 100644 plugins/dotnet/scripts/session-start.ps1 delete mode 100644 plugins/dotnet/scripts/session-start.sh diff --git a/plugins/dotnet/global.json b/plugins/dotnet/global.json new file mode 100644 index 0000000000..193d409c2d --- /dev/null +++ b/plugins/dotnet/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "10.0.100", + "rollForward": "latestMajor", + "allowPrerelease": true + } +} \ No newline at end of file diff --git a/plugins/dotnet/hooks.json b/plugins/dotnet/hooks.json deleted file mode 100644 index 379cde7602..0000000000 --- a/plugins/dotnet/hooks.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "version": 1, - "hooks": { - "sessionStart": [ - { - "type": "command", - "bash": "./scripts/session-start.sh", - "powershell": "./scripts/session-start.ps1", - "timeoutSec": 30 - } - ] - } -} \ No newline at end of file diff --git a/plugins/dotnet/lsp.json b/plugins/dotnet/lsp.json index c4db658dec..17a6cfdfb9 100644 --- a/plugins/dotnet/lsp.json +++ b/plugins/dotnet/lsp.json @@ -1,14 +1,22 @@ { "lspServers": { "csharp": { - "command": "roslyn-language-server", + "command": "dnx", "args": [ + "roslyn-language-server", + "--yes", + "--prerelease", + "--", "--stdio", "--autoLoadProjects" ], + "cwd": "${PLUGIN_ROOT}", "fileExtensions": { - ".cs": "csharp" - } + ".cs": "csharp", + ".razor": "aspnetcorerazor", + ".cshtml": "aspnetcorerazor" + }, + "warmupTimeoutMs": 120000 } } } \ No newline at end of file diff --git a/plugins/dotnet/scripts/session-start.ps1 b/plugins/dotnet/scripts/session-start.ps1 deleted file mode 100644 index 60b8e23cf8..0000000000 --- a/plugins/dotnet/scripts/session-start.ps1 +++ /dev/null @@ -1,84 +0,0 @@ -#Requires -Version 5.1 -<# -.SYNOPSIS - Initializes the development session by ensuring the roslyn-language-server tool is installed and up to date. -.DESCRIPTION - Creates a temporary working directory for the session and installs or updates the - roslyn-language-server .NET global tool to the latest prerelease version. -#> -[CmdletBinding()] -param() - -Set-StrictMode -Version Latest -$ErrorActionPreference = 'Stop' - -$ToolName = 'roslyn-language-server' -$NuGetSource = 'https://api.nuget.org/v3/index.json' - -function New-SessionDirectory { - $dir = New-Item -ItemType Directory -Path (Join-Path ([System.IO.Path]::GetTempPath()) ([System.Guid]::NewGuid().ToString())) - Write-Verbose "[$ToolName] Created temp directory: $($dir.FullName)" - Set-Location -Path $dir.FullName - return $dir -} - -function Write-GlobalJson { - param([string]$Directory) - - @{ - sdk = @{ - version = '10.0.100' - rollForward = 'latestMajor' - } - } | ConvertTo-Json -Depth 3 | Set-Content -Path (Join-Path $Directory 'global.json') -Encoding utf8 - Write-Verbose "[$ToolName] Wrote global.json pinning SDK to 10.0.100 (rollForward: latestMajor)" -} - -function Get-InstalledToolVersion { - param([string]$Name) - - $toolList = dotnet tool list --global - $installedEntry = $toolList | Select-String -Pattern $Name - if ($installedEntry) { ($installedEntry -split '\s+')[1] } else { $null } -} - -function Get-LatestToolVersion { - param([string]$Name, [string]$Source) - - $searchJson = dotnet package search $Name --source $Source --exact-match --prerelease --format json | ConvertFrom-Json - $packages = $searchJson.searchResult | ForEach-Object { $_.packages } | Where-Object { $_.id -eq $Name } - if ($packages) { ($packages | Select-Object -Last 1).version } else { $null } -} - -function Install-OrUpdateTool { - $currentVersion = Get-InstalledToolVersion -Name $ToolName - - if ($currentVersion) { - Write-Host "[$ToolName] Installed version: $currentVersion" - - $latestVersion = Get-LatestToolVersion -Name $ToolName -Source $NuGetSource - - if (-not $latestVersion) { - Write-Warning "[$ToolName] Unable to determine the latest version. Skipping update." - return - } - - Write-Host "[$ToolName] Latest available version: $latestVersion" - - if ($currentVersion -ne $latestVersion) { - Write-Host "[$ToolName] Updating from $currentVersion to $latestVersion..." - dotnet tool update --global --prerelease $ToolName --add-source $NuGetSource - Write-Host "[$ToolName] Update complete." - } else { - Write-Host "[$ToolName] Already up to date." - } - } else { - Write-Host "[$ToolName] Not found. Installing latest prerelease..." - dotnet tool install --global --prerelease $ToolName --add-source $NuGetSource - Write-Host "[$ToolName] Installation complete." - } -} - -$tempDir = New-SessionDirectory -Write-GlobalJson -Directory $tempDir.FullName -Install-OrUpdateTool diff --git a/plugins/dotnet/scripts/session-start.sh b/plugins/dotnet/scripts/session-start.sh deleted file mode 100644 index e02fdd7981..0000000000 --- a/plugins/dotnet/scripts/session-start.sh +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env bash -# -# Initializes the development session by ensuring the roslyn-language-server -# tool is installed and up to date. - -set -euo pipefail - -TOOL_NAME="roslyn-language-server" -NUGET_SOURCE="https://api.nuget.org/v3/index.json" - -create_session_directory() { - local dir - dir="$(mktemp -d)" - echo "[$TOOL_NAME] Created temp directory: $dir" >&2 - cd "$dir" - echo "$dir" -} - -write_global_json() { - local dir="$1" - cat > "$dir/global.json" <<'EOF' -{ - "sdk": { - "version": "10.0.100", - "rollForward": "latestMajor" - } -} -EOF - echo "[$TOOL_NAME] Wrote global.json pinning SDK to 10.0.100 (rollForward: latestMajor)" >&2 -} - -get_installed_version() { - local name="$1" - dotnet tool list --global | grep -i "$name" | awk '{print $2}' || true -} - -get_latest_version() { - local name="$1" - local source="$2" - local search_json - search_json=$(dotnet package search "$name" --source "$source" --exact-match --prerelease --format json) - echo "$search_json" | jq -r \ - --arg name "$name" \ - '.searchResult[].packages[] | select(.id == $name) | .version' | tail -1 -} - -install_or_update_tool() { - local current_version - current_version=$(get_installed_version "$TOOL_NAME") - - if [[ -n "$current_version" ]]; then - echo "[$TOOL_NAME] Installed version: $current_version" - - local latest_version - latest_version=$(get_latest_version "$TOOL_NAME" "$NUGET_SOURCE") - - if [[ -z "$latest_version" ]]; then - echo "[$TOOL_NAME] WARNING: Unable to determine the latest version. Skipping update." >&2 - return - fi - - echo "[$TOOL_NAME] Latest available version: $latest_version" - - if [[ "$current_version" != "$latest_version" ]]; then - echo "[$TOOL_NAME] Updating from $current_version to $latest_version..." - dotnet tool update --global --prerelease "$TOOL_NAME" --add-source "$NUGET_SOURCE" - echo "[$TOOL_NAME] Update complete." - else - echo "[$TOOL_NAME] Already up to date." - fi - else - echo "[$TOOL_NAME] Not found. Installing latest prerelease..." - dotnet tool install --global --prerelease "$TOOL_NAME" --add-source "$NUGET_SOURCE" - echo "[$TOOL_NAME] Installation complete." - fi -} - -temp_dir=$(create_session_directory) -write_global_json "$temp_dir" -install_or_update_tool From 2300147e8924925a8ddbd8bf6f65f0fd57077a9f Mon Sep 17 00:00:00 2001 From: Joey Robichaud Date: Fri, 5 Jun 2026 14:12:03 -0700 Subject: [PATCH 6/7] Apply suggestion from @JoeRobich --- plugins/dotnet/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/dotnet/README.md b/plugins/dotnet/README.md index e434f7c546..f0ca96307c 100644 --- a/plugins/dotnet/README.md +++ b/plugins/dotnet/README.md @@ -9,7 +9,7 @@ Core .NET and C# skills for coding agents. ## LSP -This plugin declares a C# LSP server that is installed through the .NET CLI. +This plugin declares a C# LSP server that is launched through the .NET CLI. Prerequisites: - .NET 10 SDK installed From 8fbddc03b5d8c0891533387d833265e78ac67008 Mon Sep 17 00:00:00 2001 From: Joey Robichaud Date: Fri, 5 Jun 2026 14:16:49 -0700 Subject: [PATCH 7/7] Change indentation of json file --- plugins/dotnet/global.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/dotnet/global.json b/plugins/dotnet/global.json index 193d409c2d..ae42e436bf 100644 --- a/plugins/dotnet/global.json +++ b/plugins/dotnet/global.json @@ -1,7 +1,7 @@ { - "sdk": { - "version": "10.0.100", - "rollForward": "latestMajor", - "allowPrerelease": true - } + "sdk": { + "version": "10.0.100", + "rollForward": "latestMajor", + "allowPrerelease": true + } } \ No newline at end of file