zed-industries/zed · error

unexpected character '{next:?}'

Error message

unexpected character '{next:?}'

What it means

KeyBindingContextPredicate::parse parses a complete context predicate expression and requires the entire source to be consumed. After parse_expr returns, any remaining character (whitespace is consumed by atoms and operators, so this is real syntax) means trailing text the grammar could not attach: typically an unbalanced ')' or a second predicate with no operator between them.

Source

Thrown at crates/gpui/src/keymap/context.rs:253

    /// `StatusBar && mode == visible` -> A predicate that will match a context with the
    ///                                   identifier `StatusBar` and the key `mode`
    ///                                   with the value `visible`
    ///
    ///
    /// There is also a special child `>` operator that will match a predicate that is
    /// below another predicate:
    ///
    /// `StatusBar > mode == visible` -> A predicate that will match a context identifier `StatusBar`
    ///                                  and a child context that has the key `mode` with the
    ///                                  value `visible`
    ///
    /// This syntax supports `!=`, `||` and `&&` as logical operators.
    /// You can also preface an operation or check with a `!` to negate it.
    pub fn parse(source: &str) -> Result<Self> {
        let source = skip_whitespace(source);
        let (predicate, rest) = Self::parse_expr(source, 0)?;
        if let Some(next) = rest.chars().next() {
            anyhow::bail!("unexpected character '{next:?}'");
        } else {
            Ok(predicate)
        }
    }

    /// Find the deepest depth at which the predicate matches.
    pub fn depth_of(&self, contexts: &[KeyContext]) -> Option<usize> {
        for depth in (0..=contexts.len()).rev() {
            let context_slice = &contexts[0..depth];
            if self.eval_inner(context_slice, contexts) {
                return Some(depth);
            }
        }
        None
    }

    /// Eval a predicate against a set of contexts, arranged from lowest to highest.
    #[allow(unused)]

View on GitHub (pinned to f4178619ac)

Solutions

  1. Delete or fix the trailing text: usually an extra ')' or a predicate missing its '&&'/'||'/'>' operator
  2. Rebalance parentheses across the whole expression
  3. Check the editor log: the parse error is reported against the exact context string, which pinpoints the binding

Example fix

// before (keymap.json)
"context": "mode == visual)"

// after
"context": "mode == visual"
Defensive patterns

Strategy: validation

Validate before calling

// validate keymap context strings at load time
for binding in &keymap.bindings {
    KeyBindingContextPredicate::parse(binding.context.as_str())
        .with_context(|| format!("bad context in binding {binding:?}"))?;
}

Type guard

fn is_parseable_context(source: &str) -> bool {
    KeyBindingContextPredicate::parse(source).is_ok()
}

Prevention

When it happens

Trigger: Keymap context strings like 'mode == visual)' (stray close paren), 'normal (mode == visual)' (missing &&), or 'a b' (two identifiers not joined by an operator).

Common situations: Hand-edited keymap.json typos: an extra parenthesis left after editing; parentheses unbalanced across the expression; a fragment left behind after deleting an operator.

Related errors


AI-assisted analysis of zed-industries/zed@f4178619ac (2026-08-20). Data as JSON: /api/errors/570a25603457a31b. Report an issue: GitHub.