183 lines
6.5 KiB
Rust

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<CompletionResponse> {
let doc = &params.text_document_position;
let uri = &params.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 &params.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::<Vec<CompletionItem>>(
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::<Vec<CompletionItem>>(
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
}
}