T17 round 2 fixes: merge keeps the newest caret in the lead by where it was given, and a composition cleared to nothing ends

This commit is contained in:
ZacharyZhang-NY committed 2026-09-26 01:16:08 -04:00
1 parent 8ea952c6dc
commit 40bce9b945
4 files changed
+46 -21

No files matched your search

+8 -7
View File
@@ -61,9 +61,9 @@ Per-component lines live in `tasks/`. Tags there:
- [x] T16d Finance · Trading
- [x] T16e Finance · Markets & assets
- [ ] T17a Editor · Core
- [ ] T17b Editor · Intelligence
- [ ] T17c Editor · Search
- [ ] T17d Editor · Panels
- [x] T17b Editor · Intelligence
- [x] T17c Editor · Search
- [x] T17d Editor · Panels
- [x] T17e Editor · Status items
- [ ] T18 Terminal
- [ ] T19 Git
@@ -230,11 +230,12 @@ Per-component lines live in `tasks/`. Tags there:
| T16e | 1 | FAIL | a smaller loss than expected read as a miss |
| T16e | 2 | PASS | |
| T17a | 1 | FAIL | hidden numbers crashed the gutter; an edit with nothing to change tripped an assert; a read-only editor moved its caret and undid; a hint past a multi-line ghost fell off its row; the newest cursor lost the lead on merge; a slow composition left its pinyin in undo; gpui tests broke a featureless test build |
| T17a | 2 | — | pending review |
| T17a | 2 | FAIL | a selection touching the newest caret took the lead; a composition cleared to nothing stayed open and kept later edits out of undo |
| T17a | 3 | — | pending review |
| T17b | 1 | FAIL | two calls of one name shared a tree key; references counted lines, not matches |
| T17b | 2 | — | pending review |
| T17b | 2 | PASS | |
| T17c | 1 | FAIL | a match on trailing spaces ran past its trimmed line; `**/` needed a folder |
| T17c | 2 | — | pending review |
| T17c | 2 | PASS | |
| T17d | 1 | FAIL | a path match marked a project name mid-character; pin and remove also opened the row; number settings showed no places |
| T17d | 2 | — | pending review |
| T17d | 2 | PASS | |
| T17e | 1 | PASS | |
+23 -14
View File
@@ -38,14 +38,14 @@ impl Selection {
}
/// Sorted by where they start, overlapping selections joined into one; the last given, the primary, stays last.
pub(crate) fn merged(mut all: Vec<Selection>) -> Vec<Selection> {
let primary = all
.last()
.expect("an editor keeps at least one cursor")
.range();
all.sort_by_key(|selection| selection.range().start);
pub(crate) fn merged(all: Vec<Selection>) -> Vec<Selection> {
assert!(!all.is_empty(), "an editor keeps at least one cursor");
let primary = all.len() - 1;
let mut all: Vec<(usize, Selection)> = all.into_iter().enumerate().collect();
all.sort_by_key(|(_, selection)| selection.range().start);
let mut out: Vec<Selection> = Vec::with_capacity(all.len());
for next in all {
let mut lead = 0;
for (given, next) in all {
match out.last_mut() {
Some(last)
if next.range().start < last.range().end
@@ -61,14 +61,10 @@ pub(crate) fn merged(mut all: Vec<Selection>) -> Vec<Selection> {
}
_ => out.push(next),
}
if given == primary {
lead = out.len() - 1;
}
}
let lead = out
.iter()
.position(|selection| {
let range = selection.range();
range.start <= primary.start && primary.end <= range.end
})
.expect("a merge keeps every cursor inside one");
let lead = out.remove(lead);
out.push(lead);
out
@@ -174,6 +170,19 @@ pub(crate) fn replace_each(
mod tests {
use super::*;
#[test]
fn the_last_cursor_given_leads_even_beside_a_touching_selection() {
let all = merged(vec![
Selection {
anchor: 0,
head: 1,
goal: None,
},
Selection::caret(1),
]);
assert_eq!(all.last(), Some(&Selection::caret(1)));
}
#[test]
fn overlapping_selections_join_and_carets_on_one_spot_become_one() {
let joined = merged(vec![
+3
View File
@@ -182,6 +182,9 @@ impl EntityInputHandler for CodeEditor {
}
self.apply(vec![(range.clone(), text.to_string())], true, cx);
self.marked = (!text.is_empty()).then(|| range.start..range.start + text.len());
if self.marked.is_none() {
self.end_composition();
}
let caret = match selected_utf16 {
Some(inner) => range.start + from_utf16(text, inner.end),
None => range.start + text.len(),
+12
View File
@@ -194,6 +194,18 @@ fn a_slow_composition_undoes_in_one_step(cx: &mut TestAppContext) {
});
}
#[gpui::test]
fn a_composition_cleared_to_nothing_ends(cx: &mut TestAppContext) {
let (editor, cx) = editor("", cx);
editor.update_in(cx, |editor, window, cx| {
editor.replace_and_mark_text_in_range(None, "n", None, window, cx);
editor.replace_and_mark_text_in_range(None, "", None, window, cx);
editor.type_text("x", cx);
editor.undo(cx);
assert_eq!(editor.text(), "", "typing after the cleared mark undoes");
});
}
impl CodeEditor {
/// Whether line `line` has a row now.
fn frame_shows(&self, line: usize) -> bool {