Skip to content

Commit 3d56c99

Browse files
authored
initialize: Add LSP initializationOptions support (#350)
Add support for static initialization options that are set once during server startup and require a restart to take effect. This differs from dynamic configuration managed by `ConfigManager`. Currently supports `n_analysis_workers` option for configuring the number of concurrent analysis worker tasks. Due to current architecture constraints around package environment switching and world age management, parallelization is limited to the signature analysis phase. Also updates `jetls-client` and documentation accordingly. Written by Claude
1 parent c774d25 commit 3d56c99

File tree

11 files changed

+187
-12
lines changed

11 files changed

+187
-12
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1616
- Commit: [`HEAD`](https://github.com/aviatesk/JETLS.jl/commit/HEAD)
1717
- Diff: [`f9b2c2f...HEAD`](https://github.com/aviatesk/JETLS.jl/compare/f9b2c2f...HEAD)
1818

19+
### Added
20+
21+
- Added support for LSP `initializationOptions` with the `n_analysis_workers`
22+
option for configuring concurrent analysis worker tasks.
23+
See [Initialization options](https://aviatesk.github.io/JETLS.jl/dev/launching/#init-options)
24+
for details.
25+
1926
### Fixed
2027

2128
- Fixed handling of messages received before the initialize request per

docs/src/launching.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,77 @@ client cannot execute the normal LSP shutdown sequence.
127127
When specified via command line, the process ID should match the
128128
`processId` field that the client sends in the LSP `initialize` request
129129
parameters.
130+
131+
## [Initialization options](@id init-options)
132+
133+
JETLS accepts static initialization options via the LSP `initializationOptions`
134+
field in the `initialize` request. Unlike [dynamic configuration](@ref config/schema)
135+
that can be changed at runtime, these options are set once at server startup and
136+
require a server restart to take effect.
137+
138+
### [Schema](@id init-options/schema)
139+
140+
```json
141+
{
142+
"n_analysis_workers": 1
143+
}
144+
```
145+
146+
### [Reference](@id init-options/reference)
147+
148+
#### [`n_analysis_workers`](@id init-options/n_analysis_workers)
149+
150+
- **Type**: integer
151+
- **Default**: `1`
152+
- **Minimum**: `1`
153+
154+
Number of concurrent analysis worker tasks for running full analysis.
155+
156+
```json
157+
{
158+
"n_analysis_workers": 3
159+
}
160+
```
161+
162+
!!! warning "Current limitations"
163+
JETLS currently runs all analysis within a single server process. These
164+
"workers" are concurrent tasks, not separate processes. Due to constraints
165+
around package environment switching and world age management during code
166+
loading, all workers are forced to execute sequentially during the code
167+
loading phase of full analysis. Only the signature analysis phase of
168+
package analysis actually runs in parallel.
169+
170+
As a result, increasing `n_analysis_workers` may not significantly speed up
171+
overall analysis in many scenarios. The semantics of this option may also
172+
change substantially in future versions as the full analysis architecture
173+
evolves.
174+
175+
### [Client configuration](@id init-options/client-config)
176+
177+
#### [VSCode (`jetls-client` extension)](@id init-options/client-config/vscode)
178+
179+
Configure initialization options in VSCode's `settings.json`:
180+
181+
```json
182+
{
183+
"jetls-client.initializationOptions": {
184+
"n_analysis_workers": 3
185+
}
186+
}
187+
```
188+
189+
#### [Zed (`aviatesk/zed-julia` extension)](@id init-options/client-config/zed)
190+
191+
Configure initialization options in Zed's `settings.json`:
192+
193+
```json
194+
{
195+
"lsp": {
196+
"JETLS": {
197+
"initialization_options": {
198+
"n_analysis_workers": 3
199+
}
200+
}
201+
}
202+
}
203+
```

jetls-client/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
- Commit: [`HEAD`](https://github.com/aviatesk/JETLS.jl/commit/HEAD)
1111
- Diff: [`dd21f78...HEAD`](https://github.com/aviatesk/JETLS.jl/compare/dd21f78...HEAD)
1212

13+
### Added
14+
15+
- Added `jetls-client.initializationOptions` setting for static server options
16+
that require a restart to take effect. Currently supports `n_analysis_workers`
17+
for configuring concurrent analysis worker tasks.
18+
See <https://aviatesk.github.io/JETLS.jl/dev/launching/#init-options> for details.
19+
1320
## v0.2.4
1421

1522
- Commit: [`dd21f78`](https://github.com/aviatesk/JETLS.jl/commit/dd21f78)

jetls-client/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,29 @@ You can override the automatic selection using `"jetls-client.communicationChann
103103
For detailed information about each communication channel and when to use them,
104104
see the [Communication channels documentation](https://aviatesk.github.io/JETLS.jl/dev/launching/#Communication-channels).
105105
106+
### Initialization options
107+
108+
Static options that are sent to JETLS during startup. These settings require a
109+
server restart to take effect.
110+
111+
Configure via `"jetls-client.initializationOptions"`:
112+
113+
- `"n_analysis_workers": number`: Number of concurrent analysis worker threads
114+
(default: `1`, minimum: `1`)
115+
116+
Example:
117+
118+
```jsonc
119+
{
120+
"jetls-client.initializationOptions": {
121+
"n_analysis_workers": 3
122+
}
123+
}
124+
```
125+
126+
For more details, see the
127+
[Initialization options documentation](https://aviatesk.github.io/JETLS.jl/dev/launching/#Initialization-options).
128+
106129
## Configuring JETLS
107130
108131
JETLS behavior (diagnostics, formatting, etc.) can be configured through VSCode's

jetls-client/jetls-client.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,8 @@ async function startLanguageServer() {
444444
};
445445
}
446446

447+
const initializationOptions = vscode.workspace.getConfiguration("jetls-client").get("initializationOptions", {});
448+
447449
const clientOptions: LanguageClientOptions = {
448450
documentSelector: [
449451
{
@@ -455,6 +457,7 @@ async function startLanguageServer() {
455457
language: "julia",
456458
},
457459
],
460+
initializationOptions,
458461
outputChannel,
459462
};
460463

jetls-client/package.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,21 @@
104104
"markdownDescription": "Port number for socket communication (`0` = auto-assign). Only used when `'socket'` communication channel is used.",
105105
"order": 3
106106
},
107+
"jetls-client.initializationOptions": {
108+
"scope": "resource",
109+
"type": "object",
110+
"default": {},
111+
"markdownDescription": "Static initialization options sent to JETLS during startup. These settings require a server restart to take effect.",
112+
"properties": {
113+
"n_analysis_workers": {
114+
"type": "integer",
115+
"default": 1,
116+
"minimum": 1,
117+
"markdownDescription": "Number of analysis worker tasks. Default is `1`. See [documentation](https://aviatesk.github.io/JETLS.jl/dev/launching/#init-options/n_analysis_workers) for details and current limitations."
118+
}
119+
},
120+
"order": 4
121+
},
107122
"jetls-client.settings": {
108123
"scope": "resource",
109124
"type": "object",

src/JETLS.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ include("utils/binding.jl")
7575
include("utils/lsp.jl")
7676
include("utils/server.jl")
7777

78+
include("init_options.jl")
7879
include("config.jl")
7980
include("workspace-configuration.jl")
8081

src/analysis/full-analysis.jl

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,10 @@ end
7272
# ===============
7373

7474
function start_analysis_workers!(server::Server)
75-
for i = 1:length(server.state.analysis_manager.worker_tasks)
76-
server.state.analysis_manager.worker_tasks[i] = Threads.@spawn :default try
75+
n_workers = get_init_option(server.state.init_options, :n_analysis_workers)
76+
@info "Starting $n_workers analysis workers"
77+
for _ = 1:n_workers
78+
Threads.@spawn :default try
7779
analysis_worker(server)
7880
catch err
7981
@error "Critical error happened in analysis worker"
@@ -82,7 +84,7 @@ function start_analysis_workers!(server::Server)
8284
end
8385
end
8486

85-
# Analysis queue processing implementation (analysis serialized per AnalysisEntry)
87+
# Analysis queue processing implementation (analysis serialized per AnalysisEntry)
8688
function analysis_worker(server::Server)
8789
# Note: Currently single worker, but designed for future multi-worker scaling.
8890
# When multiple workers exist, the per-entry serialization ensures correctness.

src/init_options.jl

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
const DEFAULT_INIT_OPTIONS = InitOptions(; n_analysis_workers=1)
2+
3+
function merge_init_options(base::InitOptions, overlay::InitOptions)
4+
InitOptions(;
5+
n_analysis_workers = something(overlay.n_analysis_workers, base.n_analysis_workers))
6+
end
7+
8+
function validate_init_options(opts::InitOptions)
9+
n = opts.n_analysis_workers
10+
if n !== nothing && n < 1
11+
@warn "n_analysis_workers must be at least 1, using default" n
12+
return InitOptions(; n_analysis_workers=DEFAULT_INIT_OPTIONS.n_analysis_workers)
13+
end
14+
return opts
15+
end
16+
17+
function parse_init_options(@nospecialize init_options)
18+
init_options === nothing && return DEFAULT_INIT_OPTIONS
19+
init_options isa AbstractDict || return DEFAULT_INIT_OPTIONS
20+
parsed = try
21+
validate_init_options(Configurations.from_dict(InitOptions, init_options))
22+
catch err
23+
@warn "Failed to parse initializationOptions, using defaults" err
24+
return DEFAULT_INIT_OPTIONS
25+
end
26+
return merge_init_options(DEFAULT_INIT_OPTIONS, parsed)
27+
end
28+
29+
get_init_option(opts::InitOptions, key::Symbol) = @something getfield(opts, key) error(lazy"Invalid init option: $key")

src/initialize.jl

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
# NOTE: Static server settings that require a server restart to take effect should be
2-
# accessed during server initialization via `state.init_params.initializationOptions`.
3-
# These settings differ from dynamic configuration options managed by `ConfigManager`
4-
# that can be changed at throughout server lifecycle.
5-
61
"""
72
Receives `msg::InitializeRequest` and sets up the `server.state` based on `msg.params`.
83
As a response to this `msg`, it returns an `InitializeResponse` and performs registration of
@@ -22,6 +17,12 @@ function handle_InitializeRequest(
2217
state = server.state
2318
init_params = state.init_params = msg.params
2419

20+
# NOTE: Static server settings that require a server restart to take effect should be
21+
# accessed during server initialization via `state.init_params.initializationOptions`.
22+
# These settings differ from dynamic configuration options managed by `ConfigManager`
23+
# that can be changed at throughout server lifecycle.
24+
state.init_options = parse_init_options(init_params.initializationOptions)
25+
2526
workspaceFolders = init_params.workspaceFolders
2627
if workspaceFolders !== nothing
2728
state.workspaceFolders = URI[uri for (; uri) in workspaceFolders]
@@ -427,4 +428,5 @@ function handle_InitializedNotification(server::Server)
427428
register(server, registrations)
428429

429430
JETLS_DEV_MODE && show_setup_info("Initialized JETLS with the following setup:")
431+
JETLS_DEV_MODE && @info "JETLS intialization options" init_options=state.init_options
430432
end

0 commit comments

Comments
 (0)