On Windows, crossterm's console backend never emits Event::Paste: a terminal paste arrives as a per-character key burst that the event loop reassembles with timing heuristics. Three of its bounds split real pastes into fragments: - the 10 ms continue window is below one Windows scheduler quantum (~15.6 ms), so a routine mid-burst gap ended collection early; - the 5000-event safety cap truncated any paste past ~5 KB; - the 2 ms detection window missed bursts whose second chunk lagged, so collection never started. Each fragment became its own synthetic paste. The paste chip then held only the first fragment, and pasting again could never byte-equal the chip content, so repaste-to-expand inserted a duplicate instead of expanding — while macOS worked, because the unix parser buffers a bracketed paste to the end marker and always delivers one whole event. Widen the continue window past one quantum (25 ms), raise the cap above any real paste (200k events), and skip the detection window entirely when the batch already holds a >=8-key burst, which typing and auto-repeat never produce. Unix paths are untouched: batches containing a real Event::Paste never enter this reassembly. Covered by a paused-clock test that trickles a burst tail with one-quantum gaps and asserts it reassembles into a single paste.
285 lines
9.1 KiB
TOML
285 lines
9.1 KiB
TOML
[package]
|
|
name = "kigi-tui"
|
|
version.workspace = true
|
|
edition.workspace = true
|
|
license = "Apache-2.0"
|
|
authors = ["xAI"]
|
|
|
|
[dependencies]
|
|
# TTY safety — detach subprocesses from the controlling terminal
|
|
dunce = { workspace = true }
|
|
kigi-tty-utils = { workspace = true }
|
|
kigi-version = { workspace = true }
|
|
|
|
# Shared prompt-queue wire types
|
|
kigi-prompt-queue = { workspace = true }
|
|
|
|
# Presentation-primitives layer (extracted)
|
|
kigi-pager-render = { path = "../kigi-pager-render" }
|
|
|
|
# Rendering
|
|
ratatui = { workspace = true, features = ["crossterm", "unstable-widget-ref"] }
|
|
crossterm = { workspace = true, features = ["event-stream", "bracketed-paste"] }
|
|
textwrap = { workspace = true }
|
|
regex = { workspace = true }
|
|
unicode-width = { workspace = true }
|
|
unicode-segmentation = { workspace = true }
|
|
strip-ansi-escapes = { workspace = true }
|
|
enum_delegate = { workspace = true }
|
|
|
|
# Markdown / syntax highlighting
|
|
kigi-markdown = { workspace = true }
|
|
# Inline Mermaid rendering engine. Always compiled (no feature gate): the
|
|
# dagre-based vendored engine renders detected ```mermaid fences out of process,
|
|
# gated only at runtime by the `appearance.render_mermaid` setting.
|
|
kigi-mermaid = { workspace = true }
|
|
syntect = { workspace = true }
|
|
|
|
# Data
|
|
similar = { workspace = true }
|
|
indexmap = { workspace = true }
|
|
blake3 = { workspace = true }
|
|
serde = { workspace = true, features = ["derive"] }
|
|
serde_json = { workspace = true }
|
|
toml = { workspace = true }
|
|
toml_edit = { workspace = true, features = ["serde"] }
|
|
|
|
# ANSI parsing (tracing pane)
|
|
ansi-to-tui = { workspace = true }
|
|
|
|
# PTY for ssh wrapper, e2e tests, and benchmarking.
|
|
portable-pty = "0.9"
|
|
|
|
# Utilities
|
|
derive_more = { workspace = true, features = ["deref", "deref_mut"] }
|
|
anyhow = { workspace = true }
|
|
clap = { workspace = true, features = ["derive"] }
|
|
clap_complete = { workspace = true }
|
|
rand = { workspace = true }
|
|
tokio = { workspace = true, features = [
|
|
"sync",
|
|
"rt",
|
|
"macros",
|
|
"rt-multi-thread",
|
|
"signal",
|
|
"io-util",
|
|
"io-std",
|
|
] }
|
|
tokio-util = { workspace = true, features = ["compat"] }
|
|
tracing = { workspace = true }
|
|
tracing-subscriber = { workspace = true }
|
|
|
|
# Binary hardening: compile-time string obfuscation.
|
|
# (cryptify / rustls / semver / tikv-jemallocator moved to the composition-root
|
|
# binary `kigi-bin` with `main.rs`; this lib no longer references them.)
|
|
obfstr = { workspace = true }
|
|
|
|
# Fuzzy matching (history search daemon)
|
|
nucleo = { workspace = true }
|
|
|
|
# Prompt text input
|
|
kigi-ratatui-textarea = { workspace = true }
|
|
kigi-ratatui-inline = { workspace = true }
|
|
|
|
# Update
|
|
kigi-update = { path = "../kigi-update" }
|
|
|
|
# ACP
|
|
agent-client-protocol = { workspace = true }
|
|
kigi-acp-lib = { workspace = true }
|
|
kigi-file-utils = { path = "../kigi-file-utils" }
|
|
kigi-config = { workspace = true }
|
|
kigi-shell = { workspace = true }
|
|
kigi-env = { workspace = true }
|
|
kigi-workspace = { workspace = true }
|
|
kigi-token-estimation = { workspace = true }
|
|
kigi-log = { workspace = true }
|
|
kigi-fast-worktree = { path = "../kigi-fast-worktree", features = ["metadata"] }
|
|
backon = { workspace = true }
|
|
chrono = { workspace = true }
|
|
flate2 = { workspace = true }
|
|
tar = { workspace = true }
|
|
kigi-tools = { workspace = true }
|
|
kigi-agent = { workspace = true }
|
|
kigi-hooks-plugins-types = { path = "../kigi-hooks-plugins-types" }
|
|
wait-timeout = { workspace = true }
|
|
kigi-sandbox = { path = "../kigi-sandbox", default-features = false }
|
|
|
|
# Base64 encoding for image data
|
|
base64 = { workspace = true }
|
|
|
|
# Image decoding/resizing for clipboard image validation before send
|
|
image = { workspace = true, features = ["png", "jpeg", "tiff", "gif", "webp"] }
|
|
|
|
# ID generation
|
|
uuid = { workspace = true, features = ["v4"] }
|
|
|
|
# Path computation (plan file, etc.)
|
|
dirs = { workspace = true }
|
|
shellexpand = "3.1"
|
|
|
|
# libgit2 for the startup git-info populator (no fork/exec, runs once on launch
|
|
# in spawn_blocking; the render path never touches git directly).
|
|
git2 = { workspace = true }
|
|
urlencoding = { workspace = true }
|
|
|
|
# Parsing (web_search)
|
|
url = { workspace = true }
|
|
|
|
# Crash diagnostics
|
|
kigi-crash-handler = { path = "../kigi-crash-handler" }
|
|
|
|
# Non-poisoning mutex for signal handler (active-sessions crash recovery).
|
|
parking_lot.workspace = true
|
|
|
|
# nix: signal handling in notifications/.
|
|
# signal-hook: SIGWINCH handler for the `kigi wrap` PTY wrapper (pty_wrap.rs).
|
|
# libc: poll/read for non-blocking terminal reads in theme/osc11.rs.
|
|
[target.'cfg(unix)'.dependencies]
|
|
nix = { workspace = true, features = ["signal"] }
|
|
signal-hook = { workspace = true }
|
|
libc = { workspace = true }
|
|
|
|
# core-foundation: IOKit power assertions for sleep prevention (SleepInhibitor).
|
|
[target.'cfg(target_os = "macos")'.dependencies]
|
|
core-foundation = { workspace = true }
|
|
|
|
# SetConsoleCtrlHandler for the exec-emulating screen-mode relaunch
|
|
# (`/minimal` / `/fullscreen`): the waiting parent must ignore console Ctrl
|
|
# events so only the relaunched child handles them. Version matches the other
|
|
# in-repo consumers (kigi-crash-handler, kigi-system-power).
|
|
[target.'cfg(windows)'.dependencies]
|
|
windows-sys = { version = "0.59", features = ["Win32_System_Console"] }
|
|
|
|
[dev-dependencies]
|
|
# Enable the render crate's test-only helpers for the pager's test build.
|
|
kigi-pager-render = { path = "../kigi-pager-render", features = ["test-support"] }
|
|
# `start_paused = true` for deterministic paste-reassembly timeout tests.
|
|
tokio = { workspace = true, features = ["test-util"] }
|
|
pretty_assertions = { workspace = true }
|
|
insta = { workspace = true }
|
|
criterion = { workspace = true }
|
|
tempfile = { workspace = true }
|
|
serial_test = { workspace = true }
|
|
kigi-agent = { workspace = true }
|
|
kigi-pager-pty-harness = { path = "../kigi-pager-pty-harness" }
|
|
# In-process leader-cluster harness (mock inference server; no cycle:
|
|
# kigi-test-support depends on neither the pager nor the shell).
|
|
kigi-test-support = { workspace = true, features = [] }
|
|
# Ring CryptoProvider install for the in-process leader-cluster tests. Explicit
|
|
# spec mirroring the shell's, NOT `workspace = true`: the workspace pin enables
|
|
# aws-lc-rs, under which `rustls::crypto::ring` does not exist. Dev-only — the
|
|
# production lib deliberately dropped rustls (moved to kigi-bin).
|
|
rustls = { version = "0.23", default-features = false, features = [
|
|
"ring",
|
|
"logging",
|
|
"std",
|
|
"tls12",
|
|
] }
|
|
|
|
[[bench]]
|
|
name = "render"
|
|
harness = false
|
|
|
|
[[bench]]
|
|
name = "search"
|
|
harness = false
|
|
|
|
[[bench]]
|
|
name = "edit_highlight"
|
|
harness = false
|
|
|
|
# PTY integration tests are split into coherent scheduling families. The test
|
|
# modules remain under tests/pty_e2e/; these roots only define which cases share
|
|
# one Cargo/Bazel process. All cases stay #[ignore]d for ordinary Cargo runs.
|
|
[[test]]
|
|
name = "pty_e2e_smoke"
|
|
path = "tests/pty_e2e_smoke.rs"
|
|
|
|
[[test]]
|
|
name = "pty_e2e_queue"
|
|
path = "tests/pty_e2e_queue.rs"
|
|
|
|
[[test]]
|
|
name = "pty_e2e_scroll_selection"
|
|
path = "tests/pty_e2e_scroll_selection.rs"
|
|
|
|
[[test]]
|
|
name = "pty_e2e_minimal"
|
|
path = "tests/pty_e2e_minimal.rs"
|
|
|
|
[[test]]
|
|
name = "pty_e2e_config_ui"
|
|
path = "tests/pty_e2e_config_ui.rs"
|
|
|
|
[[test]]
|
|
name = "pty_e2e_shell_tools"
|
|
path = "tests/pty_e2e_shell_tools.rs"
|
|
|
|
[[test]]
|
|
name = "pty_e2e_persistence"
|
|
path = "tests/pty_e2e_persistence.rs"
|
|
|
|
[[test]]
|
|
name = "pty_e2e_clipboard"
|
|
path = "tests/pty_e2e_clipboard.rs"
|
|
|
|
# Leader-mode PTY e2e — split from ordinary PTY families so multi-process
|
|
# cluster bring-up never contends with their bounded pools.
|
|
[[test]]
|
|
name = "leader_pty_e2e"
|
|
path = "tests/leader_pty_e2e/mod.rs"
|
|
|
|
# Settings modal regression tests — keyboard/mouse dispatch + registry
|
|
# contracts. Not gated by `--ignored`; runs on every `cargo test`.
|
|
[[test]]
|
|
name = "settings_e2e"
|
|
path = "tests/settings_e2e.rs"
|
|
|
|
# Declarative scripted TUI scenarios — opt-in because they spawn the real pager.
|
|
[[test]]
|
|
name = "scripted_scenarios"
|
|
path = "tests/scripted_scenarios.rs"
|
|
|
|
# Permission Auto mode PTY e2e (Shift+Tab → Auto banner via PtyHarness).
|
|
[[test]]
|
|
name = "pty_auto_mode"
|
|
path = "tests/pty_auto_mode.rs"
|
|
|
|
[[bin]]
|
|
name = "mouse-events-playground"
|
|
path = "src/bin/mouse_events_playground.rs"
|
|
|
|
[[bin]]
|
|
name = "scrollback-selection-playground"
|
|
path = "src/bin/scrollback_selection_playground.rs"
|
|
|
|
[[bin]]
|
|
name = "question-view-playground"
|
|
path = "src/bin/question_view_playground.rs"
|
|
|
|
[[bin]]
|
|
name = "todo-pane-playground"
|
|
path = "src/bin/todo_pane_playground.rs"
|
|
|
|
[features]
|
|
default = ["jemalloc", "sandbox-enforce"]
|
|
default-bazel = [
|
|
"jemalloc",
|
|
"sandbox-enforce",
|
|
]
|
|
# No-op on this crate: the actual `#[global_allocator]` (and the
|
|
# `tikv-jemallocator` dep) live on the composition-root binary
|
|
# `kigi-bin`, which owns `main.rs`. Kept as an empty feature so the
|
|
# workspace-uniform `[jemalloc, sandbox-enforce]` default set resolves
|
|
# here too and the generated `crate_features` stay valid.
|
|
jemalloc = []
|
|
sandbox-enforce = ["kigi-sandbox/enforce"]
|
|
release-dist = []
|
|
# Exposes the `#[cfg(test)]` seams (minimal_api test-only view-model
|
|
# constructors/setters, …) to dependent crates' test builds, and forwards to
|
|
# the render crate's seams (theme cache lock, clipboard hooks, …) so
|
|
# `kigi_tui::theme::cache::test_lock` and friends resolve too. Enable from a
|
|
# dependent's [dev-dependencies] entry only — never in production.
|
|
test-support = ["kigi-pager-render/test-support"]
|