@@ -10,10 +10,11 @@ use helix_core::{
10 object, pos_at_coords,
10 object, pos_at_coords,
11 regex::{self, Regex, RegexBuilder},
11 regex::{self, Regex, RegexBuilder},
12 register::Register,
12 register::Register,
13− search, selection, surround, textobject, LineEnding, Position, Range, Rope, RopeGraphemes,
13+ search, selection, surround, textobject,
14+ unicode::width::UnicodeWidthChar,
14− RopeSlice, Selection, SmallVec, Tendril, Transaction,
15+ LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, SmallVec, Tendril,
17use helix_view::{
18use helix_view::{
18 clipboard::ClipboardType,
19 clipboard::ClipboardType,
19 document::{Mode, SCRATCH_BUFFER_NAME},
20 document::{Mode, SCRATCH_BUFFER_NAME},
@@ -4014,19 +4015,70 @@ pub mod insert {
4014 doc.apply(&transaction, view.id);
4015 doc.apply(&transaction, view.id);
4017− // TODO: handle indent-aware delete
4018 pub fn delete_char_backward(cx: &mut Context) {
4018 pub fn delete_char_backward(cx: &mut Context) {
4019 let count = cx.count();
4019 let count = cx.count();
4020 let (view, doc) = current!(cx.editor);
4020 let (view, doc) = current!(cx.editor);
4021 let text = doc.text().slice(..);
4021 let text = doc.text().slice(..);
4022+ let indent_unit = doc.indent_unit();
4023+ let tab_size = doc.tab_width();
4022 let transaction =
4025 let transaction =
4023 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
4026 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
4024 let pos = range.cursor(text);
4027 let pos = range.cursor(text);
4025− (
4028+ let line_start_pos = text.line_to_char(range.cursor_line(text));
4029+ // considier to delete by indent level if all characters before `pos` are indent units.
4030+ let fragment = Cow::from(text.slice(line_start_pos..pos));
4031+ if !fragment.is_empty() && fragment.chars().all(|ch| ch.is_whitespace()) {
4032+ if text.get_char(pos.saturating_sub(1)) == Some('\t') {
4033+ // fast path, delete one char
4026− graphemes::nth_prev_grapheme_boundary(text, pos, count),
4035+ graphemes::nth_prev_grapheme_boundary(text, pos, 1),
4040+ let unit_len = indent_unit.chars().count();
4041+ // NOTE: indent_unit always contains 'only spaces' or 'only tab' according to `IndentStyle` definition.
4042+ let unit_size = if indent_unit.starts_with('\t') {
4043+ tab_size * unit_len
4047+ let width: usize = fragment
4053+ // it can be none if it still meet control characters other than '\t'
4054+ // here just set the width to 1 (or some value better?).
4055+ ch.width().unwrap_or(1)
4059+ let mut drop = width % unit_size; // round down to nearest unit
4062+ }; // if it's already at a unit, consume a whole unit
4063+ let mut chars = fragment.chars().rev();
4064+ let mut start = pos;
4066+ // delete up to `drop` spaces
4067+ match chars.next() {
4068+ Some(' ') => start -= 1,
4072+ (start, pos, None) // delete!
4077+ graphemes::nth_prev_grapheme_boundary(text, pos, count),
4031 doc.apply(&transaction, view.id);
4083 doc.apply(&transaction, view.id);