aboutsummaryrefslogtreecommitdiff
path: root/src/app
diff options
context:
space:
mode:
authorChihGodlee <61595521+ChihGodlee@users.noreply.github.com>2026-05-20 20:05:38 +0800
committerCan Celik <ogulcancelik@gmail.com>2026-05-21 03:10:52 +0300
commit6172275252f5f724d874163462f5394800f8d55b (patch)
tree86ff28c99faf9e20b74a75ee0abcd503e44e06db /src/app
parentd1576ade07a7236996869d78f37a4aee00367b42 (diff)
feat(experimental): add reveal_hidden_cursor_for_cjk_ime opt-in for CJK IME tracking
When the focused pane requests ?25l (cursor hidden) but paints its own cursor — common in TUIs like Claude Code, pi, and codex — macOS native input methods stop tracking the candidate-window position. CJK users (Chinese / Japanese / Korean) cannot see candidate windows follow as they type. Setting [experimental] reveal_hidden_cursor_for_cjk_ime = true exposes the focused pane's cursor anchor to the outer terminal regardless of the pane's hidden-cursor request, restoring IME tracking for CJK input. [experimental] cjk_ime_agents is an optional allow-list. When empty, the reveal applies to any focused pane. When non-empty (e.g. ["claude", "codex"]), the reveal only applies if the focused pane's detected agent matches one of the listed names. This lets users enable the workaround only for AI-agent TUIs that paint their own cursor, leaving plain shells and other programs untouched. Accepted names: pi, claude, codex, gemini, cursor, cline, opencode, copilot, kimi, kiro, droid, amp, grok, hermes. Unknown names are ignored. When the pane reports no cursor position (cursor_state returns None for some TUIs), the anchor falls back to the pane's top-left so a stable IME hint is always available. [experimental] cjk_ime_cursor_shape (default "steady_block") controls the DECSCUSR shape rendered for the revealed cursor; six variants are accepted: block, steady_block, underline, steady_underline, bar, steady_bar. Trade-off when enabled: a hardware cursor will be visible in the outer terminal for apps that hide the cursor without painting a replacement. Default false preserves the behavior set in 2c95071. Pair the reveal with cjk_ime_agents to scope it to specific TUIs. Manually verified on macOS 26.5 aarch64 with system Pinyin IME against Claude Code: candidate window tracks cursor with flag enabled, returns to anchored-at-start behavior with flag disabled. refs #149
Diffstat (limited to 'src/app')
-rw-r--r--src/app/mod.rs23
-rw-r--r--src/app/state.rs11
2 files changed, 34 insertions, 0 deletions
diff --git a/src/app/mod.rs b/src/app/mod.rs
index b87eec66..5cc40ecc 100644
--- a/src/app/mod.rs
+++ b/src/app/mod.rs
@@ -153,6 +153,21 @@ fn agent_panel_scope_from_config(
}
}
+/// Parse the configured agent name list into a deduplicated set of `Agent`
+/// values. Unknown agent names are silently dropped so a typo cannot disable
+/// other valid entries.
+fn parse_cjk_ime_agents(names: &[String]) -> Vec<crate::detect::Agent> {
+ let mut out = Vec::with_capacity(names.len());
+ for name in names {
+ if let Some(agent) = crate::detect::parse_agent_label(name) {
+ if !out.contains(&agent) {
+ out.push(agent);
+ }
+ }
+ }
+ out
+}
+
/// Resolve the palette from config: base theme + optional custom overrides.
fn resolve_palette(config: &crate::config::Config) -> state::Palette {
resolve_palette_with_legacy_accent(config, true)
@@ -412,6 +427,9 @@ impl App {
confirm_close: config.ui.confirm_close,
prompt_new_tab_name: config.ui.prompt_new_tab_name,
show_agent_labels_on_pane_borders: config.ui.show_agent_labels_on_pane_borders,
+ reveal_hidden_cursor_for_cjk_ime: config.experimental.reveal_hidden_cursor_for_cjk_ime,
+ cjk_ime_agents: parse_cjk_ime_agents(&config.experimental.cjk_ime_agents),
+ cjk_ime_cursor_shape: config.experimental.cjk_ime_cursor_shape.to_decscusr(),
kitty_graphics_enabled: config.experimental.kitty_graphics,
default_shell: config.terminal.default_shell.clone(),
pane_scrollback_limit_bytes: config.advanced.scrollback_limit_bytes,
@@ -887,6 +905,11 @@ impl App {
if was_kitty_graphics_enabled && !config.experimental.kitty_graphics {
let _ = crate::kitty_graphics::clear_all_host_graphics();
}
+ self.state.reveal_hidden_cursor_for_cjk_ime =
+ config.experimental.reveal_hidden_cursor_for_cjk_ime;
+ self.state.cjk_ime_agents = parse_cjk_ime_agents(&config.experimental.cjk_ime_agents);
+ self.state.cjk_ime_cursor_shape =
+ config.experimental.cjk_ime_cursor_shape.to_decscusr();
}
if !invalid_section("advanced") {
diff --git a/src/app/state.rs b/src/app/state.rs
index 7ee79c4a..1566fdeb 100644
--- a/src/app/state.rs
+++ b/src/app/state.rs
@@ -965,6 +965,14 @@ pub struct AppState {
pub confirm_close: bool,
pub prompt_new_tab_name: bool,
pub show_agent_labels_on_pane_borders: bool,
+ /// Expose the focused pane's cursor anchor to the outer terminal even when
+ /// the pane requested `?25l`. See `[experimental] reveal_hidden_cursor_for_cjk_ime`.
+ pub reveal_hidden_cursor_for_cjk_ime: bool,
+ /// Restrict cursor reveal to focused panes whose detected agent matches
+ /// one of these. Empty means apply to any focused pane.
+ pub cjk_ime_agents: Vec<crate::detect::Agent>,
+ /// DECSCUSR shape parameter (1–6) for the IME anchor cursor.
+ pub cjk_ime_cursor_shape: u8,
pub kitty_graphics_enabled: bool,
pub default_shell: String,
pub pane_scrollback_limit_bytes: usize,
@@ -1224,6 +1232,9 @@ impl AppState {
confirm_close: true,
prompt_new_tab_name: true,
show_agent_labels_on_pane_borders: false,
+ reveal_hidden_cursor_for_cjk_ime: false,
+ cjk_ime_agents: Vec::new(),
+ cjk_ime_cursor_shape: 2, // steady_block
kitty_graphics_enabled: false,
default_shell: String::new(),
pane_scrollback_limit_bytes: crate::config::DEFAULT_SCROLLBACK_LIMIT_BYTES,