rune/languageserver/
completion.rs

1use std::borrow::ToOwned;
2
3use anyhow::Result;
4use lsp::CompletionItem;
5use lsp::CompletionItemKind;
6use lsp::CompletionItemLabelDetails;
7use lsp::CompletionTextEdit;
8use lsp::Documentation;
9use lsp::MarkupContent;
10use lsp::MarkupKind;
11use lsp::TextEdit;
12
13use crate::alloc::fmt::TryWrite;
14use crate::alloc::prelude::*;
15use crate::alloc::{String, Vec};
16use crate::compile::meta;
17use crate::runtime::debug::DebugArgs;
18use crate::Context;
19use crate::Unit;
20
21use super::state::ServerSource;
22
23pub(super) fn complete_for_unit(
24    workspace_source: &ServerSource,
25    unit: &Unit,
26    symbol: &str,
27    position: lsp::Position,
28    results: &mut Vec<CompletionItem>,
29) -> Result<()> {
30    let Some(debug_info) = unit.debug_info() else {
31        return Ok(());
32    };
33
34    for (hash, function) in debug_info.functions.iter() {
35        let func_name = function.try_to_string()?;
36
37        if !func_name.starts_with(symbol) {
38            continue;
39        }
40
41        let Some(last) = function.path.base_name() else {
42            continue;
43        };
44
45        let args = match &function.args {
46            DebugArgs::EmptyArgs => None,
47            DebugArgs::TupleArgs(n) => Some({
48                let mut o = String::new();
49
50                let mut it = 0..*n;
51                let last = it.next_back();
52
53                for n in it {
54                    write!(o, "_{n}, ")?;
55                }
56
57                if let Some(n) = last {
58                    write!(o, "_{n}")?;
59                }
60
61                o
62            }),
63            DebugArgs::Named(names) => Some(names.iter().map(|s| s.as_ref()).try_join(", ")?),
64        };
65
66        let docs = workspace_source
67            .get_docs_by_hash(*hash)
68            .map(|docs| docs.docs.join("\n"));
69
70        let detail = args.map(|a| format!("({a:}) -> ?"));
71
72        results.try_push(CompletionItem {
73            label: last.to_owned(),
74            kind: Some(CompletionItemKind::FUNCTION),
75            detail: detail.clone(),
76            documentation: docs.map(|d| {
77                Documentation::MarkupContent(MarkupContent {
78                    kind: MarkupKind::Markdown,
79                    value: d,
80                })
81            }),
82            text_edit: Some(CompletionTextEdit::Edit(TextEdit {
83                range: lsp::Range {
84                    start: lsp::Position {
85                        line: position.line,
86                        character: position.character - symbol.len() as u32,
87                    },
88                    end: position,
89                },
90                new_text: format!("{}", function.path),
91            })),
92            label_details: Some(CompletionItemLabelDetails {
93                detail,
94                description: None,
95            }),
96            commit_characters: Some(vec!["(".into()]),
97            ..Default::default()
98        })?;
99    }
100
101    Ok(())
102}
103
104pub(super) fn complete_native_instance_data(
105    context: &Context,
106    symbol: &str,
107    position: lsp::Position,
108    results: &mut Vec<CompletionItem>,
109) -> Result<()> {
110    for (meta, signature) in context.iter_functions() {
111        let (prefix, kind, n) = match (&meta.item, &meta.kind) {
112            (
113                Some(item),
114                meta::Kind::Function {
115                    associated: Some(meta::AssociatedKind::Instance(name)),
116                    ..
117                },
118            ) => (item, CompletionItemKind::FUNCTION, name),
119            _ => continue,
120        };
121
122        if n.starts_with(symbol) {
123            let return_type = signature
124                .return_type
125                .base
126                .as_non_empty()
127                .and_then(|hash| context.lookup_meta_by_hash(hash).next())
128                .and_then(|r| r.item.as_deref());
129
130            let docs = meta.docs.lines().join("\n");
131            let args = meta.docs.args().join(", ");
132
133            let detail = return_type.map(|r| format!("({args}) -> {r}"));
134
135            let Ok(data) = serde_json::to_value(meta.hash.into_inner()) else {
136                continue;
137            };
138
139            results.try_push(CompletionItem {
140                label: n.try_to_string()?.into_std(),
141                kind: Some(kind),
142                detail,
143                documentation: Some(lsp::Documentation::MarkupContent(MarkupContent {
144                    kind: MarkupKind::Markdown,
145                    value: docs,
146                })),
147                text_edit: Some(CompletionTextEdit::Edit(TextEdit {
148                    range: lsp::Range {
149                        start: lsp::Position {
150                            line: position.line,
151                            character: position.character - symbol.len() as u32,
152                        },
153                        end: position,
154                    },
155                    new_text: n.try_to_string()?.into_std(),
156                })),
157                label_details: Some(CompletionItemLabelDetails {
158                    detail: None,
159                    description: Some(prefix.try_to_string()?.into_std()),
160                }),
161                data: Some(data),
162                ..Default::default()
163            })?;
164        }
165    }
166
167    Ok(())
168}
169
170pub(super) fn complete_native_loose_data(
171    context: &Context,
172    symbol: &str,
173    position: lsp::Position,
174    results: &mut Vec<CompletionItem>,
175) -> Result<()> {
176    for (meta, signature) in context.iter_functions() {
177        let (item, kind) = match (&meta.item, &meta.kind) {
178            (Some(item), meta::Kind::Function { .. }) => (item, CompletionItemKind::FUNCTION),
179            _ => continue,
180        };
181
182        let func_name = item
183            .try_to_string()?
184            .trim_start_matches("::")
185            .try_to_owned()?;
186
187        if func_name.starts_with(symbol) {
188            let return_type = signature
189                .return_type
190                .base
191                .as_non_empty()
192                .and_then(|hash| context.lookup_meta_by_hash(hash).next())
193                .and_then(|r| r.item.as_deref());
194
195            let docs = meta.docs.lines().join("\n");
196            let args = meta.docs.args().join(", ");
197
198            let detail = return_type.map(|r| format!("({args}) -> {r}"));
199
200            let Ok(data) = serde_json::to_value(meta.hash.into_inner()) else {
201                continue;
202            };
203
204            results.try_push(CompletionItem {
205                label: func_name.try_clone()?.into_std(),
206                kind: Some(kind),
207                detail,
208                documentation: Some(lsp::Documentation::MarkupContent(MarkupContent {
209                    kind: MarkupKind::Markdown,
210                    value: docs,
211                })),
212                text_edit: Some(lsp::CompletionTextEdit::Edit(TextEdit {
213                    range: lsp::Range {
214                        start: lsp::Position {
215                            line: position.line,
216                            character: position.character - symbol.len() as u32,
217                        },
218                        end: position,
219                    },
220                    new_text: func_name.into_std(),
221                })),
222                data: Some(data),
223                ..Default::default()
224            })?;
225        }
226    }
227
228    Ok(())
229}