aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWindSoilder2021-11-18 15:19:40 +0000
committerGitHub2021-11-18 15:19:40 +0000
commit5959356a2404a8c317d07934ee388d6637c2888a (patch)
treea05696e5685e47eaadcc3a1fe392c01648040eec
parentbd56dde6e28b22b661ad991d0f23b66e089a9700 (diff)
Implement indent-aware delete (#1120)
* delete character backward can make undent behavior * improve to handle mixed indentation
-rw-r--r--helix-term/src/commands.rs70
1 files changed, 61 insertions, 9 deletions
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index e1120ef1..e5cf7bb2 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -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},
@@ -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);
}