Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 47 additions & 4 deletions cli/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -602,11 +602,27 @@ fn parse_command_inner(args: &[String], flags: &Flags) -> Result<Value, ParseErr
// `--key-events` (alias `--keys`): send real per-character keystrokes
// instead of Input.insertText, so autocomplete/combobox widgets that
// only react to key events fire (e.g. Google address postal lookup).
let key_events = rest.iter().any(|a| *a == "--key-events" || *a == "--keys");
let mut key_events = rest.iter().any(|a| *a == "--key-events" || *a == "--keys");
// `--enter` (alias `--commit-enter`): press Enter after typing to commit
// the highlighted candidate in an async-autocomplete widget (e.g.
// juejin's tag input — issue #50). Implies `--key-events`: a bulk
// Input.insertText never fires the keydown/input the dropdown queries
// off, so the candidate Enter would commit never appears.
let commit_enter = rest
.iter()
.any(|a| *a == "--enter" || *a == "--commit-enter");
if commit_enter {
key_events = true;
}
let rest: Vec<&str> = rest
.iter()
.copied()
.filter(|a| *a != "--key-events" && *a != "--keys")
.filter(|a| {
*a != "--key-events"
&& *a != "--keys"
&& *a != "--enter"
&& *a != "--commit-enter"
})
.collect();
// `type --focused <text>` types into whatever element currently has
// focus (no selector) — for custom widgets that move focus to a hidden
Expand All @@ -615,14 +631,16 @@ fn parse_command_inner(args: &[String], flags: &Flags) -> Result<Value, ParseErr
return Ok(json!({
"id": id, "action": "type", "focused": true,
"text": rest[1..].join(" "), "keyEvents": key_events,
"commitEnter": commit_enter,
}));
}
let sel = rest.first().ok_or_else(|| ParseError::MissingArguments {
context: "type".to_string(),
usage: "type <selector> <text> (or: type --focused <text>) [--key-events]",
usage:
"type <selector> <text> (or: type --focused <text>) [--key-events] [--enter]",
})?;
Ok(
json!({ "id": id, "action": "type", "selector": sel, "text": rest[1..].join(" "), "keyEvents": key_events }),
json!({ "id": id, "action": "type", "selector": sel, "text": rest[1..].join(" "), "keyEvents": key_events, "commitEnter": commit_enter }),
)
}
"pick" => {
Expand Down Expand Up @@ -4422,6 +4440,7 @@ mod tests {
assert_eq!(cmd["selector"], "#input");
assert_eq!(cmd["text"], "some text");
assert_eq!(cmd["keyEvents"], false);
assert_eq!(cmd["commitEnter"], false);
}

#[test]
Expand All @@ -4445,6 +4464,30 @@ mod tests {
assert_eq!(focused["keyEvents"], true);
}

#[test]
fn test_type_commit_enter() {
// --enter commits the typed value with a trailing Enter (async-autocomplete
// tag widgets, issue #50) and must imply --key-events so the dropdown the
// Enter commits has actually been triggered.
let cmd = parse_command(&args("type #tag ChatGPT --enter"), &default_flags()).unwrap();
assert_eq!(cmd["action"], "type");
assert_eq!(cmd["selector"], "#tag");
assert_eq!(cmd["text"], "ChatGPT");
assert_eq!(cmd["commitEnter"], true);
assert_eq!(cmd["keyEvents"], true);

// Alias --commit-enter behaves identically, on the --focused path too.
let focused = parse_command(
&args("type --focused ChatGPT --commit-enter"),
&default_flags(),
)
.unwrap();
assert_eq!(focused["focused"], true);
assert_eq!(focused["text"], "ChatGPT");
assert_eq!(focused["commitEnter"], true);
assert_eq!(focused["keyEvents"], true);
}

#[test]
fn test_select() {
let cmd = parse_command(&args("select #menu option1"), &default_flags()).unwrap();
Expand Down
19 changes: 17 additions & 2 deletions cli/src/native/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3576,6 +3576,15 @@ async fn handle_type(cmd: &Value, state: &mut DaemonState) -> Result<Value, Stri
.and_then(|v| v.as_bool())
.unwrap_or(false);

// `--enter`: after typing, press Enter to commit the highlighted candidate in
// an async-autocomplete widget (juejin tag input — issue #50). The parser
// already forces key_events on when this is set, so the dropdown the Enter
// commits has actually been triggered by the per-character key events.
let commit_enter = cmd
.get("commitEnter")
.and_then(|v| v.as_bool())
.unwrap_or(false);

// `type --focused <text>`: type into the currently-focused element without a
// selector (custom widgets that move focus to a hidden input on open).
if cmd
Expand All @@ -3595,7 +3604,10 @@ async fn handle_type(cmd: &Value, state: &mut DaemonState) -> Result<Value, Stri
key_events,
)
.await?;
return Ok(json!({ "typed": text, "focused": true }));
if commit_enter {
interaction::commit_with_enter(&mgr.client, &session_id).await?;
}
return Ok(json!({ "typed": text, "focused": true, "committed": commit_enter }));
}

let selector = cmd
Expand All @@ -3621,7 +3633,10 @@ async fn handle_type(cmd: &Value, state: &mut DaemonState) -> Result<Value, Stri
key_events,
)
.await?;
Ok(json!({ "typed": text }))
if commit_enter {
interaction::commit_with_enter(&mgr.client, &session_id).await?;
}
Ok(json!({ "typed": text, "committed": commit_enter }))
}

/// Atomic combobox select: `pick <selector> --option "<text>"`. Opens the control
Expand Down
14 changes: 14 additions & 0 deletions cli/src/native/interaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,20 @@ pub async fn type_text_into_active_context(
Ok(())
}

/// Commit the value just typed into an async-autocomplete / tag widget by pressing
/// Enter (issue #50: juejin's 「添加标签」 input). Such widgets query their suggestion
/// list off the per-character key events `type --key-events` fires, but the
/// dropdown lands a tick later — so we let the page settle (RAF + microtask) so the
/// candidate is mounted/highlighted before the Enter, which the widget reads as
/// "accept the current candidate". Used by `type --enter`, which forces key-events
/// typing on (a bulk insertText never triggers the dropdown Enter would commit).
pub async fn commit_with_enter(client: &CdpClient, session_id: &str) -> Result<(), String> {
wait_for_paint_settled(client, session_id).await;
press_key(client, session_id, "enter").await?;
wait_for_paint_settled(client, session_id).await;
Ok(())
}

pub async fn press_key(client: &CdpClient, session_id: &str, key: &str) -> Result<(), String> {
press_key_with_modifiers(client, session_id, key, None).await
}
Expand Down
7 changes: 7 additions & 0 deletions skill-data/core/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,13 @@ chrome-use type @e5 "201-0001" --key-events # real keystrokes (not insertText)
# use for autocomplete/combobox fields that
# only react to key events (e.g. a postal box
# that auto-fills city/prefecture, Google Places)
chrome-use type @e6 "ChatGPT" --enter # type (real keystrokes, implies --key-events)
# then press Enter to COMMIT the candidate in an
# async-autocomplete / tag widget. Use when typing
# alone shows no dropdown and the field needs a tag
# confirmed (e.g. juejin 「添加标签」). If you'd rather
# pick from the list, type --key-events first, then
# snapshot -i and click the candidate.
chrome-use press Enter # press a key at current focus (down+up)
chrome-use press Control+a # key combination
chrome-use keydown d # HOLD a key down (no auto-release)
Expand Down
Loading