aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-04-03 01:48:15 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-04-03 01:48:15 +0300
commit94e6a2ff646715cb214c6f9bac8d146c5486dd35 (patch)
tree34a0757b1c0214f09f89d24f519b8024449d19b0
parent94809b2022f78216309036650e3d73841c2ee022 (diff)
fix workspace identity and tab-aware notifications
-rw-r--r--src/app/actions.rs57
-rw-r--r--src/app/input.rs22
-rw-r--r--src/app/mod.rs85
-rw-r--r--src/cli.rs4
-rw-r--r--src/persist.rs13
-rw-r--r--src/ui.rs3
-rw-r--r--src/workspace.rs70
-rw-r--r--tests/cli_wrapper.rs5
8 files changed, 210 insertions, 49 deletions
diff --git a/src/app/actions.rs b/src/app/actions.rs
index 7d9e27fd..dc52b174 100644
--- a/src/app/actions.rs
+++ b/src/app/actions.rs
@@ -11,7 +11,7 @@ use crate::pane::EffectiveStateChange;
use super::state::{AppState, Mode, ToastKind, ToastNotification};
fn notification_sound_for_state_change(
- is_active_ws: bool,
+ is_active_tab: bool,
prev_state: AgentState,
new_state: AgentState,
) -> Option<crate::sound::Sound> {
@@ -21,7 +21,7 @@ fn notification_sound_for_state_change(
match new_state {
AgentState::Blocked => Some(crate::sound::Sound::Request),
- AgentState::Idle if prev_state != AgentState::Idle && !is_active_ws => {
+ AgentState::Idle if prev_state != AgentState::Idle && !is_active_tab => {
Some(crate::sound::Sound::Done)
}
_ => None,
@@ -29,11 +29,11 @@ fn notification_sound_for_state_change(
}
fn notification_toast_for_state_change(
- is_active_ws: bool,
+ is_active_tab: bool,
prev_state: AgentState,
new_state: AgentState,
) -> Option<ToastKind> {
- if is_active_ws || new_state == prev_state {
+ if is_active_tab || new_state == prev_state {
return None;
}
@@ -60,7 +60,11 @@ fn agent_label(agent: Agent) -> &'static str {
}
}
-fn notification_context(ws: &crate::workspace::Workspace, ws_idx: usize, pane_id: PaneId) -> String {
+fn notification_context(
+ ws: &crate::workspace::Workspace,
+ ws_idx: usize,
+ pane_id: PaneId,
+) -> String {
let mut context = format!("{} · {}", ws.display_name(), ws_idx + 1);
if ws.tabs.len() > 1 {
if let Some(tab_idx) = ws.find_tab_index_for_pane(pane_id) {
@@ -86,6 +90,18 @@ pub struct PaneStateUpdate {
// ---------------------------------------------------------------------------
impl AppState {
+ fn pane_is_in_active_tab(&self, ws_idx: usize, pane_id: PaneId) -> bool {
+ let Some(active_ws_idx) = self.active else {
+ return false;
+ };
+ if active_ws_idx != ws_idx {
+ return false;
+ }
+ self.workspaces[ws_idx]
+ .find_tab_index_for_pane(pane_id)
+ .is_some_and(|tab_idx| tab_idx == self.workspaces[ws_idx].active_tab)
+ }
+
pub fn switch_workspace(&mut self, idx: usize) {
if idx < self.workspaces.len() {
self.active = Some(idx);
@@ -380,7 +396,7 @@ impl AppState {
pane_id: PaneId,
change: EffectiveStateChange,
) {
- let is_active_ws = self.active == Some(ws_idx);
+ let is_active_tab = self.pane_is_in_active_tab(ws_idx, pane_id);
let Some(pane) = self.workspaces[ws_idx]
.tabs
.iter_mut()
@@ -391,14 +407,14 @@ impl AppState {
if change.state == AgentState::Idle
&& change.previous_state != AgentState::Idle
- && !is_active_ws
+ && !is_active_tab
{
pane.seen = false;
}
if self.sound.allows(change.agent) {
if let Some(sound) = notification_sound_for_state_change(
- is_active_ws,
+ is_active_tab,
change.previous_state,
change.state,
) {
@@ -410,7 +426,7 @@ impl AppState {
if let (Some(agent), Some(kind)) = (
change.agent,
notification_toast_for_state_change(
- is_active_ws,
+ is_active_tab,
change.previous_state,
change.state,
),
@@ -725,7 +741,28 @@ mod tests {
}
#[test]
- fn active_workspace_does_not_set_toast() {
+ fn background_tab_in_active_workspace_still_sets_toast() {
+ let mut state = app_with_workspaces(&["active"]);
+ state.active = Some(0);
+ state.toast_config.enabled = true;
+ state.workspaces[0].tabs[0].set_custom_name("main".into());
+ let second_tab = state.workspaces[0].test_add_tab(Some("logs"));
+ let bg_pane_id = state.workspaces[0].tabs[second_tab].root_pane;
+
+ state.handle_app_event(AppEvent::StateChanged {
+ pane_id: bg_pane_id,
+ agent: Some(Agent::Pi),
+ state: AgentState::Blocked,
+ });
+
+ let toast = state.toast.as_ref().unwrap();
+ assert_eq!(toast.kind, ToastKind::NeedsAttention);
+ assert_eq!(toast.title, "pi needs attention");
+ assert_eq!(toast.context, "active · 1 · logs");
+ }
+
+ #[test]
+ fn active_workspace_active_tab_does_not_set_toast() {
let mut state = app_with_workspaces(&["active"]);
state.active = Some(0);
state.toast_config.enabled = true;
diff --git a/src/app/input.rs b/src/app/input.rs
index 3b63f28f..7341059c 100644
--- a/src/app/input.rs
+++ b/src/app/input.rs
@@ -383,7 +383,9 @@ impl App {
let protocol = crate::input::KeyboardProtocol::from_kitty_flags(flags);
let bytes = crate::input::encode_terminal_key(key, protocol);
if matches!(key_event.code, KeyCode::Esc)
- || key_event.modifiers.contains(crossterm::event::KeyModifiers::ALT)
+ || key_event
+ .modifiers
+ .contains(crossterm::event::KeyModifiers::ALT)
{
debug!(
code = ?key_event.code,
@@ -1863,7 +1865,8 @@ impl AppState {
return None;
}
- if let Some((ws_idx, tab_idx, pane_id)) = self.agent_detail_target_at(mouse.row) {
+ if let Some((ws_idx, tab_idx, pane_id)) = self.agent_detail_target_at(mouse.row)
+ {
self.switch_workspace(ws_idx);
if let Some(ws) = self.workspaces.get_mut(ws_idx) {
ws.switch_tab(tab_idx);
@@ -2103,10 +2106,7 @@ impl AppState {
None
}
- fn agent_detail_target_at(
- &self,
- row: u16,
- ) -> Option<(usize, usize, crate::layout::PaneId)> {
+ fn agent_detail_target_at(&self, row: u16) -> Option<(usize, usize, crate::layout::PaneId)> {
if self.sidebar_collapsed {
return None;
}
@@ -2129,7 +2129,10 @@ impl AppState {
content.width,
total_h.saturating_sub(ws_h) as u16,
);
- if detail_area.height < 4 || row < detail_area.y + 3 || row >= detail_area.y + detail_area.height {
+ if detail_area.height < 4
+ || row < detail_area.y + 3
+ || row >= detail_area.y + detail_area.height
+ {
return None;
}
@@ -2993,7 +2996,10 @@ mod tests {
app.handle_mouse(mouse(MouseEventKind::Down(MouseButton::Left), 2, 14));
assert_eq!(app.state.workspaces[0].active_tab, 1);
- assert_eq!(app.state.workspaces[0].tabs[1].layout.focused(), second_pane);
+ assert_eq!(
+ app.state.workspaces[0].tabs[1].layout.focused(),
+ second_pane
+ );
assert_eq!(app.state.mode, Mode::Terminal);
}
diff --git a/src/app/mod.rs b/src/app/mod.rs
index 311e5f82..202fa8c3 100644
--- a/src/app/mod.rs
+++ b/src/app/mod.rs
@@ -710,7 +710,9 @@ impl App {
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) {
+ 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 {
@@ -1402,7 +1404,12 @@ impl App {
})
.unwrap();
};
- let Some(tab_idx) = self.state.workspaces.get(ws_idx).and_then(|ws| ws.find_tab_index_for_pane(pane_id)) else {
+ let Some(tab_idx) = self
+ .state
+ .workspaces
+ .get(ws_idx)
+ .and_then(|ws| ws.find_tab_index_for_pane(pane_id))
+ else {
return serde_json::to_string(&ErrorResponse {
id: request.id,
error: ErrorBody {
@@ -1776,14 +1783,30 @@ impl App {
});
}
+ fn seed_cwd_from_workspace(&self, ws_idx: usize) -> Option<std::path::PathBuf> {
+ self.state.workspaces.get(ws_idx)?.resolved_identity_cwd()
+ }
+
+ fn workspace_creation_source(&self) -> Option<usize> {
+ if self.state.mode == Mode::Navigate
+ && self.state.workspaces.get(self.state.selected).is_some()
+ {
+ return Some(self.state.selected);
+ }
+
+ self.state.active.or_else(|| {
+ self.state
+ .workspaces
+ .get(self.state.selected)
+ .map(|_| self.state.selected)
+ })
+ }
+
/// Create a workspace with a real PTY (needs event_tx).
fn create_workspace(&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())
+ .workspace_creation_source()
+ .and_then(|ws_idx| self.seed_cwd_from_workspace(ws_idx))
.or_else(|| std::env::current_dir().ok())
.unwrap_or_else(|| std::path::PathBuf::from("/"));
if let Err(e) = self.create_workspace_with_options(initial_cwd, true) {
@@ -1796,9 +1819,7 @@ impl App {
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())
+ .and_then(|ws_idx| self.seed_cwd_from_workspace(ws_idx))
.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) {
@@ -1968,7 +1989,9 @@ impl App {
focused: self.state.active == Some(index),
pane_count: ws.public_pane_numbers.len(),
tab_count: ws.tabs.len(),
- active_tab_id: self.public_tab_id(index, ws.active_tab).unwrap_or_else(|| format!("{}:{}", index + 1, ws.active_tab + 1)),
+ active_tab_id: self
+ .public_tab_id(index, ws.active_tab)
+ .unwrap_or_else(|| format!("{}:{}", index + 1, ws.active_tab + 1)),
agent_state: pane_agent_state(agg_state),
}
}
@@ -2061,6 +2084,20 @@ fn agent_name(agent: crate::detect::Agent) -> String {
#[cfg(test)]
mod tests {
use super::*;
+ use crate::config::Config;
+ use crate::workspace::Workspace;
+
+ fn test_app() -> App {
+ let (_api_tx, api_rx) = tokio::sync::mpsc::unbounded_channel();
+ App::new(
+ &Config::default(),
+ true,
+ None,
+ None,
+ api_rx,
+ crate::api::EventHub::default(),
+ )
+ }
#[tokio::test]
async fn raw_input_waits_when_reader_is_gone() {
@@ -2089,4 +2126,30 @@ mod tests {
assert!(!api_request_changes_ui(&read_only));
assert!(api_request_changes_ui(&mutating));
}
+
+ #[test]
+ fn workspace_creation_in_navigate_mode_uses_selected_workspace_seed_cwd() {
+ let mut app = test_app();
+ let mut first = Workspace::test_new("herdr");
+ let first_root = first.tabs[0].root_pane;
+ first.tabs[0]
+ .pane_cwds
+ .insert(first_root, std::path::PathBuf::from("/tmp/herdr"));
+ let mut second = Workspace::test_new("pion");
+ let second_root = second.tabs[0].root_pane;
+ second.tabs[0]
+ .pane_cwds
+ .insert(second_root, std::path::PathBuf::from("/tmp/pion"));
+
+ app.state.workspaces = vec![first, second];
+ app.state.active = Some(0);
+ app.state.selected = 1;
+ app.state.mode = Mode::Navigate;
+
+ let ws_idx = app.workspace_creation_source().unwrap();
+ let seed_cwd = app.seed_cwd_from_workspace(ws_idx).unwrap();
+
+ assert_eq!(ws_idx, 1);
+ assert_eq!(seed_cwd, std::path::PathBuf::from("/tmp/pion"));
+ }
}
diff --git a/src/cli.rs b/src/cli.rs
index 30f11a0e..0516b3ce 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -8,8 +8,8 @@ use crate::api;
use crate::api::schema::{
EmptyParams, Method, OutputMatch, PaneAgentState, PaneListParams, PaneReadParams,
PaneSendKeysParams, PaneSendTextParams, PaneSplitParams, PaneTarget, PaneWaitForOutputParams,
- ReadSource, Request, SplitDirection, Subscription, TabCreateParams, TabListParams, TabRenameParams,
- TabTarget, WorkspaceCreateParams, WorkspaceRenameParams, WorkspaceTarget,
+ ReadSource, Request, SplitDirection, Subscription, TabCreateParams, TabListParams,
+ TabRenameParams, TabTarget, WorkspaceCreateParams, WorkspaceRenameParams, WorkspaceTarget,
};
pub enum CommandOutcome {
diff --git a/src/persist.rs b/src/persist.rs
index 3585228e..ba88df8e 100644
--- a/src/persist.rs
+++ b/src/persist.rs
@@ -97,7 +97,9 @@ pub fn capture(
fn capture_workspace(ws: &Workspace) -> WorkspaceSnapshot {
WorkspaceSnapshot {
custom_name: ws.custom_name.clone(),
- identity_cwd: ws.identity_cwd.clone(),
+ identity_cwd: ws
+ .resolved_identity_cwd()
+ .unwrap_or_else(|| ws.identity_cwd.clone()),
tabs: ws.tabs.iter().map(capture_tab).collect(),
active_tab: ws.active_tab,
}
@@ -107,9 +109,7 @@ fn capture_tab(tab: &crate::workspace::Tab) -> TabSnapshot {
let mut panes = HashMap::new();
for id in tab.panes.keys() {
let cwd = tab
- .runtimes
- .get(id)
- .and_then(|rt| rt.cwd())
+ .cwd_for_pane(*id)
.unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| "/".into()));
panes.insert(id.raw(), PaneSnapshot { cwd });
}
@@ -233,6 +233,7 @@ fn restore_tab(
let layout = TileLayout::from_saved(node, focus);
let mut panes = HashMap::new();
+ let mut pane_cwds = HashMap::new();
let mut runtimes = HashMap::new();
for id in &pane_ids {
let old_id = id_map
@@ -248,13 +249,14 @@ fn restore_tab(
*id,
rows,
cols,
- cwd,
+ cwd.clone(),
events.clone(),
render_notify.clone(),
render_dirty.clone(),
) {
Ok(runtime) => {
panes.insert(*id, PaneState::new());
+ pane_cwds.insert(*id, cwd.clone());
runtimes.insert(*id, runtime);
}
Err(e) => {
@@ -276,6 +278,7 @@ fn restore_tab(
root_pane,
layout,
panes,
+ pane_cwds,
runtimes,
zoomed: snap.zoomed,
events,
diff --git a/src/ui.rs b/src/ui.rs
index dd344f87..05b5a634 100644
--- a/src/ui.rs
+++ b/src/ui.rs
@@ -316,7 +316,8 @@ fn compute_sidebar_width(app: &AppState) -> u16 {
.map(|detail| 3 + detail.label.len() + 1 + state_label(detail.state, detail.seen).len())
.max()
.unwrap_or(0);
- ((max_workspace_line.max(max_agent_line) as u16) + 2).clamp(MIN_SIDEBAR_WIDTH, MAX_SIDEBAR_WIDTH)
+ ((max_workspace_line.max(max_agent_line) as u16) + 2)
+ .clamp(MIN_SIDEBAR_WIDTH, MAX_SIDEBAR_WIDTH)
}
/// Collapsed sidebar: pure glance mode.
diff --git a/src/workspace.rs b/src/workspace.rs
index 81e1f2f2..24c17521 100644
--- a/src/workspace.rs
+++ b/src/workspace.rs
@@ -21,6 +21,8 @@ pub struct Tab {
pub layout: TileLayout,
/// Pane state — always present, testable without PTYs.
pub panes: HashMap<PaneId, PaneState>,
+ /// Best-effort cwd cache per pane. Live runtime cwd wins when available.
+ pub pane_cwds: HashMap<PaneId, PathBuf>,
/// Pane runtimes — only present in production (empty in tests).
pub runtimes: HashMap<PaneId, PaneRuntime>,
pub zoomed: bool,
@@ -53,7 +55,7 @@ impl Tab {
root_id,
rows,
cols,
- initial_cwd,
+ initial_cwd.clone(),
events.clone(),
render_notify.clone(),
render_dirty.clone(),
@@ -61,6 +63,8 @@ impl Tab {
let mut panes = HashMap::new();
panes.insert(root_id, PaneState::new());
+ let mut pane_cwds = HashMap::new();
+ pane_cwds.insert(root_id, initial_cwd);
let mut runtimes = HashMap::new();
runtimes.insert(root_id, runtime);
@@ -70,6 +74,7 @@ impl Tab {
root_pane: root_id,
layout,
panes,
+ pane_cwds,
runtimes,
zoomed: false,
events,
@@ -102,12 +107,13 @@ impl Tab {
new_id,
rows,
cols,
- actual_cwd,
+ actual_cwd.clone(),
self.events.clone(),
self.render_notify.clone(),
self.render_dirty.clone(),
)?;
self.panes.insert(new_id, PaneState::new());
+ self.pane_cwds.insert(new_id, actual_cwd);
self.runtimes.insert(new_id, runtime);
self.zoomed = false;
Ok(new_id)
@@ -143,6 +149,7 @@ impl Tab {
}
self.panes.remove(&pane_id);
+ self.pane_cwds.remove(&pane_id);
let runtime = self.runtimes.remove(&pane_id);
self.zoomed = false;
if let Some(next_root) = next_root {
@@ -162,6 +169,17 @@ impl Tab {
self.runtimes.get(&self.layout.focused())
}
+ pub fn cwd_for_pane(&self, pane_id: PaneId) -> Option<PathBuf> {
+ self.runtimes
+ .get(&pane_id)
+ .and_then(|rt| rt.cwd())
+ .or_else(|| self.pane_cwds.get(&pane_id).cloned())
+ }
+
+ pub fn root_cwd(&self) -> Option<PathBuf> {
+ self.cwd_for_pane(self.root_pane)
+ }
+
pub fn has_working_pane(&self) -> bool {
self.panes
.values()
@@ -214,7 +232,7 @@ impl Tab {
pub struct Workspace {
/// User-provided override. If set, auto-derived identity stops updating.
pub custom_name: Option<String>,
- /// Fixed workspace identity source, seeded when the workspace is created.
+ /// Fallback workspace identity source for tests, old snapshots, or missing runtimes.
pub identity_cwd: PathBuf,
/// Cached ahead/behind counts for the workspace repo's current branch upstream.
pub(crate) cached_git_ahead_behind: Option<(usize, usize)>,
@@ -412,16 +430,26 @@ impl Workspace {
self.custom_name = Some(name);
}
+ pub fn resolved_identity_cwd(&self) -> Option<PathBuf> {
+ self.tabs
+ .first()
+ .and_then(Tab::root_cwd)
+ .or_else(|| Some(self.identity_cwd.clone()))
+ }
+
pub fn display_name(&self) -> String {
if let Some(name) = &self.custom_name {
return name.clone();
}
- derive_label_from_cwd(&self.identity_cwd)
+ self.resolved_identity_cwd()
+ .map(|cwd| derive_label_from_cwd(&cwd))
+ .unwrap_or_else(|| "workspace".into())
}
pub fn branch(&self) -> Option<String> {
- git_branch(&self.identity_cwd)
+ self.resolved_identity_cwd()
+ .and_then(|cwd| git_branch(&cwd))
}
pub fn git_ahead_behind(&self) -> Option<(usize, usize)> {
@@ -429,7 +457,9 @@ impl Workspace {
}
pub fn refresh_git_ahead_behind(&mut self) {
- self.cached_git_ahead_behind = git_ahead_behind(&self.identity_cwd);
+ self.cached_git_ahead_behind = self
+ .resolved_identity_cwd()
+ .and_then(|cwd| git_ahead_behind(&cwd));
}
pub fn aggregate_state(&self) -> (AgentState, bool) {
@@ -666,12 +696,15 @@ impl Workspace {
let (layout, root_id) = TileLayout::new();
let mut panes = HashMap::new();
panes.insert(root_id, PaneState::new());
+ let mut pane_cwds = HashMap::new();
+ pane_cwds.insert(root_id, identity_cwd.clone());
let tab = Tab {
custom_name: None,
number: 1,
root_pane: root_id,
layout,
panes,
+ pane_cwds,
runtimes: HashMap::new(),
zoomed: false,
events,
@@ -695,6 +728,8 @@ impl Workspace {
let tab = self.active_tab_mut().expect("workspace must have tab");
let new_id = tab.layout.split_focused(direction);
tab.panes.insert(new_id, PaneState::new());
+ let cwd = std::env::current_dir().unwrap_or_else(|_| "/".into());
+ tab.pane_cwds.insert(new_id, cwd);
self.register_new_pane(new_id);
new_id
}
@@ -706,12 +741,16 @@ impl Workspace {
let (layout, root_id) = TileLayout::new();
let mut panes = HashMap::new();
panes.insert(root_id, PaneState::new());
+ let cwd = std::env::current_dir().unwrap_or_else(|_| "/".into());
+ let mut pane_cwds = HashMap::new();
+ pane_cwds.insert(root_id, cwd);
let tab = Tab {
custom_name: name.map(str::to_string),
number: self.tabs.len() + 1,
root_pane: root_id,
layout,
panes,
+ pane_cwds,
runtimes: HashMap::new(),
zoomed: false,
events,
@@ -776,15 +815,24 @@ mod tests {
}
#[test]
+ fn workspace_identity_follows_first_tab_root_pane_cwd() {
+ let mut ws = Workspace::test_new("ignored");
+ ws.custom_name = None;
+ let root_pane = ws.tabs[0].root_pane;
+ ws.tabs[0]
+ .pane_cwds
+ .insert(root_pane, PathBuf::from("/tmp/pion"));
+
+ assert_eq!(ws.display_name(), "pion");
+ assert_eq!(ws.resolved_identity_cwd(), Some(PathBuf::from("/tmp/pion")));
+ }
+
+ #[test]
fn pane_details_include_tab_context_when_workspace_has_multiple_tabs() {
let mut ws = Workspace::test_new("test");
ws.tabs[0].set_custom_name("main".into());
let root_pane = ws.tabs[0].root_pane;
- ws.tabs[0]
- .panes
- .get_mut(&root_pane)
- .unwrap()
- .detected_agent = Some(Agent::Pi);
+ ws.tabs[0].panes.get_mut(&root_pane).unwrap().detected_agent = Some(Agent::Pi);
let tab_idx = ws.test_add_tab(Some("logs"));
let second_root_pane = ws.tabs[tab_idx].root_pane;
diff --git a/tests/cli_wrapper.rs b/tests/cli_wrapper.rs
index 0f90baa2..18588605 100644
--- a/tests/cli_wrapper.rs
+++ b/tests/cli_wrapper.rs
@@ -223,7 +223,10 @@ fn tab_management_commands_work() {
let listed_tabs = run_cli(&socket_path, &["tab", "list", "--workspace", "1"]);
assert!(listed_tabs.status.success());
let listed_tabs_json: serde_json::Value = serde_json::from_slice(&listed_tabs.stdout).unwrap();
- assert_eq!(listed_tabs_json["result"]["tabs"].as_array().unwrap().len(), 2);
+ assert_eq!(
+ listed_tabs_json["result"]["tabs"].as_array().unwrap().len(),
+ 2
+ );
let renamed_tab = run_cli(&socket_path, &["tab", "rename", "1:2", "logs"]);
assert!(renamed_tab.status.success());