From 21143e8d22c13ead8c1835063acb518aa5a42822 Mon Sep 17 00:00:00 2001 From: Bob Date: Tue, 23 Nov 2021 22:08:05 +0800 Subject: [PATCH] Align selections via & (#1101) * align lines * remove log statement * use selections to align * fix a clippy issue * only accept 1,2,3 as user count * Update helix-term/src/commands.rs Co-authored-by: Ivan Tham * return if user count is not correct * add doc Co-authored-by: Ivan Tham --- book/src/keymap.md | 1 + helix-term/src/commands.rs | 71 ++++++++++++++++++++++++++++++++++++++ helix-term/src/keymap.rs | 2 +- 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index c88ed767..fbe77267 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -92,6 +92,7 @@ | `s` | Select all regex matches inside selections | `select_regex` | | `S` | Split selection into subselections on regex matches | `split_selection` | | `Alt-s` | Split selection on newlines | `split_selection_on_newline` | +| `&` | Align selection in columns | `align_selections` | | `_` | Trim whitespace from the selection | `trim_selections` | | `;` | Collapse selection onto a single cursor | `collapse_selection` | | `Alt-;` | Flip selection cursor and anchor | `flip_selections` | diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index fde505fd..a7179c30 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -314,6 +314,7 @@ impl Command { join_selections, "Join lines inside selection", keep_selections, "Keep selections matching regex", remove_selections, "Remove selections matching regex", + align_selections, "Align selections in column", keep_primary_selection, "Keep primary selection", remove_primary_selection, "Remove primary selection", completion, "Invoke completion popup", @@ -657,6 +658,76 @@ fn trim_selections(cx: &mut Context) { }; } +// align text in selection +fn align_selections(cx: &mut Context) { + let align_style = cx.count(); + if align_style > 3 { + cx.editor.set_error( + "align only accept 1,2,3 as count to set left/center/right align".to_string(), + ); + return; + } + + let (view, doc) = current!(cx.editor); + let text = doc.text().slice(..); + let selection = doc.selection(view.id); + let mut column_widths = vec![]; + let mut last_line = text.len_lines(); + let mut column = 0; + // first of all, we need compute all column's width, let use max width of the selections in a column + for sel in selection { + let (l1, l2) = sel.line_range(text); + if l1 != l2 { + cx.editor + .set_error("align cannot work with multi line selections".to_string()); + return; + } + // if the selection is not in the same line with last selection, we set the column to 0 + column = if l1 != last_line { 0 } else { column + 1 }; + last_line = l1; + + if column < column_widths.len() { + if sel.to() - sel.from() > column_widths[column] { + column_widths[column] = sel.to() - sel.from(); + } + } else { + // a new column, current selection width is the temp width of the column + column_widths.push(sel.to() - sel.from()); + } + } + last_line = text.len_lines(); + // once we get the with of each column, we transform each selection with to it's column width based on the align style + let transaction = Transaction::change_by_selection(doc.text(), selection, |range| { + let l = range.cursor_line(text); + column = if l != last_line { 0 } else { column + 1 }; + last_line = l; + + ( + range.from(), + range.to(), + Some( + align_fragment_to_width(&range.fragment(text), column_widths[column], align_style) + .into(), + ), + ) + }); + + doc.apply(&transaction, view.id); + doc.append_changes_to_history(view.id); +} + +fn align_fragment_to_width(fragment: &str, width: usize, align_style: usize) -> String { + let trimed = fragment.trim_matches(|c| c == ' '); + let mut s = " ".repeat(width - trimed.chars().count()); + match align_style { + 1 => s.insert_str(0, trimed), // left align + 2 => s.insert_str(s.len() / 2, trimed), // center align + 3 => s.push_str(trimed), // right align + n => unimplemented!("{}", n), + } + s +} + fn goto_window(cx: &mut Context, align: Align) { let (view, doc) = current!(cx.editor); diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index bf3b594e..7f6d0c6b 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -604,7 +604,7 @@ impl Default for Keymaps { // "q" => record_macro, // "Q" => replay_macro, - // & align selections + "&" => align_selections, "_" => trim_selections, "(" => rotate_selections_backward,