From 9f210e973834700d14c6c0283ed750ed5b038e8b Mon Sep 17 00:00:00 2001 From: Ogulcan Celik Date: Sat, 9 May 2026 23:16:24 +0300 Subject: [PATCH] chore: enable clippy linting --- .githooks/pre-commit | 4 +- clippy.toml | 1 + justfile | 12 +++-- src/api/mod.rs | 14 ++--- src/app/input/mod.rs | 21 +++++--- src/app/input/modal.rs | 12 ++--- src/app/input/settings.rs | 24 ++++++--- src/app/mod.rs | 2 +- src/app/runtime.rs | 9 ++-- src/app/state.rs | 9 +--- src/cli.rs | 7 +-- src/client/mod.rs | 10 ++-- src/config/model.rs | 8 +-- src/ghostty/mod.rs | 8 +-- src/main.rs | 97 +++++++++++++++++----------------- src/pane.rs | 2 + src/pane/state.rs | 4 +- src/pane/terminal.rs | 4 +- src/raw_input.rs | 17 ++---- src/server/client_transport.rs | 3 +- src/server/headless.rs | 4 +- src/ui/mobile.rs | 6 +-- src/ui/panes.rs | 76 +++++++++++++------------- src/ui/sidebar.rs | 4 +- src/ui/tabs.rs | 8 +-- tests/client_mode.rs | 11 ++-- tests/cross_area.rs | 1 - tests/multi_client.rs | 1 - tests/support/mod.rs | 2 +- 29 files changed, 184 insertions(+), 197 deletions(-) create mode 100644 clippy.toml diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 249d41f7..d8b05146 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -4,5 +4,5 @@ set -euo pipefail repo_root="$(git rev-parse --show-toplevel)" cd "$repo_root" -echo "pre-commit: running cargo fmt --check" -cargo fmt --check +echo "pre-commit: running just lint" +just lint diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 00000000..6134cb5f --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +too-many-arguments-threshold = 11 diff --git a/justfile b/justfile index 1b9b3df0..7409c746 100644 --- a/justfile +++ b/justfile @@ -2,13 +2,17 @@ # Run tests test: - cargo nextest run --locked + cargo nextest run --locked --status-level fail --final-status-level fail --failure-output final --success-output never python3 -m unittest scripts.test_changelog scripts.test_vendor_libghostty_vt -# Run PR CI checks -ci: +# Run fast local lint checks +lint: cargo fmt --check - cargo nextest run --locked + cargo clippy --all-targets --locked -- -D warnings + +# Run PR CI checks +ci: lint + cargo nextest run --locked --status-level fail --final-status-level fail --failure-output final --success-output never # Check formatting + run unit tests + maintenance script tests check: ci diff --git a/src/api/mod.rs b/src/api/mod.rs index f256e4a8..318a0ee7 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -368,7 +368,7 @@ fn output_match_read_source( ) -> crate::api::schema::ReadSource { match source { crate::api::schema::ReadSource::Recent => crate::api::schema::ReadSource::RecentUnwrapped, - other => other.clone(), + other => *other, } } @@ -741,14 +741,12 @@ impl ActiveSubscription { let probe = pane_read( format!("{request_id}:sub:{index}:probe"), &pane_id, - source.clone(), + source, lines, strip_ansi, api_tx, ); - if let Err(error) = probe { - return Err(error); - } + probe?; Ok(Self::OutputMatched(ActiveOutputMatchedSubscription { pane_id, @@ -765,11 +763,7 @@ impl ActiveSubscription { pane_id, agent_status, } => { - let probe = - match pane_get(format!("{request_id}:sub:{index}:probe"), &pane_id, api_tx) { - Ok(probe) => probe, - Err(error) => return Err(error), - }; + let probe = pane_get(format!("{request_id}:sub:{index}:probe"), &pane_id, api_tx)?; Ok(Self::AgentStatusChanged( ActiveAgentStatusChangedSubscription { diff --git a/src/app/input/mod.rs b/src/app/input/mod.rs index 56ad09a9..aa607c78 100644 --- a/src/app/input/mod.rs +++ b/src/app/input/mod.rs @@ -92,10 +92,13 @@ impl App { pub(crate) fn handle_onboarding_key(&mut self, key: KeyEvent) { match key.code { KeyCode::Right | KeyCode::Char('l') => self.open_settings_from_onboarding(), - _ => match modal_action_from_key(&key, ONBOARDING_WELCOME_ACTIONS) { - Some(ModalAction::Continue) => self.open_settings_from_onboarding(), - _ => {} - }, + _ => { + if let Some(ModalAction::Continue) = + modal_action_from_key(&key, ONBOARDING_WELCOME_ACTIONS) + { + self.open_settings_from_onboarding(); + } + } } } @@ -116,10 +119,12 @@ impl App { notes.scroll = max_scroll; } } - _ => match modal_action_from_key(&key, RELEASE_NOTES_ACTIONS) { - Some(ModalAction::Close) => self.dismiss_release_notes(), - _ => {} - }, + _ => { + if let Some(ModalAction::Close) = modal_action_from_key(&key, RELEASE_NOTES_ACTIONS) + { + self.dismiss_release_notes(); + } + } } } diff --git a/src/app/input/modal.rs b/src/app/input/modal.rs index 998bb69b..a76576bf 100644 --- a/src/app/input/modal.rs +++ b/src/app/input/modal.rs @@ -261,13 +261,11 @@ pub(super) fn apply_rename_action(state: &mut AppState, action: ModalAction) { state.name_input.trim().to_string() }; match state.mode { - Mode::RenameWorkspace if !state.workspaces.is_empty() => { - if !new_name.is_empty() { - let workspace_id = state.workspaces[state.selected].id.clone(); - state.workspaces[state.selected].set_custom_name(new_name); - crate::logging::workspace_renamed(&workspace_id); - state.mark_session_dirty(); - } + Mode::RenameWorkspace if !state.workspaces.is_empty() && !new_name.is_empty() => { + let workspace_id = state.workspaces[state.selected].id.clone(); + state.workspaces[state.selected].set_custom_name(new_name); + crate::logging::workspace_renamed(&workspace_id); + state.mark_session_dirty(); } Mode::RenameTab if state.creating_new_tab => { state.request_new_tab = true; diff --git a/src/app/input/settings.rs b/src/app/input/settings.rs index 2c92bc63..67144a28 100644 --- a/src/app/input/settings.rs +++ b/src/app/input/settings.rs @@ -10,6 +10,8 @@ use crate::{ }; #[derive(Debug, Clone, PartialEq, Eq)] +// The shared `Save` verb is semantic: these actions persist settings. +#[allow(clippy::enum_variant_names)] pub(super) enum SettingsAction { SaveTheme(String), SaveSound(bool), @@ -135,10 +137,13 @@ pub(super) fn update_settings_state(state: &mut AppState, key: KeyEvent) -> Opti state.settings.section = SettingsSection::Theme; state.settings.list.selected = current_theme_index(&state.theme_name); } - _ => match super::modal::modal_action_from_key(&key, super::modal::SETTINGS_ACTIONS) { - Some(super::modal::ModalAction::Close) => cancel_settings(state), - _ => {} - }, + _ => { + if let Some(super::modal::ModalAction::Close) = + super::modal::modal_action_from_key(&key, super::modal::SETTINGS_ACTIONS) + { + cancel_settings(state); + } + } }, SettingsSection::Toast => match key.code { KeyCode::Up | KeyCode::Char('k') => state.settings.list.move_prev(), @@ -155,10 +160,13 @@ pub(super) fn update_settings_state(state: &mut AppState, key: KeyEvent) -> Opti state.settings.section = SettingsSection::Theme; state.settings.list.selected = current_theme_index(&state.theme_name); } - _ => match super::modal::modal_action_from_key(&key, super::modal::SETTINGS_ACTIONS) { - Some(super::modal::ModalAction::Close) => cancel_settings(state), - _ => {} - }, + _ => { + if let Some(super::modal::ModalAction::Close) = + super::modal::modal_action_from_key(&key, super::modal::SETTINGS_ACTIONS) + { + cancel_settings(state); + } + } }, } diff --git a/src/app/mod.rs b/src/app/mod.rs index 1ea315cf..60e38f5b 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -351,7 +351,7 @@ impl App { toast_config: config.ui.toast.clone(), keybinds: config.keybinds(), spinner_tick: 0, - palette: resolve_palette(&config), + palette: resolve_palette(config), theme_name: config .theme .name diff --git a/src/app/runtime.rs b/src/app/runtime.rs index a4e93d3b..3c67c306 100644 --- a/src/app/runtime.rs +++ b/src/app/runtime.rs @@ -229,10 +229,11 @@ impl App { } pub(crate) fn start_git_status_refresh_if_due(&mut self, now: Instant) { - if !self - .git_refresh_deadline() - .is_some_and(|deadline| now >= deadline) - { + let Some(deadline) = self.git_refresh_deadline() else { + return; + }; + + if now < deadline { return; } diff --git a/src/app/state.rs b/src/app/state.rs index f7c4daa1..134d86bd 100644 --- a/src/app/state.rs +++ b/src/app/state.rs @@ -391,18 +391,13 @@ pub enum Mode { KeybindHelp, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum AgentPanelScope { CurrentWorkspace, + #[default] AllWorkspaces, } -impl Default for AgentPanelScope { - fn default() -> Self { - Self::AllWorkspaces - } -} - // --------------------------------------------------------------------------- // Settings UI state // --------------------------------------------------------------------------- diff --git a/src/cli.rs b/src/cli.rs index 1d3c29e2..a5ba8b1a 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -202,7 +202,7 @@ fn compatibility_label(protocol: Option) -> &'static str { fn restart_needed_label(server: &ServerRuntimeStatus) -> &'static str { match server { ServerRuntimeStatus::Running { version, .. } => match version.as_deref() { - Some(version) if version == env!("CARGO_PKG_VERSION") => "no", + Some(env!("CARGO_PKG_VERSION")) => "no", Some(_) => "yes", None => "unknown", }, @@ -1391,10 +1391,7 @@ fn parse_session_name_and_json(args: &[String], usage: &str) -> Result<(String, } fn print_session_table(sessions: &[crate::session::SessionInfo]) { - println!( - "{:<20} {:<8} {:<48} {}", - "name", "status", "directory", "socket" - ); + println!("{:<20} {:<8} {:<48} socket", "name", "status", "directory"); for session in sessions { println!( "{:<20} {:<8} {:<48} {}", diff --git a/src/client/mod.rs b/src/client/mod.rs index 1e0beded..475a079f 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -231,9 +231,8 @@ fn do_handshake( rows, requested_encoding, }; - protocol::write_message(stream, &hello).map_err(|e| { - ClientError::ConnectionFailed(io::Error::new(io::ErrorKind::Other, e.to_string())) - })?; + protocol::write_message(stream, &hello) + .map_err(|e| ClientError::ConnectionFailed(io::Error::other(e.to_string())))?; // Read Welcome. stream @@ -339,7 +338,7 @@ pub fn run_client() -> io::Result<()> { let rt = tokio::runtime::Builder::new_current_thread() .enable_all() .build() - .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + .map_err(io::Error::other)?; let should_quit = Arc::new(AtomicBool::new(false)); @@ -577,8 +576,7 @@ fn server_reader_thread( /// Writes a message to the server stream (blocking). fn write_to_server(stream: &mut UnixStream, msg: &ClientMessage) -> io::Result<()> { - protocol::write_message(stream, msg) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string())) + protocol::write_message(stream, msg).map_err(|e| io::Error::other(e.to_string())) } // --------------------------------------------------------------------------- diff --git a/src/config/model.rs b/src/config/model.rs index 7e6c7382..a11192fb 100644 --- a/src/config/model.rs +++ b/src/config/model.rs @@ -209,11 +209,11 @@ impl<'de> Deserialize<'de> for ToastConfig { } let raw = RawToastConfig::deserialize(deserializer)?; - let delivery = raw.delivery.unwrap_or_else(|| match raw.enabled { + let legacy_delivery = match raw.enabled { Some(true) => ToastDelivery::Herdr, - Some(false) => ToastDelivery::Off, - None => ToastDelivery::Off, - }); + Some(false) | None => ToastDelivery::Off, + }; + let delivery = raw.delivery.unwrap_or(legacy_delivery); Ok(Self { delivery }) } } diff --git a/src/ghostty/mod.rs b/src/ghostty/mod.rs index 27fcb6f9..02bae73f 100644 --- a/src/ghostty/mod.rs +++ b/src/ghostty/mod.rs @@ -234,8 +234,10 @@ impl CellWide { } } +type WritePtyCallback = dyn FnMut(&[u8]) + Send; + struct WritePtyCallbackState { - callback: Box, + callback: Box, } unsafe extern "C" fn write_pty_trampoline( @@ -1469,10 +1471,10 @@ mod tests { ); render_state.update(&terminal).unwrap(); - assert_eq!(render_state.cursor_visible().unwrap(), true); + assert!(render_state.cursor_visible().unwrap()); terminal.write(b"\x1b[?25l"); render_state.update(&terminal).unwrap(); - assert_eq!(render_state.cursor_visible().unwrap(), false); + assert!(!render_state.cursor_visible().unwrap()); terminal.write(b"\x1b[?1049h\x1b[HALT"); assert_eq!(terminal.active_screen().unwrap(), ActiveScreen::Alternate); diff --git a/src/main.rs b/src/main.rs index ee2c30e4..c35a4084 100644 --- a/src/main.rs +++ b/src/main.rs @@ -243,57 +243,56 @@ fn main() -> io::Result<()> { println!(" herdr integration ..."); println!(); println!("Common commands:"); - println!( - " {:<32} {}", - "herdr", "Launch or attach to the persistent session" - ); - println!( - " {:<32} {}", - "herdr status [server|client]", "Show local client and running server status" - ); - println!( - " {:<32} {}", - "herdr update", "Download and install the latest version" - ); - println!( - " {:<32} {}", - "herdr server stop", "Stop the running server via the API socket" - ); - println!( - " {:<32} {}", - "herdr server reload-config", "Reload config.toml in the running server" - ); - println!( - " {:<32} {}", - "herdr workspace ", "Workspace helpers over the socket API" - ); - println!( - " {:<32} {}", - "herdr tab ", "Tab helpers over the socket API" - ); - println!( - " {:<32} {}", - "herdr pane ", "Pane control helpers over the socket API" - ); - println!( - " {:<32} {}", - "herdr wait ", "Blocking wait helpers over the socket API" - ); - println!( - " {:<32} {}", - "herdr session ", "Manage named persistent sessions" - ); - println!( - " {:<32} {}", - "herdr integration ", "Manage built-in agent integrations" - ); + for (command, description) in [ + ("herdr", "Launch or attach to the persistent session"), + ( + "herdr status [server|client]", + "Show local client and running server status", + ), + ("herdr update", "Download and install the latest version"), + ( + "herdr server stop", + "Stop the running server via the API socket", + ), + ( + "herdr server reload-config", + "Reload config.toml in the running server", + ), + ( + "herdr workspace ", + "Workspace helpers over the socket API", + ), + ("herdr tab ", "Tab helpers over the socket API"), + ( + "herdr pane ", + "Pane control helpers over the socket API", + ), + ( + "herdr wait ", + "Blocking wait helpers over the socket API", + ), + ( + "herdr session ", + "Manage named persistent sessions", + ), + ( + "herdr integration ", + "Manage built-in agent integrations", + ), + ] { + println!(" {command:<32} {description}"); + } println!(); println!("Advanced commands:"); - println!(" {:<32} {}", "herdr server", "Run as headless server"); - println!( - " {:<32} {}", - "herdr client", "Connect to a running server as a thin client" - ); + for (command, description) in [ + ("herdr server", "Run as headless server"), + ( + "herdr client", + "Connect to a running server as a thin client", + ), + ] { + println!(" {command:<32} {description}"); + } println!(); println!("Options:"); println!(" --no-session Run monolithically (no server/client, escape hatch)"); diff --git a/src/pane.rs b/src/pane.rs index c741c04d..7d4ec729 100644 --- a/src/pane.rs +++ b/src/pane.rs @@ -602,6 +602,8 @@ impl PaneRuntime { } let pid = child_pid.load(Ordering::Acquire); + // Keep the terminal restore side effect separate from render notification state. + #[allow(clippy::collapsible_if)] if pid > 0 && terminal.maybe_restore_host_terminal_theme(pane_id, pid) { if !render_dirty.swap(true, Ordering::AcqRel) { render_notify.notify_one(); diff --git a/src/pane/state.rs b/src/pane/state.rs index aac80920..590f2025 100644 --- a/src/pane/state.rs +++ b/src/pane/state.rs @@ -104,9 +104,7 @@ impl PaneState { source: &str, agent_label: &str, ) -> Option { - let Some(current_agent_label) = self.effective_agent_label() else { - return None; - }; + let current_agent_label = self.effective_agent_label()?; if current_agent_label != agent_label { return None; } diff --git a/src/pane/terminal.rs b/src/pane/terminal.rs index 80f83915..ad040ba2 100644 --- a/src/pane/terminal.rs +++ b/src/pane/terminal.rs @@ -712,10 +712,12 @@ impl GhosttyPaneTerminal { } } +type VisibleHyperlinks = Vec<((u16, u16), String, String)>; + fn ghostty_visible_hyperlinks( core: &mut GhosttyPaneCore, area: Rect, -) -> Result, crate::ghostty::Error> { +) -> Result { let GhosttyPaneCore { terminal, render_state, diff --git a/src/raw_input.rs b/src/raw_input.rs index 6640ec29..0cf9d84b 100644 --- a/src/raw_input.rs +++ b/src/raw_input.rs @@ -39,10 +39,7 @@ pub fn parse_raw_input_bytes_with_ranges(data: &[u8]) -> Vec Vec { let mut buffer = data.to_vec(); let mut events = Vec::new(); - loop { - let Some((event, consumed)) = extract_one_event(&buffer) else { - break; - }; + while let Some((event, consumed)) = extract_one_event(&buffer) { buffer.drain(..consumed); events.push(event); } @@ -175,10 +169,7 @@ fn drain_buffer(buffer: &mut Vec, tx: &mpsc::Sender) { pub(crate) fn drain_complete_input_bytes(buffer: &mut Vec) -> Vec> { let mut chunks = Vec::new(); - loop { - let Some((_event, consumed)) = extract_one_event(buffer) else { - break; - }; + while let Some((_event, consumed)) = extract_one_event(buffer) { chunks.push(buffer[..consumed].to_vec()); buffer.drain(..consumed); } @@ -244,7 +235,7 @@ fn stdin_read_ready(_reader: &R, _timeout_ms: i32) -> Option { #[cfg(unix)] { let fd = _reader.as_raw_fd(); - return poll_read_ready(fd, _timeout_ms); + poll_read_ready(fd, _timeout_ms) } } diff --git a/src/server/client_transport.rs b/src/server/client_transport.rs index 9f5183d9..ec1adf0b 100644 --- a/src/server/client_transport.rs +++ b/src/server/client_transport.rs @@ -158,8 +158,7 @@ pub(crate) fn handle_client_handshake( encoding: render_encoding, error: None, }; - protocol::write_message(&mut stream, &welcome) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?; + protocol::write_message(&mut stream, &welcome).map_err(|e| io::Error::other(e.to_string()))?; // Clear read timeout for normal operation. stream.set_read_timeout(None)?; diff --git a/src/server/headless.rs b/src/server/headless.rs index ffcb6bbb..7bd8638a 100644 --- a/src/server/headless.rs +++ b/src/server/headless.rs @@ -612,7 +612,7 @@ impl HeadlessServer { crate::raw_input::RawInputEvent::OuterFocusLost => Some(false), _ => None, }) - .last(); + .next_back(); let Some(next_focus) = next_focus else { return; @@ -1708,7 +1708,7 @@ pub fn run_server() -> io::Result<()> { let rt = tokio::runtime::Builder::new_multi_thread() .enable_all() .build() - .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + .map_err(io::Error::other)?; let result = rt.block_on(async { // Create the App (with AppState, event channels, etc.). diff --git a/src/ui/mobile.rs b/src/ui/mobile.rs index 50cbed0c..41f29f89 100644 --- a/src/ui/mobile.rs +++ b/src/ui/mobile.rs @@ -745,11 +745,7 @@ fn render_left_scrollbar( .max(1) .min(track.height as usize) as u16; let travel = track.height.saturating_sub(thumb_len); - let thumb_top = if max_scroll == 0 { - track.y - } else { - track.y + ((travel as usize * scroll.min(max_scroll)) / max_scroll) as u16 - }; + let thumb_top = track.y + ((travel as usize * scroll.min(max_scroll)) / max_scroll) as u16; for y in track.y..track.y + track.height { let is_thumb = y >= thumb_top && y < thumb_top + thumb_len; diff --git a/src/ui/panes.rs b/src/ui/panes.rs index 82be8bf1..c4ba0c54 100644 --- a/src/ui/panes.rs +++ b/src/ui/panes.rs @@ -219,6 +219,44 @@ fn render_selection_highlight( } } +fn render_empty(app: &AppState, frame: &mut Frame, area: Rect) { + let p = &app.palette; + let lines = vec![ + Line::from(""), + Line::from(""), + Line::from(Span::styled( + " No workspaces yet", + Style::default().fg(p.overlay0), + )), + Line::from(""), + Line::from(Span::styled( + " A workspace is one project context.", + Style::default().fg(p.overlay1), + )), + Line::from(Span::styled( + " Its root pane (top-left) sets the default repo or folder name.", + Style::default().fg(p.overlay1), + )), + Line::from(""), + Line::from(vec![ + Span::styled(" Press ", Style::default().fg(p.overlay0)), + Span::styled( + app.keybinds.new_workspace_label.to_string(), + Style::default().fg(p.accent).add_modifier(Modifier::BOLD), + ), + Span::styled(" to create one", Style::default().fg(p.overlay0)), + ]), + ]; + frame.render_widget( + Paragraph::new(lines).block( + Block::default() + .borders(Borders::ALL) + .border_style(Style::default().fg(p.surface_dim)), + ), + area, + ); +} + #[cfg(test)] mod tests { use super::*; @@ -315,41 +353,3 @@ mod tests { assert_eq!(info.inner_rect, Rect::new(10, 3, 39, 8)); } } - -fn render_empty(app: &AppState, frame: &mut Frame, area: Rect) { - let p = &app.palette; - let lines = vec![ - Line::from(""), - Line::from(""), - Line::from(Span::styled( - " No workspaces yet", - Style::default().fg(p.overlay0), - )), - Line::from(""), - Line::from(Span::styled( - " A workspace is one project context.", - Style::default().fg(p.overlay1), - )), - Line::from(Span::styled( - " Its root pane (top-left) sets the default repo or folder name.", - Style::default().fg(p.overlay1), - )), - Line::from(""), - Line::from(vec![ - Span::styled(" Press ", Style::default().fg(p.overlay0)), - Span::styled( - format!("{}", app.keybinds.new_workspace_label), - Style::default().fg(p.accent).add_modifier(Modifier::BOLD), - ), - Span::styled(" to create one", Style::default().fg(p.overlay0)), - ]), - ]; - frame.render_widget( - Paragraph::new(lines).block( - Block::default() - .borders(Borders::ALL) - .border_style(Style::default().fg(p.surface_dim)), - ), - area, - ); -} diff --git a/src/ui/sidebar.rs b/src/ui/sidebar.rs index 04a3acd9..83ceebe0 100644 --- a/src/ui/sidebar.rs +++ b/src/ui/sidebar.rs @@ -32,7 +32,7 @@ fn sidebar_section_heights(total_h: u16, split_ratio: f32) -> (u16, u16) { } if total_h < 6 { - let ws_h = (total_h + 1) / 2; + let ws_h = total_h.div_ceil(2); return (ws_h, total_h.saturating_sub(ws_h)); } @@ -385,7 +385,7 @@ pub(crate) fn collapsed_sidebar_sections(area: Rect) -> (Rect, Option, Rect } let total_h = content.height as usize; - let ws_h = (total_h + 1) / 2; + let ws_h = total_h.div_ceil(2); let detail_h = total_h.saturating_sub(ws_h + 1); if ws_h == 0 || detail_h == 0 { return (content, None, Rect::default()); diff --git a/src/ui/tabs.rs b/src/ui/tabs.rs index 400ee65f..a6ce0e68 100644 --- a/src/ui/tabs.rs +++ b/src/ui/tabs.rs @@ -33,14 +33,14 @@ fn layout_tab_hit_areas(ws: &crate::workspace::Workspace, area: Rect, scroll: us let mut x = area.x; let right = area.x + area.width; - for idx in scroll..ws.tabs.len() { + for (idx, rect) in rects.iter_mut().enumerate().skip(scroll) { if x >= right { break; } let desired = tab_width(&ws.tabs[idx]); let remaining = right.saturating_sub(x); let width = desired.min(remaining).max(1); - rects[idx] = Rect::new(x, area.y, width, 1); + *rect = Rect::new(x, area.y, width, 1); x = x.saturating_add(width + 1); } rects @@ -178,14 +178,14 @@ fn tab_drop_indicator_x( ws: &crate::workspace::Workspace, insert_idx: usize, ) -> Option { - let visible_tabs = app + let mut visible_tabs = app .view .tab_hit_areas .iter() .enumerate() .filter(|(_, rect)| rect.width > 0); let first_visible = visible_tabs.clone().next()?; - let last_visible = visible_tabs.last().unwrap_or(first_visible); + let last_visible = visible_tabs.next_back().unwrap_or(first_visible); if insert_idx == 0 { return Some(if first_visible.0 == 0 { diff --git a/tests/client_mode.rs b/tests/client_mode.rs index 7b6639f9..d5a798ec 100644 --- a/tests/client_mode.rs +++ b/tests/client_mode.rs @@ -188,7 +188,6 @@ struct CursorWire { fn decode_frame_payload(payload: &[u8]) -> std::io::Result { bincode::serde::decode_from_slice(payload, bincode::config::standard()) - .map(|(frame, consumed)| (frame, consumed)) .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err.to_string())) .and_then(|(frame, consumed): (FrameWire, usize)| { if consumed != payload.len() { @@ -441,7 +440,7 @@ fn client_resize_sends_message() { stream .set_read_timeout(Some(Duration::from_secs(2))) .unwrap(); - while let Ok(_) = read_server_message(&mut stream) {} + while read_server_message(&mut stream).is_ok() {} // Send a Resize message: ClientMessage::Resize is variant 2: { cols: u16, rows: u16 } let resize_payload = { @@ -779,7 +778,7 @@ fn navigate_mode_keybind_dispatch_in_server() { stream .set_read_timeout(Some(Duration::from_secs(2))) .unwrap(); - while let Ok(_) = read_server_message(&mut stream) {} + while read_server_message(&mut stream).is_ok() {} // Send Ctrl+B (prefix key) as raw bytes. In kitty mode, Ctrl+B is 0x02. // In legacy mode, it's also 0x02 (control character). @@ -797,7 +796,7 @@ fn navigate_mode_keybind_dispatch_in_server() { stream .set_read_timeout(Some(Duration::from_millis(200))) .unwrap(); - while let Ok(_) = read_server_message(&mut stream) {} + while read_server_message(&mut stream).is_ok() {} stream.set_read_timeout(None).unwrap(); // Send 'n' (new workspace in navigate mode). @@ -897,7 +896,7 @@ fn graceful_shutdown_sends_server_shutdown_to_client() { stream .set_read_timeout(Some(Duration::from_secs(2))) .unwrap(); - while let Ok(_) = read_server_message(&mut stream) {} + while read_server_message(&mut stream).is_ok() {} // Send SIGINT to the server process to trigger graceful shutdown. if let Some(pid) = spawned.child.process_id() { @@ -995,7 +994,7 @@ fn client_receives_notify_on_agent_state_change() { stream .set_read_timeout(Some(Duration::from_secs(2))) .unwrap(); - while let Ok(_) = read_server_message(&mut stream) {} + while read_server_message(&mut stream).is_ok() {} // Create a workspace via the API. let mut ws_stream = UnixStream::connect(&api_socket).expect("connect to API"); diff --git a/tests/cross_area.rs b/tests/cross_area.rs index db7f5b1f..494b694b 100644 --- a/tests/cross_area.rs +++ b/tests/cross_area.rs @@ -518,7 +518,6 @@ struct CursorWire { fn decode_frame_payload(payload: &[u8]) -> io::Result { bincode::serde::decode_from_slice(payload, bincode::config::standard()) - .map(|(frame, consumed)| (frame, consumed)) .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string())) .and_then(|(frame, consumed): (FrameWire, usize)| { if consumed != payload.len() { diff --git a/tests/multi_client.rs b/tests/multi_client.rs index 3294c98b..582fecf7 100644 --- a/tests/multi_client.rs +++ b/tests/multi_client.rs @@ -599,7 +599,6 @@ struct CursorWire { fn decode_frame_payload(payload: &[u8]) -> io::Result { bincode::serde::decode_from_slice(payload, bincode::config::standard()) - .map(|(frame, consumed)| (frame, consumed)) .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string())) .and_then(|(frame, consumed): (FrameWire, usize)| { if consumed != payload.len() { diff --git a/tests/support/mod.rs b/tests/support/mod.rs index d26a2043..cc1fb67b 100644 --- a/tests/support/mod.rs +++ b/tests/support/mod.rs @@ -287,7 +287,7 @@ pub fn drain_messages(stream: &mut UnixStream) { stream .set_read_timeout(Some(Duration::from_millis(200))) .unwrap(); - while let Ok(_) = read_server_message(stream) {} + while read_server_message(stream).is_ok() {} stream.set_read_timeout(None).unwrap(); }