tinymist_query/
code_context.rs1use serde::{Deserialize, Serialize};
2use tinymist_analysis::analyze_expr;
3use tinymist_world::ShadowApi;
4use typst::foundations::{Bytes, IntoValue, StyleChain};
5use typst_shim::syntax::LinkedNodeExt;
6
7use crate::{
8 prelude::*,
9 syntax::{InterpretMode, interpret_mode_at},
10};
11
12#[derive(Debug, Clone, Deserialize)]
14#[serde(tag = "kind", rename_all = "camelCase")]
15pub enum InteractCodeContextQuery {
16 ModeAt {
18 position: LspPosition,
20 },
21 StyleAt {
23 position: LspPosition,
25 style: Vec<String>,
27 },
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
32#[serde(tag = "kind", rename_all = "camelCase")]
33pub enum InteractCodeContextResponse {
34 ModeAt {
36 mode: InterpretMode,
38 },
39 StyleAt {
41 style: Vec<Option<JsonValue>>,
43 },
44}
45
46#[derive(Debug, Clone, Deserialize)]
48#[serde(tag = "kind")]
49pub struct InteractCodeContextRequest {
50 pub path: PathBuf,
52 pub query: Vec<Option<InteractCodeContextQuery>>,
54}
55
56impl SemanticRequest for InteractCodeContextRequest {
57 type Response = Vec<Option<InteractCodeContextResponse>>;
58
59 fn request(self, ctx: &mut LocalContext) -> Option<Self::Response> {
60 let mut responses = Vec::new();
61
62 let source = ctx.source_by_path(&self.path).ok()?;
63
64 for query in self.query {
65 responses.push(query.and_then(|query| match query {
66 InteractCodeContextQuery::ModeAt { position } => {
67 let cursor = ctx.to_typst_pos(position, &source)?;
68 let mode = Self::mode_at(&source, cursor)?;
69 Some(InteractCodeContextResponse::ModeAt { mode })
70 }
71 InteractCodeContextQuery::StyleAt { position, style } => {
72 let mut world = ctx.world().clone();
73 log::info!(
74 "style at position {position:?} . {style:?} when main is {:?}",
75 world.main()
76 );
77 let cursor = ctx.to_typst_pos(position, &source)?;
78 let root = LinkedNode::new(source.root());
79 let mut leaf = root.leaf_at_compat(cursor)?;
80 log::info!("style at leaf {leaf:?} . {style:?}");
81
82 if !matches!(leaf.kind(), SyntaxKind::Text | SyntaxKind::MathText) {
83 return None;
84 }
85
86 if matches!(leaf.parent_kind(), Some(SyntaxKind::Raw)) {
87 leaf = leaf.parent()?.clone();
88 }
89
90 let mode = Self::mode_at(&source, cursor);
91 if !matches!(
92 mode,
93 Some(InterpretMode::Code | InterpretMode::Markup | InterpretMode::Math)
94 ) {
95 leaf = leaf.parent()?.clone();
96 }
97 let mut mapped_source = source.clone();
98 let (with, offset) = match mode {
99 Some(InterpretMode::Code) => ("context text.font", 8),
100 _ => ("#context text.font", 10),
101 };
102 let start = leaf.range().start;
103 mapped_source.edit(leaf.range(), with);
104
105 let _ = world.map_shadow_by_id(
106 mapped_source.id(),
107 Bytes::new(mapped_source.text().as_bytes().to_vec()),
108 );
109 world.take_db();
110
111 let root = LinkedNode::new(mapped_source.root());
112 let leaf = root.leaf_at_compat(start + offset)?;
113
114 log::info!("style at new_leaf {leaf:?} . {style:?}");
115
116 let mut cursor_styles = analyze_expr(&world, &leaf)
117 .iter()
118 .filter_map(|s| s.1.clone())
119 .collect::<Vec<_>>();
120 cursor_styles.sort_by_key(|x| x.as_slice().len());
121 log::info!("style at styles {cursor_styles:?} . {style:?}");
122 let cursor_style = cursor_styles.into_iter().next_back().unwrap_or_default();
123 let cursor_style = StyleChain::new(&cursor_style);
124
125 log::info!("style at style {cursor_style:?} . {style:?}");
126
127 let style = style
128 .iter()
129 .map(|style| Self::style_at(cursor_style, style))
130 .collect();
131 let _ = world.map_shadow_by_id(
132 mapped_source.id(),
133 Bytes::new(source.text().as_bytes().to_vec()),
134 );
135
136 Some(InteractCodeContextResponse::StyleAt { style })
137 }
138 }));
139 }
140
141 Some(responses)
142 }
143}
144
145impl InteractCodeContextRequest {
146 fn mode_at(source: &Source, pos: usize) -> Option<InterpretMode> {
147 if pos == 0 || pos >= source.text().len() {
149 return Some(InterpretMode::Markup);
150 }
151
152 let root = LinkedNode::new(source.root());
154 Some(interpret_mode_at(root.leaf_at_compat(pos).as_ref()))
155 }
156
157 fn style_at(cursor_style: StyleChain, style: &str) -> Option<JsonValue> {
158 match style {
159 "text.font" => {
160 let font = typst::text::TextElem::font_in(cursor_style)
161 .clone()
162 .into_value();
163 serde_json::to_value(font).ok()
164 }
165 _ => None,
166 }
167 }
168}