Skip to content

Commit

Permalink
Implement indent-aware delete (#1120)
Browse files Browse the repository at this point in the history
* delete character backward can make undent behavior

* improve to handle mixed indentation
  • Loading branch information
WindSoilder authored Nov 18, 2021
1 parent bd56dde commit 5959356
Showing 1 changed file with 61 additions and 9 deletions.
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

0 comments on commit 5959356

Please sign in to comment.