use erg_compiler::varinfo::VarInfo; use serde::Deserialize; use serde_json::json; use serde_json::Value; use erg_common::impl_u8_enum; use erg_common::traits::Locational; use erg_compiler::artifact::BuildRunnable; use erg_compiler::erg_parser::token::TokenKind; use erg_compiler::ty::Type; use erg_compiler::varinfo::AbsLocation; use erg_compiler::AccessKind; use lsp_types::{ CompletionItem, CompletionItemKind, CompletionParams, Documentation, MarkedString, MarkupContent, MarkupKind, }; use crate::server::{ELSResult, Server}; use crate::util; fn mark_to_string(mark: MarkedString) -> String { match mark { MarkedString::String(s) => s, MarkedString::LanguageString(ls) => format!("```{}\n{}\n```", ls.language, ls.value), } } fn markdown_order(block: &str) -> usize { if block.starts_with("```") { usize::MAX } else { 0 } } impl_u8_enum! { CompletionOrder; i32; TypeMatched = -8, SingularAttr = -2, Normal = 1000000, Builtin = 1, Escaped = 4, DoubleEscaped = 16, } impl CompletionOrder { pub fn score(vi: &VarInfo, label: &str) -> i32 { let mut orders = vec![Self::Normal]; if label.starts_with("__") { orders.push(Self::DoubleEscaped); } else if label.starts_with('_') { orders.push(Self::Escaped); } if vi.kind.is_builtin() { orders.push(Self::Builtin); } orders.into_iter().map(i32::from).sum() } pub fn mangle(vi: &VarInfo, label: &str) -> String { let score = Self::score(vi, label); format!("{}_{}", char::from_u32(score as u32).unwrap(), label) } fn set(vi: &VarInfo, item: &mut CompletionItem) { item.sort_text = Some(Self::mangle(vi, &item.label)); } } impl Server { pub(crate) fn show_completion(&mut self, msg: &Value) -> ELSResult<()> { Self::send_log(format!("completion requested: {msg}"))?; let params = CompletionParams::deserialize(&msg["params"])?; let uri = util::normalize_url(params.text_document_position.text_document.uri); let path = util::uri_to_path(&uri); let pos = params.text_document_position.position; let trigger = params .context .as_ref() .and_then(|comp_ctx| comp_ctx.trigger_character.as_ref().map(|s| &s[..])); let acc_kind = match trigger { Some(".") => AccessKind::Attr, Some(":") => AccessKind::Attr, // or type ascription _ => AccessKind::Name, }; Self::send_log(format!("AccessKind: {acc_kind:?}"))?; let mut result: Vec = vec![]; let contexts = if acc_kind.is_local() { let prev_token = self.file_cache.get_token_relatively(&uri, pos, -1)?; if prev_token .as_ref() .map(|t| t.is(TokenKind::Dot) || t.is(TokenKind::DblColon)) .unwrap_or(false) { let dot_pos = util::loc_to_pos(prev_token.unwrap().loc()).unwrap(); self.get_receiver_ctxs(&uri, dot_pos)? } else { self.get_local_ctx(&uri, pos) } } else { self.get_receiver_ctxs(&uri, pos)? }; // Self::send_log(format!("contexts: {:?}", contexts.iter().map(|ctx| &ctx.name).collect::>())).unwrap(); for (name, vi) in contexts.into_iter().flat_map(|ctx| ctx.dir()) { if acc_kind.is_attr() && vi.vis.is_private() { continue; } // don't show overriden items if result .iter() .any(|item| item.label[..] == name.inspect()[..]) { continue; } // don't show future defined items if vi.def_loc.module.as_ref() == Some(&path) && name.ln_begin().unwrap_or(0) > pos.line + 1 { continue; } let readable_t = self .modules .get(&uri) .map(|module| { module .context .readable_type(vi.t.clone(), vi.kind.is_parameter()) }) .unwrap_or_else(|| vi.t.clone()); let mut item = CompletionItem::new_simple(name.to_string(), readable_t.to_string()); CompletionOrder::set(vi, &mut item); item.kind = match &vi.t { Type::Subr(subr) if subr.self_t().is_some() => Some(CompletionItemKind::METHOD), Type::Quantified(quant) if quant.self_t().is_some() => { Some(CompletionItemKind::METHOD) } Type::Subr(_) | Type::Quantified(_) => Some(CompletionItemKind::FUNCTION), Type::ClassType => Some(CompletionItemKind::CLASS), Type::TraitType => Some(CompletionItemKind::INTERFACE), t if &t.qual_name()[..] == "Module" || &t.qual_name()[..] == "GenericModule" => { Some(CompletionItemKind::MODULE) } _ if vi.muty.is_const() => Some(CompletionItemKind::CONSTANT), _ => Some(CompletionItemKind::VARIABLE), }; item.data = Some(Value::String(vi.def_loc.to_string())); result.push(item); } Self::send_log(format!("completion items: {}", result.len()))?; Self::send( &json!({ "jsonrpc": "2.0", "id": msg["id"].as_i64().unwrap(), "result": result }), ) } pub(crate) fn resolve_completion(&self, msg: &Value) -> ELSResult<()> { Self::send_log(format!("completion resolve requested: {msg}"))?; let mut item = CompletionItem::deserialize(&msg["params"])?; if let Some(data) = &item.data { let mut contents = vec![]; let Ok(def_loc) = data.as_str().unwrap().parse::() else { return Self::send(&json!({ "jsonrpc": "2.0", "id": msg["id"].as_i64().unwrap(), "result": item })); }; self.show_doc_comment(None, &mut contents, &def_loc)?; let mut contents = contents.into_iter().map(mark_to_string).collect::>(); contents.sort_by_key(|cont| markdown_order(cont)); item.documentation = Some(Documentation::MarkupContent(MarkupContent { kind: MarkupKind::Markdown, value: contents.join("\n"), })); } Self::send(&json!({ "jsonrpc": "2.0", "id": msg["id"].as_i64().unwrap(), "result": item })) } }