1
1
mirror of https://github.com/oxalica/nil.git synced 2024-11-22 11:22:46 +03:00

Impl simple completion

This commit is contained in:
oxalica 2022-08-01 08:30:39 +08:00
parent bd8be85e34
commit 023a99fd34
4 changed files with 131 additions and 6 deletions

View File

@ -1,7 +1,7 @@
use crate::vfs::{Vfs, VfsPath};
use crossbeam_channel::Sender;
use lsp_types::{self as lsp, notification as notif, request as req, OneOf, Url};
use nil::{Analysis, AnalysisHost, Change, FilePos, InFile};
use nil::{Analysis, AnalysisHost, Change, CompletionItemKind, FilePos, InFile};
use std::sync::{Arc, RwLock};
use text_size::TextRange;
@ -15,6 +15,10 @@ pub fn server_capabilities() -> lsp::ServerCapabilities {
},
)),
definition_provider: Some(OneOf::Left(true)),
completion_provider: Some(lsp::CompletionOptions {
trigger_characters: Some(vec![".".into()]),
..Default::default()
}),
..Default::default()
}
}
@ -80,6 +84,36 @@ impl State {
None => Some(lsp::GotoDefinitionResponse::Array(Vec::new())),
}
})
.on::<req::Completion>(|st, params| {
let pos = get_file_pos(&st.vfs.read().unwrap(), &params.text_document_position)?;
let items = st.analysis.completions(pos).ok()??;
let vfs = st.vfs.read().unwrap();
let items = items
.into_iter()
.filter_map(|item| {
let kind = match item.kind {
// FIXME: More specific?
CompletionItemKind::Builtin => lsp::CompletionItemKind::KEYWORD,
CompletionItemKind::Binding => lsp::CompletionItemKind::VARIABLE,
};
Some(lsp::CompletionItem {
label: item.label.into(),
kind: Some(kind),
insert_text: None,
insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
// We don't support indentation yet.
insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: to_lsp_range(&vfs, pos.map(|_| item.source_range))?,
new_text: item.replace.into(),
})),
// TODO
..Default::default()
})
})
.collect::<Vec<_>>();
Some(lsp::CompletionResponse::Array(items))
})
.finish();
}
@ -159,11 +193,14 @@ fn get_file_pos(vfs: &Vfs, params: &lsp::TextDocumentPositionParams) -> Option<F
fn to_lsp_location(vfs: &Vfs, pos: InFile<TextRange>) -> Option<lsp::Location> {
let url = vfs.file_path(pos.file_id)?.try_into().ok()?;
Some(lsp::Location::new(url, to_lsp_range(vfs, pos)?))
}
fn to_lsp_range(vfs: &Vfs, pos: InFile<TextRange>) -> Option<lsp::Range> {
let (_, line1, col1) = vfs.get_file_line_col(pos.map(|range| range.start()))?;
let (_, line2, col2) = vfs.get_file_line_col(pos.map(|range| range.end()))?;
let range = lsp::Range::new(
Some(lsp::Range::new(
lsp::Position::new(line1, col1),
lsp::Position::new(line2, col2),
);
Some(lsp::Location::new(url, range))
))
}

79
src/ide/completion.rs Normal file
View File

@ -0,0 +1,79 @@
use crate::{
builtin,
def::{AstPtr, DefDatabase},
FileId,
};
use rowan::ast::AstNode;
use smol_str::SmolStr;
use syntax::{ast, match_ast, SyntaxKind, TextRange, TextSize, T};
/// A single completion variant in the editor pop-up.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CompletionItem {
/// The label to show in the completion menu.
pub label: SmolStr,
/// Range of identifier that is being completed.
pub source_range: TextRange,
/// What content replaces the source range when user selects this item.
pub replace: SmolStr,
/// What item (struct, function, etc) are we completing.
pub kind: CompletionItemKind,
}
/// The type of the completion item.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum CompletionItemKind {
Builtin,
Binding,
}
pub(crate) fn completions(
db: &dyn DefDatabase,
file_id: FileId,
pos: TextSize,
) -> Option<Vec<CompletionItem>> {
let parse = db.parse(file_id).value;
let tok = parse.syntax_node().token_at_offset(pos).left_biased()?;
let source_range = match tok.kind() {
T![.] => TextRange::empty(pos),
SyntaxKind::IDENT => tok.text_range(),
_ => return None,
};
let ref_node = tok.parent_ancestors().find_map(|node| {
match_ast! {
match node {
ast::Ref(n) => Some(n),
_ => None,
}
}
})?;
let source_map = db.source_map(file_id);
let expr_id = source_map.node_expr(AstPtr::new(ref_node.syntax()))?;
let scopes = db.scopes(file_id);
let scope_id = scopes.scope_by_expr(expr_id)?;
// TODO: Better sorting.
let mut items = scopes
.ancestors(scope_id)
.filter_map(|scope| scope.as_name_defs())
.flat_map(|scope| scope.keys())
.map(|name| CompletionItem {
label: name.clone(),
source_range,
replace: name.clone(),
kind: CompletionItemKind::Binding,
})
.chain(builtin::NAMES.iter().map(|name| CompletionItem {
label: name.into(),
source_range,
replace: name.into(),
kind: CompletionItemKind::Builtin,
}))
.collect::<Vec<_>>();
items.sort_by(|lhs, rhs| lhs.label.cmp(&rhs.label));
items.dedup_by(|lhs, rhs| lhs.label == rhs.label);
Some(items)
}

View File

@ -1,9 +1,12 @@
mod completion;
mod goto_definition;
use crate::{base::SourceDatabaseStorage, def::DefDatabaseStorage, Change, FileId, FilePos};
use rowan::TextRange;
use salsa::{Cancelled, Database, Durability, ParallelDatabase};
use std::fmt;
mod goto_definition;
pub use completion::{CompletionItem, CompletionItemKind};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NavigationTarget {
@ -78,4 +81,8 @@ impl Analysis {
pub fn goto_definition(&self, pos: FilePos) -> Cancellable<Option<Vec<NavigationTarget>>> {
self.with_db(|db| goto_definition::goto_definition(db, pos.file_id, pos.value))
}
pub fn completions(&self, pos: FilePos) -> Cancellable<Option<Vec<CompletionItem>>> {
self.with_db(|db| completion::completions(db, pos.file_id, pos.value))
}
}

View File

@ -9,4 +9,6 @@ mod tests;
pub use base::{Change, FileId, FilePos, FileRange, InFile};
pub use diagnostic::Diagnostic;
pub use ide::{Analysis, AnalysisHost, RootDatabase};
pub use ide::{
Analysis, AnalysisHost, CompletionItem, CompletionItemKind, NavigationTarget, RootDatabase,
};