rune/ace/
autocomplete.rs

1use core::str;
2
3use anyhow::{Context as _, Result};
4use pulldown_cmark::{Options, Parser};
5use syntect::parsing::SyntaxSet;
6
7use crate as rune;
8use crate::alloc::borrow::Cow;
9use crate::alloc::fmt::TryWrite;
10use crate::alloc::prelude::*;
11use crate::alloc::{HashMap, String};
12use crate::compile::Prelude;
13use crate::doc::markdown;
14use crate::doc::{Artifacts, Context, Function, Kind, Meta, Signature, Visitor};
15use crate::{hash, ItemBuf};
16
17pub(crate) fn build(
18    artifacts: &mut Artifacts,
19    context: &crate::Context,
20    visitors: &[Visitor],
21    extensions: bool,
22) -> Result<()> {
23    let context = Context::new(Some(context), visitors);
24
25    let mut acx = AutoCompleteCtx::new(&context, extensions);
26
27    for item in context.iter_modules() {
28        let item = item?;
29        acx.collect_meta(&item)?;
30    }
31
32    acx.build(artifacts)?;
33    Ok(())
34}
35
36struct AutoCompleteCtx<'a> {
37    ctx: &'a Context<'a>,
38    extensions: bool,
39    syntax_set: SyntaxSet,
40    fixed: HashMap<ItemBuf, Meta<'a>>,
41    instance: HashMap<ItemBuf, Meta<'a>>,
42    prelude: Prelude,
43}
44
45impl<'a> AutoCompleteCtx<'a> {
46    fn new(ctx: &'a Context, extensions: bool) -> AutoCompleteCtx<'a> {
47        AutoCompleteCtx {
48            ctx,
49            extensions,
50            syntax_set: SyntaxSet::load_defaults_newlines(),
51            fixed: HashMap::new(),
52            instance: HashMap::new(),
53            prelude: Prelude::with_default_prelude().unwrap_or_default(),
54        }
55    }
56
57    fn collect_meta(&mut self, item: &ItemBuf) -> Result<()> {
58        for meta in self.ctx.meta(item)?.into_iter() {
59            match meta.kind {
60                Kind::Type => {
61                    self.fixed.try_insert(item.try_clone()?, meta)?;
62                }
63                Kind::Struct => {
64                    for (_, name) in self.ctx.iter_components(item)? {
65                        let item = item.join([name])?;
66                        self.collect_meta(&item)?;
67                    }
68                    self.fixed.try_insert(item.try_clone()?, meta)?;
69                }
70                Kind::Variant => {
71                    self.fixed.try_insert(item.try_clone()?, meta)?;
72                }
73                Kind::Enum => {
74                    for (_, name) in self.ctx.iter_components(item)? {
75                        let item = item.join([name])?;
76                        self.collect_meta(&item)?;
77                    }
78                    self.fixed.try_insert(item.try_clone()?, meta)?;
79                }
80                Kind::Macro => {
81                    self.fixed.try_insert(item.try_clone()?, meta)?;
82                }
83                Kind::Function(f) => {
84                    if matches!(f.signature, Signature::Instance) {
85                        self.instance.try_insert(item.try_clone()?, meta)?;
86                    } else {
87                        self.fixed.try_insert(item.try_clone()?, meta)?;
88                    }
89                }
90                Kind::Const(_) => {
91                    self.fixed.try_insert(item.try_clone()?, meta)?;
92                }
93                Kind::Module => {
94                    for (_, name) in self.ctx.iter_components(item)? {
95                        let item = item.join([name])?;
96                        self.collect_meta(&item)?;
97                    }
98                    self.fixed.try_insert(item.try_clone()?, meta)?;
99                }
100                Kind::Unsupported | Kind::Trait => {}
101            }
102        }
103
104        Ok(())
105    }
106
107    fn build(&mut self, artifacts: &mut Artifacts) -> Result<()> {
108        let mut content = Vec::new();
109        self.write(&mut content)?;
110
111        artifacts.asset(false, "rune-autocomplete.js", || Ok(content.into()))?;
112
113        Ok(())
114    }
115
116    fn doc_to_html(&self, meta: &Meta) -> Result<Option<String>> {
117        let mut input = String::new();
118
119        for line in meta.docs {
120            let line = line.strip_prefix(' ').unwrap_or(line);
121            input.try_push_str(line)?;
122            input.try_push('\n')?;
123        }
124
125        let mut o = String::new();
126        write!(o, "<div class=\"docs\">")?;
127        let mut options = Options::empty();
128        options.insert(Options::ENABLE_STRIKETHROUGH);
129
130        let iter = Parser::new_ext(&input, options);
131
132        markdown::push_html(Some(&self.syntax_set), &mut o, iter, None)?;
133
134        write!(o, "</div>")?;
135        let o = String::try_from(o.replace('`', "\\`"))?;
136
137        Ok(Some(o))
138    }
139
140    fn get_name(&self, item: &ItemBuf) -> Result<String> {
141        // shorten item name with auto prelude when available
142        if let Some(name) = self.prelude.get_local(item) {
143            return Ok(name.try_to_string()?);
144        }
145
146        // take default name and remove starting double points
147        let mut name = item.try_to_string()?;
148        if name.starts_with("::") {
149            name.try_replace_range(..2, "")?;
150        }
151
152        Ok(name)
153    }
154
155    fn get_fn_ext(f: &Function) -> Result<String> {
156        let mut ext = String::new();
157        // automatic .await for async functions
158        if f.is_async {
159            ext.try_push_str(".await")?;
160        }
161
162        // automatic questionmark for result and option
163        if matches!(
164            f.return_type.base,
165            hash!(::std::option::Option) | hash!(::std::result::Result)
166        ) {
167            ext.try_push_str("?")?;
168        }
169
170        Ok(ext)
171    }
172
173    fn get_fn_param(f: &Function) -> Result<String> {
174        let mut param = String::try_from("(")?;
175
176        // add arguments when no argument names are provided
177        if let Some(args) = f.arguments {
178            for (n, arg) in args.iter().enumerate() {
179                if n > 0 {
180                    param.try_push_str(", ")?;
181                }
182
183                write!(param, "{}", arg.name)?;
184            }
185        }
186
187        param.try_push(')')?;
188        Ok(param)
189    }
190
191    fn get_fn_ret_typ(&self, f: &Function) -> Result<String> {
192        let mut param = String::new();
193
194        if !self.extensions {
195            return Ok(param);
196        }
197
198        if let Some(item) = self
199            .ctx
200            .meta_by_hash(f.return_type.base)
201            .ok()
202            .and_then(|v| v.into_iter().next())
203            .and_then(|m| m.item.last())
204        {
205            param.try_push_str(" -> ")?;
206            param.try_push_str(&item.try_to_string()?)?;
207        }
208
209        Ok(param)
210    }
211
212    fn write_hint(
213        &self,
214        f: &mut Vec<u8>,
215        value: &str,
216        meta: &str,
217        score: usize,
218        caption: Option<&str>,
219        doc: Option<&str>,
220    ) -> Result<()> {
221        write!(f, r#"{{"#)?;
222        write!(f, r#"value: "{value}""#)?;
223
224        if let Some(caption) = caption {
225            write!(f, r#", caption: "{caption}""#)?;
226        }
227
228        write!(f, r#", meta: "{meta}""#)?;
229        write!(f, r#", score: {score}"#)?;
230
231        if let Some(doc) = doc {
232            let doc = escape(doc)?;
233            write!(f, r#", docHTML: "{doc}""#)?;
234        }
235
236        write!(f, "}}")?;
237        Ok(())
238    }
239
240    fn write_instances(&self, f: &mut Vec<u8>) -> Result<()> {
241        write!(f, r#"var instance = ["#)?;
242
243        for (item, meta) in self.instance.iter() {
244            let Kind::Function(fnc) = meta.kind else {
245                continue;
246            };
247
248            write!(f, "  ")?;
249
250            let mut iter = item.iter().rev();
251            let mut value = iter
252                .next()
253                .context("No function name found for instance function")?
254                .try_to_string()?;
255            value.try_push_str(&Self::get_fn_param(&fnc)?)?;
256
257            let mut typ = String::new();
258            typ.try_push_str(&value)?;
259            typ.try_push_str(&self.get_fn_ret_typ(&fnc)?)?;
260            value.try_push_str(&Self::get_fn_ext(&fnc)?)?;
261            if let Some(pre) = iter.next().and_then(|t| t.try_to_string().ok()) {
262                typ.try_push_str(" [")?;
263                typ.try_push_str(&pre)?;
264                typ.try_push(']')?;
265            }
266
267            let doc = self.doc_to_html(meta).ok().flatten();
268
269            let info = if fnc.is_async {
270                "async Instance"
271            } else {
272                "Instance"
273            };
274
275            self.write_hint(f, &value, info, 0, Some(&typ), doc.as_deref())?;
276            writeln!(f, ",")?;
277        }
278        write!(f, "];")?;
279
280        Ok(())
281    }
282
283    fn write_fixed(&self, f: &mut Vec<u8>) -> Result<()> {
284        writeln!(f, r#"var fixed = ["#)?;
285
286        for (item, meta) in self
287            .fixed
288            .iter()
289            .filter(|(_, m)| !matches!(m.kind, Kind::Unsupported | Kind::Trait))
290        {
291            write!(f, "  ")?;
292
293            match meta.kind {
294                Kind::Type => {
295                    let name = self.get_name(item)?;
296                    let doc = self.doc_to_html(meta).ok().flatten();
297                    self.write_hint(f, &name, "Type", 0, None, doc.as_deref())?;
298                }
299                Kind::Struct => {
300                    let name = self.get_name(item)?;
301                    let doc = self.doc_to_html(meta).ok().flatten();
302                    self.write_hint(f, &name, "Struct", 0, None, doc.as_deref())?;
303                }
304                Kind::Variant => {
305                    let name = self.get_name(item)?;
306                    let doc = self.doc_to_html(meta).ok().flatten();
307                    self.write_hint(f, &name, "Variant", 0, None, doc.as_deref())?;
308                }
309                Kind::Enum => {
310                    let name = self.get_name(item)?;
311                    let doc = self.doc_to_html(meta).ok().flatten();
312                    self.write_hint(f, &name, "Enum", 0, None, doc.as_deref())?;
313                }
314                Kind::Macro => {
315                    let mut name = self.get_name(item)?;
316                    name.try_push_str("!()")?;
317                    let doc = self.doc_to_html(meta).ok().flatten();
318                    self.write_hint(f, &name, "Type", 0, None, doc.as_deref())?;
319                }
320                Kind::Function(fnc) => {
321                    let mut value = self.get_name(item)?;
322                    value.try_push_str(&Self::get_fn_param(&fnc)?)?;
323                    let mut caption = value.try_clone()?;
324                    caption.try_push_str(&self.get_fn_ret_typ(&fnc)?)?;
325                    value.try_push_str(&Self::get_fn_ext(&fnc)?)?;
326                    let doc = self.doc_to_html(meta).ok().flatten();
327
328                    let info = if fnc.is_async {
329                        "async Function"
330                    } else {
331                        "Function"
332                    };
333
334                    self.write_hint(f, &value, info, 0, Some(&caption), doc.as_deref())?;
335                }
336                Kind::Const(_) => {
337                    let name = self.get_name(item)?;
338                    let doc = self.doc_to_html(meta).ok().flatten();
339                    self.write_hint(f, &name, "Const", 10, None, doc.as_deref())?;
340                }
341                Kind::Module => {
342                    let name = self.get_name(item)?;
343                    let doc = self.doc_to_html(meta).ok().flatten();
344                    self.write_hint(f, &name, "Module", 9, None, doc.as_deref())?;
345                }
346                _ => {}
347            }
348
349            writeln!(f, ",")?;
350        }
351
352        writeln!(f, "];")?;
353        Ok(())
354    }
355
356    fn write(&self, f: &mut Vec<u8>) -> Result<()> {
357        let completer =
358            super::embed::Assets::get("rune-completer.js").context("missing rune-completer.js")?;
359
360        f.try_extend_from_slice(completer.data.as_ref())?;
361        self.write_fixed(f)?;
362        self.write_instances(f)?;
363        Ok(())
364    }
365}
366
367fn escape(s: &str) -> Result<Cow<'_, str>> {
368    let n = 'escape: {
369        for (n, c) in s.char_indices() {
370            match c {
371                '\"' | '\n' => break 'escape n,
372                _ => {}
373            }
374        }
375
376        return Ok(Cow::Borrowed(s));
377    };
378
379    let mut out = String::new();
380
381    let (head, tail) = s.split_at(n);
382    out.try_push_str(head)?;
383
384    for c in tail.chars() {
385        match c {
386            '\"' => {
387                out.try_push_str(r#"\""#)?;
388            }
389            '\n' => {
390                out.try_push_str(r#"\n"#)?;
391            }
392            _ => {
393                out.try_push(c)?;
394            }
395        }
396    }
397
398    Ok(Cow::Owned(out))
399}