Skip to content

Commit 99faf9c

Browse files
committed
feat(extension): add Roslyn backend packaging + nvim harness
1 parent 5f2757f commit 99faf9c

41 files changed

Lines changed: 2863 additions & 31 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,27 @@ jobs:
1919
run: dotnet test test/VbNet.LanguageServer.Tests.Vb/VbNet.LanguageServer.Tests.Vb.vbproj -c Release
2020
- name: Test Extension Manifest
2121
run: dotnet test test/VbNet.Extension.Tests.Vb/VbNet.Extension.Tests.Vb.vbproj -c Release
22+
23+
roslyn-bundle:
24+
runs-on: windows-latest
25+
steps:
26+
- uses: actions/checkout@v4
27+
- uses: actions/setup-node@v4
28+
with:
29+
node-version: "20"
30+
cache: "npm"
31+
cache-dependency-path: src/extension/package-lock.json
32+
- name: Install extension dependencies
33+
working-directory: src/extension
34+
run: npm ci
35+
- name: Cache Roslyn downloads
36+
uses: actions/cache@v4
37+
with:
38+
path: src/extension/.roslyn-downloads
39+
key: roslyn-downloads-${{ runner.os }}-win32-x64-${{ hashFiles('src/extension/scripts/copy-roslyn-server.js') }}
40+
- name: Bundle Roslyn (win-x64)
41+
working-directory: src/extension
42+
run: npm run bundle-roslyn:win32-x64
43+
- name: Validate Roslyn bundle
44+
working-directory: src/extension
45+
run: npm run validate-roslyn

.github/workflows/package-vsix.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,20 @@ jobs:
3737
working-directory: src/extension
3838
run: npm ci
3939

40+
- name: Cache Roslyn downloads
41+
uses: actions/cache@v4
42+
with:
43+
path: src/extension/.roslyn-downloads
44+
key: roslyn-downloads-${{ runner.os }}-${{ inputs.target }}-${{ hashFiles('src/extension/scripts/copy-roslyn-server.js') }}
45+
4046
- name: Package VSIX
4147
working-directory: src/extension
4248
shell: pwsh
4349
run: |
4450
$ErrorActionPreference = 'Stop'
4551
npm run bundle-server
52+
npm run bundle-roslyn:${{ inputs.target }}
53+
npm run validate-roslyn
4654
npm run bundle-debugger -- --target "${{ inputs.target }}"
4755
npm run package
4856
$vsceArgs = @("package", "--target", "${{ inputs.target }}", "--out", "vbnet-language-support-${{ inputs.target }}.vsix")
@@ -51,6 +59,23 @@ jobs:
5159
}
5260
npx vsce @vsceArgs
5361
62+
- name: Validate VSIX contains Roslyn bundles
63+
working-directory: src/extension
64+
shell: pwsh
65+
run: |
66+
$ErrorActionPreference = 'Stop'
67+
$vsix = "vbnet-language-support-${{ inputs.target }}.vsix"
68+
if (-not (Test-Path $vsix)) { throw "VSIX not found at $vsix" }
69+
$extract = Join-Path $env:TEMP ("vbnet-vsix-" + [guid]::NewGuid().ToString("N"))
70+
Expand-Archive -Path $vsix -DestinationPath $extract -Force
71+
$root = Join-Path $extract "extension"
72+
if (-not (Test-Path (Join-Path $root ".roslyn"))) { throw "Missing .roslyn in VSIX" }
73+
if (-not (Test-Path (Join-Path $root ".roslyn-vb"))) { throw "Missing .roslyn-vb in VSIX" }
74+
if (-not (Test-Path (Join-Path $root ".roslyn\\Microsoft.CodeAnalysis.LanguageServer.dll")) -and -not (Test-Path (Join-Path $root ".roslyn\\Microsoft.CodeAnalysis.LanguageServer.exe"))) {
75+
throw "Roslyn LSP server binary missing from VSIX"
76+
}
77+
Remove-Item -Recurse -Force $extract
78+
5479
- name: Upload VSIX artifact
5580
uses: actions/upload-artifact@v4
5681
with:

.github/workflows/publish-vsix.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,20 @@ jobs:
4343
working-directory: src/extension
4444
run: npm ci
4545

46+
- name: Cache Roslyn downloads
47+
uses: actions/cache@v4
48+
with:
49+
path: src/extension/.roslyn-downloads
50+
key: roslyn-downloads-${{ runner.os }}-${{ inputs.target }}-${{ hashFiles('src/extension/scripts/copy-roslyn-server.js') }}
51+
4652
- name: Package VSIX
4753
working-directory: src/extension
4854
shell: pwsh
4955
run: |
5056
$ErrorActionPreference = 'Stop'
5157
npm run bundle-server
58+
npm run bundle-roslyn:${{ inputs.target }}
59+
npm run validate-roslyn
5260
npm run bundle-debugger -- --target "${{ inputs.target }}"
5361
npm run package
5462
$vsceArgs = @("package", "--target", "${{ inputs.target }}", "--out", "vbnet-language-support-${{ inputs.target }}.vsix")
@@ -57,6 +65,23 @@ jobs:
5765
}
5866
npx vsce @vsceArgs
5967
68+
- name: Validate VSIX contains Roslyn bundles
69+
working-directory: src/extension
70+
shell: pwsh
71+
run: |
72+
$ErrorActionPreference = 'Stop'
73+
$vsix = "vbnet-language-support-${{ inputs.target }}.vsix"
74+
if (-not (Test-Path $vsix)) { throw "VSIX not found at $vsix" }
75+
$extract = Join-Path $env:TEMP ("vbnet-vsix-" + [guid]::NewGuid().ToString("N"))
76+
Expand-Archive -Path $vsix -DestinationPath $extract -Force
77+
$root = Join-Path $extract "extension"
78+
if (-not (Test-Path (Join-Path $root ".roslyn"))) { throw "Missing .roslyn in VSIX" }
79+
if (-not (Test-Path (Join-Path $root ".roslyn-vb"))) { throw "Missing .roslyn-vb in VSIX" }
80+
if (-not (Test-Path (Join-Path $root ".roslyn\\Microsoft.CodeAnalysis.LanguageServer.dll")) -and -not (Test-Path (Join-Path $root ".roslyn\\Microsoft.CodeAnalysis.LanguageServer.exe"))) {
81+
throw "Roslyn LSP server binary missing from VSIX"
82+
}
83+
Remove-Item -Recurse -Force $extract
84+
6085
- name: Publish VSIX
6186
working-directory: src/extension
6287
env:

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,12 @@ _external/
505505
# Exploratory harness artifacts (logs, downloaded runtimes, caches)
506506
test-explore/clients/emacs/emacs/
507507
test-explore/clients/vscode/.vscode-test/
508+
test-explore/clients/nvim/nvim/
508509
test-explore/**/logs/
509510
test-explore/vbnet-lsp/snapshots/
510511

512+
# Roslyn LSP bundle artifacts (generated during packaging)
513+
src/extension/.roslyn/
514+
src/extension/.roslyn-vb/
515+
src/extension/.roslyn-downloads/
516+

PROJECT_PLAN.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,30 @@ The language server is organized into five distinct layers:
299299
- Test Explorer integration
300300
- Performance tuning and indexing improvements
301301
- Inlay hints (deferred pending stable Roslyn APIs)
302+
- TODO: Document the exact Roslyn package versions used by the extension in README.md for transparency.
303+
304+
#### Phase 3 Extension UX Backlog (User-Facing Parity, Non-Razor/XAML/Blazor)
305+
306+
High-impact, low/medium effort:
307+
- Add solution/project picker command for multi-solution workspaces (show active selection).
308+
- Add "Show Logs" and "Record/Toggle LSP Trace" commands for quick diagnostics capture.
309+
- Add restore commands (workspace or selected project) with clear status notifications.
310+
- Add activation for `.slnf` to match solution filter usage in large repos.
311+
- Add file nesting defaults for VB artifacts (e.g., `.Designer.vb`, `.g.vb`, `.g.i.vb`, `.generated.vb`, `.AssemblyInfo.vb`, `My*.vb`, `*.resx -> .Designer.vb`).
312+
313+
High-impact, medium effort:
314+
- Add "Reanalyze / Reload Workspace" command to clear caches and re-open the solution.
315+
- Add attach-to-process picker command and richer debug configuration snippets (infer program via `projectPath`).
316+
- Add "Run Tests in Context" and "Debug Tests in Context" commands (initial implementation can call `dotnet test`).
317+
- Align package.json settings with documented options (e.g., `vbnet.msbuildPath`, `vbnet.maxMemoryMB`, etc.).
318+
319+
SDK-style VB projects targeting .NET Framework 4.x (support + guidance):
320+
- Detect SDK-style `net4x` projects and validate environment prerequisites.
321+
- On non-Windows hosts, surface a clear "not supported" message for `net4x` targets.
322+
- On Windows, detect missing .NET Framework targeting packs/VS Build Tools and guide installation.
323+
- Prefer full MSBuild when `net4x` is detected; expose `vbnet.msbuildPath` override in settings.
324+
- If restore is missing, surface restore errors and provide a restore command shortcut.
325+
- Document `Microsoft.NETFramework.ReferenceAssemblies` as a fallback when targeting packs are absent.
302326

303327
### Phase 4
304328
**Goal:** Enterprise and complex scenarios

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ See [docs/architecture.md](docs/architecture.md) for detailed architectural info
142142
- [Architecture](docs/architecture.md) - System architecture and design decisions
143143
- [Development Guide](docs/development.md) - Building, testing, and contributing
144144
- [Configuration](docs/configuration.md) - Settings and customization
145+
- [Roslyn Packaging](docs/roslyn-packaging.md) - Roslyn LSP packaging and layout
145146
- [Feature Support](docs/features.md) - LSP feature matrix and roadmap
146147

147148
## Project Goals

docs/architecture.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,13 @@ This document serves as the **single authoritative source** for all architectura
168168
- `vbnet.debugger.path` - netcoredbg path override
169169
- `vbnet.debugger.args` - Additional netcoredbg arguments
170170

171+
**Backend Selection (planned)**:
172+
- The extension will support selecting which LSP server to launch (our VB.NET LSP vs Roslyn LSP),
173+
but only one server is active at a time. This keeps behavior predictable and avoids regressions.
174+
- Alternative approach (not implemented): run both servers and route specific requests to a
175+
secondary backend for missing features. We are intentionally not doing this yet due to
176+
complexity and regression risk.
177+
171178
### 3.2 Language Server (VB.NET/.NET)
172179

173180
**Location**: `src/VbNet.LanguageServer.Vb/`

docs/configuration.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,44 @@ View traces: "View > Output" > "`VB.NET` Language Support"
9494

9595
**Note**: Prefer setting this in workspace settings when developing the server. For a new user experience, leave it unset so the extension uses the bundled server.
9696

97+
#### `vbnet.server.backend`
98+
**Type**: `enum`
99+
**Values**: `"vbnet"`, `"roslyn"`
100+
**Default**: `"vbnet"`
101+
**Description**: Select which language server backend to launch.
102+
103+
```json
104+
{
105+
"vbnet.server.backend": "vbnet"
106+
}
107+
```
108+
109+
**Notes**:
110+
- `vbnet` uses the bundled VB.NET language server (current default).
111+
- `roslyn` uses the Roslyn language server with VB assemblies loaded via extension path.
112+
113+
#### `vbnet.roslyn.server.path`
114+
**Type**: `string`
115+
**Default**: `""` (use bundled Roslyn server)
116+
**Description**: Override the Roslyn language server binary path.
117+
118+
```json
119+
{
120+
"vbnet.roslyn.server.path": "C:\\path\\to\\Microsoft.CodeAnalysis.LanguageServer.dll"
121+
}
122+
```
123+
124+
#### `vbnet.roslyn.server.extensionPath`
125+
**Type**: `string`
126+
**Default**: `""`
127+
**Description**: Directory containing Roslyn VB extension assemblies (`Microsoft.CodeAnalysis.VisualBasic*.dll`).
128+
129+
```json
130+
{
131+
"vbnet.roslyn.server.extensionPath": "C:\\path\\to\\roslyn-vb-extension"
132+
}
133+
```
134+
97135
---
98136

99137
### Feature Toggles
@@ -647,6 +685,18 @@ $env:VBNET_LS_LOG_LEVEL="Trace"
647685
$env:VBNET_LS_MAX_MEMORY_MB="4096"
648686
```
649687

688+
Additional environment variables (Roslyn backend):
689+
690+
```bash
691+
# Linux/macOS
692+
export VBNET_ROSLYN_SERVER_PATH=/path/to/Microsoft.CodeAnalysis.LanguageServer.dll
693+
export VBNET_ROSLYN_EXTENSION_PATH=/path/to/roslyn-vb-extension
694+
695+
# Windows (PowerShell)
696+
$env:VBNET_ROSLYN_SERVER_PATH="C:\\path\\to\\Microsoft.CodeAnalysis.LanguageServer.dll"
697+
$env:VBNET_ROSLYN_EXTENSION_PATH="C:\\path\\to\\roslyn-vb-extension"
698+
```
699+
650700
**Note**: VS Code settings override environment variables.
651701

652702
---

docs/development.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
**`VB.NET` Language Support - Developer Documentation**
44

55
Version: 1.0
6-
Last Updated: 2026-01-19
6+
Last Updated: 2026-01-21
77

88
## Table of Contents
99

@@ -652,6 +652,7 @@ Current scope: Windows-only (multi-platform planned).
652652
```yaml
653653
- Run language server unit/integration tests (test/VbNet.LanguageServer.Tests.Vb)
654654
- Run extension manifest checks (test/VbNet.Extension.Tests)
655+
- Bundle Roslyn LSP (win-x64) and validate `.roslyn` + `.roslyn-vb` layout
655656
```
656657
657658
#### Planned (not yet in repo)
@@ -667,7 +668,8 @@ Two workflows are available via `workflow_dispatch` (manual trigger):
667668
- **package-vsix.yml**: Build a VSIX artifact for a selected target.
668669
- **publish-vsix.yml**: Build and publish a VSIX to the Marketplace.
669670

670-
Both workflows bundle the curated netcoredbg assets listed in `src/extension/scripts/netcoredbg-assets.json`.
671+
Both workflows bundle the curated netcoredbg assets listed in `src/extension/scripts/netcoredbg-assets.json`,
672+
bundle Roslyn LSP assets via NuGet, and validate that `.roslyn` + `.roslyn-vb` are present in the VSIX.
671673
Publishing also requires the `VSCE_PAT` secret (Marketplace PAT with publish rights).
672674

673675
### CI Duration Note (Tracking)

docs/neovim.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Neovim (VB.NET LSP) Prototype Notes
2+
3+
This document captures a minimal Neovim setup snippet for the VB.NET language server,
4+
modeled after patterns used by `roslyn.nvim`, but scoped to our VB.NET server. It is
5+
not a published plugin and exists only for reference and future work.
6+
7+
## Goals
8+
9+
- Mirror roslyn.nvim-style root detection (search for `.sln/.slnf/.slnx/.vbproj`).
10+
- Allow filewatching behavior toggles without changing server code.
11+
- Keep VS Code behaviors isolated and untouched.
12+
13+
## Minimal Neovim Config (built-in LSP)
14+
15+
```lua
16+
local function root_dir(fname)
17+
local start = vim.fs.dirname(fname)
18+
local found = vim.fs.find({ ".sln", ".slnf", ".slnx", ".vbproj" }, {
19+
path = start,
20+
upward = true,
21+
stop = vim.loop.os_homedir(),
22+
})
23+
if #found > 0 then
24+
return vim.fs.dirname(found[1])
25+
end
26+
return start
27+
end
28+
29+
local function setup_vbnet(filewatching)
30+
local capabilities = vim.lsp.protocol.make_client_capabilities()
31+
32+
if filewatching == "off" or filewatching == "server" then
33+
capabilities.workspace = capabilities.workspace or {}
34+
capabilities.workspace.didChangeWatchedFiles = {
35+
-- If "off", we strip watcher registrations on the client.
36+
dynamicRegistration = (filewatching == "off"),
37+
}
38+
end
39+
40+
vim.lsp.start({
41+
name = "vbnet",
42+
cmd = {
43+
"dotnet",
44+
"C:\\path\\to\\VbNet.LanguageServer.dll",
45+
"--stdio",
46+
"--logLevel",
47+
"Information",
48+
},
49+
root_dir = root_dir(vim.api.nvim_buf_get_name(0)),
50+
capabilities = capabilities,
51+
handlers = {
52+
["client/registerCapability"] = function(err, res, ctx)
53+
if filewatching == "off" and res and res.registrations then
54+
for _, reg in ipairs(res.registrations) do
55+
if reg.method == "workspace/didChangeWatchedFiles" then
56+
reg.registerOptions.watchers = {}
57+
end
58+
end
59+
end
60+
return vim.lsp.handlers["client/registerCapability"](err, res, ctx)
61+
end,
62+
},
63+
})
64+
end
65+
66+
-- "auto" | "server" | "off"
67+
setup_vbnet("auto")
68+
```
69+
70+
## Notes
71+
72+
- The VB.NET server does **not** implement Roslyn-specific `solution/open` or `project/open`
73+
notifications. Root detection relies on standard LSP workspace discovery.
74+
- Filewatching is controlled from the client side only; the server already supports
75+
`workspace/didChangeWatchedFiles`.
76+
- For headless validation, use the Neovim harness under `test-explore/clients/nvim`.

0 commit comments

Comments
 (0)