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

  1. Inspect the 'Remaining input' portion — the first character of it is exactly where parsing stopped; fix or escape it (use \$ and \} for literals)
  2. 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 \$ \} \\
  3. 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
  4. 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

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

Related errors


AI-assisted analysis of helix-editor/helix@079a789e8c (2026-08-16). Data as JSON: /api/errors/702ae426c29b0f66. Report an issue: GitHub.