Skip to main content

lattice_snippet/
token.rs

1//! Parsed snippet body: the token stream a [`crate::parse::parse`]
2//! produces and [`crate::render::render`] walks.
3//!
4//! Tokens map 1:1 to TextMate / LSP snippet syntax constructs
5//! plus a tail `Literal` for plain text runs:
6//!
7//! | Construct | Token |
8//! |---|---|
9//! | `foo bar` | [`SnippetToken::Literal`] |
10//! | `$1`, `$0`, `$99` | [`SnippetToken::Tabstop`] |
11//! | `${1:default text}` | [`SnippetToken::Placeholder`] |
12//! | `${1\|opt1,opt2,opt3\|}` | [`SnippetToken::Choice`] |
13//! | `$VAR` / `${VAR}` | [`SnippetToken::Variable`] |
14//! | `${VAR:fallback}` | [`SnippetToken::Variable`] (with `default`) |
15//! | `${1/pat/repl/flags}` / `${VAR/pat/repl/flags}` | [`SnippetToken::Transform`] |
16//! | `\$`, `\\`, `\}` | unescaped → contributes to a [`SnippetToken::Literal`] |
17
18use serde::{Deserialize, Serialize};
19
20/// A parsed snippet body. Just a wrapper over the token vec so
21/// the type signature in registry / load / render stays
22/// readable.
23#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
24pub struct SnippetBody {
25    pub tokens: Vec<SnippetToken>,
26}
27
28impl SnippetBody {
29    pub fn new(tokens: Vec<SnippetToken>) -> Self {
30        Self { tokens }
31    }
32
33    pub fn iter(&self) -> std::slice::Iter<'_, SnippetToken> {
34        self.tokens.iter()
35    }
36
37    pub fn is_empty(&self) -> bool {
38        self.tokens.is_empty()
39    }
40}
41
42/// Choice placeholder option (`${1|opt1,opt2,opt3|}` -> three
43/// options). Stored verbatim; the picker UI renders them as
44/// alternatives.
45#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
46pub struct ChoiceOption {
47    pub text: String,
48}
49
50/// One token from a parsed snippet body.
51#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
52pub enum SnippetToken {
53    /// Plain text that lands in the rendered output verbatim.
54    /// Adjacent literal tokens are merged by the parser so a
55    /// rendered body never has two literals in a row.
56    Literal(String),
57    /// `$N` -- bare tabstop. `$0` is the final cursor position
58    /// (exited automatically); `$1..$N` are visited in order.
59    Tabstop(u32),
60    /// `${N:default}` -- tabstop with default text. The default
61    /// can itself contain nested tokens (e.g. `${1:foo${2:bar}}`)
62    /// so users can place inner placeholders inside.
63    Placeholder { idx: u32, default: SnippetBody },
64    /// `${N|opt1,opt2,opt3|}` -- pick from a fixed list. The
65    /// host's choice-picker UI surfaces these on focus.
66    Choice {
67        idx: u32,
68        options: Vec<ChoiceOption>,
69    },
70    /// `$NAME` / `${NAME}` / `${NAME:fallback}` -- variable
71    /// substitution. `default` runs only when the variable
72    /// resolves to None.
73    Variable {
74        name: String,
75        default: Option<SnippetBody>,
76    },
77    /// `${N/pat/repl/flags}` / `${NAME/pat/repl/flags}` --
78    /// regex transformation on the bound text. v1 parses but
79    /// renders as the bound text un-transformed; full regex
80    /// support lands as polish (transformations are rare in
81    /// practice, and supporting the full regex format syntax
82    /// is its own feature).
83    Transform {
84        target: TransformTarget,
85        pattern: String,
86        replacement: String,
87        flags: String,
88    },
89}
90
91/// What a `Transform` token applies to.
92#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
93pub enum TransformTarget {
94    Tabstop(u32),
95    Variable(String),
96}