Skip to content

Commit 29b3278

Browse files
committed
allow textDocument/didChange notification to be synchronous Co-authored-by: junglerobba <junglerobba@jngl.one> Add code actions on save * Add code-actions-on-save config * Match VS Code config to allow future flexibility * Refactor lsp commands to allow for code reuse * Attempt code actions for all language servers for the document * Add lsp specific integration tests * Update documentation in book * Canonicalize path argument when retrieving documents by path * Resolves issue when running lsp integration tests in windows commands: add no-code-actions flag to write and write-all Same as with auto-format, auto save will not trigger code actions
1 parent d9432be commit 29b3278

File tree

22 files changed

+691
-99
lines changed

22 files changed

+691
-99
lines changed

.cargo/config.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ rustflags = ["--cfg", "tokio_unstable", "-C", "target-feature=-crt-static"]
1414
[alias]
1515
xtask = "run --package xtask --"
1616
integration-test = "test --features integration --profile integration --workspace --test integration"
17+
integration-test-lsp = "test --features integration-lsp --profile integration --workspace --test integration-lsp -- --test-threads=1"
1718

.github/workflows/build.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,25 @@ jobs:
6969
key: ${{ runner.os }}-${{ runner.arch }}-stable-v${{ env.GRAMMAR_CACHE_VERSION }}-tree-sitter-grammars-${{ hashFiles('languages.toml') }}
7070
restore-keys: ${{ runner.os }}-${{ runner.arch }}-stable-v${{ env.GRAMMAR_CACHE_VERSION }}-tree-sitter-grammars-
7171

72+
- name: Install go lsp integration tests
73+
uses: actions/setup-go@v5
74+
with:
75+
go-version: '^1.22.0'
76+
77+
- name: Install gopls for lsp integration tests
78+
run: go install golang.org/x/tools/gopls@latest
79+
80+
81+
7282
- name: Run cargo test
7383
run: cargo test --workspace
7484

7585
- name: Run cargo integration-test
7686
run: cargo integration-test
7787

88+
- name: Run cargo integration-test-lsp
89+
run: cargo integration-test-lsp
90+
7891
strategy:
7992
matrix:
8093
os: [ubuntu-latest, macos-latest, windows-latest, ubuntu-24.04-arm]

book/src/languages.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ These configuration keys are available:
7575
| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml`. Overwrites the setting of the same name in `config.toml` if set. |
7676
| `persistent-diagnostic-sources` | An array of LSP diagnostic sources assumed unchanged when the language server resends the same set of diagnostics. Helix can track the position for these diagnostics internally instead. Useful for diagnostics that are recomputed on save.
7777
| `rainbow-brackets` | Overrides the `editor.rainbow-brackets` config key for the language |
78+
| `code-actions-on-save` | List of LSP code actions to be run in order on save, for example `[{ code-action = "source.organizeImports", enabled = true }]` |
7879

7980
### File-type detection and the `file-types` key
8081

docs/CONTRIBUTING.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,14 @@ Contributors using MacOS might encounter `Too many open files (os error 24)`
5959
failures while running integration tests. This can be resolved by increasing
6060
the default value (e.g. to `10240` from `256`) by running `ulimit -n 10240`.
6161

62+
### Language Server tests
63+
64+
There are integration tests specific for language server integration that can be
65+
run with `cargo integration-test-lsp` and have additional dependencies.
66+
67+
* [go](https://go.dev)
68+
* [gopls](https://pkg.go.dev/golang.org/x/tools/gopls)
69+
6270
## Minimum Stable Rust Version (MSRV) Policy
6371

6472
Helix keeps an intentionally low MSRV for the sake of easy building and packaging

helix-core/src/syntax/config.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ pub struct LanguageConfiguration {
5656
#[serde(default)]
5757
pub auto_format: bool,
5858

59+
#[serde(default)]
60+
pub code_actions_on_save: Option<Vec<CodeActionsOnSave>>,
61+
5962
#[serde(skip_serializing_if = "Option::is_none")]
6063
pub formatter: Option<FormatterConfiguration>,
6164

@@ -435,6 +438,13 @@ pub struct AdvancedCompletion {
435438
pub default: Option<String>,
436439
}
437440

441+
#[derive(Debug, Clone, Serialize, Deserialize)]
442+
#[serde(rename_all = "kebab-case")]
443+
pub struct CodeActionsOnSave {
444+
pub code_action: String,
445+
pub enabled: bool,
446+
}
447+
438448
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
439449
#[serde(rename_all = "kebab-case", untagged)]
440450
pub enum DebugConfigCompletion {

helix-lsp/src/client.rs

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use crate::lsp::{
1212
};
1313
use helix_core::{find_workspace, syntax::config::LanguageServerFeature, ChangeSet, Rope};
1414
use helix_loader::VERSION_AND_GIT_HASH;
15+
use helix_lsp_types::TextDocumentContentChangeEvent;
1516
use helix_stdx::path;
1617
use parking_lot::Mutex;
1718
use serde::Deserialize;
@@ -517,6 +518,27 @@ impl Client {
517518
}
518519
}
519520

521+
pub fn notify_sync<R: lsp::notification::Notification>(&self, params: R::Params) -> Result<()>
522+
where
523+
R::Params: serde::Serialize,
524+
{
525+
let server_tx = self.server_tx.clone();
526+
527+
let params = serde_json::to_value(params)?;
528+
529+
let notification = jsonrpc::Notification {
530+
jsonrpc: Some(jsonrpc::Version::V2),
531+
method: R::METHOD.to_string(),
532+
params: Self::value_into_params(params),
533+
};
534+
535+
server_tx
536+
.send(Payload::Notification(notification))
537+
.map_err(|e| Error::Other(e.into()))?;
538+
539+
Ok(())
540+
}
541+
520542
/// Reply to a language server RPC call.
521543
pub fn reply(
522544
&self,
@@ -958,6 +980,51 @@ impl Client {
958980
new_text: &Rope,
959981
changes: &ChangeSet,
960982
) -> Option<()> {
983+
if let Some(content_changes) =
984+
self.text_document_did_change_impl(old_text, new_text, changes)
985+
{
986+
return {
987+
self.notify::<lsp::notification::DidChangeTextDocument>(
988+
lsp::DidChangeTextDocumentParams {
989+
text_document,
990+
content_changes,
991+
},
992+
);
993+
Some(())
994+
};
995+
}
996+
None
997+
}
998+
999+
/// Will send textDocument/didChange notification synchronously
1000+
pub fn text_document_did_change_sync(
1001+
&self,
1002+
text_document: lsp::VersionedTextDocumentIdentifier,
1003+
old_text: &Rope,
1004+
new_text: &Rope,
1005+
changes: &ChangeSet,
1006+
) -> Option<Result<()>> {
1007+
if let Some(content_changes) =
1008+
self.text_document_did_change_impl(old_text, new_text, changes)
1009+
{
1010+
return Some(
1011+
self.notify_sync::<lsp::notification::DidChangeTextDocument>(
1012+
lsp::DidChangeTextDocumentParams {
1013+
text_document,
1014+
content_changes,
1015+
},
1016+
),
1017+
);
1018+
}
1019+
None
1020+
}
1021+
1022+
pub fn text_document_did_change_impl(
1023+
&self,
1024+
old_text: &Rope,
1025+
new_text: &Rope,
1026+
changes: &ChangeSet,
1027+
) -> Option<Vec<TextDocumentContentChangeEvent>> {
9611028
let capabilities = self.capabilities.get().unwrap();
9621029

9631030
// Return early if the server does not support document sync.
@@ -989,11 +1056,7 @@ impl Client {
9891056
kind => unimplemented!("{:?}", kind),
9901057
};
9911058

992-
self.notify::<lsp::notification::DidChangeTextDocument>(lsp::DidChangeTextDocumentParams {
993-
text_document,
994-
content_changes: changes,
995-
});
996-
Some(())
1059+
Some(changes)
9971060
}
9981061

9991062
pub fn text_document_did_close(&self, text_document: lsp::TextDocumentIdentifier) {

helix-term/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ assets = [
3434
default = ["git"]
3535
unicode-lines = ["helix-core/unicode-lines", "helix-view/unicode-lines"]
3636
integration = ["helix-event/integration_test"]
37+
integration-lsp = ["integration"]
3738
git = ["helix-vcs/git"]
3839

3940
[[bin]]

0 commit comments

Comments
 (0)