gluon_language_server/command/
completion.rs

1use futures::channel::mpsc;
2
3use lsp_types::CompletionItem;
4
5use crate::completion;
6
7use lsp_types::{CompletionParams, CompletionResponse};
8
9use crate::{check_importer::Module, name::with_import, rpc::LanguageServerCommand, BoxFuture};
10
11use serde::Deserialize;
12use serde_json;
13
14use super::*;
15
16#[derive(Serialize, Deserialize)]
17pub struct CompletionData {
18    pub text_document_uri: Url,
19    pub position: Position,
20}
21
22#[derive(Clone)]
23struct Completion(RootedThread);
24impl LanguageServerCommand<CompletionParams> for Completion {
25    type Future = BoxFuture<Self::Output, ServerError<()>>;
26    type Output = Option<CompletionResponse>;
27    type Error = ();
28    fn execute(&self, change: CompletionParams) -> BoxFuture<Self::Output, ServerError<()>> {
29        let thread = self.0.clone();
30        let text_document_uri = change.text_document_position.text_document.uri.clone();
31        async move {
32            retrieve_expr(&thread.clone(), &text_document_uri, |module| {
33                let Module {
34                    ref expr,
35                    ref source,
36                    ..
37                } = *module;
38
39                let expr = expr.expr();
40
41                let byte_index =
42                    position_to_byte_index(&**source, &change.text_document_position.position)?;
43
44                let query = completion::SuggestionQuery {
45                    modules: with_import(&thread, |import| {
46                        import.modules(&mut thread.module_compiler(&mut thread.get_database()))
47                    }),
48                    ..completion::SuggestionQuery::default()
49                };
50
51                let db = thread.get_database();
52                let suggestions = query
53                    .suggest(&db.as_env(), source.span(), expr, byte_index)
54                    .into_iter()
55                    .filter(|suggestion| !suggestion.name.starts_with("__"))
56                    .collect::<Vec<_>>();
57
58                let mut items: Vec<_> = suggestions
59                    .into_iter()
60                    .map(|ident| {
61                        // Remove the `:Line x, Row y suffix`
62                        let name: &str = ident.name.as_ref();
63                        let label =
64                            String::from(name.split(':').next().unwrap_or(ident.name.as_ref()));
65                        CompletionItem {
66                            insert_text: if label.starts_with(char::is_alphabetic) {
67                                None
68                            } else {
69                                Some(format!("({})", label))
70                            },
71                            kind: Some(ident_to_completion_item_kind(&label, ident.typ.as_ref())),
72                            label,
73                            detail: match ident.typ {
74                                either::Either::Right(ref typ) => match **typ {
75                                    Type::Hole => None,
76                                    _ => Some(format!("{}", ident.typ)),
77                                },
78                                either::Either::Left(_) => Some(format!("{}", ident.typ)),
79                            },
80                            data: Some(
81                                serde_json::to_value(CompletionData {
82                                    text_document_uri: change
83                                        .text_document_position
84                                        .text_document
85                                        .uri
86                                        .clone(),
87                                    position: change.text_document_position.position,
88                                })
89                                .expect("CompletionData"),
90                            ),
91                            ..CompletionItem::default()
92                        }
93                    })
94                    .collect();
95
96                items.sort_by(|l, r| l.label.cmp(&r.label));
97
98                Ok(Some(CompletionResponse::Array(items)))
99            })
100            .await
101        }
102        .boxed()
103    }
104
105    fn invalid_params(&self) -> Option<Self::Error> {
106        None
107    }
108}
109
110pub fn register(io: &mut IoHandler, thread: &RootedThread, message_log: &mpsc::Sender<String>) {
111    io.add_async_method(
112        request!("textDocument/completion"),
113        Completion(thread.clone()),
114    );
115
116    let thread = thread.clone();
117    let message_log = message_log.clone();
118    let resolve = move |mut item: CompletionItem| {
119        let thread = thread.clone();
120        let message_log = message_log.clone();
121        async move {
122            let data: CompletionData =
123                CompletionData::deserialize(item.data.as_ref().unwrap()).expect("CompletionData");
124
125            let message_log2 = message_log.clone();
126            let thread = thread.clone();
127            let label = item.label.clone();
128            log_message!(message_log.clone(), "{:?}", data.text_document_uri).await;
129
130            let comment = retrieve_expr_with_pos(
131                &thread,
132                &data.text_document_uri,
133                &data.position,
134                |module, byte_index| {
135                    let db = thread.get_database();
136                    let type_env = db.as_env();
137                    let module_expr = module.expr.expr();
138                    let (_, metadata_map) =
139                        gluon::check::metadata::metadata(&type_env, module_expr);
140                    Ok(completion::suggest_metadata(
141                        &metadata_map,
142                        &type_env,
143                        module.source.span(),
144                        module_expr,
145                        byte_index,
146                        &label,
147                    )
148                    .and_then(|metadata| metadata.comment.clone()))
149                },
150            )
151            .await?;
152
153            log_message!(message_log2, "{:?}", comment).await;
154
155            item.documentation = Some(make_documentation(
156                None::<&str>,
157                comment.as_ref().map_or("", |comment| &comment.content),
158            ));
159            Ok(item)
160        }
161    };
162    io.add_async_method(request!("completionItem/resolve"), resolve);
163}