Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

align selections #1101

Merged
merged 9 commits into from
Nov 23, 2021
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions book/src/keymap.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,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` |
Expand Down
66 changes: 66 additions & 0 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,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",
Expand Down Expand Up @@ -625,6 +626,71 @@ 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(),
);
}
sudormrfbin marked this conversation as resolved.
Show resolved Hide resolved

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;
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;
}
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 {
column_widths.push(sel.to() - sel.from());
}
}
last_line = text.len_lines();
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;
pickfire marked this conversation as resolved.
Show resolved Hide resolved

(
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 {
2 => s.insert_str(s.len() / 2, trimed), // center align
3 => s.push_str(trimed), // right align
1 => s.insert_str(0, trimed), // left align
Copy link
Contributor

@pickfire pickfire Nov 20, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
2 => s.insert_str(s.len() / 2, trimed), // center align
3 => s.push_str(trimed), // right align
1 => s.insert_str(0, trimed), // left align
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

Also, I think it's better to create an enum or reuse an existing one (if possible) for this, so we don't need unimplemented!.

Also, I think it is better to use https://doc.rust-lang.org/std/primitive.slice.html#method.swap_with_slice swap_with_slice rather than having multiple allocations. We can just swap it twice and it should move all the whitespaces. The alternative is to use String::with_capacity and push it manually.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

swap_with_slice need two mut str or string, but the trimmed or the fragment is not own by us, to use it we have make another string, which is same thing, right?

n => unimplemented!(n),
}
s
}

fn goto_window(cx: &mut Context, align: Align) {
let (view, doc) = current!(cx.editor);

Expand Down
2 changes: 1 addition & 1 deletion helix-term/src/keymap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,7 @@ impl Default for Keymaps {
// "q" => record_macro,
// "Q" => replay_macro,

// & align selections
"&" => align_selections,
"_" => trim_selections,

"(" => rotate_selections_backward,
Expand Down