mirror of
https://github.com/oxalica/nil.git
synced 2024-11-25 18:41:40 +03:00
Impl simple completion
This commit is contained in:
parent
bd8be85e34
commit
023a99fd34
@ -1,7 +1,7 @@
|
|||||||
use crate::vfs::{Vfs, VfsPath};
|
use crate::vfs::{Vfs, VfsPath};
|
||||||
use crossbeam_channel::Sender;
|
use crossbeam_channel::Sender;
|
||||||
use lsp_types::{self as lsp, notification as notif, request as req, OneOf, Url};
|
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 std::sync::{Arc, RwLock};
|
||||||
use text_size::TextRange;
|
use text_size::TextRange;
|
||||||
|
|
||||||
@ -15,6 +15,10 @@ pub fn server_capabilities() -> lsp::ServerCapabilities {
|
|||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
definition_provider: Some(OneOf::Left(true)),
|
definition_provider: Some(OneOf::Left(true)),
|
||||||
|
completion_provider: Some(lsp::CompletionOptions {
|
||||||
|
trigger_characters: Some(vec![".".into()]),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -80,6 +84,36 @@ impl State {
|
|||||||
None => Some(lsp::GotoDefinitionResponse::Array(Vec::new())),
|
None => Some(lsp::GotoDefinitionResponse::Array(Vec::new())),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.on::<req::Completion>(|st, params| {
|
||||||
|
let pos = get_file_pos(&st.vfs.read().unwrap(), ¶ms.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();
|
.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> {
|
fn to_lsp_location(vfs: &Vfs, pos: InFile<TextRange>) -> Option<lsp::Location> {
|
||||||
let url = vfs.file_path(pos.file_id)?.try_into().ok()?;
|
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 (_, 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 (_, 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(line1, col1),
|
||||||
lsp::Position::new(line2, col2),
|
lsp::Position::new(line2, col2),
|
||||||
);
|
))
|
||||||
Some(lsp::Location::new(url, range))
|
|
||||||
}
|
}
|
||||||
|
79
src/ide/completion.rs
Normal file
79
src/ide/completion.rs
Normal 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)
|
||||||
|
}
|
@ -1,9 +1,12 @@
|
|||||||
|
mod completion;
|
||||||
|
mod goto_definition;
|
||||||
|
|
||||||
use crate::{base::SourceDatabaseStorage, def::DefDatabaseStorage, Change, FileId, FilePos};
|
use crate::{base::SourceDatabaseStorage, def::DefDatabaseStorage, Change, FileId, FilePos};
|
||||||
use rowan::TextRange;
|
use rowan::TextRange;
|
||||||
use salsa::{Cancelled, Database, Durability, ParallelDatabase};
|
use salsa::{Cancelled, Database, Durability, ParallelDatabase};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
mod goto_definition;
|
pub use completion::{CompletionItem, CompletionItemKind};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct NavigationTarget {
|
pub struct NavigationTarget {
|
||||||
@ -78,4 +81,8 @@ impl Analysis {
|
|||||||
pub fn goto_definition(&self, pos: FilePos) -> Cancellable<Option<Vec<NavigationTarget>>> {
|
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))
|
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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,4 +9,6 @@ mod tests;
|
|||||||
|
|
||||||
pub use base::{Change, FileId, FilePos, FileRange, InFile};
|
pub use base::{Change, FileId, FilePos, FileRange, InFile};
|
||||||
pub use diagnostic::Diagnostic;
|
pub use diagnostic::Diagnostic;
|
||||||
pub use ide::{Analysis, AnalysisHost, RootDatabase};
|
pub use ide::{
|
||||||
|
Analysis, AnalysisHost, CompletionItem, CompletionItemKind, NavigationTarget, RootDatabase,
|
||||||
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user