aboutsummaryrefslogtreecommitdiff
path: root/src/app/mod.rs
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-04-02 05:10:13 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-04-02 23:24:46 +0300
commitb695adee60e50764f4713c8e586ca02fb844f99a (patch)
tree6fd769682289fc8053e3cf966ad2ca2d5d4898dd /src/app/mod.rs
parent28fb23cdaf4fce03ed6dbfcb4bb13be324b181ac (diff)
feat: add tabs within workspaces
Diffstat (limited to 'src/app/mod.rs')
-rw-r--r--src/app/mod.rs76
1 files changed, 61 insertions, 15 deletions
diff --git a/src/app/mod.rs b/src/app/mod.rs
index f6eb9af3..b69c324e 100644
--- a/src/app/mod.rs
+++ b/src/app/mod.rs
@@ -186,6 +186,7 @@ impl App {
mode,
should_quit: false,
request_new_workspace: false,
+ request_new_tab: false,
request_complete_onboarding: false,
name_input: String::new(),
onboarding_step: 0,
@@ -198,6 +199,9 @@ impl App {
}),
view: state::ViewState {
sidebar_rect: Rect::default(),
+ tab_bar_rect: Rect::default(),
+ tab_hit_areas: Vec::new(),
+ new_tab_hit_area: Rect::default(),
terminal_area: Rect::default(),
pane_infos: Vec::new(),
split_borders: Vec::new(),
@@ -250,7 +254,7 @@ impl App {
state
.workspaces
.get(idx)
- .map(|ws| (idx, ws.layout.focused()))
+ .and_then(|ws| ws.focused_pane_id().map(|pane_id| (idx, pane_id)))
});
Self {
@@ -319,6 +323,12 @@ impl App {
needs_render = true;
}
+ if self.state.request_new_tab {
+ self.state.request_new_tab = false;
+ self.create_tab();
+ needs_render = true;
+ }
+
let now = Instant::now();
self.sync_animation_timer(now);
@@ -682,7 +692,7 @@ impl App {
self.state
.workspaces
.get(idx)
- .map(|ws| (idx, ws.layout.focused()))
+ .and_then(|ws| ws.focused_pane_id().map(|pane_id| (idx, pane_id)))
});
if current_focus == self.last_focus {
return;
@@ -717,7 +727,7 @@ impl App {
.workspaces
.iter()
.enumerate()
- .find_map(|(ws_idx, ws)| ws.panes.get(&pane_id).map(|pane| (ws_idx, pane)))
+ .find_map(|(ws_idx, ws)| ws.pane_state(pane_id).map(|pane| (ws_idx, pane)))
}
fn public_workspace_id(&self, ws_idx: usize) -> String {
@@ -1451,6 +1461,38 @@ impl App {
}
}
+ fn create_tab(&mut self) {
+ let initial_cwd = self
+ .state
+ .active
+ .and_then(|i| self.state.workspaces.get(i))
+ .and_then(|ws| ws.focused_runtime())
+ .and_then(|rt| rt.cwd())
+ .or_else(|| std::env::current_dir().ok())
+ .unwrap_or_else(|| std::path::PathBuf::from("/"));
+ if let Err(e) = self.create_tab_with_options(initial_cwd, true) {
+ error!(err = %e, "failed to create tab");
+ }
+ }
+
+ fn create_tab_with_options(
+ &mut self,
+ initial_cwd: std::path::PathBuf,
+ focus: bool,
+ ) -> std::io::Result<usize> {
+ let Some(ws_idx) = self.state.active else {
+ return self.create_workspace_with_options(initial_cwd, focus);
+ };
+ let (rows, cols) = self.state.estimate_pane_size();
+ let ws = &mut self.state.workspaces[ws_idx];
+ let idx = ws.create_tab(rows, cols, initial_cwd)?;
+ if focus {
+ ws.switch_tab(idx);
+ self.state.mode = Mode::Terminal;
+ }
+ Ok(idx)
+ }
+
fn create_workspace_with_options(
&mut self,
initial_cwd: std::path::PathBuf,
@@ -1493,9 +1535,9 @@ impl App {
));
};
Ok(ws
- .layout
- .pane_ids()
- .into_iter()
+ .tabs
+ .iter()
+ .flat_map(|tab| tab.layout.pane_ids().into_iter())
.filter_map(|pane_id| self.pane_info(ws_idx, pane_id))
.collect())
} else {
@@ -1505,9 +1547,9 @@ impl App {
.iter()
.enumerate()
.flat_map(|(ws_idx, ws)| {
- ws.layout
- .pane_ids()
- .into_iter()
+ ws.tabs
+ .iter()
+ .flat_map(|tab| tab.layout.pane_ids().into_iter())
.filter_map(move |pane_id| self.pane_info(ws_idx, pane_id))
})
.collect())
@@ -1520,12 +1562,16 @@ impl App {
pane_id: crate::layout::PaneId,
) -> Option<crate::api::schema::PaneInfo> {
let ws = self.state.workspaces.get(ws_idx)?;
- let pane = ws.panes.get(&pane_id)?;
- let runtime = ws.runtimes.get(&pane_id);
+ let pane = ws.pane_state(pane_id)?;
+ let runtime = ws.runtime(pane_id);
+ let focused = self.state.active == Some(ws_idx)
+ && ws
+ .focused_pane_id()
+ .is_some_and(|focused| focused == pane_id);
Some(crate::api::schema::PaneInfo {
pane_id: self.public_pane_id(ws_idx, pane_id)?,
workspace_id: self.public_workspace_id(ws_idx),
- focused: self.state.active == Some(ws_idx) && ws.layout.focused() == pane_id,
+ focused,
cwd: runtime
.and_then(|rt| rt.cwd())
.map(|cwd| cwd.display().to_string()),
@@ -1541,7 +1587,7 @@ impl App {
pane_id: crate::layout::PaneId,
) -> Option<(&crate::pane::PaneRuntime, String)> {
let ws = self.state.workspaces.get(ws_idx)?;
- let runtime = ws.runtimes.get(&pane_id)?;
+ let runtime = ws.runtime(pane_id)?;
Some((runtime, self.public_workspace_id(ws_idx)))
}
@@ -1554,7 +1600,7 @@ impl App {
&crate::pane::PaneRuntime,
)> {
let ws = self.state.workspaces.get(ws_idx)?;
- let runtime = ws.runtimes.get(&pane_id)?;
+ let runtime = ws.runtime(pane_id)?;
Some((&runtime.sender, runtime))
}
@@ -1566,7 +1612,7 @@ impl App {
number: index + 1,
label: ws.display_name(),
focused: self.state.active == Some(index),
- pane_count: ws.panes.len(),
+ pane_count: ws.public_pane_numbers.len(),
agent_state: pane_agent_state(agg_state),
}
}