183 lines
6.5 KiB
Rust
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 = ¶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::<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
|
|
}
|
|
}
|
|
|