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.rs301
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)))
+ });
}