aboutsummaryrefslogtreecommitdiff
path: root/src/server/clients.rs
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/server/clients.rs
parentcc88b3b8e5bb9f7d9f23ed6ae85a52fd7b5b9ed6 (diff)
feat: support independent multi-client tab views (#3526)
Diffstat (limited to 'src/server/clients.rs')
-rw-r--r--src/server/clients.rs77
1 files changed, 77 insertions, 0 deletions
diff --git a/src/server/clients.rs b/src/server/clients.rs
index 3860f826..4f1baadb 100644
--- a/src/server/clients.rs
+++ b/src/server/clients.rs
@@ -60,6 +60,75 @@ pub(crate) enum DeferredRender {
Full,
}
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub(crate) struct ClientShellLocation {
+ pub(crate) focused_workspace_id: Option<String>,
+ pub(crate) active_tab_ids: HashMap<String, String>,
+}
+
+pub(crate) struct ClientShellTopology {
+ pub(crate) focused_workspace_id: Option<String>,
+ pub(crate) fallback_workspace_id: Option<String>,
+ pub(crate) active_tab_ids: HashMap<String, String>,
+ pub(crate) tab_workspace_ids: HashMap<String, String>,
+}
+
+impl ClientShellLocation {
+ pub(crate) fn from_snapshot(snapshot: &crate::protocol::ClientShellSnapshot) -> Self {
+ Self {
+ focused_workspace_id: snapshot.focused_workspace_id.clone(),
+ active_tab_ids: snapshot
+ .workspaces
+ .iter()
+ .map(|workspace| {
+ (
+ workspace.workspace_id.clone(),
+ workspace.active_tab_id.clone(),
+ )
+ })
+ .collect(),
+ }
+ }
+
+ pub(crate) fn focused_tab_id(&self) -> Option<&str> {
+ self.focused_workspace_id
+ .as_deref()
+ .and_then(|workspace_id| self.active_tab_ids.get(workspace_id))
+ .map(String::as_str)
+ }
+
+ pub(crate) fn focus_workspace(&mut self, workspace_id: String) {
+ self.focused_workspace_id = Some(workspace_id);
+ }
+
+ pub(crate) fn focus_tab(&mut self, workspace_id: String, tab_id: String) {
+ self.focused_workspace_id = Some(workspace_id.clone());
+ self.active_tab_ids.insert(workspace_id, tab_id);
+ }
+
+ pub(crate) fn reconcile(&mut self, topology: &ClientShellTopology) {
+ self.active_tab_ids.retain(|workspace_id, tab_id| {
+ topology.active_tab_ids.contains_key(workspace_id)
+ && topology.tab_workspace_ids.get(tab_id) == Some(workspace_id)
+ });
+ for (workspace_id, tab_id) in &topology.active_tab_ids {
+ self.active_tab_ids
+ .entry(workspace_id.clone())
+ .or_insert_with(|| tab_id.clone());
+ }
+ if self
+ .focused_workspace_id
+ .as_ref()
+ .is_none_or(|workspace_id| !topology.active_tab_ids.contains_key(workspace_id))
+ {
+ self.focused_workspace_id = topology
+ .focused_workspace_id
+ .clone()
+ .or_else(|| topology.fallback_workspace_id.clone());
+ }
+ }
+}
+
/// A connected client tracked by the server.
pub(crate) struct ClientConnection {
/// Whether this connection owns the Herdr shell or one direct terminal stream.
@@ -102,12 +171,17 @@ pub(crate) struct ClientConnection {
shell_held_inputs: HashMap<ClientShellPressId, ClientShellHeldInput>,
/// Temporary files staged from this client's local clipboard image pastes.
pub(crate) staged_clipboard_files: Vec<PathBuf>,
+ /// Connection-local workspace and tab projection for a client-owned shell.
+ pub(crate) shell_location: Option<ClientShellLocation>,
/// Last coherent shell replacement sent to this client.
pub(crate) shell_snapshot: Option<crate::protocol::ClientShellSnapshot>,
/// Monotonic shell replacement revision for this connection.
pub(crate) shell_projection_revision: u64,
/// Whether this shell is waiting for one ordered endpoint command response.
pub(crate) shell_endpoint_command_in_flight: bool,
+ /// Request id and buffered response for a deferred worktree-created navigation.
+ pub(crate) shell_deferred_navigation_request_id: Option<String>,
+ pub(crate) shell_deferred_navigation_response: Option<Vec<u8>>,
/// Whether this shell uses the endpoint-owned keymap rather than a client-owned keymap.
pub(crate) shell_uses_endpoint_keybindings: bool,
/// Channels for sending framed ServerMessage data to the client writer thread.
@@ -162,9 +236,12 @@ impl ClientConnection {
host_keyboard_protocol_active: None,
shell_held_inputs: HashMap::new(),
staged_clipboard_files: Vec::new(),
+ shell_location: None,
shell_snapshot: None,
shell_projection_revision: 0,
shell_endpoint_command_in_flight: false,
+ shell_deferred_navigation_request_id: None,
+ shell_deferred_navigation_response: None,
shell_uses_endpoint_keybindings: false,
writer,
}