aboutsummaryrefslogtreecommitdiff
path: root/src/app
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-09-02 14:24:33 +0300
committerGitHub <noreply@github.com>2026-09-02 14:24:33 +0300
commit6c0bb273d5d5405a00985621b17e36f8b4d64609 (patch)
treeceef5ea8adf337bf3c1e977b133e76cb763c6f1b /src/app
parentcc88b3b8e5bb9f7d9f23ed6ae85a52fd7b5b9ed6 (diff)
feat: support independent multi-client tab views (#3526)
Diffstat (limited to 'src/app')
-rw-r--r--src/app/api.rs81
-rw-r--r--src/app/creation.rs19
-rw-r--r--src/app/ids.rs8
-rw-r--r--src/app/popup.rs5
-rw-r--r--src/app/state.rs39
5 files changed, 73 insertions, 79 deletions
diff --git a/src/app/api.rs b/src/app/api.rs
index ff5a7dc4..e6f6b6ec 100644
--- a/src/app/api.rs
+++ b/src/app/api.rs
@@ -657,8 +657,56 @@ impl App {
self.sync_focus_events_with_outer_event(None);
}
- pub(crate) fn send_outer_focus_event(&mut self, event: crate::ghostty::FocusEvent) {
- self.sync_focus_events_with_outer_event(Some(event));
+ pub(crate) fn accept_current_focus_without_events(&mut self) {
+ self.last_focus = self.state.active.and_then(|idx| {
+ self.state
+ .workspaces
+ .get(idx)
+ .and_then(|workspace| workspace.focused_pane_id().map(|pane_id| (idx, pane_id)))
+ });
+ }
+
+ pub(crate) fn accept_current_focus_with_api_events(&mut self) {
+ let current_focus = self.state.active.and_then(|idx| {
+ self.state
+ .workspaces
+ .get(idx)
+ .and_then(|workspace| workspace.focused_pane_id().map(|pane_id| (idx, pane_id)))
+ });
+ if current_focus == self.last_focus {
+ return;
+ }
+ self.last_focus = current_focus;
+ if let Some((ws_idx, pane_id)) = current_focus {
+ self.emit_focus_api_events(ws_idx, pane_id);
+ }
+ }
+
+ fn emit_focus_api_events(&mut self, ws_idx: usize, pane_id: crate::layout::PaneId) {
+ self.emit_event(crate::api::schema::EventEnvelope {
+ event: crate::api::schema::EventKind::WorkspaceFocused,
+ data: crate::api::schema::EventData::WorkspaceFocused {
+ workspace_id: self.public_workspace_id(ws_idx),
+ },
+ });
+ if let Some(tab_id) = self.public_tab_id(ws_idx, self.state.workspaces[ws_idx].active_tab) {
+ self.emit_event(crate::api::schema::EventEnvelope {
+ event: crate::api::schema::EventKind::TabFocused,
+ data: crate::api::schema::EventData::TabFocused {
+ tab_id,
+ workspace_id: self.public_workspace_id(ws_idx),
+ },
+ });
+ }
+ if let Some(public_pane_id) = self.public_pane_id(ws_idx, pane_id) {
+ self.emit_event(crate::api::schema::EventEnvelope {
+ event: crate::api::schema::EventKind::PaneFocused,
+ data: crate::api::schema::EventData::PaneFocused {
+ pane_id: public_pane_id,
+ workspace_id: self.public_workspace_id(ws_idx),
+ },
+ });
+ }
}
fn sync_focus_events_with_outer_event(
@@ -690,38 +738,13 @@ impl App {
}
});
self.send_pane_focus_event(ws_idx, pane_id, event);
- self.emit_event(crate::api::schema::EventEnvelope {
- event: crate::api::schema::EventKind::WorkspaceFocused,
- data: crate::api::schema::EventData::WorkspaceFocused {
- workspace_id: self.public_workspace_id(ws_idx),
- },
- });
- if let Some(tab_id) =
- self.public_tab_id(ws_idx, self.state.workspaces[ws_idx].active_tab)
- {
- self.emit_event(crate::api::schema::EventEnvelope {
- event: crate::api::schema::EventKind::TabFocused,
- data: crate::api::schema::EventData::TabFocused {
- tab_id,
- workspace_id: self.public_workspace_id(ws_idx),
- },
- });
- }
- if let Some(public_pane_id) = self.public_pane_id(ws_idx, pane_id) {
- self.emit_event(crate::api::schema::EventEnvelope {
- event: crate::api::schema::EventKind::PaneFocused,
- data: crate::api::schema::EventData::PaneFocused {
- pane_id: public_pane_id,
- workspace_id: self.public_workspace_id(ws_idx),
- },
- });
- }
+ self.emit_focus_api_events(ws_idx, pane_id);
}
self.last_focus = current_focus;
}
- fn send_pane_focus_event(
+ pub(crate) fn send_pane_focus_event(
&self,
ws_idx: usize,
pane_id: crate::layout::PaneId,
diff --git a/src/app/creation.rs b/src/app/creation.rs
index fbe61902..af34e540 100644
--- a/src/app/creation.rs
+++ b/src/app/creation.rs
@@ -79,8 +79,23 @@ impl App {
}
pub(crate) fn resolved_new_workspace_cwd_from(&self, ws_idx: usize) -> PathBuf {
- let follow_cwd = self
- .focused_pane_cwd_in_workspace(ws_idx)
+ let tab_idx = self
+ .state
+ .workspaces
+ .get(ws_idx)
+ .map(crate::workspace::Workspace::active_tab_index);
+ self.resolved_new_workspace_cwd_from_tab(ws_idx, tab_idx)
+ }
+
+ pub(crate) fn resolved_new_workspace_cwd_from_tab(
+ &self,
+ ws_idx: usize,
+ tab_idx: Option<usize>,
+ ) -> PathBuf {
+ let follow_cwd = tab_idx
+ .and_then(|tab_idx| self.state.workspaces.get(ws_idx)?.tabs.get(tab_idx))
+ .map(|tab| tab.layout.focused())
+ .and_then(|pane_id| self.launch_cwd_for_pane_in_workspace(ws_idx, pane_id))
.or_else(|| self.seed_cwd_from_workspace(ws_idx));
self.resolve_new_terminal_cwd(follow_cwd)
}
diff --git a/src/app/ids.rs b/src/app/ids.rs
index c33a5fff..df273250 100644
--- a/src/app/ids.rs
+++ b/src/app/ids.rs
@@ -12,11 +12,11 @@ impl App {
.find_map(|(ws_idx, ws)| ws.pane_state(pane_id).map(|pane| (ws_idx, pane)))
}
- pub(super) fn public_workspace_id(&self, ws_idx: usize) -> String {
+ pub(crate) fn public_workspace_id(&self, ws_idx: usize) -> String {
self.state.workspaces[ws_idx].id.clone()
}
- pub(super) fn public_tab_id(&self, ws_idx: usize, tab_idx: usize) -> Option<String> {
+ pub(crate) fn public_tab_id(&self, ws_idx: usize, tab_idx: usize) -> Option<String> {
let ws = self.state.workspaces.get(ws_idx)?;
let tab_number = ws.public_tab_number(tab_idx)?;
Some(crate::workspace::public_tab_id_for_number(
@@ -57,7 +57,7 @@ impl App {
)
}
- pub(super) fn parse_workspace_id(&self, id: &str) -> Option<usize> {
+ pub(crate) fn parse_workspace_id(&self, id: &str) -> Option<usize> {
self.state
.workspaces
.iter()
@@ -66,7 +66,7 @@ impl App {
.or_else(|| id.parse::<usize>().ok()?.checked_sub(1))
}
- pub(super) fn parse_tab_id(&self, id: &str) -> Option<(usize, usize)> {
+ pub(crate) fn parse_tab_id(&self, id: &str) -> Option<(usize, usize)> {
if let Some(rest) = id.strip_prefix("t_") {
let (ws_raw, tab_raw) = rest.rsplit_once('_')?;
let ws_idx = self.parse_workspace_id(ws_raw)?;
diff --git a/src/app/popup.rs b/src/app/popup.rs
index 8bcd8ae3..0643ea8f 100644
--- a/src/app/popup.rs
+++ b/src/app/popup.rs
@@ -13,11 +13,6 @@ pub(crate) struct PopupGeometry {
}
impl App {
- pub(crate) fn popup_runtime(&self) -> Option<&TerminalRuntime> {
- let terminal_id = &self.state.popup_pane.as_ref()?.terminal_id;
- self.terminal_runtimes.get(terminal_id)
- }
-
pub(crate) fn close_popup_pane(&mut self) -> bool {
let Some(popup) = self.state.popup_pane.take() else {
return false;
diff --git a/src/app/state.rs b/src/app/state.rs
index 0ce9fb6d..3d3700b2 100644
--- a/src/app/state.rs
+++ b/src/app/state.rs
@@ -921,35 +921,6 @@ impl AppState {
})
}
- pub(crate) fn app_surface_pane_ids(&self) -> std::collections::HashSet<PaneId> {
- let mut pane_ids = std::collections::HashSet::new();
- if let Some(popup) = &self.popup_pane {
- pane_ids.insert(popup.pane_id);
- }
- let Some(tab) = self
- .active
- .and_then(|ws_idx| self.workspaces.get(ws_idx))
- .and_then(crate::workspace::Workspace::active_tab)
- else {
- return pane_ids;
- };
- if tab.zoomed {
- pane_ids.insert(tab.layout.focused());
- } else {
- pane_ids.extend(tab.panes.keys().copied());
- }
- pane_ids
- }
-
- pub(crate) fn focused_pane_requests_mouse_capture_from(
- &self,
- terminal_runtimes: &crate::terminal::TerminalRuntimeRegistry,
- ) -> bool {
- self.active
- .and_then(|idx| self.focused_runtime_in_workspace(terminal_runtimes, idx))
- .is_some_and(crate::terminal::TerminalRuntime::mouse_reporting_enabled)
- }
-
pub fn estimate_pane_size(&self) -> (u16, u16) {
if let Some(info) = self.view.pane_infos.first() {
(info.rect.height, info.rect.width)
@@ -984,16 +955,6 @@ impl AppState {
terminal_runtimes.get(terminal_id)
}
- pub(crate) fn focused_runtime_in_workspace<'a>(
- &'a self,
- terminal_runtimes: &'a crate::terminal::TerminalRuntimeRegistry,
- ws_idx: usize,
- ) -> Option<&'a crate::terminal::TerminalRuntime> {
- let ws = self.workspaces.get(ws_idx)?;
- let pane_id = ws.focused_pane_id()?;
- self.runtime_for_pane_in_workspace(terminal_runtimes, ws_idx, pane_id)
- }
-
pub(crate) fn pane_visible_on_active_surface(
&self,
ws_idx: usize,