1use core::fmt;
9use std::sync::Arc;
10
11use crate::analysis::{SemanticTokens, SharedContext};
12use crate::index::protocol::ResultSet;
13use crate::prelude::Definition;
14use crate::{LocalContext, path_to_url};
15use ecow::EcoString;
16use lsp_types::{Hover, Url};
17use tinymist_analysis::docs::DefDocs;
18use tinymist_analysis::syntax::classify_syntax;
19use tinymist_std::error::WithContextUntyped;
20use tinymist_std::hash::FxHashMap;
21use tinymist_world::EntryReader;
22use typst::syntax::{FileId, LinkedNode, Source, Span};
23use typst_shim::syntax::source_range;
24
25pub mod protocol;
26pub mod query;
27mod scip_utils;
28use protocol as p;
29
30pub struct Knowledge {
32 pub meta: p::MetaData,
34 pub files: Vec<FileIndex>,
36}
37
38impl Knowledge {
39 pub fn bind<'a>(&'a self, ctx: &'a Arc<SharedContext>) -> KnowledgeWithContext<'a> {
41 KnowledgeWithContext {
42 knowledge: self,
43 ctx,
44 }
45 }
46}
47
48pub struct KnowledgeWithContext<'a> {
50 knowledge: &'a Knowledge,
51 ctx: &'a Arc<SharedContext>,
52}
53
54pub mod scip;
55pub mod scip_query;
56pub use scip::{ScipPublicApi, ScipPublicModule};
57
58impl fmt::Display for KnowledgeWithContext<'_> {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 let mut files = FxHashMap::default();
61 let mut encoder = LsifEncoder {
62 ctx: self.ctx,
63 writer: f,
64 id: IdCounter::new(),
65 files: &mut files,
66 results: FxHashMap::default(),
67 hover_results: FxHashMap::default(),
68 };
69
70 encoder
71 .emit_meta(self.knowledge.meta.clone())
72 .map_err(|err| {
73 log::error!("cannot write meta data: {err}");
74 fmt::Error
75 })?;
76 encoder.emit_files(&self.knowledge.files).map_err(|err| {
77 log::error!("cannot write files: {err}");
78 fmt::Error
79 })
80 }
81}
82
83struct IdCounter {
84 next: usize,
85}
86
87impl IdCounter {
88 fn new() -> Self {
89 Self { next: 0 }
90 }
91
92 fn next(&mut self) -> usize {
93 let id = self.next;
94 self.next += 1;
95 id
96 }
97}
98
99trait LsifWrite {
100 fn write_element(&mut self, id: i32, element: p::Element) -> fmt::Result;
101}
102
103impl<T: fmt::Write> LsifWrite for T {
104 fn write_element(&mut self, id: i32, element: p::Element) -> fmt::Result {
105 let entry = p::Entry { id, data: element };
106 self.write_str(&serde_json::to_string(&entry).unwrap())?;
107 self.write_char('\n')
108 }
109}
110
111struct LsifEncoder<'a, W: fmt::Write> {
112 ctx: &'a Arc<SharedContext>,
113 writer: &'a mut W,
114 id: IdCounter,
115 files: &'a mut FxHashMap<FileId, i32>,
116 results: FxHashMap<Span, i32>,
117 hover_results: FxHashMap<String, i32>,
118}
119
120impl<'a, W: fmt::Write> LsifEncoder<'a, W> {
121 fn alloc_file_id(&mut self, fid: FileId) -> i32 {
122 *self.files.entry(fid).or_insert_with(|| {
123 let id = self.id.next() as i32;
124 self.writer
125 .write_element(
126 id,
127 p::Element::Vertex(p::Vertex::Document(p::Document {
128 uri: self.ctx.uri_for_id(fid).unwrap_or_else(|err| {
129 log::error!("cannot get uri for {fid:?}: {err}");
130 Url::parse("file:///unknown").unwrap()
131 }),
132 language_id: EcoString::inline("typst"),
133 })),
134 )
135 .unwrap();
136
137 id
138 })
139 }
140
141 fn alloc_result_id(&mut self, span: Span) -> tinymist_std::Result<i32> {
142 if let Some(id) = self.results.get(&span) {
143 return Ok(*id);
144 }
145
146 let id = self.emit_element(p::Element::Vertex(p::Vertex::ResultSet(ResultSet {
147 key: None,
148 })))?;
149
150 self.results.insert(span, id);
151 Ok(id)
152 }
153
154 fn emit_element(&mut self, element: p::Element) -> tinymist_std::Result<i32> {
155 let id = self.id.next() as i32;
156 self.writer
157 .write_element(id, element)
158 .context_ut("cannot write element")?;
159 Ok(id)
160 }
161
162 fn emit_meta(&mut self, meta: p::MetaData) -> tinymist_std::Result<()> {
163 let obj = p::Element::Vertex(p::Vertex::MetaData(meta));
164 self.emit_element(obj).map(|_| ())
165 }
166
167 fn emit_files(&mut self, files: &[FileIndex]) -> tinymist_std::Result<()> {
168 for (idx, file) in files.iter().enumerate() {
169 eprintln!("emit file: {:?}, {idx} of {}", file.fid, files.len());
170 let source = self
171 .ctx
172 .source_by_id(file.fid)
173 .context_ut("cannot get source")?;
174 let fid = self.alloc_file_id(file.fid);
175 let semantic_tokens_id =
176 self.emit_element(p::Element::Vertex(p::Vertex::SemanticTokensResult {
177 result: lsp_types::SemanticTokens {
178 result_id: None,
179 data: file.semantic_tokens.as_ref().clone(),
180 },
181 }))?;
182 self.emit_element(p::Element::Edge(p::Edge::SemanticTokens(p::EdgeData {
183 out_v: fid,
184 in_v: semantic_tokens_id,
185 })))?;
186
187 let mut tokens_id = vec![];
188 for (s, reference) in &file.references {
189 let def = &reference.definition;
190 if let Some(range_id) = self.emit_span(*s, &source) {
191 let res_id = self.alloc_result_id(*s)?;
192 self.emit_element(p::Element::Edge(p::Edge::Next(p::EdgeData {
193 out_v: range_id,
194 in_v: res_id,
195 })))?;
196 if let Some(hover) = &reference.hover {
197 self.emit_hover(res_id, hover)?;
198 }
199 tokens_id.push(range_id);
200 }
201
202 if let Some(def_range_id) = self.emit_def_span(def, &source, false) {
203 if let Some(hover) = &reference.hover {
204 self.emit_hover(def_range_id, hover)?;
205 }
206 tokens_id.push(def_range_id);
207 }
208 }
209 self.emit_element(p::Element::Edge(p::Edge::Contains(p::EdgeDataMultiIn {
210 out_v: fid,
211 in_vs: tokens_id,
212 })))?;
213
214 for (s, reference) in &file.references {
215 let def = &reference.definition;
216 let res_id = self.alloc_result_id(*s)?;
217 let def_id = self.emit_element(p::Element::Vertex(p::Vertex::DefinitionResult))?;
218 let Some(def_range) = self.emit_def_span(def, &source, true) else {
219 continue;
220 };
221 let Some(file_id) = def.file_id() else {
222 continue;
223 };
224 let file_vertex_id = self.alloc_file_id(file_id);
225
226 self.emit_element(p::Element::Edge(p::Edge::Item(p::Item {
227 document: file_vertex_id,
228 property: None,
229 edge_data: p::EdgeDataMultiIn {
230 in_vs: vec![def_range],
231 out_v: def_id,
232 },
233 })))?;
234 self.emit_element(p::Element::Edge(p::Edge::Definition(p::EdgeData {
235 in_v: def_id,
236 out_v: res_id,
237 })))?;
238 }
239 }
240 Ok(())
241 }
242
243 fn emit_hover(&mut self, out_v: i32, hover: &Hover) -> tinymist_std::Result<()> {
244 let hover_id = self.alloc_hover_id(hover)?;
245 self.emit_element(p::Element::Edge(p::Edge::Hover(p::EdgeData {
246 out_v,
247 in_v: hover_id,
248 })))?;
249 Ok(())
250 }
251
252 fn alloc_hover_id(&mut self, hover: &Hover) -> tinymist_std::Result<i32> {
253 let key = serde_json::to_string(hover).context_ut("cannot serialize hover")?;
254 if let Some(id) = self.hover_results.get(&key) {
255 return Ok(*id);
256 }
257
258 let id = self.emit_element(p::Element::Vertex(p::Vertex::HoverResult {
259 result: hover.clone(),
260 }))?;
261 self.hover_results.insert(key, id);
262 Ok(id)
263 }
264
265 fn emit_span(&mut self, span: Span, source: &Source) -> Option<i32> {
266 let range = source_range(source, span)?;
267 self.emit_element(p::Element::Vertex(p::Vertex::Range {
268 range: self.ctx.to_lsp_range(range, source),
269 tag: None,
270 }))
271 .ok()
272 }
273
274 fn emit_def_span(&mut self, def: &Definition, source: &Source, external: bool) -> Option<i32> {
275 let s = def.decl.span();
276 if !s.is_detached() && s.id() == Some(source.id()) {
277 self.emit_span(s, source)
278 } else if let Some(fid) = def.file_id()
279 && fid == source.id()
280 {
281 None
283 } else if external && !s.is_detached() {
284 let external_src = self.ctx.source_by_id(def.file_id()?).ok()?;
285 self.emit_span(s, &external_src)
286 } else {
287 None
288 }
289 }
290}
291
292pub struct FileIndex {
294 pub fid: FileId,
296 pub semantic_tokens: SemanticTokens,
298 pub documentation: Option<EcoString>,
300 pub references: FxHashMap<Span, ReferenceIndex>,
302}
303
304pub struct ReferenceIndex {
306 pub definition: Definition,
308 pub hover: Option<Hover>,
310 pub def_docs: Option<DefDocs>,
312}
313
314pub fn knowledge(ctx: &mut LocalContext) -> tinymist_std::Result<Knowledge> {
318 let root = ctx
319 .world()
320 .entry_state()
321 .workspace_root()
322 .ok_or_else(|| tinymist_std::error_once!("workspace root is not set"))?;
323
324 let mut files = ctx.source_files().clone();
325 if let Some(main) = ctx.world().entry_state().main()
326 && !files.contains(&main)
327 {
328 files.push(main);
329 }
330 for fid in ctx.depended_source_files() {
331 if !files.contains(&fid) {
332 files.push(fid);
333 }
334 }
335
336 let mut worker = DumpWorker {
337 ctx,
338 strings: FxHashMap::default(),
339 references: FxHashMap::default(),
340 hovers: FxHashMap::default(),
341 def_docs: FxHashMap::default(),
342 };
343 let files = files
344 .iter()
345 .map(move |fid| worker.file(fid))
346 .collect::<tinymist_std::Result<Vec<FileIndex>>>()?;
347
348 Ok(Knowledge {
349 meta: p::MetaData {
350 version: "0.6.0".to_string(),
351 project_root: path_to_url(&root)?,
352 position_encoding: p::Encoding::Utf16,
353 tool_info: Some(p::ToolInfo {
354 name: "tinymist".to_string(),
355 args: vec![],
356 version: Some(env!("CARGO_PKG_VERSION").to_string()),
357 }),
358 },
359 files,
360 })
361}
362
363struct DumpWorker<'a> {
364 ctx: &'a mut LocalContext,
366 strings: FxHashMap<EcoString, EcoString>,
368 references: FxHashMap<Span, ReferenceIndex>,
370 hovers: FxHashMap<Definition, Option<Hover>>,
372 def_docs: FxHashMap<Definition, Option<DefDocs>>,
374}
375
376impl DumpWorker<'_> {
377 fn file(&mut self, fid: &FileId) -> tinymist_std::Result<FileIndex> {
378 let source = self.ctx.source_by_id(*fid).context_ut("cannot parse")?;
379 let semantic_tokens = crate::SemanticTokensFullRequest::compute(self.ctx, &source);
380
381 let root = LinkedNode::new(source.root());
382 self.walk(&source, &root);
383 let references = std::mem::take(&mut self.references);
384
385 Ok(FileIndex {
386 fid: *fid,
387 semantic_tokens,
388 documentation: Some(self.intern("File documentation.")), references,
390 })
391 }
392
393 fn intern(&mut self, s: &str) -> EcoString {
394 if let Some(v) = self.strings.get(s) {
395 return v.clone();
396 }
397 let v = EcoString::from(s);
398 self.strings.insert(v.clone(), v.clone());
399 v
400 }
401
402 fn walk(&mut self, source: &Source, node: &LinkedNode) {
403 if node.get().children().len() == 0 {
404 let Some(syntax) = classify_syntax(node.clone(), node.offset()) else {
405 return;
406 };
407 let span = syntax.node().span();
408 if self.references.contains_key(&span) {
409 return;
410 }
411
412 let Some(definition) = self.ctx.def_of_syntax(source, syntax) else {
413 return;
414 };
415 let hover = self.hover(&definition);
416 let def_docs = self.def_docs(&definition);
417 self.references.insert(
418 span,
419 ReferenceIndex {
420 definition,
421 hover,
422 def_docs,
423 },
424 );
425
426 return;
427 }
428
429 for child in node.children() {
430 self.walk(source, &child);
431 }
432 }
433
434 fn hover(&mut self, definition: &Definition) -> Option<Hover> {
435 if let Some(hover) = self.hovers.get(definition) {
436 return hover.clone();
437 }
438
439 let hover = crate::hover::hover_from_definition_shared(self.ctx.shared(), definition, None);
440 self.hovers.insert(definition.clone(), hover.clone());
441 hover
442 }
443
444 fn def_docs(&mut self, definition: &Definition) -> Option<DefDocs> {
445 if let Some(docs) = self.def_docs.get(definition) {
446 return docs.clone();
447 }
448
449 let docs = self.ctx.def_docs(definition);
450 self.def_docs.insert(definition.clone(), docs.clone());
451 docs
452 }
453}