Skip to content

Commit e40d16e

Browse files
committed
Add code actions on save
1 parent 941dc6c commit e40d16e

File tree

7 files changed

+260
-138
lines changed

7 files changed

+260
-138
lines changed

book/src/languages.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ These configuration keys are available:
6868
| `formatter` | The formatter for the language, it will take precedence over the lsp when defined. The formatter must be able to take the original file as input from stdin and write the formatted file to stdout |
6969
| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap-at-text-width` is set, defaults to `editor.text-width` |
7070
| `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. |
71+
| `code-actions-on-save` | List of LSP code actions to be run in order on save, for example `["source.organizeImports"]` |
7172

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

helix-core/src/syntax.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ pub struct LanguageConfiguration {
105105
pub comment_token: Option<String>,
106106
pub text_width: Option<usize>,
107107
pub soft_wrap: Option<SoftWrap>,
108+
#[serde(default)]
109+
pub code_actions_on_save: Vec<String>, // List of LSP code actions to be run in order upon saving
108110

109111
#[serde(default)]
110112
pub auto_format: bool,

helix-term/src/application.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -319,11 +319,11 @@ impl Application {
319319
self.handle_terminal_events(event).await;
320320
}
321321
Some(callback) = self.jobs.futures.next() => {
322-
self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback);
322+
self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback).await;
323323
self.render().await;
324324
}
325325
Some(callback) = self.jobs.wait_futures.next() => {
326-
self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback);
326+
self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback).await;
327327
self.render().await;
328328
}
329329
event = self.editor.wait_event() => {

helix-term/src/commands.rs

Lines changed: 87 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ use crate::{
5050
args,
5151
compositor::{self, Component, Compositor},
5252
filter_picker_entry,
53-
job::Callback,
53+
job::{Callback, OnSaveCallbackData},
5454
keymap::ReverseKeymap,
5555
ui::{
5656
self, editor::InsertEvent, lsp::SignatureHelp, overlay::overlaid, CompletionItem, Picker,
@@ -3024,38 +3024,105 @@ async fn make_format_callback(
30243024
doc_version: i32,
30253025
view_id: ViewId,
30263026
format: impl Future<Output = Result<Transaction, FormatterError>> + Send + 'static,
3027-
write: Option<(Option<PathBuf>, bool)>,
30283027
) -> anyhow::Result<job::Callback> {
30293028
let format = format.await;
30303029

30313030
let call: job::Callback = Callback::Editor(Box::new(move |editor| {
3032-
if !editor.documents.contains_key(&doc_id) || !editor.tree.contains(view_id) {
3033-
return;
3031+
format_callback(doc_id, doc_version, view_id, format, editor);
3032+
}));
3033+
3034+
Ok(call)
3035+
}
3036+
3037+
pub fn format_callback(
3038+
doc_id: DocumentId,
3039+
doc_version: i32,
3040+
view_id: ViewId,
3041+
format: Result<Transaction, FormatterError>,
3042+
editor: &mut Editor,
3043+
) {
3044+
if !editor.documents.contains_key(&doc_id) || !editor.tree.contains(view_id) {
3045+
return;
3046+
}
3047+
3048+
let scrolloff = editor.config().scrolloff;
3049+
let doc = doc_mut!(editor, &doc_id);
3050+
let view = view_mut!(editor, view_id);
3051+
3052+
if let Ok(format) = format {
3053+
if doc.version() == doc_version {
3054+
doc.apply(&format, view.id);
3055+
doc.append_changes_to_history(view);
3056+
doc.detect_indent_and_line_ending();
3057+
view.ensure_cursor_in_view(doc, scrolloff);
3058+
} else {
3059+
log::info!("discarded formatting changes because the document changed");
30343060
}
3061+
}
3062+
}
30353063

3036-
let scrolloff = editor.config().scrolloff;
3037-
let doc = doc_mut!(editor, &doc_id);
3038-
let view = view_mut!(editor, view_id);
3064+
pub async fn on_save_callback(
3065+
mut editor: &mut Editor,
3066+
doc_id: DocumentId,
3067+
view_id: ViewId,
3068+
path: Option<PathBuf>,
3069+
force: bool,
3070+
) {
3071+
let doc = doc!(editor, &doc_id);
3072+
if let Some(code_actions_on_save_cfg) = doc
3073+
.language_config()
3074+
.map(|c| c.code_actions_on_save.clone())
3075+
{
3076+
for code_action_on_save_cfg in code_actions_on_save_cfg {
3077+
log::debug!(
3078+
"Attempting code action on save {:?}",
3079+
code_action_on_save_cfg
3080+
);
3081+
let doc = doc!(editor, &doc_id);
3082+
let lsp_item_result = code_action_on_save(&doc, code_action_on_save_cfg.clone()).await;
30393083

3040-
if let Ok(format) = format {
3041-
if doc.version() == doc_version {
3042-
doc.apply(&format, view.id);
3043-
doc.append_changes_to_history(view);
3044-
doc.detect_indent_and_line_ending();
3045-
view.ensure_cursor_in_view(doc, scrolloff);
3084+
if let Some(lsp_item) = lsp_item_result {
3085+
log::debug!("Applying code action on save {:?}", code_action_on_save_cfg);
3086+
apply_code_action(&mut editor, &lsp_item);
30463087
} else {
3047-
log::info!("discarded formatting changes because the document changed");
3088+
log::debug!(
3089+
"Code action on save not found {:?}",
3090+
code_action_on_save_cfg
3091+
);
3092+
editor.set_error(format!(
3093+
"Code Action not found: {:?}",
3094+
code_action_on_save_cfg
3095+
));
30483096
}
30493097
}
3098+
}
30503099

3051-
if let Some((path, force)) = write {
3052-
let id = doc.id();
3053-
if let Err(err) = editor.save(id, path, force) {
3054-
editor.set_error(format!("Error saving: {}", err));
3055-
}
3100+
if editor.config().auto_format {
3101+
let doc = doc!(editor, &doc_id);
3102+
if let Some(fmt) = doc.auto_format() {
3103+
format_callback(doc.id(), doc.version(), view_id, fmt.await, &mut editor);
30563104
}
3057-
}));
3105+
}
30583106

3107+
if let Err(err) = editor.save::<PathBuf>(doc_id, path, force) {
3108+
editor.set_error(format!("Error saving: {}", err));
3109+
}
3110+
}
3111+
3112+
pub async fn make_on_save_callback(
3113+
doc_id: DocumentId,
3114+
view_id: ViewId,
3115+
path: Option<PathBuf>,
3116+
force: bool,
3117+
) -> anyhow::Result<job::Callback> {
3118+
let call: job::Callback = Callback::OnSave(Box::new({
3119+
OnSaveCallbackData {
3120+
doc_id,
3121+
view_id,
3122+
path,
3123+
force,
3124+
}
3125+
}));
30593126
Ok(call)
30603127
}
30613128

0 commit comments

Comments
 (0)