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

Implement indent-aware delete #1120

Merged
merged 2 commits into from
Nov 18, 2021
Merged
Changes from all 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
70 changes: 61 additions & 9 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ use helix_core::{
object, pos_at_coords,
regex::{self, Regex, RegexBuilder},
register::Register,
search, selection, surround, textobject, LineEnding, Position, Range, Rope, RopeGraphemes,
RopeSlice, Selection, SmallVec, Tendril, Transaction,
search, selection, surround, textobject,
unicode::width::UnicodeWidthChar,
LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, SmallVec, Tendril,
Transaction,
};

use helix_view::{
clipboard::ClipboardType,
document::{Mode, SCRATCH_BUFFER_NAME},
Expand Down Expand Up @@ -4014,19 +4015,70 @@ pub mod insert {
doc.apply(&transaction, view.id);
}

// TODO: handle indent-aware delete
pub fn delete_char_backward(cx: &mut Context) {
let count = cx.count();
let (view, doc) = current!(cx.editor);
let text = doc.text().slice(..);
let indent_unit = doc.indent_unit();
let tab_size = doc.tab_width();

let transaction =
Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
let pos = range.cursor(text);
(
graphemes::nth_prev_grapheme_boundary(text, pos, count),
pos,
None,
)
let line_start_pos = text.line_to_char(range.cursor_line(text));
// considier to delete by indent level if all characters before `pos` are indent units.
let fragment = Cow::from(text.slice(line_start_pos..pos));
if !fragment.is_empty() && fragment.chars().all(|ch| ch.is_whitespace()) {
if text.get_char(pos.saturating_sub(1)) == Some('\t') {
// fast path, delete one char
(
graphemes::nth_prev_grapheme_boundary(text, pos, 1),
pos,
None,
)
} else {
let unit_len = indent_unit.chars().count();
// NOTE: indent_unit always contains 'only spaces' or 'only tab' according to `IndentStyle` definition.
let unit_size = if indent_unit.starts_with('\t') {
tab_size * unit_len
} else {
unit_len
};
let width: usize = fragment
.chars()
.map(|ch| {
if ch == '\t' {
tab_size
} else {
// it can be none if it still meet control characters other than '\t'
// here just set the width to 1 (or some value better?).
ch.width().unwrap_or(1)
}
})
.sum();
let mut drop = width % unit_size; // round down to nearest unit
if drop == 0 {
drop = unit_size
}; // if it's already at a unit, consume a whole unit
let mut chars = fragment.chars().rev();
let mut start = pos;
for _ in 0..drop {
// delete up to `drop` spaces
match chars.next() {
Some(' ') => start -= 1,
_ => break,
}
}
(start, pos, None) // delete!
}
} else {
// delete char
(
graphemes::nth_prev_grapheme_boundary(text, pos, count),
pos,
None,
)
}
});
doc.apply(&transaction, view.id);
}
Expand Down