diff --git a/helix-core/src/state.rs b/helix-core/src/state.rs index 90e75eb4..55ca0673 100644 --- a/helix-core/src/state.rs +++ b/helix-core/src/state.rs @@ -292,7 +292,8 @@ fn categorize(ch: char) -> Category { } } -fn skip_over_next(slice: RopeSlice, pos: &mut usize, fun: F) +#[inline] +pub fn skip_over_next(slice: RopeSlice, pos: &mut usize, fun: F) where F: Fn(char) -> bool, { @@ -306,7 +307,8 @@ where } } -fn skip_over_prev(slice: RopeSlice, pos: &mut usize, fun: F) +#[inline] +pub fn skip_over_prev(slice: RopeSlice, pos: &mut usize, fun: F) where F: Fn(char) -> bool, { diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 898981ae..07e71a70 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -308,6 +308,7 @@ pub fn select_regex(cx: &mut Context) { let prompt = ui::regex_prompt(cx, "select:".to_string(), |doc, regex| { let text = doc.text().slice(..); // TODO: if select on matches returns empty range, we need to abort + // if regex empty or no matches, return let selection = selection::select_on_matches(text, doc.selection(), ®ex).expect("no matches"); doc.set_selection(selection); @@ -929,6 +930,50 @@ pub fn format_selections(cx: &mut Context) { doc.append_changes_to_history(); } +pub fn join_selections(cx: &mut Context) { + use helix_core::state::skip_over_next; + let doc = cx.doc(); + let text = doc.text(); + let slice = doc.text().slice(..); + + let mut changes = Vec::new(); + let fragment = Tendril::from(" "); + + for selection in doc.selection().ranges() { + let start = text.char_to_line(selection.from()); + let mut end = text.char_to_line(selection.to()); + if start == end { + end += 1 + } + let lines = start..end; + + changes.reserve(lines.len()); + + for line in lines { + let mut start = text.line_to_char(line + 1).saturating_sub(1); + let mut end = start + 1; + skip_over_next(slice, &mut end, |ch| matches!(ch, ' ' | '\t')); + + // need to skip from start, not end + let change = (start, end, Some(fragment.clone())); + changes.push(change); + } + } + + changes.sort_unstable_by_key(|(from, _to, _text)| *from); + changes.dedup(); + + // TODO: joining multiple empty lines should be replaced by a single space. + // need to merge change ranges that touch + + let transaction = Transaction::change(&doc.state, changes.into_iter()); + // TODO: select inserted spaces + // .with_selection(selection); + + doc.apply(&transaction); + doc.append_changes_to_history(); +} + // pub fn save(cx: &mut Context) { diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 589740fc..e684a9ff 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -138,28 +138,35 @@ pub fn default() -> Keymaps { vec![key!('h')] => commands::move_char_left as Command, vec![key!('j')] => commands::move_line_down, vec![key!('k')] => commands::move_line_up, + vec![key!('l')] => commands::move_char_right, + vec![key!('0')] => commands::move_line_start, vec![key!('$')] => commands::move_line_end, - vec![key!('l')] => commands::move_char_right, + vec![shift!('H')] => commands::extend_char_left, vec![shift!('J')] => commands::extend_line_down, vec![shift!('K')] => commands::extend_line_up, vec![shift!('L')] => commands::extend_char_right, + vec![key!('w')] => commands::move_next_word_start, vec![shift!('W')] => commands::extend_next_word_start, vec![key!('b')] => commands::move_prev_word_start, vec![shift!('B')] => commands::extend_prev_word_start, vec![key!('e')] => commands::move_next_word_end, vec![key!('E')] => commands::extend_next_word_end, - // TODO: E + vec![key!('g')] => commands::goto_mode, + vec![key!(':')] => commands::command_mode, + vec![key!('i')] => commands::insert_mode, vec![shift!('I')] => commands::prepend_to_line, vec![key!('a')] => commands::append_mode, vec![shift!('A')] => commands::append_to_line, vec![key!('o')] => commands::open_below, + vec![key!('d')] => commands::delete_selection, vec![key!('c')] => commands::change_selection, + vec![key!('s')] => commands::select_regex, vec![alt!('s')] => commands::split_selection_on_newline, vec![shift!('S')] => commands::split_selection, @@ -167,19 +174,24 @@ pub fn default() -> Keymaps { vec![alt!(';')] => commands::flip_selections, vec![key!('%')] => commands::select_all, vec![key!('x')] => commands::select_line, + // TODO: figure out what key to use vec![key!('[')] => commands::expand_selection, + vec![key!('/')] => commands::search, vec![key!('n')] => commands::search_next, vec![key!('*')] => commands::search_selection, + vec![key!('u')] => commands::undo, vec![shift!('U')] => commands::redo, + vec![key!('y')] => commands::yank, vec![key!('p')] => commands::paste, + vec![key!('>')] => commands::indent, vec![key!('<')] => commands::unindent, vec![key!('=')] => commands::format_selections, - vec![key!(':')] => commands::command_mode, + vec![ctrl!('j')] => commands::join_selections, vec![Key { code: KeyCode::Esc, modifiers: Modifiers::NONE