aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-05-21 01:55:59 +0300
committerCan Celik <ogulcancelik@gmail.com>2026-05-21 03:10:52 +0300
commit7515695a1996e9b406a3cf1eef43208c6c248929 (patch)
treec0fd480d20b17a0309fad7a1933aa64d043dafc3 /src
parent6172275252f5f724d874163462f5394800f8d55b (diff)
fix: keep cjk ime agent allow-list safe
refs #149
Diffstat (limited to 'src')
-rw-r--r--src/app/mod.rs3
-rw-r--r--src/app/state.rs4
-rw-r--r--src/config/model.rs3
-rw-r--r--src/main.rs1
-rw-r--r--src/server/headless.rs29
-rw-r--r--src/server/render_stream.rs4
6 files changed, 40 insertions, 4 deletions
diff --git a/src/app/mod.rs b/src/app/mod.rs
index 5cc40ecc..4c4845a1 100644
--- a/src/app/mod.rs
+++ b/src/app/mod.rs
@@ -428,6 +428,7 @@ impl App {
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_agent_filter_configured: !config.experimental.cjk_ime_agents.is_empty(),
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,
@@ -907,6 +908,8 @@ impl App {
}
self.state.reveal_hidden_cursor_for_cjk_ime =
config.experimental.reveal_hidden_cursor_for_cjk_ime;
+ self.state.cjk_ime_agent_filter_configured =
+ !config.experimental.cjk_ime_agents.is_empty();
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();
diff --git a/src/app/state.rs b/src/app/state.rs
index 1566fdeb..6924dc65 100644
--- a/src/app/state.rs
+++ b/src/app/state.rs
@@ -969,7 +969,8 @@ pub struct AppState {
/// 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.
+ /// one of these. When false, apply to any focused pane.
+ pub cjk_ime_agent_filter_configured: bool,
pub cjk_ime_agents: Vec<crate::detect::Agent>,
/// DECSCUSR shape parameter (1–6) for the IME anchor cursor.
pub cjk_ime_cursor_shape: u8,
@@ -1233,6 +1234,7 @@ impl AppState {
prompt_new_tab_name: true,
show_agent_labels_on_pane_borders: false,
reveal_hidden_cursor_for_cjk_ime: false,
+ cjk_ime_agent_filter_configured: false,
cjk_ime_agents: Vec::new(),
cjk_ime_cursor_shape: 2, // steady_block
kitty_graphics_enabled: false,
diff --git a/src/config/model.rs b/src/config/model.rs
index ba52d579..40d425a3 100644
--- a/src/config/model.rs
+++ b/src/config/model.rs
@@ -265,7 +265,8 @@ pub struct ExperimentalConfig {
pub reveal_hidden_cursor_for_cjk_ime: bool,
/// Restrict `reveal_hidden_cursor_for_cjk_ime` to focused panes whose
/// detected agent matches one of these names (case-insensitive). Empty
- /// list means apply to any focused pane. Unknown agent names are ignored.
+ /// list means apply to any focused pane. Unknown agent names are ignored;
+ /// if the list contains no valid names, the reveal does not apply.
/// Accepted names: pi, claude, codex, gemini, cursor, cline, opencode,
/// copilot, kimi, kiro, droid, amp, grok, hermes. Default: empty.
pub cjk_ime_agents: Vec<String>,
diff --git a/src/main.rs b/src/main.rs
index 440a87a5..17052159 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -209,6 +209,7 @@ const DEFAULT_CONFIG: &str = r##"# herdr configuration
# reveal_hidden_cursor_for_cjk_ime = false
# Optional allow-list: only reveal for focused panes whose detected agent
# matches one of these names. Empty means apply to any focused pane.
+# If the list contains no valid names, the reveal does not apply.
# Accepted: pi, claude, codex, gemini, cursor, cline, opencode, copilot,
# kimi, kiro, droid, amp, grok, hermes.
# cjk_ime_agents = []
diff --git a/src/server/headless.rs b/src/server/headless.rs
index f56dbdeb..539892e0 100644
--- a/src/server/headless.rs
+++ b/src/server/headless.rs
@@ -2804,6 +2804,7 @@ mod tests {
state.reveal_hidden_cursor_for_cjk_ime = true;
// Filter only Claude, but the test pane has no detected agent, so the
// reveal must not apply.
+ state.cjk_ime_agent_filter_configured = true;
state.cjk_ime_agents = vec![crate::detect::Agent::Claude];
let mut ws = crate::workspace::Workspace::test_new("test");
let pane_id = ws.tabs[0].root_pane;
@@ -2828,6 +2829,34 @@ mod tests {
}
#[tokio::test]
+ async fn virtual_render_skips_reveal_when_agent_filter_has_no_valid_entries() {
+ let mut state = AppState::test_new();
+ state.reveal_hidden_cursor_for_cjk_ime = true;
+ state.cjk_ime_agent_filter_configured = true;
+ state.cjk_ime_agents = Vec::new();
+ let mut ws = crate::workspace::Workspace::test_new("test");
+ let pane_id = ws.tabs[0].root_pane;
+ ws.insert_test_runtime(
+ pane_id,
+ crate::terminal::TerminalRuntime::test_with_screen_bytes(20, 5, b"left\x1b[?25l"),
+ );
+
+ state.workspaces = vec![ws];
+ state.active = Some(0);
+ state.selected = 0;
+ state.mode = crate::app::Mode::Terminal;
+
+ let area = Rect::new(0, 0, 80, 24);
+ let (_buffer, cursor) =
+ crate::server::render_stream::render_virtual(&mut state, area, true);
+
+ assert!(
+ cursor.as_ref().is_none_or(|cursor| !cursor.visible),
+ "agent filter with no valid entries should suppress reveal; got {cursor:?}",
+ );
+ }
+
+ #[tokio::test]
async fn virtual_render_omits_focused_pane_cursor_while_mobile_switcher_open() {
let mut state = AppState::test_new();
let mut ws = crate::workspace::Workspace::test_new("test");
diff --git a/src/server/render_stream.rs b/src/server/render_stream.rs
index 6dd4ad48..b6e50444 100644
--- a/src/server/render_stream.rs
+++ b/src/server/render_stream.rs
@@ -327,9 +327,9 @@ fn focused_terminal_cursor(app_state: &AppState) -> Option<CursorState> {
// Determine whether the IME-anchor reveal applies to this focused pane.
// The master switch must be on, and either no agent filter is configured
// (apply to any pane) or the focused pane's detected agent matches the
- // allow-list.
+ // allow-list. A configured list with no valid entries reveals nothing.
let reveal = app_state.reveal_hidden_cursor_for_cjk_ime
- && (app_state.cjk_ime_agents.is_empty() || {
+ && (!app_state.cjk_ime_agent_filter_configured || {
let detected = app_state
.workspaces
.get(ws_idx)