feat(tui): bracketed-paste config toggle + capacity hot-path bench

Closes #77, refs #75.

#77 — bracketed paste was unconditionally enabled at terminal init. Add a
\`bracketed_paste\` field to Settings (default true) and propagate it through
TuiOptions → App → run_tui / pause_terminal / resume_terminal so users on
the rare terminal that mishandles \`\e[?2004h\` can disable it via
\`/set bracketed_paste off\` or \`bracketed_paste = false\` in
\`~/.config/deepseek/settings.toml\`. Modern terminals continue to work as
before. All TuiOptions construction sites updated in one pass.

#75 — added an ignored-test microbench for \`compute_profile\` in
\`crates/tui/src/core/capacity.rs\`. Run with:
  cargo test -p deepseek-tui --release bench_compute_profile -- --ignored --nocapture

Baseline (release, M1):
  window=  16  per-call=  48ns
  window=  64  per-call= 126ns
  window= 256  per-call= 385ns
  window=1024  per-call=1438ns

Sub-µs at typical window sizes — no optimization shipped, bench locks in
the regression contract. No new dev-deps (uses std::time::Instant +
black_box, gated as #[ignore]).
This commit is contained in:
Hunter Bown
2026-04-26 14:10:50 -05:00
parent 432082e956
commit ebc70176ad
23 changed files with 106 additions and 6 deletions
+1
View File
@@ -75,6 +75,7 @@ mod tests {
allow_shell: false,
use_alt_screen: false,
use_mouse_capture: false,
use_bracketed_paste: true,
max_subagents: 1,
skills_dir: tmpdir.path().join("skills"),
memory_path: tmpdir.path().join("memory.md"),
+1
View File
@@ -437,6 +437,7 @@ mod tests {
allow_shell: false,
use_alt_screen: true,
use_mouse_capture: false,
use_bracketed_paste: true,
max_subagents: 1,
skills_dir: PathBuf::from("."),
memory_path: PathBuf::from("memory.md"),
+1
View File
@@ -215,6 +215,7 @@ mod tests {
allow_shell: false,
use_alt_screen: true,
use_mouse_capture: false,
use_bracketed_paste: true,
max_subagents: 1,
skills_dir: PathBuf::from("/tmp/test-skills"),
memory_path: PathBuf::from("memory.md"),
+1
View File
@@ -128,6 +128,7 @@ mod tests {
allow_shell: false,
use_alt_screen: true,
use_mouse_capture: false,
use_bracketed_paste: true,
max_subagents: 1,
skills_dir: PathBuf::from("/tmp/test-skills"),
memory_path: PathBuf::from("memory.md"),
+1
View File
@@ -166,6 +166,7 @@ mod tests {
allow_shell: false,
use_alt_screen: true,
use_mouse_capture: false,
use_bracketed_paste: true,
max_subagents: 1,
skills_dir: tmpdir.path().join("skills"),
memory_path: tmpdir.path().join("memory.md"),
+1
View File
@@ -526,6 +526,7 @@ mod tests {
allow_shell: false,
use_alt_screen: true,
use_mouse_capture: false,
use_bracketed_paste: true,
max_subagents: 1,
skills_dir: PathBuf::from("."),
memory_path: PathBuf::from("memory.md"),
+1
View File
@@ -63,6 +63,7 @@ mod tests {
allow_shell: false,
use_alt_screen: true,
use_mouse_capture: false,
use_bracketed_paste: true,
max_subagents: 1,
skills_dir: tmpdir.path().join("skills"),
memory_path: tmpdir.path().join("memory.md"),
+1
View File
@@ -82,6 +82,7 @@ mod tests {
allow_shell: false,
use_alt_screen: true,
use_mouse_capture: false,
use_bracketed_paste: true,
max_subagents: 1,
skills_dir: PathBuf::from("."),
memory_path: PathBuf::from("memory.md"),
+1
View File
@@ -142,6 +142,7 @@ mod tests {
allow_shell: false,
use_alt_screen: true,
use_mouse_capture: false,
use_bracketed_paste: true,
max_subagents: 1,
skills_dir: tmpdir.path().join("skills"),
memory_path: tmpdir.path().join("memory.md"),
+1
View File
@@ -76,6 +76,7 @@ mod tests {
allow_shell: false,
use_alt_screen: true,
use_mouse_capture: false,
use_bracketed_paste: true,
max_subagents: 1,
skills_dir: tmpdir.path().join("skills"),
memory_path: tmpdir.path().join("memory.md"),
+1
View File
@@ -200,6 +200,7 @@ mod tests {
allow_shell: false,
use_alt_screen: true,
use_mouse_capture: false,
use_bracketed_paste: true,
max_subagents: 1,
skills_dir: tmpdir.path().join("skills"),
memory_path: tmpdir.path().join("memory.md"),
+1
View File
@@ -122,6 +122,7 @@ mod tests {
allow_shell: false,
use_alt_screen: true,
use_mouse_capture: false,
use_bracketed_paste: true,
max_subagents: 1,
skills_dir: tmpdir.path().join("skills"),
memory_path: tmpdir.path().join("memory.md"),
+1
View File
@@ -55,6 +55,7 @@ mod tests {
allow_shell: false,
use_alt_screen: false,
use_mouse_capture: false,
use_bracketed_paste: true,
max_subagents: 2,
skills_dir: PathBuf::from("."),
memory_path: PathBuf::from("memory.md"),
+36
View File
@@ -693,4 +693,40 @@ mod tests {
assert_eq!(decision.action, GuardrailAction::NoIntervention);
assert!(decision.cooldown_blocked);
}
/// Hot-path microbench for `compute_profile`. Run with:
///
/// ```text
/// cargo test -p deepseek-tui --release capacity::tests::bench_compute_profile -- --ignored --nocapture
/// ```
///
/// Establishes a baseline cost so we can detect regressions when the
/// observation cadence is high (50+ message turns × per-step calls). Adds
/// no dev-deps; we measure with `Instant` and print rather than gating CI.
#[test]
#[ignore]
fn bench_compute_profile() {
use std::time::Instant;
for &window_len in &[16usize, 64, 256, 1024] {
let mut window: VecDeque<f64> = VecDeque::with_capacity(window_len);
for i in 0..window_len {
#[allow(clippy::cast_precision_loss)]
window.push_back((i as f64).sin() * 0.5);
}
let iters = 100_000usize;
let start = Instant::now();
for _ in 0..iters {
let profile = compute_profile(&window);
std::hint::black_box(profile);
}
let elapsed = start.elapsed();
let per_call_ns = elapsed.as_nanos() as f64 / iters as f64;
println!(
"compute_profile window={window_len:>4} total={:?} per-call={per_call_ns:>8.0}ns",
elapsed
);
}
}
}
+4
View File
@@ -2731,6 +2731,9 @@ async fn run_interactive(
);
let use_alt_screen = should_use_alt_screen(cli, config);
let use_mouse_capture = should_use_mouse_capture(cli, config, use_alt_screen);
let use_bracketed_paste = crate::settings::Settings::load()
.map(|s| s.bracketed_paste)
.unwrap_or(true);
tui::run_tui(
config,
@@ -2740,6 +2743,7 @@ async fn run_interactive(
allow_shell: cli.yolo || config.allow_shell(),
use_alt_screen,
use_mouse_capture,
use_bracketed_paste,
skills_dir: config.skills_dir(),
memory_path: config.memory_path(),
notes_path: config.notes_path(),
+13
View File
@@ -21,6 +21,10 @@ pub struct Settings {
pub low_motion: bool,
/// Enable fancy footer animations (water-spout strip, pulsing text)
pub fancy_animations: bool,
/// Enable terminal bracketed-paste mode. Default true. Disable if your
/// terminal mishandles the `\e[?2004h` escape (rare; some legacy
/// terminals over SSH+screen multiplex without the cap).
pub bracketed_paste: bool,
/// Show thinking blocks from the model
pub show_thinking: bool,
/// Show detailed tool output
@@ -50,6 +54,7 @@ impl Default for Settings {
calm_mode: false,
low_motion: false,
fancy_animations: false,
bracketed_paste: true,
show_thinking: true,
show_tool_details: true,
composer_density: "comfortable".to_string(),
@@ -142,6 +147,9 @@ impl Settings {
"fancy_animations" | "fancy" | "animations" => {
self.fancy_animations = parse_bool(value)?;
}
"bracketed_paste" | "paste" => {
self.bracketed_paste = parse_bool(value)?;
}
"show_thinking" | "thinking" => {
self.show_thinking = parse_bool(value)?;
}
@@ -251,6 +259,7 @@ impl Settings {
lines.push(format!(" calm_mode: {}", self.calm_mode));
lines.push(format!(" low_motion: {}", self.low_motion));
lines.push(format!(" fancy_animations: {}", self.fancy_animations));
lines.push(format!(" bracketed_paste: {}", self.bracketed_paste));
lines.push(format!(" show_thinking: {}", self.show_thinking));
lines.push(format!(" show_tool_details: {}", self.show_tool_details));
lines.push(format!(" composer_density: {}", self.composer_density));
@@ -286,6 +295,10 @@ impl Settings {
"fancy_animations",
"Fancy footer animations (water-spout strip): on/off",
),
(
"bracketed_paste",
"Terminal bracketed-paste mode: on/off (rare to disable)",
),
("show_thinking", "Show model thinking: on/off"),
("show_tool_details", "Show detailed tool output: on/off"),
(
+8
View File
@@ -321,6 +321,10 @@ pub struct TuiOptions {
pub use_alt_screen: bool,
/// Capture mouse input for internal scrolling/selection.
pub use_mouse_capture: bool,
/// Enable terminal bracketed-paste mode (OSC `?2004h` / `?2004l`). Defaults
/// on; settable via `bracketed_paste = false` in `settings.toml` for the
/// rare terminal that mishandles it.
pub use_bracketed_paste: bool,
/// Maximum number of concurrent sub-agents.
pub max_subagents: usize,
#[allow(dead_code)]
@@ -397,6 +401,7 @@ pub struct App {
pub skills_dir: PathBuf,
pub use_alt_screen: bool,
pub use_mouse_capture: bool,
pub use_bracketed_paste: bool,
#[allow(dead_code)]
pub system_prompt: Option<SystemPrompt>,
pub input_history: Vec<String>,
@@ -638,6 +643,7 @@ impl App {
allow_shell,
use_alt_screen,
use_mouse_capture,
use_bracketed_paste,
max_subagents,
skills_dir: global_skills_dir,
memory_path: _,
@@ -745,6 +751,7 @@ impl App {
skills_dir,
use_alt_screen,
use_mouse_capture,
use_bracketed_paste,
system_prompt: None,
input_history: Vec::new(),
history_index: None,
@@ -1773,6 +1780,7 @@ mod tests {
allow_shell: yolo,
use_alt_screen: true,
use_mouse_capture: false,
use_bracketed_paste: true,
max_subagents: 1,
skills_dir: PathBuf::from("."),
memory_path: PathBuf::from("memory.md"),
+1
View File
@@ -347,6 +347,7 @@ mod tests {
allow_shell: false,
use_alt_screen: true,
use_mouse_capture: false,
use_bracketed_paste: true,
max_subagents: 1,
skills_dir: PathBuf::from("."),
memory_path: PathBuf::from("memory.md"),
+27 -6
View File
@@ -124,6 +124,7 @@ const SIDEBAR_VISIBLE_MIN_WIDTH: u16 = 100;
pub async fn run_tui(config: &Config, options: TuiOptions) -> Result<()> {
let use_alt_screen = options.use_alt_screen;
let use_mouse_capture = options.use_mouse_capture;
let use_bracketed_paste = options.use_bracketed_paste;
enable_raw_mode()?;
let mut stdout = io::stdout();
if use_alt_screen {
@@ -132,7 +133,9 @@ pub async fn run_tui(config: &Config, options: TuiOptions) -> Result<()> {
if use_mouse_capture {
execute!(stdout, EnableMouseCapture)?;
}
execute!(stdout, EnableBracketedPaste)?;
if use_bracketed_paste {
execute!(stdout, EnableBracketedPaste)?;
}
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
let event_broker = EventBroker::new();
@@ -295,7 +298,9 @@ pub async fn run_tui(config: &Config, options: TuiOptions) -> Result<()> {
if use_mouse_capture {
execute!(terminal.backend_mut(), DisableMouseCapture)?;
}
execute!(terminal.backend_mut(), DisableBracketedPaste)?;
if use_bracketed_paste {
execute!(terminal.backend_mut(), DisableBracketedPaste)?;
}
terminal.show_cursor()?;
result
@@ -688,13 +693,23 @@ async fn run_event_loop(
}
EngineEvent::PauseEvents => {
if !event_broker.is_paused() {
pause_terminal(terminal, app.use_alt_screen, app.use_mouse_capture)?;
pause_terminal(
terminal,
app.use_alt_screen,
app.use_mouse_capture,
app.use_bracketed_paste,
)?;
event_broker.pause_events();
}
}
EngineEvent::ResumeEvents => {
if event_broker.is_paused() {
resume_terminal(terminal, app.use_alt_screen, app.use_mouse_capture)?;
resume_terminal(
terminal,
app.use_alt_screen,
app.use_mouse_capture,
app.use_bracketed_paste,
)?;
event_broker.resume_events();
}
}
@@ -3034,6 +3049,7 @@ fn pause_terminal(
terminal: &mut Terminal<CrosstermBackend<Stdout>>,
use_alt_screen: bool,
use_mouse_capture: bool,
use_bracketed_paste: bool,
) -> Result<()> {
disable_raw_mode()?;
if use_alt_screen {
@@ -3042,7 +3058,9 @@ fn pause_terminal(
if use_mouse_capture {
execute!(terminal.backend_mut(), DisableMouseCapture)?;
}
execute!(terminal.backend_mut(), DisableBracketedPaste)?;
if use_bracketed_paste {
execute!(terminal.backend_mut(), DisableBracketedPaste)?;
}
Ok(())
}
@@ -3050,6 +3068,7 @@ fn resume_terminal(
terminal: &mut Terminal<CrosstermBackend<Stdout>>,
use_alt_screen: bool,
use_mouse_capture: bool,
use_bracketed_paste: bool,
) -> Result<()> {
enable_raw_mode()?;
if use_alt_screen {
@@ -3058,7 +3077,9 @@ fn resume_terminal(
if use_mouse_capture {
execute!(terminal.backend_mut(), EnableMouseCapture)?;
}
execute!(terminal.backend_mut(), EnableBracketedPaste)?;
if use_bracketed_paste {
execute!(terminal.backend_mut(), EnableBracketedPaste)?;
}
terminal.clear()?;
Ok(())
}
+1
View File
@@ -117,6 +117,7 @@ fn create_test_app() -> App {
allow_shell: false,
use_alt_screen: true,
use_mouse_capture: false,
use_bracketed_paste: true,
max_subagents: 1,
skills_dir: PathBuf::from("."),
memory_path: PathBuf::from("memory.md"),
+1
View File
@@ -1521,6 +1521,7 @@ mod tests {
allow_shell: false,
use_alt_screen: true,
use_mouse_capture: false,
use_bracketed_paste: true,
max_subagents: 1,
skills_dir: PathBuf::from("."),
memory_path: PathBuf::from("memory.md"),
+1
View File
@@ -371,6 +371,7 @@ mod tests {
allow_shell: false,
use_alt_screen: true,
use_mouse_capture: false,
use_bracketed_paste: true,
max_subagents: 1,
skills_dir: PathBuf::from("."),
memory_path: PathBuf::from("memory.md"),
+1
View File
@@ -1189,6 +1189,7 @@ mod tests {
allow_shell: false,
use_alt_screen: true,
use_mouse_capture: false,
use_bracketed_paste: true,
max_subagents: 1,
skills_dir: PathBuf::from("."),
memory_path: PathBuf::from("memory.md"),