mirror of
https://github.com/tweag/nickel.git
synced 2024-10-06 08:07:37 +03:00
Allow completion after field name.
This commit is contained in:
parent
06d9989667
commit
9b4ffa5569
@ -77,15 +77,15 @@ fn extract_ident(ty: &Box<Types>) -> Vec<Ident> {
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the identifier before the `.`, for record completion.
|
lazy_static! {
|
||||||
fn get_identifier(text: &str) -> Result<String, ResponseError> {
|
// unwrap is safe here because we know this is a correct regex.
|
||||||
lazy_static! {
|
// This regexp must be the reverse of the regexp in the lexer (nickel_lang::parser::lexer)
|
||||||
// unwrap is safe here because we know this is a correct regex.
|
// to correctly parse identifiers.
|
||||||
// This regexp must be the reverse of the regexp in the lexer (nickel_lang::parser::lexer)
|
static ref RE: regex::Regex = regex::Regex::new(r"[_a-zA-Z0-9-']*[a-zA-Z]_?").unwrap();
|
||||||
// to correctly parse identifiers.
|
}
|
||||||
static ref RE: regex::Regex = regex::Regex::new(r"[_a-zA-Z0-9-']*[a-zA-Z]_?").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// Get the identifier before the `.`, for record completion.
|
||||||
|
fn get_identifier_before_dot(text: &str) -> Result<String, ResponseError> {
|
||||||
let text: String = text
|
let text: String = text
|
||||||
.chars()
|
.chars()
|
||||||
.rev()
|
.rev()
|
||||||
@ -101,6 +101,23 @@ fn get_identifier(text: &str) -> Result<String, ResponseError> {
|
|||||||
Ok(name.as_str().chars().rev().collect())
|
Ok(name.as_str().chars().rev().collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the identifier before `.<text>` for record completion.
|
||||||
|
fn get_identifier_before_field(text: &str) -> Option<String> {
|
||||||
|
// Skip `<text>`
|
||||||
|
let text: String = text
|
||||||
|
.chars()
|
||||||
|
.rev()
|
||||||
|
.skip_while(|c| RE.is_match(c.to_string().as_ref()))
|
||||||
|
.collect::<String>()
|
||||||
|
.chars()
|
||||||
|
.rev()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let name = get_identifier_before_dot(&text[..]).ok()?;
|
||||||
|
|
||||||
|
Some(name)
|
||||||
|
}
|
||||||
|
|
||||||
fn remove_duplicates(items: &Vec<CompletionItem>) -> Vec<CompletionItem> {
|
fn remove_duplicates(items: &Vec<CompletionItem>) -> Vec<CompletionItem> {
|
||||||
let mut seen: Vec<CompletionItem> = Vec::new();
|
let mut seen: Vec<CompletionItem> = Vec::new();
|
||||||
for item in items {
|
for item in items {
|
||||||
@ -111,6 +128,46 @@ fn remove_duplicates(items: &Vec<CompletionItem>) -> Vec<CompletionItem> {
|
|||||||
seen
|
seen
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Search the linearization to find the record information associated with a
|
||||||
|
/// partiular ID.
|
||||||
|
fn collect_record_info(
|
||||||
|
linearization: &Completed,
|
||||||
|
item: &LinearizationItem<Types>,
|
||||||
|
name: usize,
|
||||||
|
) -> Vec<(Vec<Ident>, Types)> {
|
||||||
|
linearization
|
||||||
|
.get_in_scope(item)
|
||||||
|
.iter()
|
||||||
|
.filter_map(|item| {
|
||||||
|
let (ty, _) = linearization.resolve_item_type_meta(item);
|
||||||
|
match (&item.kind, ty) {
|
||||||
|
// Get record fields from static type info
|
||||||
|
(TermKind::Declaration(..), Types(AbsType::Record(row))) if name == item.id => {
|
||||||
|
Some((extract_ident(&row), item.ty.clone()))
|
||||||
|
}
|
||||||
|
(TermKind::Declaration(_, _, ValueState::Known(body_id)), _) if name == item.id => {
|
||||||
|
match find_record_contract(&linearization.linearization, *body_id) {
|
||||||
|
// Get record fields from contract metadata
|
||||||
|
Some(contract) => {
|
||||||
|
let fields = match &contract.types {
|
||||||
|
Types(AbsType::Record(row)) => extract_ident(row),
|
||||||
|
_ => Vec::with_capacity(0),
|
||||||
|
};
|
||||||
|
Some((fields, item.ty.clone()))
|
||||||
|
}
|
||||||
|
// Get record fields from lexical scoping
|
||||||
|
None => find_record_fields(&linearization.linearization, *body_id)
|
||||||
|
.map(|idents| (idents, item.ty.clone())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate possible completion Identifiers given a source text, and its
|
||||||
|
/// linearization.
|
||||||
fn get_completion_identifiers(
|
fn get_completion_identifiers(
|
||||||
source: &str,
|
source: &str,
|
||||||
trigger: Option<&str>,
|
trigger: Option<&str>,
|
||||||
@ -121,72 +178,50 @@ fn get_completion_identifiers(
|
|||||||
let in_scope = match trigger {
|
let in_scope = match trigger {
|
||||||
// Record Completion
|
// Record Completion
|
||||||
Some(server::DOT_COMPL_TRIGGER) => {
|
Some(server::DOT_COMPL_TRIGGER) => {
|
||||||
let name = get_identifier(source)?;
|
let name = get_identifier_before_dot(source)?;
|
||||||
let name_id = lin_env
|
let name_id = lin_env
|
||||||
.iter()
|
.iter()
|
||||||
.find(|(ident, _)| ident.label() == name)
|
.find(|(ident, _)| ident.label() == name)
|
||||||
.map(|(_, id)| *id);
|
.map(|(_, id)| *id);
|
||||||
|
|
||||||
match name_id {
|
match name_id {
|
||||||
Some(name_id) => linearization
|
Some(name_id) => collect_record_info(linearization, item, name_id),
|
||||||
.get_in_scope(item)
|
None => return Ok(Vec::new()),
|
||||||
.iter()
|
|
||||||
.filter_map(|item| {
|
|
||||||
let (ty, _) = linearization.resolve_item_type_meta(item);
|
|
||||||
match (&item.kind, ty) {
|
|
||||||
// Get record fields from static type info
|
|
||||||
(TermKind::Declaration(..), Types(AbsType::Record(row)))
|
|
||||||
if name_id == item.id =>
|
|
||||||
{
|
|
||||||
Some((extract_ident(&row), item.ty.clone()))
|
|
||||||
}
|
|
||||||
(TermKind::Declaration(_, _, ValueState::Known(body_id)), _)
|
|
||||||
if name_id == item.id =>
|
|
||||||
{
|
|
||||||
match find_record_contract(&linearization.linearization, *body_id) {
|
|
||||||
// Get record fields from contract metadata
|
|
||||||
Some(contract) => {
|
|
||||||
let fields = match &contract.types {
|
|
||||||
Types(AbsType::Record(row)) => extract_ident(row),
|
|
||||||
_ => Vec::with_capacity(0),
|
|
||||||
};
|
|
||||||
Some((fields, item.ty.clone()))
|
|
||||||
}
|
|
||||||
// Get record fields from lexical scoping
|
|
||||||
None => {
|
|
||||||
find_record_fields(&linearization.linearization, *body_id)
|
|
||||||
.map(|idents| (idents, item.ty.clone()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
None => return Ok(Vec::with_capacity(0)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// variable name completion
|
// variable name completion
|
||||||
Some(..) | None => linearization
|
Some(..) | None => {
|
||||||
.get_in_scope(item)
|
if let Some(name) = get_identifier_before_field(source) {
|
||||||
.iter()
|
let name_id = lin_env
|
||||||
.filter_map(|i| match i.kind {
|
.iter()
|
||||||
TermKind::Declaration(ref ident, _, _) => Some((vec![ident.clone()], i.ty.clone())),
|
.find(|(ident, _)| ident.label() == name)
|
||||||
_ => None,
|
.map(|(_, id)| *id);
|
||||||
})
|
match name_id {
|
||||||
.collect::<Vec<_>>(),
|
Some(name_id) => collect_record_info(linearization, item, name_id),
|
||||||
|
None => return Ok(Vec::new()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
linearization
|
||||||
|
.get_in_scope(item)
|
||||||
|
.iter()
|
||||||
|
.filter_map(|i| match i.kind {
|
||||||
|
TermKind::Declaration(ref ident, _, _) => {
|
||||||
|
Some((vec![ident.clone()], i.ty.clone()))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let in_scope: Vec<_> = in_scope
|
let in_scope: Vec<_> = in_scope
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|(idents, _)| {
|
.flat_map(|(idents, _)| {
|
||||||
idents
|
idents.iter().map(|ident| CompletionItem {
|
||||||
.iter()
|
label: ident.into_label(),
|
||||||
.map(|ident| CompletionItem {
|
..Default::default()
|
||||||
label: ident.into_label(),
|
})
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let in_scope = remove_duplicates(&in_scope);
|
let in_scope = remove_duplicates(&in_scope);
|
||||||
@ -217,12 +252,12 @@ pub fn handle_completion(
|
|||||||
let linearization = server.lin_cache_get(&file_id)?;
|
let linearization = server.lin_cache_get(&file_id)?;
|
||||||
// Cloning to avoid mutable borrow and shared borrow at the same time,
|
// Cloning to avoid mutable borrow and shared borrow at the same time,
|
||||||
// which won't compile
|
// which won't compile
|
||||||
let linearization = linearization.clone();
|
// let linearization = linearization.clone();
|
||||||
|
|
||||||
let item = linearization.item_at(&locator);
|
let item = linearization.item_at(&locator).cloned();
|
||||||
Trace::enrich(&id, &linearization);
|
Trace::enrich(&id, linearization);
|
||||||
|
|
||||||
match item {
|
let result = match &item {
|
||||||
Some(item) => {
|
Some(item) => {
|
||||||
let trigger = params.context.as_ref().and_then(|context| {
|
let trigger = params.context.as_ref().and_then(|context| {
|
||||||
context
|
context
|
||||||
@ -232,16 +267,21 @@ pub fn handle_completion(
|
|||||||
});
|
});
|
||||||
|
|
||||||
let in_scope =
|
let in_scope =
|
||||||
get_completion_identifiers(&text[..start], trigger, &linearization, &item)?;
|
get_completion_identifiers(&text[..start], trigger, linearization, item)?;
|
||||||
|
|
||||||
server.reply(Response::new_ok(id, in_scope));
|
Some(in_scope)
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
result
|
||||||
|
.map(|in_scope| {
|
||||||
|
server.reply(Response::new_ok(id.clone(), in_scope));
|
||||||
debug!("found closest item: {:?}", item);
|
debug!("found closest item: {:?}", item);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
})
|
||||||
None => {
|
.unwrap_or_else(|| {
|
||||||
server.reply(Response::new_ok(id, Value::Null));
|
server.reply(Response::new_ok(id, Value::Null));
|
||||||
return Ok(());
|
Ok(())
|
||||||
}
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user