Skip to content

Commit

Permalink
Add code actions on save
Browse files Browse the repository at this point in the history
  • Loading branch information
jpttrssn committed Sep 16, 2023
1 parent 941dc6c commit e40d16e
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 138 deletions.
1 change: 1 addition & 0 deletions book/src/languages.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ These configuration keys are available:
| `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 |
| `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` |
| `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. |
| `code-actions-on-save` | List of LSP code actions to be run in order on save, for example `["source.organizeImports"]` |

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

Expand Down
2 changes: 2 additions & 0 deletions helix-core/src/syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ pub struct LanguageConfiguration {
pub comment_token: Option<String>,
pub text_width: Option<usize>,
pub soft_wrap: Option<SoftWrap>,
#[serde(default)]
pub code_actions_on_save: Vec<String>, // List of LSP code actions to be run in order upon saving

#[serde(default)]
pub auto_format: bool,
Expand Down
4 changes: 2 additions & 2 deletions helix-term/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,11 +319,11 @@ impl Application {
self.handle_terminal_events(event).await;
}
Some(callback) = self.jobs.futures.next() => {
self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback);
self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback).await;
self.render().await;
}
Some(callback) = self.jobs.wait_futures.next() => {
self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback);
self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback).await;
self.render().await;
}
event = self.editor.wait_event() => {
Expand Down
107 changes: 87 additions & 20 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ use crate::{
args,
compositor::{self, Component, Compositor},
filter_picker_entry,
job::Callback,
job::{Callback, OnSaveCallbackData},
keymap::ReverseKeymap,
ui::{
self, editor::InsertEvent, lsp::SignatureHelp, overlay::overlaid, CompletionItem, Picker,
Expand Down Expand Up @@ -3024,38 +3024,105 @@ async fn make_format_callback(
doc_version: i32,
view_id: ViewId,
format: impl Future<Output = Result<Transaction, FormatterError>> + Send + 'static,
write: Option<(Option<PathBuf>, bool)>,
) -> anyhow::Result<job::Callback> {
let format = format.await;

let call: job::Callback = Callback::Editor(Box::new(move |editor| {
if !editor.documents.contains_key(&doc_id) || !editor.tree.contains(view_id) {
return;
format_callback(doc_id, doc_version, view_id, format, editor);
}));

Ok(call)
}

pub fn format_callback(
doc_id: DocumentId,
doc_version: i32,
view_id: ViewId,
format: Result<Transaction, FormatterError>,
editor: &mut Editor,
) {
if !editor.documents.contains_key(&doc_id) || !editor.tree.contains(view_id) {
return;
}

let scrolloff = editor.config().scrolloff;
let doc = doc_mut!(editor, &doc_id);
let view = view_mut!(editor, view_id);

if let Ok(format) = format {
if doc.version() == doc_version {
doc.apply(&format, view.id);
doc.append_changes_to_history(view);
doc.detect_indent_and_line_ending();
view.ensure_cursor_in_view(doc, scrolloff);
} else {
log::info!("discarded formatting changes because the document changed");
}
}
}

let scrolloff = editor.config().scrolloff;
let doc = doc_mut!(editor, &doc_id);
let view = view_mut!(editor, view_id);
pub async fn on_save_callback(
mut editor: &mut Editor,
doc_id: DocumentId,
view_id: ViewId,
path: Option<PathBuf>,
force: bool,
) {
let doc = doc!(editor, &doc_id);
if let Some(code_actions_on_save_cfg) = doc
.language_config()
.map(|c| c.code_actions_on_save.clone())
{
for code_action_on_save_cfg in code_actions_on_save_cfg {
log::debug!(
"Attempting code action on save {:?}",
code_action_on_save_cfg
);
let doc = doc!(editor, &doc_id);
let lsp_item_result = code_action_on_save(&doc, code_action_on_save_cfg.clone()).await;

if let Ok(format) = format {
if doc.version() == doc_version {
doc.apply(&format, view.id);
doc.append_changes_to_history(view);
doc.detect_indent_and_line_ending();
view.ensure_cursor_in_view(doc, scrolloff);
if let Some(lsp_item) = lsp_item_result {
log::debug!("Applying code action on save {:?}", code_action_on_save_cfg);
apply_code_action(&mut editor, &lsp_item);
} else {
log::info!("discarded formatting changes because the document changed");
log::debug!(
"Code action on save not found {:?}",
code_action_on_save_cfg
);
editor.set_error(format!(
"Code Action not found: {:?}",
code_action_on_save_cfg
));
}
}
}

if let Some((path, force)) = write {
let id = doc.id();
if let Err(err) = editor.save(id, path, force) {
editor.set_error(format!("Error saving: {}", err));
}
if editor.config().auto_format {
let doc = doc!(editor, &doc_id);
if let Some(fmt) = doc.auto_format() {
format_callback(doc.id(), doc.version(), view_id, fmt.await, &mut editor);
}
}));
}

if let Err(err) = editor.save::<PathBuf>(doc_id, path, force) {
editor.set_error(format!("Error saving: {}", err));
}
}

pub async fn make_on_save_callback(
doc_id: DocumentId,
view_id: ViewId,
path: Option<PathBuf>,
force: bool,
) -> anyhow::Result<job::Callback> {
let call: job::Callback = Callback::OnSave(Box::new({
OnSaveCallbackData {
doc_id,
view_id,
path,
force,
}
}));
Ok(call)
}

Expand Down
Loading

0 comments on commit e40d16e

Please sign in to comment.