helix-editor/helix · error · anyhow::Error
Failed to parse snippet. Remaining input: {}
Error message
Failed to parse snippet. Remaining input: {} What it means
Thrown by Snippet::parse when the internal LSP/VSCode-style snippet parser (helix-core/src/snippets/parser.rs:77) cannot consume the entire input string; the error carries the unconsumed remainder. This means the snippet text contains a construct the grammar does not accept (empty string, stray '$', unterminated placeholder '${1' or '${name', or an unescaped '}'/'\' outside a valid escape). Any caller applying a snippet (LSP completion/insert-text, user snippet recipes) surfaces this error instead of inserting text.
Source
Thrown at helix-core/src/snippets/elaborate.rs:29
use ropey::RopeSlice;
use crate::case_conversion::to_lower_case_with;
use crate::case_conversion::to_upper_case_with;
use crate::case_conversion::{to_camel_case_with, to_pascal_case_with};
use crate::snippets::parser::{self, CaseChange, FormatItem};
use crate::snippets::{TabstopIdx, LAST_TABSTOP_IDX};
use crate::Tendril;
#[derive(Debug)]
pub struct Snippet {
elements: Vec<SnippetElement>,
tabstops: Vec<Tabstop>,
}
impl Snippet {
pub fn parse(snippet: &str) -> Result<Self> {
let parsed_snippet = parser::parse(snippet)
.map_err(|rest| anyhow!("Failed to parse snippet. Remaining input: {}", rest))?;
Ok(Snippet::new(parsed_snippet))
}
pub fn new(elements: Vec<parser::SnippetElement>) -> Snippet {
let mut res = Snippet {
elements: Vec::new(),
tabstops: Vec::new(),
};
res.elements = res.elaborate(elements, None).into();
res.fixup_tabstops();
res.ensure_last_tabstop();
res.renumber_tabstops();
res
}
pub fn elements(&self) -> &[SnippetElement] {
&self.elements
}View on GitHub (pinned to 079a789e8c)
Solutions
- Inspect the 'Remaining input' portion — the first character of it is exactly where parsing stopped; fix or escape it (use \$ and \} for literals)
- If the snippet comes from your own config/recipe, validate the body against VSCode snippet syntax: tabstops $1/$0, placeholders ${1:text}, variables ${name}, choice ${1|a,b|}, escaped \$ \} \\
- If it comes from an LSP server, capture the exact insertText sent (set log file via 'hx --log' and LSP logging) and report/patch the server; as a workaround send plain-text completions
- Empty snippet strings are invalid — fall back to an empty insert or reject the completion item before calling Snippet::parse
Example fix
// before
let snippet = Snippet::parse("cost: $100 }")?; // stray '$' and '}' stop the parser
// after
let snippet = Snippet::parse("cost: \$100 \}")?; // literals escaped, parses fully Defensive patterns
Strategy: validation
Validate before calling
// Rust: dry-run the parse before applying a snippet (insert/edit path)
use helix_core::snippets::Snippet;
fn safe_snippet(body: &str) -> Option<Snippet> {
match Snippet::parse(body) {
Ok(s) => Some(s),
Err(err) => {
log::warn!("rejecting malformed snippet {body:?}: {err}");
None // fall back to inserting raw text
}
}
} Type guard
fn is_parseable_snippet(body: &str) -> bool {
helix_core::snippets::Snippet::parse(body).is_ok()
} Try / catch
// Snippet::parse returns Result<Snippet, anyhow::Error>; always handle it,
// never unwrap on text that originated from LSP or user config:
match Snippet::parse(text) {
Ok(snippet) => apply(snippet),
Err(e) => editor.set_error(format!("invalid snippet: {e}")),
} Prevention
- Escape literal '$', '}' and '\\' as '\$', '\}', '\\\' in snippet bodies
- Never pass an empty string - short-circuit empty completions to plain insert
- If you ship snippets, add a unit test that runs Snippet::parse on every body
- Log the offending snippet body together with the 'Remaining input' when it fails
When it happens
Trigger: Calling Snippet::parse with an empty string (the parser test at parser.rs:349 asserts parse("") == Err("")), a '$' not followed by a digit, '{', or variable name, an unterminated '${...' placeholder, or a '$' escape used where the grammar expects valid tabstop/variable syntax. Reached from LSP completion items whose insertTextFormat is Snippet but whose insertText is malformed, or from user-defined snippets in config.
Common situations: A language server sends a broken or non-standard snippet body; a user writes a snippet recipe with a literal '$' or '}' that is not escaped ('\$', '\}'); an empty snippet string is passed when a completion item has no text; version changes in the snippet grammar reject a construct an older release tolerated.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Language server '{name}' not defined
- Command not provided
- Incorrect transport {}
- DAP request failed
- Git command failed. Stdout: {} Stderr: {}
AI-assisted analysis of helix-editor/helix@079a789e8c (2026-08-16).
Data as JSON: /api/errors/702ae426c29b0f66.
Report an issue: GitHub.