tinymist_query/analysis/
color_expr.rs

1//! Analyze color expressions in a source file.
2use std::str::FromStr;
3
4use typst::visualize::Color;
5
6use crate::prelude::*;
7
8/// Analyzes the document and provides color information.
9pub struct ColorExprWorker<'a> {
10    /// The local analysis context to work with.
11    ctx: &'a mut LocalContext,
12    /// The source document to analyze.
13    source: Source,
14    /// The color information to provide.
15    pub colors: Vec<ColorInformation>,
16}
17
18impl<'a> ColorExprWorker<'a> {
19    /// Creates a new color expression worker.
20    pub fn new(ctx: &'a mut LocalContext, source: Source) -> Self {
21        Self {
22            ctx,
23            source,
24            colors: vec![],
25        }
26    }
27
28    /// Starts to work.
29    pub fn work(&mut self, node: LinkedNode) -> Option<()> {
30        match node.kind() {
31            SyntaxKind::FuncCall => {
32                let fc = self.on_call(node.clone());
33                if fc.is_some() {
34                    return Some(());
35                }
36            }
37            SyntaxKind::Named => {}
38            kind if kind.is_trivia() || kind.is_keyword() || kind.is_error() => return Some(()),
39            _ => {}
40        };
41
42        for child in node.children() {
43            self.work(child);
44        }
45
46        Some(())
47    }
48
49    fn on_call(&mut self, node: LinkedNode) -> Option<()> {
50        let call = node.cast::<ast::FuncCall>()?;
51        let mut callee = call.callee();
52        'check_color_fn: loop {
53            match callee {
54                ast::Expr::FieldAccess(fa) => {
55                    let target = fa.target();
56                    let ast::Expr::Ident(ident) = target else {
57                        return None;
58                    };
59                    if ident.get().as_str() != "color" {
60                        return None;
61                    }
62                    callee = ast::Expr::Ident(fa.field());
63                    continue 'check_color_fn;
64                }
65                ast::Expr::Ident(ident) => {
66                    // currently support rgb, luma
67                    match ident.get().as_str() {
68                        "rgb" => self.on_rgb(&node, call)?,
69                        "luma" | "oklab" | "oklch" | "linear-rgb" | "cmyk" | "hsl" | "hsv" => {
70                            self.on_const_call(&node, call)?
71                        }
72                        _ => return None,
73                    }
74                }
75                _ => return None,
76            }
77            return None;
78        }
79    }
80
81    fn on_rgb(&mut self, node: &LinkedNode, call: ast::FuncCall) -> Option<()> {
82        let mut args = call.args().items();
83        let hex_or_color_or_r = args.next()?;
84        let arg = args.next();
85        match (arg.is_some(), hex_or_color_or_r) {
86            (true, _) => self.on_const_call(node, call)?,
87            (false, ast::Arg::Pos(ast::Expr::Str(s))) => {
88                // parse hex
89                let color = typst::visualize::Color::from_str(s.get().as_str()).ok()?;
90                // todo: smarter
91                // let arg = node.find(hex_or_color_or_r.span())?;
92                let arg = node.find(call.span())?;
93                self.push_color(arg.range(), color);
94            }
95            (false, _) => {}
96        }
97
98        Some(())
99    }
100
101    fn on_const_call(&mut self, node: &LinkedNode, call: ast::FuncCall) -> Option<()> {
102        let color = self.ctx.mini_eval(ast::Expr::FuncCall(call))?.cast().ok()?;
103        self.push_color(node.range(), color);
104        Some(())
105    }
106
107    fn push_color(&mut self, range: Range<usize>, color: Color) -> Option<()> {
108        let rng = self.ctx.to_lsp_range(range, &self.source);
109        let [r, g, b, a] = color.to_rgb().to_vec4();
110
111        self.colors.push(ColorInformation {
112            range: rng,
113            color: lsp_types::Color {
114                red: r,
115                green: g,
116                blue: b,
117                alpha: a,
118            },
119        });
120
121        Some(())
122    }
123}