Skip to content
Merged
Show file tree
Hide file tree
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
64 changes: 58 additions & 6 deletions crates/edit/src/buffer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,12 +201,14 @@ struct ActiveEditGroupInfo {
}

/// Char- or word-wise navigation? Your choice.
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum CursorMovement {
Grapheme,
Word,
}

/// See [`TextBuffer::move_selected_lines`].
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum MoveLineDirection {
Up,
Down,
Expand Down Expand Up @@ -2580,7 +2582,7 @@ impl TextBuffer {
let mut chars = 0;
let mut columns = 0;

'outer: loop {
'outer: while columns < max_columns {
let chunk = self.read_forward(offset);
if chunk.is_empty() {
break;
Expand All @@ -2600,15 +2602,65 @@ impl TextBuffer {
}

offset += chunk.len();
}

// No need to do another round if we
// already got the exact right amount.
if columns >= max_columns {
break;
(chars, columns)
}

/// This is basically the backspace operation, the way editors typically want it:
/// It unindents the line if the cursor is within the leading indentation.
pub fn backspace_with_auto_unindent(&mut self, granularity: CursorMovement) {
'unindent: {
// If there's a selection backspace deletes it.
if self.selection.is_some() {
break 'unindent;
}
Comment on lines +2614 to +2617

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If you squint, the approach is identical to your previous version above (in commit 2a84ff4).


// If we're at a line start backspace deletes the newline.
if self.cursor.logical_pos.x <= 0 {
break 'unindent;
}

let line_start = self.goto_line_start(self.cursor, self.cursor.logical_pos.y);

// Determine the position of the new (reduced) indentation.
// For Backspace (Grapheme) it's one "tab", but for Ctrl+Backspace (Word) it's to the line start.
let prev_column = if granularity == CursorMovement::Grapheme {
self.tab_size_prev_column(self.cursor.column)
} else {
0 // Ctrl+Backspace (Word) = line start

@lhecker Leonard Hecker (lhecker) Sep 17, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Oooh... right and I added Ctrl+Backspace support! I'll edit my summary above.

};
let (from_pos, from_col) = self.measure_indent_internal(line_start.offset, prev_column);

// Check if the cursor is within the leading indentation.
// This continues the measurement where we left off, so there's some extra arithmetic involved.
let (delta, _) = self.measure_indent_internal(
line_start.offset + from_pos as usize,
self.cursor.column - from_col,
Comment on lines +2638 to +2639

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Here I avoid the redundant work.

);
if delta + from_pos < self.cursor.logical_pos.x {
break 'unindent;
}

// Here would technically just do `self.delete(CursorMovement::Grapheme, -delta);`
// but since we already got the `line_start`, etc., this is a bit more straightforward.
let to = self.cursor;
let from = if granularity == CursorMovement::Grapheme {
self.cursor_move_to_logical_internal(
line_start,
Point { x: from_pos, y: line_start.logical_pos.y },
)
} else {
line_start
};
self.edit_begin(HistoryType::Delete, from);
self.edit_delete(to);
self.edit_end();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

And this is kinda complicated, pointless wankery. Technically, your call to self.delete(Grapheme, -delta) is pretty much almost as efficient (certainly a negligible difference). Maybe I'll change this in the future.

return;
}

(chars, columns)
// If we didn't perform an unindent, fall back to a regular backspace.
self.delete(granularity, -1);
}

/// Displaces the current, cursor or the selection, line(s) in the given direction.
Expand Down
6 changes: 5 additions & 1 deletion crates/edit/src/tui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2423,7 +2423,11 @@ impl<'a> Context<'a, '_> {
} else {
CursorMovement::Grapheme
};
tb.delete(granularity, -1);
if single_line {
tb.delete(granularity, -1);
} else {
tb.backspace_with_auto_unindent(granularity);
}
}
vk::TAB => {
if single_line {
Expand Down