refactor(core): pub(crate) for the crate's own types

Last of the three item kinds. 48 structs, enums and traits were `pub` with
no name outside tty7-core, and a `pub` type hides more than a function does:
its fields and variants are invisible to the lint too.

Nine more items surfaced, and the same split as before -- most are used only
by tests and take the house idiom; three are unreached here and carry the
reason instead. `absorb` and its neighbours `len`/`is_empty` on
GitignoreChain are simply spare: the matcher sets are built whole rather than
merged.

Most of the work was the compiler correcting me, and it corrected two things
a search could not:

  - A type can be *used* without its name ever appearing -- through inference,
    a method's return, a chain. `AgentEvent`, `ManagedWorktree`,
    `ProfileUsage`, `RouteAction`, `WorkingDirectory` and `KiPrompt` are all
    named nowhere outside this crate and all needed by it.
  - Reachability is not just signatures. `AgentEventKind` sits behind a public
    *field*, `Duplex` and `PaneDirectory` behind public bounds, and `Halves`
    behind an associated type. Those are the `private_interfaces` lints, and
    only `cargo clippy` reports them -- `cargo build` was clean while five of
    them stood.

So the rule for anyone repeating this: narrow, then let the compiler put back
what it must, and read clippy rather than build.
This commit is contained in:
l0ng-ai
2026-08-15 22:46:46 +08:00
parent d22cc39a8e
commit c24ef21472
19 changed files with 70 additions and 48 deletions
+3 -2
View File
@@ -203,7 +203,7 @@ pub struct CommitPage {
/// row already on screen, which is only possible because a row says nothing
/// about the rows below it — see [`Edge`].
#[derive(Default)]
pub struct LaneAlloc {
pub(crate) struct LaneAlloc {
/// Per lane, the oid that lane is currently waiting for. `None` is free.
slots: Vec<Option<Oid>>,
/// The reverse index. A `SmallVec` because one child is the common case
@@ -245,6 +245,7 @@ impl LaneAlloc {
/// How many columns are live right now, i.e. one past the rightmost lane in
/// use. Lanes are never compacted, so this only shrinks when the rightmost
/// lane itself dies.
#[cfg_attr(not(test), allow(dead_code))]
pub fn width(&self) -> Lane {
self.slots
.iter()
@@ -406,7 +407,7 @@ const LOG_FIELDS: usize = 11;
pub(crate) const REF_FORMAT: &str = "--format=%(objectname)%x1f%(refname)%x1f%(refname:short)%x1f%(upstream)%x1f%(HEAD)%x1f%(objecttype)%x1f%(*objectname)";
/// What [`parse_log`] read, and whether it read all of it.
pub struct ParsedLog {
pub(crate) struct ParsedLog {
pub commits: Vec<Commit>,
/// The stream was cut short — by [`MAX_LOG_BYTES`], or by a record past
/// `MAX_RECORD` being dropped whole. The caller must not present the
+2 -2
View File
@@ -216,7 +216,7 @@ pub(crate) fn git_stream(
}
#[derive(Default)]
pub struct LineSplitter {
pub(crate) struct LineSplitter {
tail: Vec<u8>,
dropped: usize,
}
@@ -262,7 +262,7 @@ impl LineSplitter {
/// separator. Records come out as `&[u8]` rather than `&str` because a path in
/// a `-z` status is raw bytes and need not be UTF-8 at all — deciding what to
/// do about that belongs to the parser, not to the splitter.
pub struct RecordSplitter {
pub(crate) struct RecordSplitter {
sep: u8,
tail: Vec<u8>,
/// The record being assembled overran [`MAX_RECORD`] and is now being
+1 -1
View File
@@ -505,7 +505,7 @@ const MAX_PREFILLED_MESSAGE: u64 = 64 * 1024;
/// from the filesystem, not from the parse. Keeping them apart is what lets the
/// header and record parsing be tested without a repository.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct ParsedStatus {
pub(crate) struct ParsedStatus {
pub head: HeadState,
pub upstream: Option<String>,
/// `(ahead, behind)`. `None` means *unknown*, never *in sync*.
+9 -1
View File
@@ -5,7 +5,7 @@ use std::sync::Arc;
use ignore::gitignore::Gitignore;
#[derive(Default, Clone)]
pub struct GitignoreChain {
pub(crate) struct GitignoreChain {
matchers: HashMap<PathBuf, Option<Arc<Gitignore>>>,
}
@@ -45,6 +45,10 @@ impl GitignoreChain {
state
}
/// Unused: the matcher sets are built whole rather than merged. Kept beside
/// `len`/`is_empty`, which are the same story -- a spare accessor is better
/// than half a type.
#[allow(dead_code)]
pub fn absorb(&mut self, other: Self) {
self.matchers.extend(other.matchers);
}
@@ -53,10 +57,14 @@ impl GitignoreChain {
self.matchers.clear();
}
/// Unused, like `absorb` above and for the same reason.
#[allow(dead_code)]
pub fn len(&self) -> usize {
self.matchers.len()
}
/// Unused, like `absorb` above and for the same reason.
#[allow(dead_code)]
pub fn is_empty(&self) -> bool {
self.matchers.is_empty()
}
+7 -4
View File
@@ -73,7 +73,7 @@ pub const MAX_IMAGE_BYTES: usize = crate::daemon::protocol::MAX_FRAME - HEADER_L
/// intercept; everything else must reach the client's VT parser unchanged. State
/// persists across `feed` calls, so a sequence split over several reads is still
/// handled.
pub struct ApcTokenizer {
pub(crate) struct ApcTokenizer {
/// Bytes of a `_G` command accumulated after `ESC _ G` while it can still
/// terminate. Cleared whenever a command finishes or is abandoned.
buf: Vec<u8>,
@@ -320,12 +320,13 @@ impl ApcTokenizer {
/// returns the decoded graphics [`Event`]s — query replies to write back to the
/// PTY, and images to forward out-of-band.
#[derive(Default)]
pub struct GraphicsSniffer {
pub(crate) struct GraphicsSniffer {
tokenizer: ApcTokenizer,
parser: GraphicsParser,
}
impl GraphicsSniffer {
#[cfg_attr(not(test), allow(dead_code))]
pub fn new() -> Self {
Self::default()
}
@@ -354,6 +355,7 @@ impl GraphicsSniffer {
/// This drops the *relative order* of passthrough vs. events; for the daemon
/// loop, prefer [`sniff`](Self::sniff), which preserves it. Retained as the
/// low-level primitive the unit tests drive.
#[cfg_attr(not(test), allow(dead_code))]
pub fn feed(&mut self, bytes: &[u8], on_passthrough: impl FnMut(&[u8])) -> Vec<Event> {
let Self { tokenizer, parser } = self;
let mut events = Vec::new();
@@ -445,7 +447,7 @@ impl From<Event> for Segment {
/// The result of [`GraphicsSniffer::sniff`]: either the whole chunk borrowed as
/// output (the graphics-free fast path), or ordered [`Segment`]s.
pub enum Sniffed<'a> {
pub(crate) enum Sniffed<'a> {
/// No graphics in this chunk: the input is output, verbatim and borrowed.
Plain(&'a [u8]),
/// Graphics present: apply these in order.
@@ -1167,7 +1169,7 @@ struct Pending {
/// pane just forwards the resulting [`Image`] out-of-band and writes any
/// [`Event::Query`] reply to the PTY.
#[derive(Default)]
pub struct GraphicsParser {
pub(crate) struct GraphicsParser {
pending: Option<Pending>,
/// Whether the sender shares this host's filesystem (a local pane). Only
/// then can we honor file/shm transfer, whose names are host-local; a pane
@@ -1176,6 +1178,7 @@ pub struct GraphicsParser {
}
impl GraphicsParser {
#[cfg_attr(not(test), allow(dead_code))]
pub fn new() -> Self {
Self::default()
}
+6 -5
View File
@@ -384,7 +384,7 @@ impl ControlRequest {
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum ControlReply {
pub(crate) enum ControlReply {
#[serde(rename = "ok")]
Ok(ReplyOk),
#[serde(rename = "err")]
@@ -426,14 +426,14 @@ pub enum ReplyOk {
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct WireError {
pub(crate) struct WireError {
pub kind: WireErrorKind,
pub msg: String,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum WireErrorKind {
pub(crate) enum WireErrorKind {
NotFound,
PermissionDenied,
AlreadyExists,
@@ -727,7 +727,7 @@ fn require_nonzero(req_id: u64, what: &str) -> io::Result<()> {
}
#[derive(Clone, Debug, PartialEq)]
pub enum ControlClientMsg {
pub(crate) enum ControlClientMsg {
Hello(ControlHello),
Request {
req_id: u64,
@@ -822,7 +822,7 @@ impl ControlClientMsg {
// `send`, encoded there, and gone.
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, PartialEq)]
pub enum ControlServerMsg {
pub(crate) enum ControlServerMsg {
HelloOk(ControlHelloOk),
Response {
req_id: u64,
@@ -837,6 +837,7 @@ pub enum ControlServerMsg {
}
impl ControlServerMsg {
#[cfg_attr(not(test), allow(dead_code))]
pub fn encode<W: Write>(&self, w: &mut W) -> io::Result<()> {
let (k, payload) = self.to_frame()?;
write_frame(w, k, &payload)
+15 -12
View File
@@ -29,7 +29,7 @@ const REMOTE_POLL_INTERVAL: Duration = Duration::from_millis(400);
const REMOTE_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(10);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExecOutput {
pub(crate) struct ExecOutput {
pub status: Option<u32>,
pub stdout: String,
pub stderr: String,
@@ -53,13 +53,13 @@ impl ExecOutput {
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RemoteStat {
pub(crate) struct RemoteStat {
pub size: u64,
pub mode: u32,
pub is_dir: bool,
}
pub trait RemoteOps: Send + Sync {
pub(crate) trait RemoteOps: Send + Sync {
fn home_dir(&self) -> Result<String, String>;
fn run(&self, cmd: &str) -> Result<ExecOutput, String>;
fn spawn_detached(&self, cmd: &str) -> Result<(), String>;
@@ -100,7 +100,7 @@ pub trait AssetFetcher: Send + Sync {
}
}
pub struct LoadedBinary {
pub(crate) struct LoadedBinary {
pub bytes: Vec<u8>,
pub origin: String,
}
@@ -114,7 +114,7 @@ impl std::fmt::Debug for LoadedBinary {
}
}
pub trait ServerBinarySource: Send + Sync {
pub(crate) trait ServerBinarySource: Send + Sync {
fn load(&self, version: &str, asset: &'static str) -> Result<LoadedBinary, InstallError>;
fn load_with_progress(
@@ -128,7 +128,7 @@ pub trait ServerBinarySource: Send + Sync {
}
}
pub struct BundledOrRelease<'a> {
pub(crate) struct BundledOrRelease<'a> {
pub fetch: &'a dyn AssetFetcher,
pub bundled: Option<wsl::BundledServerBinary>,
/// When a bundled directory is configured but the requested asset is absent,
@@ -137,6 +137,9 @@ pub struct BundledOrRelease<'a> {
}
impl<'a> BundledOrRelease<'a> {
/// Unreached in this configuration; the release path is what the installer
/// takes, and the bundled one is chosen explicitly where it applies.
#[allow(dead_code)]
pub fn from_env(fetch: &'a dyn AssetFetcher) -> Self {
Self {
fetch,
@@ -189,7 +192,7 @@ impl ServerBinarySource for BundledOrRelease<'_> {
}
}
pub struct ReleaseDownload<'a> {
pub(crate) struct ReleaseDownload<'a> {
pub fetch: &'a dyn AssetFetcher,
}
@@ -256,7 +259,7 @@ pub trait InstallConfirm: Send + Sync {
fn confirm(&self, request: &InstallRequest) -> InstallDecision;
}
pub struct DenyInstall;
pub(crate) struct DenyInstall;
impl InstallConfirm for DenyInstall {
fn confirm(&self, _request: &InstallRequest) -> InstallDecision {
@@ -327,7 +330,7 @@ pub trait InstallProgress: Send + Sync {
fn report(&self, host: &str, phase: InstallPhase);
}
pub struct SilentProgress;
pub(crate) struct SilentProgress;
impl InstallProgress for SilentProgress {
fn report(&self, _host: &str, _phase: InstallPhase) {}
@@ -472,7 +475,7 @@ pub fn take_mismatched_remote_daemons() -> Vec<MismatchedRemoteDaemon> {
}
#[derive(Debug)]
pub enum InstallError {
pub(crate) enum InstallError {
Probe(String),
Unsupported(UnsupportedTarget),
NoHome(String),
@@ -586,7 +589,7 @@ impl From<InstallError> for io::Error {
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InstallReport {
pub(crate) struct InstallReport {
pub asset: &'static str,
pub paths: RemotePaths,
pub installed: bool,
@@ -596,7 +599,7 @@ pub struct InstallReport {
pub reused: Option<RemoteProtocol>,
}
pub struct Installer<'a> {
pub(crate) struct Installer<'a> {
ops: &'a dyn RemoteOps,
fetch: Option<&'a dyn AssetFetcher>,
source: Option<&'a dyn ServerBinarySource>,
@@ -11,7 +11,7 @@ use super::{ExecOutput, RemoteOps, RemoteStat};
const COMMAND_TIMEOUT: Duration = Duration::from_secs(30);
const LAUNCH_TIMEOUT: Duration = Duration::from_secs(15);
pub struct SshRemoteOps {
pub(crate) struct SshRemoteOps {
conn: Arc<SshConnection>,
}
+6 -3
View File
@@ -20,7 +20,7 @@ const LAUNCH_TIMEOUT: Duration = Duration::from_secs(30);
const PUT_TIMEOUT: Duration = Duration::from_secs(180);
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DistroNameError {
pub(crate) enum DistroNameError {
Empty,
LeadingDash(String),
Control(String),
@@ -293,7 +293,7 @@ fn truncate(s: &str) -> String {
s.chars().take(200).collect::<String>() + ""
}
pub struct WslRemoteOps {
pub(crate) struct WslRemoteOps {
distro: String,
}
@@ -501,7 +501,7 @@ pub(crate) fn bundled_search_dirs(exe: Option<&Path>, override_dir: Option<&Path
dirs
}
pub struct BundledServerBinary {
pub(crate) struct BundledServerBinary {
dirs: Vec<PathBuf>,
}
@@ -518,6 +518,9 @@ impl BundledServerBinary {
Self { dirs }
}
/// Unreached on this platform: the bundled-directory override is a Windows
/// installer path, and nothing on a unix build has a reason to ask.
#[allow(dead_code)]
pub(crate) fn from_env_only() -> Option<Self> {
let dir = std::env::var_os(BUNDLED_DIR_ENV).filter(|v| !v.is_empty())?;
Some(Self::in_dirs(vec![PathBuf::from(dir)]))
+2 -2
View File
@@ -595,7 +595,7 @@ const RING_CAP: usize = 8 * 1024 * 1024;
const MAX_RING_SEGMENTS: usize = 64;
const REMOTE_CONTEXT_POLL_INTERVAL: Duration = Duration::from_millis(500);
pub struct OutputGate {
pub(crate) struct OutputGate {
queued: AtomicI64,
park: Mutex<()>,
drained: Condvar,
@@ -835,7 +835,7 @@ struct NativeSshBackend {
connection: crate::daemon::ssh::SharedConnection,
}
pub struct DaemonPane {
pub(crate) struct DaemonPane {
pub id: u64,
owner: Option<String>,
backend: PaneBackend,
+1 -1
View File
@@ -10,7 +10,7 @@ use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use super::router::RouteChannel;
use super::ssh::ProcessStream;
pub enum RemoteLink {
pub(crate) enum RemoteLink {
StreamLocal(russh::ChannelStream<russh::client::Msg>),
SessionExec(russh::ChannelStream<russh::client::Msg>),
+3 -3
View File
@@ -158,7 +158,7 @@ impl RouteTarget {
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct InstallRequestWire {
pub(crate) struct InstallRequestWire {
pub host: String,
pub version: String,
pub asset: String,
@@ -196,7 +196,7 @@ impl InstallRequestWire {
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RoutePrompt {
pub(crate) enum RoutePrompt {
Auth {
request_id: u64,
prompt: AuthPromptKind,
@@ -216,7 +216,7 @@ pub enum RoutePrompt {
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RouteReply {
pub(crate) enum RouteReply {
Auth {
request_id: u64,
response: AuthResponse,
@@ -718,7 +718,7 @@ fn zsh_redirectors() -> [(&'static str, String); 4] {
]
}
pub struct Injection {
pub(crate) struct Injection {
pub env: HashMap<String, String>,
pub args: Vec<String>,
pub replaces_argv: bool,
@@ -1279,7 +1279,7 @@ pub mod remote {
const HEREDOC: &str = "__TTY7_RC_EOF__";
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum RemoteShell {
pub(crate) enum RemoteShell {
Zsh,
Bash,
Fish,
+2 -2
View File
@@ -132,7 +132,7 @@ where
}
#[derive(Clone, Default)]
pub struct RemoteForwardTable {
pub(crate) struct RemoteForwardTable {
inner: Arc<Mutex<HashMap<(String, u16), (String, u16)>>>,
}
@@ -260,7 +260,7 @@ struct ForwardEntry {
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum ForwardOwner {
pub(crate) enum ForwardOwner {
Pane(u64),
Workspace(WorkspaceId),
}
+1 -1
View File
@@ -11,7 +11,7 @@ use super::broker::PromptBroker;
use super::forward::{self, RemoteForwardTable};
use super::known_hosts::{self, HostKeyStatus};
pub struct ClientHandler {
pub(crate) struct ClientHandler {
pub host: String,
pub port: u16,
pub verify_host_keys: bool,
+1 -1
View File
@@ -64,7 +64,7 @@ impl ConnectionKey {
type ConnSlot = Arc<tokio::sync::Mutex<Weak<SshConnection>>>;
pub struct SshManager {
pub(crate) struct SshManager {
runtime: tokio::runtime::Runtime,
conns: Mutex<HashMap<ConnectionKey, ConnSlot>>,
forwards: SshForwardRegistry,
+3 -3
View File
@@ -41,7 +41,7 @@ impl SshSessionHandle {
}
}
pub struct SshReader {
pub(crate) struct SshReader {
rx: tokio::sync::mpsc::Receiver<Vec<u8>>,
leftover: Vec<u8>,
pos: usize,
@@ -66,7 +66,7 @@ impl Read for SshReader {
}
}
pub struct SshWriter {
pub(crate) struct SshWriter {
handle: Arc<SshSessionHandle>,
}
@@ -81,7 +81,7 @@ impl Write for SshWriter {
}
}
pub struct BridgeEnds {
pub(crate) struct BridgeEnds {
pub reader: SshReader,
pub writer: SshWriter,
pub handle: Arc<SshSessionHandle>,
+2 -2
View File
@@ -112,7 +112,7 @@ fn entry_from_attrs(name: &str, attrs: &FileAttributes) -> SftpEntry {
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct JobProgress {
pub(crate) struct JobProgress {
pub state: SftpJobState,
pub current: String,
pub bytes_done: u64,
@@ -255,7 +255,7 @@ struct CachedSession {
sftp: Arc<SftpSession>,
}
pub struct SftpManager {
pub(crate) struct SftpManager {
sessions: Mutex<HashMap<ConnectionKey, Arc<SessionSlot>>>,
jobs: Mutex<HashMap<u64, Arc<Job>>>,
next_job: AtomicU64,
+3
View File
@@ -85,6 +85,7 @@ impl AttachRegistry {
self.handover.lock().unwrap_or_else(|e| e.into_inner())
}
#[cfg_attr(not(test), allow(dead_code))]
pub fn holder(&self, workspace: &str) -> Option<(String, String)> {
self.locked()
.iter()
@@ -92,10 +93,12 @@ impl AttachRegistry {
.map(|l| (l.token.clone(), l.hostname.clone()))
}
#[cfg_attr(not(test), allow(dead_code))]
pub fn len(&self) -> usize {
self.locked().len()
}
#[cfg_attr(not(test), allow(dead_code))]
pub fn is_empty(&self) -> bool {
self.len() == 0
}