From 4d33021cbea894e6972336d68dd8708039cd75ab Mon Sep 17 00:00:00 2001 From: Konstantin Zadorozhny Date: Thu, 22 Oct 2026 03:25:11 -0700 Subject: [PATCH] chore: enforce warnings across build configurations (#1200) * Treat Rust warnings as errors Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix deny-warnings all-target checks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Enforce warnings across build configurations Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix cross-platform deny-warnings CI Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Limit foreground argv probing to supported platforms Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix Windows warnings denied by CI Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix Windows test warnings under deny-warnings Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix Windows settings test compilation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Gate Windows-unused diff probe helper Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Migrate updater to async ZIP read1 API Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Handle invalid ZIP entry names in read1 API Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Avoid moving ZIP entry name during extraction Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * ci: deny warnings in CI only, keep local builds lenient Move -D warnings out of .cargo/config.toml so an in-progress local edit still compiles. The build & test job passes it with --config, which is appended to the Windows +crt-static rustflags instead of replacing them the way RUSTFLAGS would. Nightly and release builds no longer fail on lints; CI gates them. Restore the backdrop parameter name in theme.rs. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> --- .github/workflows/ci.yml | 16 +++-- crates/tty7-core/src/core/git/diff.rs | 2 +- crates/tty7-core/src/core/github/token.rs | 1 + crates/tty7-core/src/daemon/duplex.rs | 8 +-- crates/tty7-core/src/daemon/pane.rs | 2 + crates/tty7-core/src/daemon/remote.rs | 11 +-- crates/tty7-core/src/daemon/router.rs | 1 + crates/tty7-core/src/daemon/server.rs | 2 + .../tty7-core/src/daemon/shell_integration.rs | 1 + crates/tty7-core/src/daemon/spawn.rs | 1 + crates/tty7-core/src/host/remote.rs | 2 +- crates/tty7-core/src/host/server.rs | 4 +- src/bin/tty7-updater.rs | 68 +++++++++++-------- src/core/cli_install.rs | 10 ++- src/core/explorer_context_menu.rs | 24 +++++-- src/core/update.rs | 3 +- src/terminal/git_data.rs | 6 +- src/terminal/search.rs | 1 + src/terminal/view.rs | 2 +- src/ui/app.rs | 55 ++------------- src/ui/code_editor.rs | 2 +- src/ui/code_editor/problems.rs | 2 +- src/ui/diff_overlay.rs | 1 + src/ui/i18n/mod.rs | 9 --- src/ui/right_panel.rs | 5 +- src/ui/search/mod.rs | 1 + src/ui/settings.rs | 4 +- src/ui/settings/kit.rs | 2 - src/ui/switcher.rs | 6 +- src/ui/tab_strip.rs | 49 +------------ src/ui/theme.rs | 3 + src/ui/tray/icon.rs | 1 + tests/edns_z_flag.rs | 4 +- 33 files changed, 125 insertions(+), 184 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de003c46..0d6ef77b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -107,6 +107,12 @@ jobs: # Line tables keep file:line in backtraces but shrink the cached target dir. env: CARGO_PROFILE_DEV_DEBUG: line-tables-only + # Warnings fail CI, but not local builds: a half-finished edit should + # still compile. Passed with `--config` rather than RUSTFLAGS, because + # RUSTFLAGS replaces `.cargo/config.toml`'s Windows `+crt-static` (#902) + # while a `--config` array is appended to it. Every cargo step takes the + # same flags so the tests reuse the build instead of recompiling. + DENY_WARNINGS: target.${{ matrix.target }}.rustflags=['-D','warnings'] # Backstop under the per-step timeouts below. Without any timeout at all a # hung test runs to GitHub's six-hour job limit, which is how a 90-second # step turned into a six-hour one three times in a day. @@ -158,7 +164,7 @@ jobs: if: needs.changes.outputs.code == 'true' # Cold-cache builds have been observed at ~10 min; warm ones at ~90s. timeout-minutes: 30 - run: cargo build --locked --target ${{ matrix.target }} + run: cargo build --locked --config "${{ env.DENY_WARNINGS }}" --target ${{ matrix.target }} # A binary that imports VCRUNTIME140.dll cannot start on a Windows # machine that has never installed the Visual C++ Redistributable — the @@ -199,12 +205,12 @@ jobs: - name: Test if: needs.changes.outputs.code == 'true' timeout-minutes: 20 - run: cargo test --locked --target ${{ matrix.target }} + run: cargo test --locked --config "${{ env.DENY_WARNINGS }}" --target ${{ matrix.target }} - name: Test desktop updater if: needs.changes.outputs.code == 'true' timeout-minutes: 10 - run: cargo test --locked --features updater --bin tty7-updater --target ${{ matrix.target }} + run: cargo test --locked --config "${{ env.DENY_WARNINGS }}" --features updater --bin tty7-updater --target ${{ matrix.target }} # There is deliberately no post-mortem step here, and that is worth # recording, because an earlier version of this file had one: on failure it @@ -251,7 +257,7 @@ jobs: # rather than in [profile.release] because the release profile is shared with # the GUI builds, which keep their symbols. env: - RUSTFLAGS: -C strip=symbols + RUSTFLAGS: -D warnings -C strip=symbols steps: - uses: actions/checkout@v4 @@ -344,7 +350,7 @@ jobs: - target: x86_64-apple-darwin arch: x86_64 env: - RUSTFLAGS: -C strip=symbols + RUSTFLAGS: -D warnings -C strip=symbols steps: - uses: actions/checkout@v4 diff --git a/crates/tty7-core/src/core/git/diff.rs b/crates/tty7-core/src/core/git/diff.rs index b36c4dda..7385a183 100644 --- a/crates/tty7-core/src/core/git/diff.rs +++ b/crates/tty7-core/src/core/git/diff.rs @@ -851,7 +851,7 @@ fn unquote_path(s: &str) -> String { /// value is assembled as bytes and read back as UTF-8 at the end. fn c_unescape(s: &str) -> String { let mut bytes: Vec = Vec::with_capacity(s.len()); - let mut push_char = |bytes: &mut Vec, ch: char| { + let push_char = |bytes: &mut Vec, ch: char| { let mut buf = [0u8; 4]; bytes.extend_from_slice(ch.encode_utf8(&mut buf).as_bytes()); }; diff --git a/crates/tty7-core/src/core/github/token.rs b/crates/tty7-core/src/core/github/token.rs index d8057346..7e578d7c 100644 --- a/crates/tty7-core/src/core/github/token.rs +++ b/crates/tty7-core/src/core/github/token.rs @@ -383,6 +383,7 @@ mod tests { assert_eq!(unique.len(), c.len()); } + #[cfg(unix)] const STATUS: &str = r#"{"hosts":{"github.com":[ {"state":"success","active":true,"host":"github.com","login":"me"}, {"state":"success","active":false,"host":"github.com","login":"work"}, diff --git a/crates/tty7-core/src/daemon/duplex.rs b/crates/tty7-core/src/daemon/duplex.rs index fe82780e..bb0be068 100644 --- a/crates/tty7-core/src/daemon/duplex.rs +++ b/crates/tty7-core/src/daemon/duplex.rs @@ -1,5 +1,7 @@ use std::io; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; +#[cfg(unix)] +use std::sync::Mutex; use crate::daemon::control::LinkShutdown; @@ -170,12 +172,11 @@ fn redirect_to_null(fd: libc::c_int) -> io::Result<()> { Ok(()) } -#[cfg(test)] +#[cfg(all(test, unix))] mod tests { use super::*; use std::io::{Read as _, Write as _}; - #[cfg(unix)] #[test] fn a_unix_stream_splits_into_working_halves() { use std::os::unix::net::UnixStream; @@ -206,7 +207,6 @@ mod tests { let _ = reader.join().unwrap(); } - #[cfg(unix)] #[test] fn a_shut_stdio_writer_refuses_further_writes() { let tmp = tempfile::NamedTempFile::new().unwrap(); diff --git a/crates/tty7-core/src/daemon/pane.rs b/crates/tty7-core/src/daemon/pane.rs index 3305e080..ea3a1708 100644 --- a/crates/tty7-core/src/daemon/pane.rs +++ b/crates/tty7-core/src/daemon/pane.rs @@ -3166,6 +3166,7 @@ impl ReplayRing { /// A ring carried across a handoff, with the pane's modes folded back /// from it: `head` is the carried [`Self::head_modes`], and the pane's /// modes are that plus every byte the ring still holds. + #[cfg(any(unix, test))] fn adopted( segments: Vec, size: WinSize, @@ -3180,6 +3181,7 @@ impl ReplayRing { } /// The geometry new output would be recorded at. + #[cfg(unix)] fn tail_size(&self) -> WinSize { self.segments .back() diff --git a/crates/tty7-core/src/daemon/remote.rs b/crates/tty7-core/src/daemon/remote.rs index c6fee7b8..1d0a68b7 100644 --- a/crates/tty7-core/src/daemon/remote.rs +++ b/crates/tty7-core/src/daemon/remote.rs @@ -1,13 +1,17 @@ +#[cfg(any(target_os = "macos", target_os = "linux", test))] use std::path::Path; +#[cfg(any(target_os = "macos", target_os = "linux", test))] use crate::daemon::protocol::{RemoteContext, RemoteKind}; +#[cfg(any(target_os = "macos", target_os = "linux", test))] #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct SshInvocation { pub context: RemoteContext, pub forward_args: Vec, } +#[cfg(any(target_os = "macos", target_os = "linux", test))] pub(crate) fn parse_ssh_invocation(argv: &[String]) -> Option { let program = argv.first()?; let name = Path::new(program).file_name()?.to_string_lossy(); @@ -95,6 +99,7 @@ pub(crate) fn parse_ssh_invocation(argv: &[String]) -> Option { }) } +#[cfg(any(target_os = "macos", target_os = "linux", test))] fn option_takes_value(flag: char) -> bool { matches!( flag, @@ -121,6 +126,7 @@ fn option_takes_value(flag: char) -> bool { ) } +#[cfg(any(target_os = "macos", target_os = "linux"))] pub(crate) fn foreground_argv(pid: i32) -> Option> { platform_foreground_argv(pid) } @@ -210,11 +216,6 @@ fn parse_macos_procargs(buf: &[u8]) -> Option> { (!argv.is_empty()).then_some(argv) } -#[cfg(not(any(target_os = "linux", target_os = "macos")))] -fn platform_foreground_argv(_pid: i32) -> Option> { - None -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/tty7-core/src/daemon/router.rs b/crates/tty7-core/src/daemon/router.rs index 43457707..57314f08 100644 --- a/crates/tty7-core/src/daemon/router.rs +++ b/crates/tty7-core/src/daemon/router.rs @@ -1018,6 +1018,7 @@ mod tests { assert!(routed.join().unwrap().is_err()); } + #[cfg(unix)] fn responder_lock() -> std::sync::MutexGuard<'static, ()> { static LOCK: Mutex<()> = Mutex::new(()); LOCK.lock().unwrap_or_else(|e| e.into_inner()) diff --git a/crates/tty7-core/src/daemon/server.rs b/crates/tty7-core/src/daemon/server.rs index 333b91b4..81b9af25 100644 --- a/crates/tty7-core/src/daemon/server.rs +++ b/crates/tty7-core/src/daemon/server.rs @@ -31,6 +31,7 @@ impl Registry { /// Reusing a number would be worse than skipping one: a client that /// reconnects after a handoff is still holding the old ids, and an `Attach` /// for one of them has to find the pane it means or nothing at all. + #[cfg(unix)] fn claim_ids_past(&self, next: u64) { self.next_id.fetch_max(next, Ordering::Relaxed); } @@ -659,6 +660,7 @@ fn hand_over(_registry: &Registry, _exe: &std::path::Path) -> anyhow::Error { } /// What a pane does to the registry when its shell dies. +#[cfg(unix)] fn reaper(registry: Arc, id: u64) -> impl FnOnce() + Send + 'static { move || { std::thread::Builder::new() diff --git a/crates/tty7-core/src/daemon/shell_integration.rs b/crates/tty7-core/src/daemon/shell_integration.rs index 6fc1e299..774e22d7 100644 --- a/crates/tty7-core/src/daemon/shell_integration.rs +++ b/crates/tty7-core/src/daemon/shell_integration.rs @@ -2527,6 +2527,7 @@ mod tests { } /// The OSC 0 title the shell settled on, as `user@host:dir`. + #[cfg(unix)] fn reported_title(text: &str) -> String { text.split("\u{1b}]") .filter_map(|s| s.strip_prefix("0;")) diff --git a/crates/tty7-core/src/daemon/spawn.rs b/crates/tty7-core/src/daemon/spawn.rs index b6fc87f8..1e23d412 100644 --- a/crates/tty7-core/src/daemon/spawn.rs +++ b/crates/tty7-core/src/daemon/spawn.rs @@ -1424,6 +1424,7 @@ fn detect_parent_shell() -> Option { None } +#[cfg(any(unix, test))] fn is_supported_shell(path: &Path) -> bool { let Some(name) = path.file_name().and_then(|name| name.to_str()) else { return false; diff --git a/crates/tty7-core/src/host/remote.rs b/crates/tty7-core/src/host/remote.rs index 9826b2f6..af5d5bff 100644 --- a/crates/tty7-core/src/host/remote.rs +++ b/crates/tty7-core/src/host/remote.rs @@ -488,7 +488,7 @@ fn drain_git_stream( loop { match next(idle) { Ok(GitStreamMsg::Chunk(bytes)) => { - let _ = queued.fetch_update(Ordering::AcqRel, Ordering::Acquire, |q| { + let _ = queued.try_update(Ordering::AcqRel, Ordering::Acquire, |q| { Some(q.saturating_sub(bytes.len())) }); split.push(&bytes, &mut *on_line); diff --git a/crates/tty7-core/src/host/server.rs b/crates/tty7-core/src/host/server.rs index 58fc967d..67aab785 100644 --- a/crates/tty7-core/src/host/server.rs +++ b/crates/tty7-core/src/host/server.rs @@ -1,6 +1,8 @@ use std::collections::{HashMap, VecDeque}; use std::io::{self, Read, Write}; -use std::path::{Path, PathBuf}; +#[cfg(unix)] +use std::path::Path; +use std::path::PathBuf; use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering}; use std::sync::{Arc, Condvar, Mutex}; use std::time::Duration; diff --git a/src/bin/tty7-updater.rs b/src/bin/tty7-updater.rs index 2cc96581..8a83fa45 100644 --- a/src/bin/tty7-updater.rs +++ b/src/bin/tty7-updater.rs @@ -2701,13 +2701,24 @@ mod windows { } let bytes = fs::read(archive).map_err(|error| format!("reading {}: {error}", archive.display()))?; - let archive = smol::block_on(async_zip::base::read::mem::ZipFileReader::new(bytes)) - .map_err(|error| format!("opening the portable ZIP: {error}"))?; - let entries = archive.file().entries(); - if entries.len() > MAX_PORTABLE_ENTRIES { + let options = async_zip::base::read1::ZipOptions { + max_cd_num_files: (MAX_PORTABLE_ENTRIES + 1) as u64, + max_cd_num_files_load: (MAX_PORTABLE_ENTRIES + 1) as u64, + max_uncompressed_size_per_file: MAX_PORTABLE_EXPANDED_BYTES, + max_compressed_size_per_file: MAX_PORTABLE_EXPANDED_BYTES, + ..async_zip::base::read1::ZipOptions::untrusted() + }; + let mut archive = smol::block_on( + async_zip::base::read1::seek::ZipArchiveReader::open_with_options( + smol::io::Cursor::new(bytes), + options, + ), + ) + .map_err(|error| format!("opening the portable ZIP: {error}"))?; + if archive.cdrs().len() > MAX_PORTABLE_ENTRIES { return Err(format!( "the portable ZIP has {} entries; the limit is {MAX_PORTABLE_ENTRIES}", - entries.len() + archive.cdrs().len() )); } fs::create_dir(payload) @@ -2715,23 +2726,33 @@ mod windows { let mut seen = HashSet::new(); let mut expanded_bytes = 0u64; - for (index, entry) in entries.iter().enumerate() { - let name = entry - .filename() - .as_str() - .map_err(|error| format!("reading portable ZIP entry {index} name: {error}"))?; + for index in 0..archive.cdrs().len() { + let (name, unix_mode, expected_size, is_directory) = { + let entry = &archive.cdrs()[index]; + let name = entry + .insecure_file_name + .as_str() + .ok_or_else(|| { + format!("reading portable ZIP entry {index} name: name is not UTF-8") + })? + .to_owned(); + let unix_mode = (entry.cdrh.v_made_by >> 8 == 3) + .then_some((entry.cdrh.exter_attr >> 16) as u16); + let expected_size = entry + .uncompressed_size() + .map_err(|error| format!("reading portable ZIP entry {name} size: {error}"))?; + let is_directory = name.ends_with('/'); + (name, unix_mode, expected_size, is_directory) + }; if name.contains('\\') { return Err(format!( "the portable ZIP path uses a non-canonical separator: {name}" )); } - if entry - .unix_permissions() - .is_some_and(|mode| mode & 0o170000 == 0o120000) - { + if unix_mode.is_some_and(|mode| mode & 0o170000 == 0o120000) { return Err(format!("the portable ZIP contains a symbolic link: {name}")); } - let relative = PathBuf::from(name); + let relative = PathBuf::from(name.as_str()); let key = portable_path_key(&relative)?; if !seen.insert(key) { return Err(format!( @@ -2741,7 +2762,7 @@ mod windows { } validate_portable_relative_path(&relative)?; expanded_bytes = expanded_bytes - .checked_add(entry.uncompressed_size()) + .checked_add(expected_size) .ok_or_else(|| "the portable ZIP expanded-size total overflowed".to_string())?; if expanded_bytes > MAX_PORTABLE_EXPANDED_BYTES { return Err(format!( @@ -2751,11 +2772,8 @@ mod windows { } let output = payload.join(&relative); - let is_directory = entry - .dir() - .map_err(|error| format!("reading portable ZIP entry {name}: {error}"))?; if is_directory { - if entry.uncompressed_size() != 0 { + if expected_size != 0 { return Err(format!( "the portable ZIP directory entry has file data: {name}" )); @@ -2773,10 +2791,8 @@ mod windows { .create_new(true) .open(&output) .map_err(|error| format!("creating {}: {error}", output.display()))?; - let mut entry_reader = smol::block_on(archive.reader_with_entry(index)) + let mut entry_reader = smol::block_on(archive.file(index)) .map_err(|error| format!("opening portable ZIP entry {name}: {error}"))?; - let expected_size = entry.uncompressed_size(); - let expected_crc = entry.crc32(); smol::block_on(async { let mut buffer = [0u8; 64 * 1024]; let mut written = 0u64; @@ -2805,12 +2821,6 @@ mod windows { "the portable ZIP entry size is {written}, expected {expected_size}: {name}" )); } - let actual_crc = entry_reader.compute_hash(); - if actual_crc != expected_crc { - return Err(format!( - "the portable ZIP entry failed CRC32 verification: {name}" - )); - } Ok(()) })?; } diff --git a/src/core/cli_install.rs b/src/core/cli_install.rs index b8b05fe6..c749f5f0 100644 --- a/src/core/cli_install.rs +++ b/src/core/cli_install.rs @@ -47,12 +47,14 @@ pub enum Outcome { /// Freshly linked (or copied, under AppImage) into a directory on PATH. Installed(PathBuf), /// Installed somewhere the user's PATH does not currently cover. + #[cfg(unix)] InstalledOffPath(PathBuf), /// Installed, but an earlier PATH entry holds a different `tty7` that keeps /// winning the lookup. InstalledShadowed { ours: PathBuf, winner: PathBuf }, /// Every directory we would write to is already taken by someone else's /// `tty7`, and none of them is ours to move. + #[cfg(unix)] Occupied(PathBuf), /// Nowhere to write. Failed(String), @@ -81,6 +83,7 @@ pub fn install(enabled: bool) -> Outcome { } Outcome::AlreadyInstalled(p) => log::debug!("tty7 CLI already on PATH at {}", p.display()), Outcome::Installed(p) => log::info!("put the tty7 CLI on PATH at {}", p.display()), + #[cfg(unix)] Outcome::InstalledOffPath(p) => log::warn!( "installed the tty7 CLI at {}, which is not on your PATH — add it to use `tty7` \ outside a tty7 pane", @@ -92,6 +95,7 @@ pub fn install(enabled: bool) -> Outcome { ours.display(), winner.display() ), + #[cfg(unix)] Outcome::Occupied(p) => log::info!( "leaving the existing `tty7` at {} alone; the bundled CLI was not installed", p.display() @@ -140,9 +144,9 @@ fn install_inner(enabled: bool) -> Outcome { // never has a reason to say `Occupied` — but an existing one earlier on // PATH still beats it, and "installed" alone would be a lie. let ours = match &outcome { - Outcome::AlreadyInstalled(p) | Outcome::Installed(p) | Outcome::InstalledOffPath(p) => { - p.clone() - } + Outcome::AlreadyInstalled(p) | Outcome::Installed(p) => p.clone(), + #[cfg(unix)] + Outcome::InstalledOffPath(p) => p.clone(), _ => return outcome, }; match first_cli_on(&user_path) { diff --git a/src/core/explorer_context_menu.rs b/src/core/explorer_context_menu.rs index 7c375507..9be6a823 100644 --- a/src/core/explorer_context_menu.rs +++ b/src/core/explorer_context_menu.rs @@ -12,22 +12,33 @@ //! running GUI through `GuiOpen`, then continues normal startup when no GUI //! receives it. +#[cfg(any(windows, test))] use std::ffi::OsString; -use std::path::{Path, PathBuf}; +#[cfg(any(windows, test))] +use std::path::Path; +#[cfg(windows)] +use std::path::PathBuf; -use anyhow::{Context as _, Result}; +#[cfg(windows)] +use anyhow::Context as _; +use anyhow::Result; +#[cfg(any(windows, test))] use crate::ui::i18n::{L10nKey, t}; +#[cfg(any(windows, test))] const DIRECTORY_KEY: &str = r"Software\Classes\Directory\shell\tty7"; +#[cfg(any(windows, test))] const BACKGROUND_KEY: &str = r"Software\Classes\Directory\Background\shell\tty7"; +#[cfg(any(windows, test))] #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum Location { Directory, Background, } +#[cfg(any(windows, test))] impl Location { fn key(self) -> &'static str { match self { @@ -61,6 +72,7 @@ impl Location { } } +#[cfg(any(windows, test))] #[derive(Debug)] struct Registration { location: Location, @@ -68,6 +80,7 @@ struct Registration { command: OsString, } +#[cfg(any(windows, test))] fn replace_entry_with( registration: &Registration, delete: impl FnOnce(&str) -> Result<()>, @@ -80,6 +93,7 @@ fn replace_entry_with( write(registration) } +#[cfg(any(windows, test))] impl Registration { fn new(location: Location, app: &Path) -> Self { Self { @@ -124,6 +138,7 @@ pub fn refresh_labels() { /// Quotes are unconditional: both the executable and the Explorer-substituted /// folder can contain spaces. Windows file names cannot contain a quote, so the /// resulting command is unambiguous without an additional escape layer. +#[cfg(any(windows, test))] fn quoted_open_command(executable: &Path, placeholder: &str) -> OsString { let mut command = OsString::from("\""); command.push(executable.as_os_str()); @@ -133,6 +148,7 @@ fn quoted_open_command(executable: &Path, placeholder: &str) -> OsString { command } +#[cfg(windows)] fn application_path() -> Result { let app = std::env::current_exe().context("locating the running tty7 application")?; app.parent() @@ -140,6 +156,7 @@ fn application_path() -> Result { Ok(app) } +#[cfg(any(windows, test))] fn registrations(app: &Path) -> [Registration; 2] { [ Registration::new(Location::Directory, app), @@ -158,8 +175,7 @@ mod windows { }; use windows_sys::Win32::System::Registry::{ HKEY, HKEY_CURRENT_USER, KEY_READ, KEY_WRITE, REG_OPTION_NON_VOLATILE, REG_SZ, RegCloseKey, - RegCreateKeyExW, RegDeleteTreeW, RegOpenKeyExW, RegQueryInfoKeyW, RegQueryValueExW, - RegSetValueExW, + RegCreateKeyExW, RegDeleteTreeW, RegOpenKeyExW, RegSetValueExW, }; use windows_sys::Win32::UI::Shell::{SHCNE_ASSOCCHANGED, SHCNF_IDLIST, SHChangeNotify}; diff --git a/src/core/update.rs b/src/core/update.rs index 40069e81..38b5db21 100644 --- a/src/core/update.rs +++ b/src/core/update.rs @@ -151,7 +151,7 @@ pub enum UpdateInstallHint { impl UpdateInstallHint { /// The reason in plain English, pinned by tests. Anything a user reads /// goes through `localized_update_install_hint` instead (#602). - #[cfg_attr(not(test), allow(dead_code))] + #[cfg(all(test, target_os = "windows"))] fn english(&self) -> String { match self { #[cfg(target_os = "macos")] @@ -2123,6 +2123,7 @@ struct PackageOffer { } impl PackageOffer { + #[cfg(any(target_os = "macos", target_os = "linux", test))] fn plain(name: String) -> Self { Self { name, diff --git a/src/terminal/git_data.rs b/src/terminal/git_data.rs index 51f5e837..4ae6670d 100644 --- a/src/terminal/git_data.rs +++ b/src/terminal/git_data.rs @@ -402,6 +402,7 @@ impl ScmData { /// there". A directory that is not a repository is a perfectly normal /// thing for a pane to be sitting in, and without somewhere to record the /// negative answer the next look asks again — every frame. + #[cfg(test)] pub fn known_status( &self, host: HostId, @@ -411,11 +412,6 @@ impl ScmData { Some(self.status_for(host, root)) } - /// Whether this root has been read at all, whatever the answer was. - pub fn probed(&self, host: HostId, root: &Path) -> bool { - self.read_at.get(host, root).is_some() - } - pub fn epoch(&self, host: HostId, root: &Path) -> u64 { self.epoch.get(host, root).copied().unwrap_or(0) } diff --git a/src/terminal/search.rs b/src/terminal/search.rs index a034a6a4..1eb2e823 100644 --- a/src/terminal/search.rs +++ b/src/terminal/search.rs @@ -954,6 +954,7 @@ pub(super) struct LinkRoots { impl LinkRoots { /// Roots on the machine tty7 is running on, where `$HOME` means what it /// says and paths are spelled this OS's way. + #[cfg(test)] pub fn local(dirs: Vec) -> Self { Self { dirs, diff --git a/src/terminal/view.rs b/src/terminal/view.rs index 9f5a41d0..b8a6f3cf 100644 --- a/src/terminal/view.rs +++ b/src/terminal/view.rs @@ -615,7 +615,7 @@ pub(super) enum LoopbackPlan { /// reachable once a forward exists, and building that forward is something /// this app already knows how to do. Answering only "local or not" is what /// left the Ports list showing a port it then refused to open. -pub(crate) fn port_route_of(plan: &LoopbackPlan, local: bool) -> PortRoute { +fn port_route_of(plan: &LoopbackPlan, local: bool) -> PortRoute { match plan { // WSL shares this machine's loopback, so its ports are already here // under the same number. diff --git a/src/ui/app.rs b/src/ui/app.rs index 32ecd518..2078dc11 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -20,7 +20,7 @@ use crate::core::session::{ use crate::core::shells::ShellInventory; use crate::core::ssh_config; use crate::core::window_state::{WindowGeometry as _, WindowState}; -use crate::daemon::protocol::{RemoteContext, RemoteKind, ShellSpec, ssh_option_takes_value}; +use crate::daemon::protocol::{RemoteKind, ShellSpec, ssh_option_takes_value}; use crate::daemon::spawn::DaemonMismatch; use crate::terminal::view::{ChildExited, TerminalView}; use crate::ui::forwards::{ForwardFields, added_forward, rule_of}; @@ -287,6 +287,7 @@ fn title_strip_rule_and_pad(rule: gpui::Pixels) -> (gpui::Pixels, gpui::Pixels) /// The centre of the box a row built by [`title_strip_row`] gives its contents: /// the bar's height, less whatever its bottom edge spends on rule and padding. +#[cfg(test)] fn title_strip_content_centre(rule: gpui::Pixels) -> gpui::Pixels { let (border, pad) = title_strip_rule_and_pad(rule); gpui::px((TITLE_BAR_HEIGHT - border.as_f32() - pad.as_f32()) / 2.) @@ -362,10 +363,6 @@ pub(crate) fn tile_trailing_inset() -> f32 { (CONTENT_INSET - TILE_PAD).max(TILE_EDGE_GAP) } -pub(crate) fn tile_trailing_inset_sm() -> f32 { - (CONTENT_INSET - TILE_PAD_SM).max(TILE_EDGE_GAP) -} - pub(crate) const TITLE_BAR_LEAD: f32 = if cfg!(target_os = "macos") { crate::ui::theme::TRAFFIC_LIGHTS_LEAD } else { @@ -762,18 +759,6 @@ impl Tab { /// [`Self::leaf_title`] together with what a `~` in it would mean — one /// leaf lookup, so the title and the home shortening it can never come /// from different panes (#580). - pub(crate) fn leaf_title_and_home( - &self, - window: Option<&Window>, - cx: &App, - ) -> (String, Option) { - let Some(leaf) = self.title_leaf(window, cx) else { - return (String::new(), None); - }; - let leaf = leaf.read(cx); - (leaf.title.clone(), leaf.display_home(cx)) - } - /// This tab as the shared label ladder reads it, together with what a `~` /// in whatever it ends up named would mean. /// @@ -1554,6 +1539,7 @@ impl Tty7App { .detach(); } + #[cfg(test)] pub(crate) fn with_session( workspace: Option, session: Option, @@ -3522,7 +3508,7 @@ impl Tty7App { // typed one number to forward one port has not been asked to care // which local port it lands on, and the row says where it came out. let retry_free_port = !fields.advanced && rule.bind_port != 0; - let mut failure = match self.place_forward(&route, rule.clone()) { + let failure = match self.place_forward(&route, rule.clone()) { PlaceOutcome::Placed => None, // Nobody answered, so nothing was bound and nothing would be bound // by asking again — a second round trip would only spend another @@ -8526,39 +8512,6 @@ impl Tty7App { self.leaf_close_reason(&leaf, cx) } - pub(crate) fn active_ssh_pane( - &self, - window: &Window, - cx: &App, - ) -> Option<(u64, RemoteContext)> { - let pane = self - .tabs - .get(self.active)? - .pane - .focused_or_first(window, cx)?; - let pane = pane.read(cx); - let remote = pane.remote_context()?; - (remote.kind != crate::daemon::protocol::RemoteKind::Wsl).then_some((pane.pane_id, remote)) - } - - pub(crate) fn active_connected_native_ssh_pane( - &self, - window: &Window, - cx: &App, - ) -> Option<(u64, RemoteContext)> { - use crate::daemon::protocol::{RemoteKind, SshPhase}; - let (pane_id, remote) = self.active_ssh_pane(window, cx)?; - if remote.kind != RemoteKind::NativeSsh { - return None; - } - let leaf = self - .tabs - .get(self.active)? - .pane - .focused_or_first(window, cx)?; - matches!(leaf.read(cx).ssh_phase(), Some(SshPhase::Connected)).then_some((pane_id, remote)) - } - pub(crate) fn select_settings_section( &mut self, target: SettingsSection, diff --git a/src/ui/code_editor.rs b/src/ui/code_editor.rs index 2b35078f..83c36687 100644 --- a/src/ui/code_editor.rs +++ b/src/ui/code_editor.rs @@ -4127,7 +4127,7 @@ mod close_gpui_tests { /// holds the focus as it is drawn. fn settle(vcx: &mut VisualTestContext) { vcx.run_until_parked(); - vcx.update(|window, cx| window.draw(cx)); + let _ = vcx.update(|window, cx| window.draw(cx)); vcx.run_until_parked(); } diff --git a/src/ui/code_editor/problems.rs b/src/ui/code_editor/problems.rs index a28b93bb..cba939ac 100644 --- a/src/ui/code_editor/problems.rs +++ b/src/ui/code_editor/problems.rs @@ -405,7 +405,7 @@ mod tests { app.toggle_editor_problems(cx); }); vcx.run_until_parked(); - vcx.update(|window, cx| window.draw(cx)); + let _ = vcx.update(|window, cx| window.draw(cx)); app.update_in(&mut vcx, |app, window, cx| { assert!(app.editor.problems.open); assert!(app.render_editor_problems(window, cx).is_some()); diff --git a/src/ui/diff_overlay.rs b/src/ui/diff_overlay.rs index 1c1dd291..bd25917b 100644 --- a/src/ui/diff_overlay.rs +++ b/src/ui/diff_overlay.rs @@ -1186,6 +1186,7 @@ pub(crate) mod row_probe { } /// The count since the last call, and zero from here. + #[cfg(not(target_os = "windows"))] pub(crate) fn take() -> u64 { BUILT.replace(0) } diff --git a/src/ui/i18n/mod.rs b/src/ui/i18n/mod.rs index 508d6144..6ef0a938 100644 --- a/src/ui/i18n/mod.rs +++ b/src/ui/i18n/mod.rs @@ -1977,15 +1977,6 @@ pub fn t_plural(key: L10nKey, count: usize, args: &[(&str, &str)]) -> String { ) } -/// Select a named branch of a translation and fill placeholders. -pub fn t_select(key: L10nKey, branch: &'static str, args: &[(&str, &str)]) -> String { - apply_template( - translate_variant(current_locale_index(), key, branch), - args, - None, - ) -} - fn apply_template(template: &'static str, args: &[(&str, &str)], count: Option) -> String { let mut text = template.to_string(); if let Some(n) = count { diff --git a/src/ui/right_panel.rs b/src/ui/right_panel.rs index 08693730..afdcfa83 100644 --- a/src/ui/right_panel.rs +++ b/src/ui/right_panel.rs @@ -1896,10 +1896,7 @@ impl Tty7App { // could not be asked, which is not the same as an empty // answer — see `Host::pane_procs`. let procs = match &host { - Some(host) => { - use crate::ui::host_ops::Host as _; - host.pane_procs(pane_id) - } + Some(host) => host.pane_procs(pane_id), None => Some(crate::terminal::RemoteTerminal::query_procs(pane_id)), }; let managed = route.map(|r| r.list()).unwrap_or_default(); diff --git a/src/ui/search/mod.rs b/src/ui/search/mod.rs index 91a76d1d..fea7eaf9 100644 --- a/src/ui/search/mod.rs +++ b/src/ui/search/mod.rs @@ -101,6 +101,7 @@ impl SearchTab { } /// The neighbouring tab, wrapping at the ends. + #[cfg(test)] pub(crate) fn step(self, forward: bool) -> SearchTab { let n = Self::ORDER.len(); let i = Self::ORDER.iter().position(|t| *t == self).unwrap_or(0); diff --git a/src/ui/settings.rs b/src/ui/settings.rs index 9bf41d31..bb831d7f 100644 --- a/src/ui/settings.rs +++ b/src/ui/settings.rs @@ -3381,7 +3381,7 @@ mod tests { #[test] fn previously_unsearchable_settings_are_findable() { use SettingsSection::*; - let mut cases: Vec<(&str, SettingsSection)> = vec![ + let cases: Vec<(&str, SettingsSection)> = vec![ ("opacity", Appearance), ("blur", Appearance), ("completion", Terminal), @@ -3413,7 +3413,7 @@ mod tests { ("image opacity", Appearance), ]; #[cfg(target_os = "windows")] - cases.extend([ + let cases = cases.into_iter().chain([ ("material", Appearance), ("mica", Appearance), ("acrylic", Appearance), diff --git a/src/ui/settings/kit.rs b/src/ui/settings/kit.rs index db5cad87..6f4a7e14 100644 --- a/src/ui/settings/kit.rs +++ b/src/ui/settings/kit.rs @@ -34,7 +34,6 @@ pub(crate) struct Tk { pub k04: Hsla, pub k05: Hsla, pub k06: Hsla, - pub k07: Hsla, pub k08: Hsla, pub k15: Hsla, pub k3: Hsla, @@ -89,7 +88,6 @@ impl Tk { k04: a(0.04, 0.055), k05: a(0.05, 0.07), k06: a(0.06, 0.08), - k07: a(0.07, 0.09), k08: a(0.08, 0.08), k15: a(0.15, 0.16), k3: a(0.3, 0.3), diff --git a/src/ui/switcher.rs b/src/ui/switcher.rs index 1cedeb81..7ed50a3e 100644 --- a/src/ui/switcher.rs +++ b/src/ui/switcher.rs @@ -4457,12 +4457,11 @@ mod gpui_tests { }); } - /// One of the three places on the card a test wants to put the pointer. + /// One of the places on the card a test wants to put the pointer. #[derive(Clone, Copy)] enum Spot { Workspaces, Tabs, - Search, } /// Where that part of the card lands on screen. The card is centred and @@ -4479,9 +4478,6 @@ mod gpui_tests { let left_w = super::LEFT_W.min(card_w * 0.5); let card_left = (viewport.width.as_f32() - card_w) / 2.; let (dx, dy) = match spot { - // The search row is the first thing in the card; both columns - // start below it. - Spot::Search => (100., 20.), Spot::Workspaces => (20., super::SEARCH_H + super::COLUMN_PAD + 16.), // Past the tab column's own header row, onto its first tab. Spot::Tabs => ( diff --git a/src/ui/tab_strip.rs b/src/ui/tab_strip.rs index 9434fd1d..3df50dfa 100644 --- a/src/ui/tab_strip.rs +++ b/src/ui/tab_strip.rs @@ -55,9 +55,8 @@ fn shell_spec(shell: &DetectedShell) -> ShellSpec { /// same titles. pub(crate) use tty7_core::core::tab_view::strip_host_prefix; -/// `home` is the home directory of the machine `path` is on, from -/// [`Tab::leaf_title_and_home`](crate::ui::app::Tab::leaf_title_and_home) or -/// the workspace's host; `None` leaves the path spelled out (#580). +/// `home` is the home directory of the machine `path` is on, from the +/// workspace's host; `None` leaves the path spelled out (#580). pub(crate) fn abbreviate_home<'a>( path: &'a str, home: Option<&std::path::Path>, @@ -1294,52 +1293,8 @@ pub(crate) fn agent_status_label( } pub(crate) const LIVE_DOT: u32 = 0x22C55E; - pub(crate) const UNKNOWN_DOT: u32 = 0x9AA0A6; -pub(crate) fn workspace_avatar( - name: &str, - live: crate::terminal::pane_liveness::Liveness, - size: f32, - cx: &App, -) -> impl IntoElement + use<> { - use crate::terminal::pane_liveness::Liveness; - let dot = match live { - Liveness::Alive => Some(LIVE_DOT), - Liveness::Unknown => Some(UNKNOWN_DOT), - Liveness::Stopped => None, - }; - let initial: String = name - .chars() - .next() - .map(|c| c.to_uppercase().to_string()) - .unwrap_or_else(|| "~".to_string()); - // The disc reads the same on every row, current one included: the rows that - // are the current workspace already say so with a badge, a heavier name and - // a selected background, and dimming the disc on top of that only pushed the - // monogram under the liveness dot beside it, which is never dimmed. - div() - .relative() - .flex_shrink_0() - .size(px(size)) - .child( - div() - .size(px(size)) - .rounded_full() - .bg(cx.theme().secondary) - .flex() - .items_center() - .justify_center() - .text_size(px((size * 0.46).round())) - .font_weight(FontWeight::MEDIUM) - .text_color(cx.theme().foreground.opacity(0.65)) - .child(initial), - ) - .children( - dot.map(|rgb| Tty7App::status_dot(rgb, 0, size, cx.theme().popover, false, false)), - ) -} - pub(crate) fn select_workspace_action(index: usize) -> Option> { Some(match index { 0 => Box::new(SelectWorkspace1) as Box, diff --git a/src/ui/theme.rs b/src/ui/theme.rs index 85b29993..3a52e052 100644 --- a/src/ui/theme.rs +++ b/src/ui/theme.rs @@ -319,6 +319,7 @@ pub(crate) const SYSTEM_MATERIAL_OPACITY: f32 = 0.82; /// 11 22H2 (build 22621), acrylic needs 1809 (build 17763); below that plain /// translucency is the best Windows can do. Pure function so the fallback /// table is unit-testable on every platform. +#[cfg(any(target_os = "windows", test))] pub(crate) fn windows_background_appearance( backdrop: WindowBackdrop, blur: bool, @@ -379,6 +380,7 @@ pub(crate) fn windows_background_appearance( /// render identically — hiding them beats misrepresenting them. Below 1809 /// (17763) there is no blur API at all, so only plain translucency is left. /// Pure function so the table is unit-testable on every platform. +#[cfg(any(target_os = "windows", test))] pub(crate) fn supported_backdrops_for(build: u32) -> &'static [WindowBackdrop] { if build >= 22_621 { &[ @@ -428,6 +430,7 @@ pub(crate) fn windows_build_number() -> u32 { }) } +#[cfg_attr(not(target_os = "windows"), allow(unused_variables))] pub(crate) fn resolved_background_appearance( backdrop: WindowBackdrop, blur: bool, diff --git a/src/ui/tray/icon.rs b/src/ui/tray/icon.rs index c3694b6f..85f3ab67 100644 --- a/src/ui/tray/icon.rs +++ b/src/ui/tray/icon.rs @@ -208,6 +208,7 @@ fn badge(pixmap: &mut tiny_skia::Pixmap) { #[cfg(target_os = "linux")] pub(super) fn render_argb(attention: bool) -> Option<(Vec, u32)> { let img = render(attention)?; + debug_assert_eq!(img.width, img.height); let mut argb = Vec::with_capacity(img.data.len()); for px in img.data.chunks_exact(4) { argb.extend_from_slice(&[px[3], px[0], px[1], px[2]]); diff --git a/tests/edns_z_flag.rs b/tests/edns_z_flag.rs index 47a5e898..1e4c79d0 100644 --- a/tests/edns_z_flag.rs +++ b/tests/edns_z_flag.rs @@ -5,7 +5,7 @@ use std::net::{Ipv4Addr, SocketAddr, UdpSocket}; use std::time::Duration; -use iroh::dns::{DnsProtocol, DnsResolver}; +use iroh::dns::{DnsResolver, NameserverConfig}; /// Answers one A query for `answer`, with an EDNS OPT record whose TTL is /// `opt_ttl`: [extended RCODE][version][DO + Z flags], per RFC 6891. @@ -44,7 +44,7 @@ async fn resolve_with_opt_ttl(opt_ttl: u32) -> Result, Str // No fallback: a public resolver answering instead would hide the bug. let resolver = DnsResolver::builder() - .with_nameserver(addr, DnsProtocol::Udp) + .add_nameserver_config(NameserverConfig::udp(addr.ip()).with_port(addr.port())) .disable_fallback() .build(); let result = resolver