From 4f1402001b3c0e8df61a4a327a9ec02691096beb Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Thu, 19 Mar 2026 23:22:48 +0100 Subject: fix(ai): restore url-quote-magic for ? in zsh (#3304) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I discovered that since #3178, typing or pasting a `?` in a URL no longer gets escaped by `url-quote-magic`. For example, pasting `https://example.com/search?q=foo&test` would result in `https://example.com/search?q=foo\&test` (leave the `?` unescaped, while `&` still worked correctly). The root cause is that Atuin binds `?` to `_atuin_ai_question_mark`, which bypasses `url-quote-magic` in two ways: 1. **Typed `?`**: the else branch (non-empty buffer) appended `?` directly to `LBUFFER` instead of delegating to `self-insert` which runs `url-quote-magic`. 2. **Pasted `?`**: `bracketed-paste-magic` only [activates widgets whose name matches `self-*`](https://github.com/zsh-users/zsh/blob/99f578897614f318cdad76402a7d2423ce176b5a/Functions/Zle/bracketed-paste-magic#L24). Since `_atuin_ai_question_mark` didn't match, pasted `?` characters fell through to `zle .self-insert` — the raw built-in that inserts literally without any URL escaping. The fix renames the widget to `self-atuin-ai-question-mark` (Note: I am not sure this is the best way but it is a relatively simple one). The `self-` prefix satisfies `bracketed-paste-magic`'s `active-widgets` pattern, so `?` in paste is processed by our widget and delegated to `zle self-insert`, restoring `url-quote-magic` behaviour. The typed case delegates to `zle self-insert` in the else branch for the same reason. --- crates/atuin-ai/src/commands/init.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/crates/atuin-ai/src/commands/init.rs b/crates/atuin-ai/src/commands/init.rs index 6b23e936..77abc4f4 100644 --- a/crates/atuin-ai/src/commands/init.rs +++ b/crates/atuin-ai/src/commands/init.rs @@ -32,8 +32,10 @@ _atuin_ai_cleanup() { true } -# Question mark at start of line - natural language mode -_atuin_ai_question_mark() { +# Question mark at start of line - natural language mode. +# Named with 'self-' prefix so bracketed-paste-magic activates it during +# paste, allowing url-quote-magic to escape ? in pasted URLs via self-insert. +self-atuin-ai-question-mark() { # If buffer is empty or just contains '?', trigger natural language mode if [[ -z "$BUFFER" || "$BUFFER" == "?" ]]; then BUFFER="" @@ -65,13 +67,13 @@ _atuin_ai_question_mark() { zle reset-prompt fi else - LBUFFER="${LBUFFER}?" + zle self-insert fi } # Set up keybindings -zle -N _atuin_ai_question_mark -bindkey '?' _atuin_ai_question_mark # Question mark +zle -N self-atuin-ai-question-mark +bindkey '?' self-atuin-ai-question-mark # Question mark "# .trim() } @@ -193,13 +195,14 @@ mod tests { #[test] fn test_generate_zsh_integration() { let result = generate_zsh_integration(); - assert!(result.contains("_atuin_ai_question_mark")); + assert!(result.contains("self-atuin-ai-question-mark")); assert!(result.contains("bindkey")); assert!(result.contains("atuin ai inline --hook")); assert!(result.contains("__atuin_ai_print__")); assert!(result.contains("__atuin_ai_cancel__")); assert!(result.contains("__atuin_ai_execute__")); assert!(result.contains("__atuin_ai_insert__")); + assert!(result.contains("zle self-insert")); } #[test] -- cgit v1.3.1