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
1 change: 1 addition & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ rustflags = ["--cfg", "tokio_unstable", "-C", "target-feature=-crt-static"]
[alias]
xtask = "run --package xtask --"
integration-test = "test --features integration --profile integration --workspace --test integration"
integration-test-lsp = "test --features integration-lsp --profile integration --workspace --test integration-lsp -- --test-threads=1"

13 changes: 13 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,25 @@ jobs:
key: ${{ runner.os }}-${{ runner.arch }}-stable-v${{ env.GRAMMAR_CACHE_VERSION }}-tree-sitter-grammars-${{ hashFiles('languages.toml') }}
restore-keys: ${{ runner.os }}-${{ runner.arch }}-stable-v${{ env.GRAMMAR_CACHE_VERSION }}-tree-sitter-grammars-

- name: Install go lsp integration tests
uses: actions/setup-go@v5
with:
go-version: '^1.22.0'

- name: Install gopls for lsp integration tests
run: go install golang.org/x/tools/gopls@latest



- name: Run cargo test
run: cargo test --workspace

- name: Run cargo integration-test
run: cargo integration-test

- name: Run cargo integration-test-lsp
run: cargo integration-test-lsp

strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest, ubuntu-24.04-arm]
Expand Down
1 change: 1 addition & 0 deletions book/src/languages.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ These configuration keys are available:
| `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. |
| `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.
| `rainbow-brackets` | Overrides the `editor.rainbow-brackets` config key for the language |
| `code-actions-on-save` | List of LSP code actions to be run in order on save, for example `[{ code-action = "source.organizeImports", enabled = true }]` |

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

Expand Down
8 changes: 8 additions & 0 deletions docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ Contributors using MacOS might encounter `Too many open files (os error 24)`
failures while running integration tests. This can be resolved by increasing
the default value (e.g. to `10240` from `256`) by running `ulimit -n 10240`.

### Language Server tests

There are integration tests specific for language server integration that can be
run with `cargo integration-test-lsp` and have additional dependencies.

* [go](https://go.dev)
* [gopls](https://pkg.go.dev/golang.org/x/tools/gopls)

## Minimum Stable Rust Version (MSRV) Policy

Helix keeps an intentionally low MSRV for the sake of easy building and packaging
Expand Down
10 changes: 10 additions & 0 deletions helix-core/src/syntax/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ pub struct LanguageConfiguration {
#[serde(default)]
pub auto_format: bool,

#[serde(default)]
pub code_actions_on_save: Option<Vec<CodeActionsOnSave>>,

#[serde(skip_serializing_if = "Option::is_none")]
pub formatter: Option<FormatterConfiguration>,

Expand Down Expand Up @@ -435,6 +438,13 @@ pub struct AdvancedCompletion {
pub default: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct CodeActionsOnSave {
pub code_action: String,
pub enabled: bool,
}

#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case", untagged)]
pub enum DebugConfigCompletion {
Expand Down
73 changes: 68 additions & 5 deletions helix-lsp/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::lsp::{
};
use helix_core::{find_workspace, syntax::config::LanguageServerFeature, ChangeSet, Rope};
use helix_loader::VERSION_AND_GIT_HASH;
use helix_lsp_types::TextDocumentContentChangeEvent;
use helix_stdx::path;
use parking_lot::Mutex;
use serde::Deserialize;
Expand Down Expand Up @@ -517,6 +518,27 @@ impl Client {
}
}

pub fn notify_sync<R: lsp::notification::Notification>(&self, params: R::Params) -> Result<()>
where
R::Params: serde::Serialize,
{
let server_tx = self.server_tx.clone();

let params = serde_json::to_value(params)?;

let notification = jsonrpc::Notification {
jsonrpc: Some(jsonrpc::Version::V2),
method: R::METHOD.to_string(),
params: Self::value_into_params(params),
};

server_tx
.send(Payload::Notification(notification))
.map_err(|e| Error::Other(e.into()))?;

Ok(())
}

/// Reply to a language server RPC call.
pub fn reply(
&self,
Expand Down Expand Up @@ -958,6 +980,51 @@ impl Client {
new_text: &Rope,
changes: &ChangeSet,
) -> Option<()> {
if let Some(content_changes) =
self.text_document_did_change_impl(old_text, new_text, changes)
{
return {
self.notify::<lsp::notification::DidChangeTextDocument>(
lsp::DidChangeTextDocumentParams {
text_document,
content_changes,
},
);
Some(())
};
}
None
}

/// Will send textDocument/didChange notification synchronously
pub fn text_document_did_change_sync(
&self,
text_document: lsp::VersionedTextDocumentIdentifier,
old_text: &Rope,
new_text: &Rope,
changes: &ChangeSet,
) -> Option<Result<()>> {
if let Some(content_changes) =
self.text_document_did_change_impl(old_text, new_text, changes)
{
return Some(
self.notify_sync::<lsp::notification::DidChangeTextDocument>(
lsp::DidChangeTextDocumentParams {
text_document,
content_changes,
},
),
);
}
None
}

pub fn text_document_did_change_impl(
&self,
old_text: &Rope,
new_text: &Rope,
changes: &ChangeSet,
) -> Option<Vec<TextDocumentContentChangeEvent>> {
let capabilities = self.capabilities.get().unwrap();

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

self.notify::<lsp::notification::DidChangeTextDocument>(lsp::DidChangeTextDocumentParams {
text_document,
content_changes: changes,
});
Some(())
Some(changes)
}

pub fn text_document_did_close(&self, text_document: lsp::TextDocumentIdentifier) {
Expand Down
1 change: 1 addition & 0 deletions helix-term/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ assets = [
default = ["git"]
unicode-lines = ["helix-core/unicode-lines", "helix-view/unicode-lines"]
integration = ["helix-event/integration_test"]
integration-lsp = ["integration"]
git = ["helix-vcs/git"]

[[bin]]
Expand Down
Loading
Loading