aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-04-06 17:45:39 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-04-06 17:45:39 +0300
commit7ec046a4646e94311fced28a193ff1f10676a7a6 (patch)
treefd5f49fab133310582291fc4dd8fa31da9243c74 /src
parentc1e100be8fa15bc51f3c2e0b1edb38aa95a7cccd (diff)
fix: defer new tab creation until dialog confirm
Diffstat (limited to 'src')
-rw-r--r--src/app/input.rs98
-rw-r--r--src/app/mod.rs23
-rw-r--r--src/app/state.rs4
-rw-r--r--src/ui.rs1
4 files changed, 103 insertions, 23 deletions
diff --git a/src/app/input.rs b/src/app/input.rs
index f13ec684..b2a721a6 100644
--- a/src/app/input.rs
+++ b/src/app/input.rs
@@ -862,10 +862,7 @@ fn execute_navigate_action(state: &mut AppState, action: NavigateAction) {
state.next_workspace();
leave_navigate_mode(state);
}
- NavigateAction::NewTab => {
- state.request_new_tab = true;
- leave_navigate_mode(state);
- }
+ NavigateAction::NewTab => open_new_tab_dialog(state),
NavigateAction::RenameTab => open_rename_active_tab(state, false),
NavigateAction::PreviousTab => {
state.previous_tab();
@@ -921,6 +918,8 @@ fn open_rename_workspace(state: &mut AppState, ws_idx: usize) {
}
fn open_rename_active_tab(state: &mut AppState, replace_on_type: bool) {
+ state.creating_new_tab = false;
+ state.requested_new_tab_name = None;
if let Some(ws) = state.active.and_then(|i| state.workspaces.get(i)) {
if let Some(name) = ws.active_tab_display_name() {
state.name_input = name;
@@ -930,6 +929,19 @@ fn open_rename_active_tab(state: &mut AppState, replace_on_type: bool) {
}
}
+fn open_new_tab_dialog(state: &mut AppState) {
+ let next_tab_name = state
+ .active
+ .and_then(|i| state.workspaces.get(i))
+ .map(|ws| (ws.tabs.len() + 1).to_string())
+ .unwrap_or_else(|| "1".to_string());
+ state.creating_new_tab = true;
+ state.requested_new_tab_name = None;
+ state.name_input = next_tab_name;
+ state.name_input_replace_on_type = true;
+ state.mode = Mode::RenameTab;
+}
+
/// Return to the appropriate mode after completing a modal action.
/// Goes to Terminal if a workspace is active, otherwise Navigate.
fn leave_modal(state: &mut AppState) {
@@ -1017,21 +1029,32 @@ fn apply_rename_action(state: &mut AppState, action: ModalAction) {
} else {
state.name_input.trim().to_string()
};
- if !new_name.is_empty() {
- match state.mode {
- Mode::RenameWorkspace if !state.workspaces.is_empty() => {
+ match state.mode {
+ Mode::RenameWorkspace if !state.workspaces.is_empty() => {
+ if !new_name.is_empty() {
state.workspaces[state.selected].set_custom_name(new_name);
}
- Mode::RenameTab => {
+ }
+ Mode::RenameTab if state.creating_new_tab => {
+ state.request_new_tab = true;
+ state.requested_new_tab_name = if new_name.is_empty() {
+ None
+ } else {
+ Some(new_name)
+ };
+ }
+ Mode::RenameTab => {
+ if !new_name.is_empty() {
if let Some(ws) = state.active.and_then(|i| state.workspaces.get_mut(i)) {
if let Some(tab) = ws.active_tab_mut() {
tab.set_custom_name(new_name);
}
}
}
- _ => {}
}
+ _ => {}
}
+ state.creating_new_tab = false;
state.name_input.clear();
state.name_input_replace_on_type = false;
leave_modal(state);
@@ -1041,6 +1064,8 @@ fn apply_rename_action(state: &mut AppState, action: ModalAction) {
state.name_input_replace_on_type = false;
}
ModalAction::Cancel => {
+ state.creating_new_tab = false;
+ state.requested_new_tab_name = None;
state.name_input.clear();
state.name_input_replace_on_type = false;
leave_modal(state);
@@ -1145,8 +1170,7 @@ fn apply_context_menu_action(state: &mut AppState, menu: ContextMenuState, idx:
state.selected = ws_idx;
state.active = Some(ws_idx);
state.switch_tab(tab_idx);
- state.request_new_tab = true;
- state.mode = Mode::Terminal;
+ open_new_tab_dialog(state);
}
(ContextMenuKind::Tab { ws_idx, tab_idx }, Some("Rename")) => {
state.selected = ws_idx;
@@ -1941,8 +1965,7 @@ impl AppState {
return None;
}
if self.on_new_tab_button(mouse.column, mouse.row) {
- self.request_new_tab = true;
- self.mode = Mode::Terminal;
+ open_new_tab_dialog(self);
return None;
}
@@ -3069,6 +3092,55 @@ mod tests {
}
#[test]
+ fn new_tab_action_opens_dialog_without_creating_tab() {
+ let mut state = state_with_workspaces(&["test"]);
+
+ execute_navigate_action(&mut state, NavigateAction::NewTab);
+
+ assert_eq!(state.mode, Mode::RenameTab);
+ assert!(state.creating_new_tab);
+ assert_eq!(state.name_input, "2");
+ assert!(state.name_input_replace_on_type);
+ assert!(!state.request_new_tab);
+ assert_eq!(state.workspaces[0].tabs.len(), 1);
+ }
+
+ #[test]
+ fn cancel_new_tab_dialog_leaves_workspace_unchanged() {
+ let mut state = state_with_workspaces(&["test"]);
+ open_new_tab_dialog(&mut state);
+
+ handle_rename_key(
+ &mut state,
+ KeyEvent::new(KeyCode::Esc, KeyModifiers::empty()),
+ );
+
+ assert_eq!(state.mode, Mode::Terminal);
+ assert!(!state.creating_new_tab);
+ assert!(!state.request_new_tab);
+ assert!(state.requested_new_tab_name.is_none());
+ assert_eq!(state.workspaces[0].tabs.len(), 1);
+ }
+
+ #[test]
+ fn saving_new_tab_dialog_requests_creation_with_name() {
+ let mut state = state_with_workspaces(&["test"]);
+ open_new_tab_dialog(&mut state);
+ state.name_input = "logs".into();
+ state.name_input_replace_on_type = false;
+
+ handle_rename_key(
+ &mut state,
+ KeyEvent::new(KeyCode::Enter, KeyModifiers::empty()),
+ );
+
+ assert_eq!(state.mode, Mode::Terminal);
+ assert!(!state.creating_new_tab);
+ assert!(state.request_new_tab);
+ assert_eq!(state.requested_new_tab_name.as_deref(), Some("logs"));
+ }
+
+ #[test]
fn confirm_close_keyboard_actions_are_direct_not_focused() {
let mut state = state_with_workspaces(&["a", "b"]);
state.mode = Mode::ConfirmClose;
diff --git a/src/app/mod.rs b/src/app/mod.rs
index 901d9528..14274210 100644
--- a/src/app/mod.rs
+++ b/src/app/mod.rs
@@ -201,6 +201,8 @@ impl App {
should_quit: false,
request_new_workspace: false,
request_new_tab: false,
+ creating_new_tab: false,
+ requested_new_tab_name: None,
request_complete_onboarding: false,
name_input: String::new(),
name_input_replace_on_type: false,
@@ -1906,6 +1908,7 @@ impl App {
}
fn create_tab(&mut self) {
+ let custom_name = self.state.requested_new_tab_name.take();
let initial_cwd = self
.state
.active
@@ -1913,16 +1916,16 @@ impl App {
.or_else(|| std::env::current_dir().ok())
.unwrap_or_else(|| std::path::PathBuf::from("/"));
match self.create_tab_with_options(initial_cwd, true) {
- Ok(_) => {
- if let Some(ws) = self
- .state
- .active
- .and_then(|ws_idx| self.state.workspaces.get(ws_idx))
- {
- if let Some(name) = ws.active_tab_display_name() {
- self.state.name_input = name;
- self.state.name_input_replace_on_type = true;
- self.state.mode = Mode::RenameTab;
+ Ok(tab_idx) => {
+ if let Some(name) = custom_name {
+ if let Some(ws) = self
+ .state
+ .active
+ .and_then(|ws_idx| self.state.workspaces.get_mut(ws_idx))
+ {
+ if let Some(tab) = ws.tabs.get_mut(tab_idx) {
+ tab.set_custom_name(name);
+ }
}
}
}
diff --git a/src/app/state.rs b/src/app/state.rs
index 9f2e8e5b..74daeeb8 100644
--- a/src/app/state.rs
+++ b/src/app/state.rs
@@ -558,6 +558,8 @@ pub struct AppState {
pub should_quit: bool,
pub request_new_workspace: bool,
pub request_new_tab: bool,
+ pub creating_new_tab: bool,
+ pub requested_new_tab_name: Option<String>,
pub request_complete_onboarding: bool,
pub name_input: String,
pub name_input_replace_on_type: bool,
@@ -657,6 +659,8 @@ impl AppState {
should_quit: false,
request_new_workspace: false,
request_new_tab: false,
+ creating_new_tab: false,
+ requested_new_tab_name: None,
request_complete_onboarding: false,
name_input: String::new(),
name_input_replace_on_type: false,
diff --git a/src/ui.rs b/src/ui.rs
index e82d5b73..1d5ebc9e 100644
--- a/src/ui.rs
+++ b/src/ui.rs
@@ -2098,6 +2098,7 @@ fn render_rename_overlay(app: &AppState, frame: &mut Frame, area: Rect) {
let title = match app.mode {
Mode::RenameWorkspace => "rename workspace",
+ Mode::RenameTab if app.creating_new_tab => "new tab",
Mode::RenameTab => "rename tab",
_ => return,
};