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}