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:
@@ -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"),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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"),
|
||||
(
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
Reference in New Issue
Block a user