chore: enable clippy linting
This commit is contained in:
parent
3a9888cd56
commit
9f210e9738
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
too-many-arguments-threshold = 11
|
||||
12
justfile
12
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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ fn compatibility_label(protocol: Option<u32>) -> &'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} {}",
|
||||
|
|
|
|||
|
|
@ -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()))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -234,8 +234,10 @@ impl CellWide {
|
|||
}
|
||||
}
|
||||
|
||||
type WritePtyCallback = dyn FnMut(&[u8]) + Send;
|
||||
|
||||
struct WritePtyCallbackState {
|
||||
callback: Box<dyn FnMut(&[u8]) + Send>,
|
||||
callback: Box<WritePtyCallback>,
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
|
|||
97
src/main.rs
97
src/main.rs
|
|
@ -243,57 +243,56 @@ fn main() -> io::Result<()> {
|
|||
println!(" herdr integration <subcommand> ...");
|
||||
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 <subcommand>", "Workspace helpers over the socket API"
|
||||
);
|
||||
println!(
|
||||
" {:<32} {}",
|
||||
"herdr tab <subcommand>", "Tab helpers over the socket API"
|
||||
);
|
||||
println!(
|
||||
" {:<32} {}",
|
||||
"herdr pane <subcommand>", "Pane control helpers over the socket API"
|
||||
);
|
||||
println!(
|
||||
" {:<32} {}",
|
||||
"herdr wait <subcommand>", "Blocking wait helpers over the socket API"
|
||||
);
|
||||
println!(
|
||||
" {:<32} {}",
|
||||
"herdr session <subcommand>", "Manage named persistent sessions"
|
||||
);
|
||||
println!(
|
||||
" {:<32} {}",
|
||||
"herdr integration <subcommand>", "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 <subcommand>",
|
||||
"Workspace helpers over the socket API",
|
||||
),
|
||||
("herdr tab <subcommand>", "Tab helpers over the socket API"),
|
||||
(
|
||||
"herdr pane <subcommand>",
|
||||
"Pane control helpers over the socket API",
|
||||
),
|
||||
(
|
||||
"herdr wait <subcommand>",
|
||||
"Blocking wait helpers over the socket API",
|
||||
),
|
||||
(
|
||||
"herdr session <subcommand>",
|
||||
"Manage named persistent sessions",
|
||||
),
|
||||
(
|
||||
"herdr integration <subcommand>",
|
||||
"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)");
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -104,9 +104,7 @@ impl PaneState {
|
|||
source: &str,
|
||||
agent_label: &str,
|
||||
) -> Option<EffectiveStateChange> {
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -712,10 +712,12 @@ impl GhosttyPaneTerminal {
|
|||
}
|
||||
}
|
||||
|
||||
type VisibleHyperlinks = Vec<((u16, u16), String, String)>;
|
||||
|
||||
fn ghostty_visible_hyperlinks(
|
||||
core: &mut GhosttyPaneCore,
|
||||
area: Rect,
|
||||
) -> Result<Vec<((u16, u16), String, String)>, crate::ghostty::Error> {
|
||||
) -> Result<VisibleHyperlinks, crate::ghostty::Error> {
|
||||
let GhosttyPaneCore {
|
||||
terminal,
|
||||
render_state,
|
||||
|
|
|
|||
|
|
@ -39,10 +39,7 @@ pub fn parse_raw_input_bytes_with_ranges(data: &[u8]) -> Vec<RawInputEventWithRa
|
|||
let mut events = Vec::new();
|
||||
let mut offset = 0usize;
|
||||
|
||||
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(RawInputEventWithRange {
|
||||
event,
|
||||
|
|
@ -85,10 +82,7 @@ pub fn parse_raw_input_bytes_sync(data: &[u8]) -> Vec<RawInputEvent> {
|
|||
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<u8>, tx: &mpsc::Sender<RawInputEvent>) {
|
|||
pub(crate) fn drain_complete_input_bytes(buffer: &mut Vec<u8>) -> Vec<Vec<u8>> {
|
||||
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<R: AsRawFd>(_reader: &R, _timeout_ms: i32) -> Option<bool> {
|
|||
#[cfg(unix)]
|
||||
{
|
||||
let fd = _reader.as_raw_fd();
|
||||
return poll_read_ready(fd, _timeout_ms);
|
||||
poll_read_ready(fd, _timeout_ms)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)?;
|
||||
|
|
|
|||
|
|
@ -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.).
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<u16>, 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());
|
||||
|
|
|
|||
|
|
@ -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<u16> {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -188,7 +188,6 @@ struct CursorWire {
|
|||
|
||||
fn decode_frame_payload(payload: &[u8]) -> std::io::Result<FrameWire> {
|
||||
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");
|
||||
|
|
|
|||
|
|
@ -518,7 +518,6 @@ struct CursorWire {
|
|||
|
||||
fn decode_frame_payload(payload: &[u8]) -> io::Result<FrameWire> {
|
||||
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() {
|
||||
|
|
|
|||
|
|
@ -599,7 +599,6 @@ struct CursorWire {
|
|||
|
||||
fn decode_frame_payload(payload: &[u8]) -> io::Result<FrameWire> {
|
||||
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() {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue