gluon_language-server 0.18.0

Language server providing completion for gluon
Documentation
use futures::channel::mpsc;

use lsp_types::CompletionItem;

use crate::completion;

use lsp_types::{CompletionParams, CompletionResponse};

use crate::{check_importer::Module, name::with_import, rpc::LanguageServerCommand, BoxFuture};

use serde::Deserialize;
use serde_json;

use super::*;

#[derive(Serialize, Deserialize)]
pub struct CompletionData {
    pub text_document_uri: Url,
    pub position: Position,
}

#[derive(Clone)]
struct Completion(RootedThread);
impl LanguageServerCommand<CompletionParams> for Completion {
    type Future = BoxFuture<Self::Output, ServerError<()>>;
    type Output = Option<CompletionResponse>;
    type Error = ();
    fn execute(&self, change: CompletionParams) -> BoxFuture<Self::Output, ServerError<()>> {
        let thread = self.0.clone();
        let text_document_uri = change.text_document_position.text_document.uri.clone();
        async move {
            retrieve_expr(&thread.clone(), &text_document_uri, |module| {
                let Module {
                    ref expr,
                    ref source,
                    ..
                } = *module;

                let expr = expr.expr();

                let byte_index =
                    position_to_byte_index(&**source, &change.text_document_position.position)?;

                let query = completion::SuggestionQuery {
                    modules: with_import(&thread, |import| {
                        import.modules(&mut thread.module_compiler(&mut thread.get_database()))
                    }),
                    ..completion::SuggestionQuery::default()
                };

                let db = thread.get_database();
                let suggestions = query
                    .suggest(&db.as_env(), source.span(), expr, byte_index)
                    .into_iter()
                    .filter(|suggestion| !suggestion.name.starts_with("__"))
                    .collect::<Vec<_>>();

                let mut items: Vec<_> = suggestions
                    .into_iter()
                    .map(|ident| {
                        // Remove the `:Line x, Row y suffix`
                        let name: &str = ident.name.as_ref();
                        let label =
                            String::from(name.split(':').next().unwrap_or(ident.name.as_ref()));
                        CompletionItem {
                            insert_text: if label.starts_with(char::is_alphabetic) {
                                None
                            } else {
                                Some(format!("({})", label))
                            },
                            kind: Some(ident_to_completion_item_kind(&label, ident.typ.as_ref())),
                            label,
                            detail: match ident.typ {
                                either::Either::Right(ref typ) => match **typ {
                                    Type::Hole => None,
                                    _ => Some(format!("{}", ident.typ)),
                                },
                                either::Either::Left(_) => Some(format!("{}", ident.typ)),
                            },
                            data: Some(
                                serde_json::to_value(CompletionData {
                                    text_document_uri: change
                                        .text_document_position
                                        .text_document
                                        .uri
                                        .clone(),
                                    position: change.text_document_position.position,
                                })
                                .expect("CompletionData"),
                            ),
                            ..CompletionItem::default()
                        }
                    })
                    .collect();

                items.sort_by(|l, r| l.label.cmp(&r.label));

                Ok(Some(CompletionResponse::Array(items)))
            })
            .await
        }
        .boxed()
    }

    fn invalid_params(&self) -> Option<Self::Error> {
        None
    }
}

pub fn register(io: &mut IoHandler, thread: &RootedThread, message_log: &mpsc::Sender<String>) {
    io.add_async_method(
        request!("textDocument/completion"),
        Completion(thread.clone()),
    );

    let thread = thread.clone();
    let message_log = message_log.clone();
    let resolve = move |mut item: CompletionItem| {
        let thread = thread.clone();
        let message_log = message_log.clone();
        async move {
            let data: CompletionData =
                CompletionData::deserialize(item.data.as_ref().unwrap()).expect("CompletionData");

            let message_log2 = message_log.clone();
            let thread = thread.clone();
            let label = item.label.clone();
            log_message!(message_log.clone(), "{:?}", data.text_document_uri).await;

            let comment = retrieve_expr_with_pos(
                &thread,
                &data.text_document_uri,
                &data.position,
                |module, byte_index| {
                    let db = thread.get_database();
                    let type_env = db.as_env();
                    let module_expr = module.expr.expr();
                    let (_, metadata_map) =
                        gluon::check::metadata::metadata(&type_env, module_expr);
                    Ok(completion::suggest_metadata(
                        &metadata_map,
                        &type_env,
                        module.source.span(),
                        module_expr,
                        byte_index,
                        &label,
                    )
                    .and_then(|metadata| metadata.comment.clone()))
                },
            )
            .await?;

            log_message!(message_log2, "{:?}", comment).await;

            item.documentation = Some(make_documentation(
                None::<&str>,
                comment.as_ref().map_or("", |comment| &comment.content),
            ));
            Ok(item)
        }
    };
    io.add_async_method(request!("completionItem/resolve"), resolve);
}