Skip to main content

just_lsp/
builtin.rs

1use super::*;
2
3#[derive(Debug)]
4pub enum Builtin<'a> {
5  Attribute {
6    name: &'a str,
7    kind: AttributeKind,
8    description: &'a str,
9    targets: &'a [AttributeTarget],
10  },
11  Constant {
12    name: &'a str,
13    description: &'a str,
14  },
15  Function {
16    name: &'a str,
17    aliases: &'a [&'a str],
18    kind: FunctionKind,
19    description: &'a str,
20    deprecated: Option<&'a str>,
21  },
22  Setting {
23    name: &'a str,
24    kind: SettingKind,
25    description: &'a str,
26    deprecated: Option<&'a str>,
27  },
28}
29
30impl Builtin<'_> {
31  #[must_use]
32  pub fn completion_items(&self) -> Vec<lsp::CompletionItem> {
33    match self {
34      Self::Attribute { name, .. } => vec![lsp::CompletionItem {
35        label: name.to_string(),
36        kind: Some(lsp::CompletionItemKind::KEYWORD),
37        documentation: Some(lsp::Documentation::MarkupContent(
38          self.description(),
39        )),
40        insert_text: Some(name.to_string()),
41        insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
42        sort_text: Some(format!("z{name}")),
43        ..Default::default()
44      }],
45      Self::Constant { name, .. } => vec![lsp::CompletionItem {
46        label: name.to_string(),
47        kind: Some(lsp::CompletionItemKind::CONSTANT),
48        documentation: Some(lsp::Documentation::MarkupContent(
49          self.description(),
50        )),
51        insert_text: Some(name.to_string()),
52        insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
53        sort_text: Some(format!("z{name}")),
54        ..Default::default()
55      }],
56      Self::Function { name, aliases, .. } => once(*name)
57        .chain(aliases.iter().copied())
58        .map(|name| self.function_completion_item(name))
59        .collect(),
60      Self::Setting { name, .. } => vec![lsp::CompletionItem {
61        label: name.to_string(),
62        kind: Some(lsp::CompletionItemKind::PROPERTY),
63        documentation: Some(lsp::Documentation::MarkupContent(
64          self.description(),
65        )),
66        insert_text: Some(name.to_string()),
67        insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
68        sort_text: Some(format!("z{name}")),
69        ..Default::default()
70      }],
71    }
72  }
73
74  #[must_use]
75  pub fn description(&self) -> lsp::MarkupContent {
76    lsp::MarkupContent {
77      kind: lsp::MarkupKind::Markdown,
78      value: (match self {
79        Self::Attribute { description, .. }
80        | Self::Constant { description, .. }
81        | Self::Function { description, .. }
82        | Self::Setting { description, .. } => description,
83      })
84      .to_string(),
85    }
86  }
87
88  fn function_completion_item(&self, name: &str) -> lsp::CompletionItem {
89    let snippet = match name {
90      "absolute_path" | "blake3_file" | "canonicalize" | "clean"
91      | "extension" | "file_name" | "file_stem" | "parent_dir"
92      | "parent_directory" | "path_exists" | "read" | "sha256_file"
93      | "without_extension" => {
94        format!("{name}(${{1:path:string}})")
95      }
96      "append" => {
97        format!("{name}(${{1:suffix:string}}, ${{2:s:string}})")
98      }
99      "arch"
100      | "num_cpus"
101      | "os"
102      | "os_family"
103      | "is_dependency"
104      | "invocation_directory"
105      | "invocation_directory_native"
106      | "invocation_dir"
107      | "invocation_dir_native"
108      | "justfile"
109      | "justfile_directory"
110      | "justfile_dir"
111      | "module_file"
112      | "module_directory"
113      | "module_dir"
114      | "module_path"
115      | "source_file"
116      | "source_directory"
117      | "source_dir"
118      | "just_executable"
119      | "just_pid"
120      | "uuid"
121      | "runtime_directory"
122      | "runtime_dir"
123      | "cache_directory"
124      | "cache_dir"
125      | "config_directory"
126      | "config_dir"
127      | "config_local_directory"
128      | "config_local_dir"
129      | "data_directory"
130      | "data_dir"
131      | "data_local_directory"
132      | "data_local_dir"
133      | "executable_directory"
134      | "executable_dir"
135      | "home_directory"
136      | "home_dir" => format!("{name}()"),
137      "blake3" | "sha256" => format!("{name}(${{1:string:string}})"),
138      "capitalize"
139      | "encode_uri_component"
140      | "kebabcase"
141      | "lowercase"
142      | "lowercamelcase"
143      | "quote"
144      | "shoutykebabcase"
145      | "shoutysnakecase"
146      | "snakecase"
147      | "titlecase"
148      | "trim"
149      | "trim_end"
150      | "trim_start"
151      | "uppercamelcase"
152      | "uppercase" => format!("{name}(${{1:s:string}})"),
153      "choose" => {
154        format!("{name}(${{1:n:string}}, ${{2:alphabet:string}})")
155      }
156      "datetime" | "datetime_utc" => {
157        format!("{name}(${{1:format:string}})")
158      }
159      "env" => {
160        format!("{name}(${{1:key:string}}${{2:, default:string}})")
161      }
162      "env_var" => format!("{name}(${{1:key:string}})"),
163      "env_var_or_default" => {
164        format!("{name}(${{1:key:string}}, ${{2:default:string}})")
165      }
166      "error" => format!("{name}(${{1:message:string}})"),
167      "join" => format!(
168        "{name}(${{1:a:string}}, ${{2:b:string}}${{3:, more:string...}})",
169      ),
170      "prepend" => {
171        format!("{name}(${{1:prefix:string}}, ${{2:s:string}})")
172      }
173      "replace" => {
174        format!("{name}(${{1:s:string}}, ${{2:from:string}}, ${{3:to:string}})")
175      }
176      "replace_regex" => {
177        format!(
178          "{name}(${{1:s:string}}, ${{2:regex:string}}, ${{3:replacement:string}})"
179        )
180      }
181      "require" | "style" | "which" => {
182        format!("{name}(${{1:name:string}})")
183      }
184      "semver_matches" => {
185        format!("{name}(${{1:version:string}}, ${{2:requirement:string}})")
186      }
187      "shell" => {
188        format!("{name}(${{1:command:string}}${{2:, args:string...}})")
189      }
190      "trim_end_match" | "trim_end_matches" | "trim_start_match"
191      | "trim_start_matches" => {
192        format!("{name}(${{1:s:string}}, ${{2:substring:string}})")
193      }
194      _ => format!("{name}(${{1:}})"),
195    };
196
197    lsp::CompletionItem {
198      label: name.to_string(),
199      kind: Some(lsp::CompletionItemKind::FUNCTION),
200      documentation: Some(lsp::Documentation::MarkupContent(
201        self.description(),
202      )),
203      insert_text: Some(snippet),
204      insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
205      sort_text: Some(format!("z{name}")),
206      ..Default::default()
207    }
208  }
209}
210
211#[cfg(test)]
212mod tests {
213  use super::*;
214
215  #[test]
216  fn fallback_function_completion_snippet() {
217    let items = Builtin::Function {
218      name: "foo",
219      aliases: &[],
220      kind: FunctionKind::Nullary,
221      description: "",
222      deprecated: None,
223    }
224    .completion_items();
225
226    assert_eq!(
227      items,
228      vec![lsp::CompletionItem {
229        label: "foo".into(),
230        kind: Some(lsp::CompletionItemKind::FUNCTION),
231        documentation: Some(lsp::Documentation::MarkupContent(
232          lsp::MarkupContent {
233            kind: lsp::MarkupKind::Markdown,
234            value: String::new()
235          },
236        )),
237        insert_text: Some("foo(${1:})".into()),
238        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
239        sort_text: Some("zfoo".into()),
240        ..Default::default()
241      }],
242    );
243  }
244
245  #[test]
246  fn function_alias_uses_alias_snippet() {
247    let items = Builtin::Function {
248      name: "home_directory",
249      aliases: &["home_dir"],
250      kind: FunctionKind::Nullary,
251      description: "bar",
252      deprecated: None,
253    }
254    .completion_items();
255
256    assert_eq!(
257      items,
258      vec![
259        lsp::CompletionItem {
260          label: "home_directory".into(),
261          kind: Some(lsp::CompletionItemKind::FUNCTION),
262          documentation: Some(lsp::Documentation::MarkupContent(
263            lsp::MarkupContent {
264              kind: lsp::MarkupKind::Markdown,
265              value: "bar".into(),
266            },
267          )),
268          insert_text: Some("home_directory()".into()),
269          insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
270          sort_text: Some("zhome_directory".into()),
271          ..Default::default()
272        },
273        lsp::CompletionItem {
274          label: "home_dir".into(),
275          kind: Some(lsp::CompletionItemKind::FUNCTION),
276          documentation: Some(lsp::Documentation::MarkupContent(
277            lsp::MarkupContent {
278              kind: lsp::MarkupKind::Markdown,
279              value: "bar".into(),
280            },
281          )),
282          insert_text: Some("home_dir()".into()),
283          insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
284          sort_text: Some("zhome_dir".into()),
285          ..Default::default()
286        },
287      ],
288    );
289  }
290}