fix(rename): stop a rename box turning typing into prepending

Rename Tab, Rename Workspace and the switcher's rename all opened with the
current name in the box and the caret at offset 0, so the first thing you
typed landed in front of it: renaming "tty7" to "build" got you "buildtty7"
until you noticed and went back. Only the file-tree rename had it right,
because it happens to fill the box with `set_value` rather than
`default_value` — and `set_value` puts the caret at the end of a
single-line input.

All three now go through one helper that does the same, with a test holding
the caret where it belongs. Selecting the name outright is what the platform
does and would be better still, but `InputState::select_all` is
`pub(super)` in the UI crate; that one wants a change over there.
This commit is contained in:
l0ng-ai
2026-08-08 18:41:14 +07:00
parent bce4a384cb
commit 04c607bd55
2 changed files with 57 additions and 6 deletions
+56 -4
View File
@@ -3346,6 +3346,27 @@ impl Tty7App {
cx.notify();
}
/// Fills a rename box with the current name and puts the caret after it.
///
/// `default_value` leaves the caret at offset 0, which turns typing into
/// prepending — renaming "tty7" reached "buildtty7" before it reached
/// "build". `set_value` lands the caret at the end of a single-line input,
/// which is where it belongs and what the file-tree rename box already
/// did. (Selecting the name outright would be better still, and is what
/// the platform does, but `InputState::select_all` is not public.)
pub(crate) fn rename_box(
current: String,
window: &mut Window,
cx: &mut Context<Self>,
) -> Entity<InputState> {
let input = cx.new(|cx| InputState::new(window, cx));
input.update(cx, |state, cx| {
state.set_value(current, window, cx);
state.focus(window, cx);
});
input
}
pub(crate) fn start_rename(
&mut self,
index: usize,
@@ -3356,8 +3377,7 @@ impl Tty7App {
return;
}
let current = self.tab_label(&self.tabs[index], index, Some(&*window), cx);
let input = cx.new(|cx| InputState::new(window, cx).default_value(current));
input.update(cx, |state, cx| state.focus(window, cx));
let input = Self::rename_box(current, window, cx);
let subs = vec![cx.subscribe_in(
&input,
window,
@@ -3377,8 +3397,7 @@ impl Tty7App {
pub(crate) fn start_workspace_rename(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let current =
crate::ui::machine_mirror::display_name_for(cx, self.workspace).unwrap_or_default();
let input = cx.new(|cx| InputState::new(window, cx).default_value(current));
input.update(cx, |state, cx| state.focus(window, cx));
let input = Self::rename_box(current, window, cx);
let subs = vec![cx.subscribe_in(
&input,
window,
@@ -7317,3 +7336,36 @@ mod shell_menu_gpui_tests {
});
}
}
#[cfg(test)]
mod rename_gpui_tests {
use gpui::TestAppContext;
use crate::ui::app::test_window::harness_with_tabs;
#[gpui::test]
fn a_rename_box_opens_with_the_caret_after_the_name(cx: &mut TestAppContext) {
let (app, mut vcx, _streams) = harness_with_tabs(cx, 1);
app.update_in(&mut vcx, |app, window, cx| app.start_rename(0, window, cx));
vcx.background_executor.run_until_parked();
app.update(&mut vcx, |app, cx| {
let input = app
.renaming
.as_ref()
.expect("the rename box is up")
.input
.clone();
let state = input.read(cx);
let value = state.value().to_string();
assert!(!value.is_empty(), "the box starts on the current name");
let end = value.len();
assert_eq!(
state.selected_range(),
end..end,
"typing has to continue {value:?}, not land in front of it"
);
});
}
}
+1 -2
View File
@@ -768,8 +768,7 @@ impl Tty7App {
fn switcher_rename(&mut self, id: WorkspaceId, window: &mut Window, cx: &mut Context<Self>) {
let current = crate::ui::machine_mirror::display_name_for(cx, id).unwrap_or_default();
let input = cx.new(|cx| InputState::new(window, cx).default_value(current));
input.update(cx, |state, cx| state.focus(window, cx));
let input = Self::rename_box(current, window, cx);
let sub = cx.subscribe_in(
&input,
window,