use crate::{completion::feature::{get_dot_completion, include_path_completion}, server::LSPServer, sources::LSPSupport, utils::get_language_id_by_uri}; use log::info; use ropey::{Rope, RopeSlice}; use tower_lsp::lsp_types::*; pub fn completion(server: &LSPServer, params: &CompletionParams) -> Option { let doc = ¶ms.text_document_position; let uri = ¶ms.text_document_position.text_document.uri; let pos = doc.position; let language_id = get_language_id_by_uri(uri); let file_id = server.srcs.get_id(uri).to_owned(); server.srcs.wait_parse_ready(file_id, false); let file = server.srcs.get_file(file_id)?; let file = file.read().ok()?; let line_text = file.text.line(doc.position.line as usize); let token = get_completion_token( &file.text, line_text.clone(), doc.position, ); // info!("trigger completion token: {}", token); let line_text = file.text.line(pos.line as usize); let response = match ¶ms.context { Some(context) => match context.trigger_kind { CompletionTriggerKind::TRIGGER_CHARACTER => { let trigger_character = context.trigger_character.clone().unwrap(); match trigger_character.as_str() { "." => { info!("trigger dot completion"); get_dot_completion(server, &line_text, uri, &pos, &language_id) }, "$" => Some(CompletionList { is_incomplete: false, items: server.sys_tasks.clone(), }), "`" => Some(CompletionList { is_incomplete: false, items: server.directives.clone(), }), "/" => { info!("trigger include"); include_path_completion(&doc.text_document.uri, &line_text, pos) }, "\"" => { info!("trigger include"); include_path_completion(&doc.text_document.uri, &line_text, pos) } _ => None, } } CompletionTriggerKind::TRIGGER_FOR_INCOMPLETE_COMPLETIONS => None, CompletionTriggerKind::INVOKED => { let mut comps = server.srcs.get_completions( &token, file.text.pos_to_byte(&doc.position), &doc.text_document.uri, )?; // complete keywords comps.items.extend::>( server.key_comps .iter() .filter(|x| x.label.starts_with(&token)) .cloned() .collect(), ); Some(comps) } _ => None, }, None => { let trigger = prev_char(&file.text, &doc.position); match trigger { '.' => Some(server.srcs.get_dot_completions( token.trim_end_matches('.'), file.text.pos_to_byte(&doc.position), &doc.text_document.uri, )?), '$' => Some(CompletionList { is_incomplete: false, items: server.sys_tasks.clone(), }), '`' => Some(CompletionList { is_incomplete: false, items: server.directives.clone(), }), _ => { let mut comps = server.srcs.get_completions( &token, file.text.pos_to_byte(&doc.position), &doc.text_document.uri, )?; comps.items.extend::>( server.key_comps .iter() .filter(|x| x.label.starts_with(&token)) .cloned() .collect(), ); Some(comps) } } } }; // eprintln!("comp response: {}", now.elapsed().as_millis()); Some(CompletionResponse::List(response?)) } /// get the previous non-whitespace character fn prev_char(text: &Rope, pos: &Position) -> char { let char_idx = text.pos_to_char(pos); if char_idx > 0 { for i in (0..char_idx).rev() { let res = text.char(i); if !res.is_whitespace() { return res; } } ' ' } else { ' ' } } /// attempt to get the token the user was trying to complete, by /// filtering out characters unneeded for name resolution fn get_completion_token(text: &Rope, line: RopeSlice, pos: Position) -> String { let mut token = String::new(); let mut line_iter = line.chars(); for _ in 0..(line.utf16_cu_to_char(pos.character as usize)) { line_iter.next(); } let mut c = line_iter.prev(); //TODO: make this a regex while c.is_some() && (c.unwrap().is_alphanumeric() || c.unwrap() == '_' || c.unwrap() == '.' || c.unwrap() == '[' || c.unwrap() == ']') { token.push(c.unwrap()); c = line_iter.prev(); } let mut result: String = token.chars().rev().collect(); if result.contains('[') { let l_bracket_offset = result.find('[').unwrap_or(result.len()); result.replace_range(l_bracket_offset.., ""); } if &result == "." { // probably a instantiation, the token should be what we're instatiating let mut char_iter = text.chars(); let mut token = String::new(); for _ in 0..text.pos_to_char(&pos) { char_iter.next(); } let mut c = char_iter.prev(); // go to the last semicolon while c.is_some() && (c.unwrap() != ';') { c = char_iter.prev(); } // go the the start of the next symbol while c.is_some() && !(c.unwrap().is_alphanumeric() || c.unwrap() == '_') { c = char_iter.next(); } // then extract the next symbol while c.is_some() && (c.unwrap().is_alphanumeric() || c.unwrap() == '_') { token.push(c.unwrap()); c = char_iter.next(); } token } else { result } }