tinymist_query/analysis/
color_expr.rs1use std::str::FromStr;
3
4use typst::visualize::Color;
5
6use crate::prelude::*;
7
8pub struct ColorExprWorker<'a> {
10 ctx: &'a mut LocalContext,
12 source: Source,
14 pub colors: Vec<ColorInformation>,
16}
17
18impl<'a> ColorExprWorker<'a> {
19 pub fn new(ctx: &'a mut LocalContext, source: Source) -> Self {
21 Self {
22 ctx,
23 source,
24 colors: vec![],
25 }
26 }
27
28 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 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 let color = typst::visualize::Color::from_str(s.get().as_str()).ok()?;
90 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}