Skip to content

Snippet choice placeholders drop escaped characters from all choices after the first #61312

Description

@Ni8crawler18

Reproduction steps

  1. Start Zed.
  2. Open the command palette and run snippets: configure snippets → "New Snippets File...". This opens a snippets file in ~/.config/zed/snippets/.
  3. Paste the following and save:

{
"Choice bug repro": {
"prefix": "tchoice",
"body": "${1|a,b\,c|}"
}
}

Image
  1. Restart Zed (the snippet may not be picked up if the snippets directory was just created).
  2. In a buffer, type tchoice and accept the snippet completion (press ctrl-space if the menu doesn't open automatically).
  3. Look at the choice dropdown that opens for the first tabstop.

Current vs. Expected behavior

Current: the dropdown offers a and bc — the backslash-escaped comma in the second choice is silently dropped

Image

Expected: a and b,c.

The snippet body ${1|a,b,c|} has two choices, where , is an escaped literal comma.
Per the LSP snippet grammar (choice ::= '${' int '|' text (',' text)* '|}'): "Within choice elements, the backslash also escapes comma and pipe characters."

VS Code offers a and b,c for the same snippet body.

Escapes in the first choice work correctly (e.g. ${1|x,y,z|} correctly offers x,y). The bug affects every choice after the first, for any escaped character (,, |, \).

Cause

In crates/snippet/src/snippet.rs, parse_choices handles escape sequences like this:

Some('\') => {
source = &source[1..];
if let Some(c) = source.chars().next() {
if !found_default_choice {
current_choice.push(c);
text.push(c);
}
source = &source[c.len_utf8()..];
}
}

current_choice.push(c) is inside the !found_default_choice guard, so once the first choice has been parsed, escaped characters in later choices are skipped entirely. The guard should only apply to text (the default value inserted into the buffer) - the plain-text branch a few lines below does this correctly, appending to current_choice unconditionally and only gating text.

The existing test (test_snippet_with_choice_placeholders) only covers escapes in the first choice, which is why it doesn't catch this.

I'd like to submit a PR fixing this (move current_choice.push(c) out of the guard) along with a regression test.

Zed version and system specs

Zed: v1.11.3+stable.326.952d712dac48a4af2c54fb22c82d82a9d69b72d4 (Zed)
OS: Linux Wayland fedora 44
Memory: 15 GiB
Architecture: x86_64
GPU: AMD Radeon Graphics (RADV RENOIR) || radv || Mesa 26.1.4

Attach Zed log file

Zed.log

Relevant Zed settings

settings.json

Relevant Keymap

keymap.json

(for AI issues) Model provider details

No response

If you are using WSL on Windows, what flavor of Linux are you using?

None

Metadata

Metadata

Assignees

No one assigned

    Labels

    state:needs triagetriage is not complete: no `area:*` label, or no `priority:*`/`frequency:*` for Bugs/Crashes

    Type

    Fields

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions