fix(tui): default mouse capture on in Windows Terminal (#1169)

The application's drag-select clamps to the transcript area; selection
that appeared to cross into the right sidebar on Windows was actually
the terminal handling the drag natively because Windows defaulted
`mouse_capture = false`. That default was set to dodge the
mouse-mode-leak failure mode from #878 / #898 on legacy conhost, but
modern Windows Terminal handles mouse mode cleanly.

`default_mouse_capture_enabled` now branches on `WT_SESSION`:
- Set (Windows Terminal): default on, sidebar isolation works.
- Unset (legacy conhost / older Windows hosts): default off, same as
  before — explicit `--mouse-capture` or `[tui] mouse_capture = true`
  still opts in.

Adds two regression tests on the Windows path covering both branches.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hunter Bown
2026-05-09 22:49:43 -05:00
parent 252bf6498c
commit 29e418474e
2 changed files with 80 additions and 18 deletions
+6
View File
@@ -23,6 +23,12 @@ published.
### Fixed
- Mouse capture now defaults on inside Windows Terminal (#1169). When
`WT_SESSION` is set, in-app text selection is enabled by default;
legacy conhost stays opt-in via `--mouse-capture` or `[tui]
mouse_capture = true` to preserve the protections from #878 / #898.
Selection now clamps to the transcript region instead of the
terminal painting native selection across the sidebar.
- The build script now invalidates its cache on `.git/HEAD` changes, so
the embedded short-SHA in `deepseek --version` stays current after
commits and branch switches without needing `cargo clean`. Both
+74 -18
View File
@@ -3644,7 +3644,14 @@ fn should_use_alt_screen(_cli: &Cli, _config: &Config) -> bool {
fn should_use_mouse_capture(cli: &Cli, config: &Config, use_alt_screen: bool) -> bool {
let terminal_emulator = std::env::var("TERMINAL_EMULATOR").ok();
should_use_mouse_capture_with(cli, config, use_alt_screen, terminal_emulator.as_deref())
let wt_session = std::env::var("WT_SESSION").ok().filter(|s| !s.is_empty());
should_use_mouse_capture_with(
cli,
config,
use_alt_screen,
terminal_emulator.as_deref(),
wt_session.as_deref(),
)
}
fn should_use_mouse_capture_with(
@@ -3652,6 +3659,7 @@ fn should_use_mouse_capture_with(
config: &Config,
use_alt_screen: bool,
terminal_emulator: Option<&str>,
wt_session: Option<&str>,
) -> bool {
if !use_alt_screen || cli.no_mouse_capture {
return false;
@@ -3663,21 +3671,28 @@ fn should_use_mouse_capture_with(
.tui
.as_ref()
.and_then(|tui| tui.mouse_capture)
.unwrap_or_else(|| default_mouse_capture_enabled(terminal_emulator))
.unwrap_or_else(|| default_mouse_capture_enabled(terminal_emulator, wt_session))
}
/// Whether to enable terminal mouse capture by default for this platform/host.
///
/// Returns `false` on Windows (legacy console mouse-mode reporting is flaky;
/// `--mouse-capture` opts in) and on JetBrains' JediTerm, which advertises
/// mouse support but delivers SGR mouse-event escape sequences as raw text
/// in the input stream — visible to users as garbled characters in the
/// composer when they move the mouse over the TUI (#878, #898). The user
/// can still opt back in with `[tui] mouse_capture = true` in
/// On Windows the default depends on the host: Windows Terminal (which sets
/// `WT_SESSION`) handles mouse-mode reporting cleanly, so default-on there
/// gives users in-app text selection and keeps the application's selection
/// clamped to the transcript area (#1169). Legacy conhost stays default-off
/// because its mouse-mode reporting can leak SGR escape sequences as raw
/// text into the composer (#878 / #898).
///
/// Off elsewhere only for JetBrains' JediTerm, which advertises mouse
/// support but forwards the same SGR escape sequences as raw input. The
/// user can still opt back in with `[tui] mouse_capture = true` in
/// `~/.deepseek/config.toml` or `--mouse-capture`.
fn default_mouse_capture_enabled(terminal_emulator: Option<&str>) -> bool {
fn default_mouse_capture_enabled(
terminal_emulator: Option<&str>,
wt_session: Option<&str>,
) -> bool {
if cfg!(windows) {
return false;
return wt_session.is_some();
}
if matches!(terminal_emulator, Some(t) if t.eq_ignore_ascii_case("JetBrains-JediTerm")) {
return false;
@@ -4641,16 +4656,43 @@ mod terminal_mode_tests {
let cli = parse_cli(&["deepseek"]);
let config = Config::default();
assert!(should_use_mouse_capture_with(&cli, &config, true, None));
assert!(should_use_mouse_capture_with(
&cli, &config, true, None, None
));
}
#[test]
#[cfg(windows)]
fn mouse_capture_defaults_off_on_windows_when_alternate_screen_is_active() {
fn mouse_capture_defaults_off_on_legacy_windows_console() {
// Legacy conhost (no `WT_SESSION`) keeps the v0.8.x default-off
// behavior: mouse-mode reporting on legacy console can leak SGR
// escapes into the composer.
let cli = parse_cli(&["deepseek"]);
let config = Config::default();
assert!(!should_use_mouse_capture_with(&cli, &config, true, None));
assert!(!should_use_mouse_capture_with(
&cli, &config, true, None, None
));
}
// #1169: Windows Terminal sets `WT_SESSION` and handles mouse-mode
// reporting cleanly, so default-on there gives users in-app text
// selection (and the side-effect of clamping selection to the
// transcript region instead of the terminal painting across the
// sidebar via native selection).
#[test]
#[cfg(windows)]
fn mouse_capture_defaults_on_in_windows_terminal() {
let cli = parse_cli(&["deepseek"]);
let config = Config::default();
assert!(should_use_mouse_capture_with(
&cli,
&config,
true,
None,
Some("{a3a3b3a8-aa00-0000-0000-000000000000}"),
));
}
#[test]
@@ -4658,7 +4700,9 @@ mod terminal_mode_tests {
let cli = parse_cli(&["deepseek", "--no-mouse-capture"]);
let config = Config::default();
assert!(!should_use_mouse_capture_with(&cli, &config, true, None));
assert!(!should_use_mouse_capture_with(
&cli, &config, true, None, None
));
}
#[test]
@@ -4676,7 +4720,9 @@ mod terminal_mode_tests {
..Config::default()
};
assert!(!should_use_mouse_capture_with(&cli, &config, true, None));
assert!(!should_use_mouse_capture_with(
&cli, &config, true, None, None
));
}
#[test]
@@ -4684,7 +4730,9 @@ mod terminal_mode_tests {
let cli = parse_cli(&["deepseek", "--mouse-capture"]);
let config = Config::default();
assert!(should_use_mouse_capture_with(&cli, &config, true, None));
assert!(should_use_mouse_capture_with(
&cli, &config, true, None, None
));
}
#[test]
@@ -4702,7 +4750,9 @@ mod terminal_mode_tests {
..Config::default()
};
assert!(should_use_mouse_capture_with(&cli, &config, true, None));
assert!(should_use_mouse_capture_with(
&cli, &config, true, None, None
));
}
#[test]
@@ -4710,7 +4760,9 @@ mod terminal_mode_tests {
let cli = parse_cli(&["deepseek", "--mouse-capture"]);
let config = Config::default();
assert!(!should_use_mouse_capture_with(&cli, &config, false, None));
assert!(!should_use_mouse_capture_with(
&cli, &config, false, None, None
));
}
// Issue #878 / #898: JetBrains JediTerm advertises mouse support but
@@ -4730,6 +4782,7 @@ mod terminal_mode_tests {
&config,
true,
Some("JetBrains-JediTerm"),
None,
));
}
@@ -4745,6 +4798,7 @@ mod terminal_mode_tests {
&config,
true,
Some("jetbrains-jediterm"),
None,
));
}
@@ -4758,6 +4812,7 @@ mod terminal_mode_tests {
&config,
true,
Some("JetBrains-JediTerm"),
None,
));
}
@@ -4781,6 +4836,7 @@ mod terminal_mode_tests {
&config,
true,
Some("JetBrains-JediTerm"),
None,
));
}
}