Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-term/src/commands.rs')
| -rw-r--r-- | helix-term/src/commands.rs | 301 |
1 files changed, 105 insertions, 196 deletions
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 430d4430..9fbb61be 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1,6 +1,5 @@ pub(crate) mod dap; pub(crate) mod lsp; -pub(crate) mod syntax; pub(crate) mod typed; pub use dap::*; @@ -12,7 +11,6 @@ use helix_stdx::{ }; use helix_vcs::{FileChange, Hunk}; pub use lsp::*; -pub use syntax::*; use tui::{ text::{Span, Spans}, widgets::Cell, @@ -22,8 +20,7 @@ pub use typed::*; use helix_core::{ char_idx_at_visual_offset, chars::char_is_word, - command_line::{self, Args}, - comment, + command_line, comment, doc_formatter::TextFormat, encoding, find_workspace, graphemes::{self, next_grapheme_boundary}, @@ -47,7 +44,6 @@ use helix_core::{ use helix_view::{ document::{FormatterError, Mode, SCRATCH_BUFFER_NAME}, editor::Action, - expansion, info::Info, input::KeyEvent, keyboard::KeyCode, @@ -409,13 +405,9 @@ impl MappableCommand { buffer_picker, "Open buffer picker", jumplist_picker, "Open jumplist picker", symbol_picker, "Open symbol picker", - syntax_symbol_picker, "Open symbol picker from syntax information", - lsp_or_syntax_symbol_picker, "Open symbol picker from LSP or syntax information", changed_file_picker, "Open changed file picker", select_references_to_symbol_under_cursor, "Select symbol references", workspace_symbol_picker, "Open workspace symbol picker", - syntax_workspace_symbol_picker, "Open workspace symbol picker from syntax information", - lsp_or_syntax_workspace_symbol_picker, "Open workspace symbol picker from LSP or syntax information", diagnostics_picker, "Open diagnostic picker", workspace_diagnostics_picker, "Open workspace diagnostic picker", last_picker, "Open last picker", @@ -474,8 +466,6 @@ impl MappableCommand { smart_tab, "Insert tab if all cursors have all whitespace to their left; otherwise, run a separate command.", insert_tab, "Insert tab char", insert_newline, "Insert newline char", - insert_char_interactive, "Insert an interactively-chosen char", - append_char_interactive, "Append an interactively-chosen char", delete_char_backward, "Delete previous char", delete_char_forward, "Delete next char", delete_word_backward, "Delete previous word", @@ -575,8 +565,6 @@ impl MappableCommand { goto_prev_comment, "Goto previous comment", goto_next_test, "Goto next test", goto_prev_test, "Goto previous test", - goto_next_xml_element, "Goto next (X)HTML element", - goto_prev_xml_element, "Goto previous (X)HTML element", goto_next_entry, "Goto next pairing", goto_prev_entry, "Goto previous pairing", goto_next_paragraph, "Goto next paragraph", @@ -3201,11 +3189,9 @@ fn buffer_picker(cx: &mut Context) { .into() }), ]; - let initial_cursor = if items.len() <= 1 { 0 } else { 1 }; let picker = Picker::new(columns, 2, items, (), |cx, meta, action| { cx.editor.switch(meta.id, action); }) - .with_initial_cursor(initial_cursor) .with_preview(|editor, meta| { let doc = &editor.documents.get(&meta.id)?; let lines = doc.selections().values().next().map(|selection| { @@ -3740,13 +3726,11 @@ fn open(cx: &mut Context, open: Open, comment_continuation: CommentContinuation) .map(|token| token.len() + 1) // `+ 1` for the extra space added .unwrap_or_default(); for i in 0..count { - // pos -> beginning of reference line, - // + (i * (line_ending_len + indent_len + comment_len)) -> beginning of i'th line from pos (possibly including comment token) + // pos -> beginning of reference line, + // + (i * (1+indent_len + comment_len)) -> beginning of i'th line from pos (possibly including comment token) // + indent_len + comment_len -> -> indent for i'th line ranges.push(Range::point( - pos + (i * (doc.line_ending.len_chars() + indent_len + comment_len)) - + indent_len - + comment_len, + pos + (i * (1 + indent_len + comment_len)) + indent_len + comment_len, )); } @@ -3870,7 +3854,6 @@ fn goto_column_impl(cx: &mut Context, movement: Movement) { let pos = graphemes::nth_next_grapheme_boundary(text, line_start, count - 1).min(line_end); range.put_cursor(text, pos, movement == Movement::Extend) }); - push_jump(view, doc); doc.set_selection(view.id, selection); } @@ -3892,7 +3875,6 @@ fn goto_last_modification(cx: &mut Context) { .selection(view.id) .clone() .transform(|range| range.put_cursor(text, pos, cx.editor.mode == Mode::Select)); - push_jump(view, doc); doc.set_selection(view.id, selection); } } @@ -3944,7 +3926,6 @@ fn goto_first_diag(cx: &mut Context) { Some(diag) => Selection::single(diag.range.start, diag.range.end), None => return, }; - push_jump(view, doc); doc.set_selection(view.id, selection); view.diagnostics_handler .immediately_show_diagnostic(doc, view.id); @@ -3956,7 +3937,6 @@ fn goto_last_diag(cx: &mut Context) { Some(diag) => Selection::single(diag.range.start, diag.range.end), None => return, }; - push_jump(view, doc); doc.set_selection(view.id, selection); view.diagnostics_handler .immediately_show_diagnostic(doc, view.id); @@ -3980,7 +3960,6 @@ fn goto_next_diag(cx: &mut Context) { Some(diag) => Selection::single(diag.range.start, diag.range.end), None => return, }; - push_jump(view, doc); doc.set_selection(view.id, selection); view.diagnostics_handler .immediately_show_diagnostic(doc, view.id); @@ -4010,7 +3989,6 @@ fn goto_prev_diag(cx: &mut Context) { Some(diag) => Selection::single(diag.range.end, diag.range.start), None => return, }; - push_jump(view, doc); doc.set_selection(view.id, selection); view.diagnostics_handler .immediately_show_diagnostic(doc, view.id); @@ -4041,7 +4019,6 @@ fn goto_first_change_impl(cx: &mut Context, reverse: bool) { }; if hunk != Hunk::NONE { let range = hunk_range(hunk, doc.text().slice(..)); - push_jump(view, doc); doc.set_selection(view.id, Selection::single(range.anchor, range.head)); } } @@ -4097,7 +4074,6 @@ fn goto_next_change_impl(cx: &mut Context, direction: Direction) { } }); - push_jump(view, doc); doc.set_selection(view.id, selection) }; cx.editor.apply_motion(motion); @@ -4118,7 +4094,7 @@ fn hunk_range(hunk: Hunk, text: RopeSlice) -> Range { } pub mod insert { - use crate::{events::PostInsertChar, key}; + use crate::events::PostInsertChar; use super::*; pub type Hook = fn(&Rope, &Selection, char) -> Option<Transaction>; @@ -4197,15 +4173,11 @@ pub mod insert { } pub fn insert_tab(cx: &mut Context) { - insert_tab_impl(cx, 1) - } - - fn insert_tab_impl(cx: &mut Context, count: usize) { let (view, doc) = current!(cx.editor); // TODO: round out to nearest indentation level (for example a line with 3 spaces should // indent by one to reach 4 spaces). - let indent = Tendril::from(doc.indent_style.as_str().repeat(count)); + let indent = Tendril::from(doc.indent_style.as_str()); let transaction = Transaction::insert( doc.text(), &doc.selection(view.id).clone().cursors(doc.text().slice(..)), @@ -4214,49 +4186,6 @@ pub mod insert { doc.apply(&transaction, view.id); } - pub fn append_char_interactive(cx: &mut Context) { - // Save the current mode, so we can restore it later. - let mode = cx.editor.mode; - append_mode(cx); - insert_selection_interactive(cx, mode); - } - - pub fn insert_char_interactive(cx: &mut Context) { - let mode = cx.editor.mode; - insert_mode(cx); - insert_selection_interactive(cx, mode); - } - - fn insert_selection_interactive(cx: &mut Context, old_mode: Mode) { - let count = cx.count(); - - // need to wait for next key - cx.on_next_key(move |cx, event| { - match event { - KeyEvent { - code: KeyCode::Char(ch), - .. - } => { - for _ in 0..count { - insert::insert_char(cx, ch) - } - } - key!(Enter) => { - if count != 1 { - cx.editor - .set_error("inserting multiple newlines not yet supported"); - return; - } - insert_newline(cx) - } - key!(Tab) => insert_tab_impl(cx, count), - _ => (), - }; - // Restore the old mode. - cx.editor.mode = old_mode; - }); - } - pub fn insert_newline(cx: &mut Context) { let config = cx.editor.config(); let (view, doc) = current_ref!(cx.editor); @@ -5380,7 +5309,6 @@ fn rotate_selections_last(cx: &mut Context) { doc.set_selection(view.id, selection); } -#[derive(Debug)] enum ReorderStrategy { RotateForward, RotateBackward, @@ -5393,50 +5321,34 @@ fn reorder_selection_contents(cx: &mut Context, strategy: ReorderStrategy) { let text = doc.text().slice(..); let selection = doc.selection(view.id); - - let mut ranges: Vec<_> = selection + let mut fragments: Vec<_> = selection .slices(text) .map(|fragment| fragment.chunks().collect()) .collect(); - let rotate_by = count.map_or(1, |count| count.get().min(ranges.len())); - - let primary_index = match strategy { - ReorderStrategy::RotateForward => { - ranges.rotate_right(rotate_by); - // Like `usize::wrapping_add`, but provide a custom range from `0` to `ranges.len()` - (selection.primary_index() + ranges.len() + rotate_by) % ranges.len() - } - ReorderStrategy::RotateBackward => { - ranges.rotate_left(rotate_by); - // Like `usize::wrapping_sub`, but provide a custom range from `0` to `ranges.len()` - (selection.primary_index() + ranges.len() - rotate_by) % ranges.len() - } - ReorderStrategy::Reverse => { - if rotate_by % 2 == 0 { - // nothing changed, if we reverse something an even - // amount of times, the output will be the same - return; - } - ranges.reverse(); - // -1 to turn 1-based len into 0-based index - (ranges.len() - 1) - selection.primary_index() - } - }; + let group = count + .map(|count| count.get()) + .unwrap_or(fragments.len()) // default to rotating everything as one group + .min(fragments.len()); + + for chunk in fragments.chunks_mut(group) { + // TODO: also modify main index + match strategy { + ReorderStrategy::RotateForward => chunk.rotate_right(1), + ReorderStrategy::RotateBackward => chunk.rotate_left(1), + ReorderStrategy::Reverse => chunk.reverse(), + }; + } let transaction = Transaction::change( doc.text(), selection .ranges() .iter() - .zip(ranges) + .zip(fragments) .map(|(range, fragment)| (range.from(), range.to(), Some(fragment))), ); - doc.set_selection( - view.id, - Selection::new(selection.ranges().into(), primary_index), - ); doc.apply(&transaction, view.id); } @@ -5920,7 +5832,6 @@ fn goto_ts_object_impl(cx: &mut Context, object: &'static str, direction: Direct } }); - push_jump(view, doc); doc.set_selection(view.id, selection); } else { editor.set_status("Syntax-tree is not available in current buffer"); @@ -5969,14 +5880,6 @@ fn goto_prev_test(cx: &mut Context) { goto_ts_object_impl(cx, "test", Direction::Backward) } -fn goto_next_xml_element(cx: &mut Context) { - goto_ts_object_impl(cx, "xml-element", Direction::Forward) -} - -fn goto_prev_xml_element(cx: &mut Context) { - goto_ts_object_impl(cx, "xml-element", Direction::Backward) -} - fn goto_next_entry(cx: &mut Context) { goto_ts_object_impl(cx, "entry", Direction::Forward) } @@ -6044,7 +5947,6 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) { 'c' => textobject_treesitter("comment", range), 'T' => textobject_treesitter("test", range), 'e' => textobject_treesitter("entry", range), - 'x' => textobject_treesitter("xml-element", range), 'p' => textobject::textobject_paragraph(text, range, objtype, count), 'm' => textobject::textobject_pair_surround_closest( doc.syntax(), @@ -6089,7 +5991,6 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) { ("e", "Data structure entry (tree-sitter)"), ("m", "Closest surrounding pair (tree-sitter)"), ("g", "Change"), - ("x", "(X)HTML element (tree-sitter)"), (" ", "... or any character acting as a pair"), ]; @@ -6267,52 +6168,64 @@ enum ShellBehavior { } fn shell_pipe(cx: &mut Context) { - shell_prompt_for_behavior(cx, "pipe:".into(), ShellBehavior::Replace); + shell_prompt(cx, "pipe:".into(), ShellBehavior::Replace); } fn shell_pipe_to(cx: &mut Context) { - shell_prompt_for_behavior(cx, "pipe-to:".into(), ShellBehavior::Ignore); + shell_prompt(cx, "pipe-to:".into(), ShellBehavior::Ignore); } fn shell_insert_output(cx: &mut Context) { - shell_prompt_for_behavior(cx, "insert-output:".into(), ShellBehavior::Insert); + shell_prompt(cx, "insert-output:".into(), ShellBehavior::Insert); } fn shell_append_output(cx: &mut Context) { - shell_prompt_for_behavior(cx, "append-output:".into(), ShellBehavior::Append); + shell_prompt(cx, "append-output:".into(), ShellBehavior::Append); } fn shell_keep_pipe(cx: &mut Context) { - shell_prompt(cx, "keep-pipe:".into(), |cx, args| { - let shell = &cx.editor.config().shell; - let (view, doc) = current!(cx.editor); - let selection = doc.selection(view.id); + ui::prompt( + cx, + "keep-pipe:".into(), + Some('|'), + ui::completers::none, + move |cx, input: &str, event: PromptEvent| { + let shell = &cx.editor.config().shell; + if event != PromptEvent::Validate { + return; + } + if input.is_empty() { + return; + } + let (view, doc) = current!(cx.editor); + let selection = doc.selection(view.id); - let mut ranges = SmallVec::with_capacity(selection.len()); - let old_index = selection.primary_index(); - let mut index: Option<usize> = None; - let text = doc.text().slice(..); + let mut ranges = SmallVec::with_capacity(selection.len()); + let old_index = selection.primary_index(); + let mut index: Option<usize> = None; + let text = doc.text().slice(..); - for (i, range) in selection.ranges().iter().enumerate() { - let fragment = range.slice(text); - if let Err(err) = shell_impl(shell, args.join(" ").as_str(), Some(fragment.into())) { - log::debug!("Shell command failed: {}", err); - } else { - ranges.push(*range); - if i >= old_index && index.is_none() { - index = Some(ranges.len() - 1); + for (i, range) in selection.ranges().iter().enumerate() { + let fragment = range.slice(text); + if let Err(err) = shell_impl(shell, input, Some(fragment.into())) { + log::debug!("Shell command failed: {}", err); + } else { + ranges.push(*range); + if i >= old_index && index.is_none() { + index = Some(ranges.len() - 1); + } } } - } - if ranges.is_empty() { - cx.editor.set_error("No selections remaining"); - return; - } + if ranges.is_empty() { + cx.editor.set_error("No selections remaining"); + return; + } - let index = index.unwrap_or_else(|| ranges.len() - 1); - doc.set_selection(view.id, Selection::new(ranges, index)); - }); + let index = index.unwrap_or_else(|| ranges.len() - 1); + doc.set_selection(view.id, Selection::new(ranges, index)); + }, + ); } fn shell_impl(shell: &[String], cmd: &str, input: Option<Rope>) -> anyhow::Result<Tendril> { @@ -6467,35 +6380,25 @@ fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) { view.ensure_cursor_in_view(doc, config.scrolloff); } -fn shell_prompt<F>(cx: &mut Context, prompt: Cow<'static, str>, mut callback_fn: F) -where - F: FnMut(&mut compositor::Context, Args) + 'static, -{ +fn shell_prompt(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) { ui::prompt( cx, prompt, Some('|'), - |editor, input| complete_command_args(editor, SHELL_SIGNATURE, &SHELL_COMPLETER, input, 0), - move |cx, input, event| { - if event != PromptEvent::Validate || input.is_empty() { + ui::completers::shell, + move |cx, input: &str, event: PromptEvent| { + if event != PromptEvent::Validate { return; } - match Args::parse(input, SHELL_SIGNATURE, true, |token| { - expansion::expand(cx.editor, token).map_err(|err| err.into()) - }) { - Ok(args) => callback_fn(cx, args), - Err(err) => cx.editor.set_error(err.to_string()), + if input.is_empty() { + return; } + + shell(cx, input, &behavior); }, ); } -fn shell_prompt_for_behavior(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) { - shell_prompt(cx, prompt, move |cx, args| { - shell(cx, args.join(" ").as_str(), &behavior) - }) -} - fn suspend(_cx: &mut Context) { #[cfg(not(windows))] { @@ -6825,10 +6728,6 @@ fn jump_to_word(cx: &mut Context, behaviour: Movement) { // Calculate the jump candidates: ranges for any visible words with two or // more characters. let alphabet = &cx.editor.config().jump_label_alphabet; - if alphabet.is_empty() { - return; - } - let jump_label_limit = alphabet.len() * alphabet.len(); let mut words = Vec::with_capacity(jump_label_limit); let (view, doc) = current_ref!(cx.editor); @@ -6919,33 +6818,43 @@ fn jump_to_word(cx: &mut Context, behaviour: Movement) { jump_to_label(cx, words, behaviour) } -fn lsp_or_syntax_symbol_picker(cx: &mut Context) { - let doc = doc!(cx.editor); - - if doc - .language_servers_with_feature(LanguageServerFeature::DocumentSymbols) - .next() - .is_some() - { - lsp::symbol_picker(cx); - } else if doc.syntax().is_some() { - syntax_symbol_picker(cx); - } else { - cx.editor - .set_error("No language server supporting document symbols or syntax info available"); +pub fn code_action(cx: &mut Context) { + impl ui::menu::Item for helix_view::Action { + type Data = (); + fn format(&self, _data: &Self::Data) -> ui::menu::Row { + self.title().into() + } } -} -fn lsp_or_syntax_workspace_symbol_picker(cx: &mut Context) { - let doc = doc!(cx.editor); + let Some(future) = cx.editor.actions() else { + cx.editor.set_error("No code actions available"); + return; + }; - if doc - .language_servers_with_feature(LanguageServerFeature::WorkspaceSymbols) - .next() - .is_some() - { - lsp::workspace_symbol_picker(cx); - } else { - syntax_workspace_symbol_picker(cx); - } + cx.jobs.callback(async move { + let actions = future.await; + + let call = move |editor: &mut Editor, compositor: &mut Compositor| { + if actions.is_empty() { + editor.set_error("No code actions available"); + return; + } + let mut picker = ui::Menu::new(actions, (), move |editor, action, event| { + if event != PromptEvent::Validate { + return; + } + + // always present here + let action = action.unwrap(); + action.execute(editor); + }); + picker.move_down(); // pre-select the first item + + let popup = Popup::new("code-action", picker).with_scrollbar(false); + + compositor.replace_or_push("code-action", popup); + }; + + Ok(Callback::EditorCompositor(Box::new(call))) + }); } |