mirror of
https://github.com/tweag/nickel.git
synced 2024-10-06 16:18:08 +03:00
Accurate record completion
This commit is contained in:
parent
6511b779e4
commit
89161e69b1
@ -55,7 +55,7 @@ impl Building {
|
||||
TermKind::Structure => unreachable!(),
|
||||
TermKind::Usage(_) => unreachable!(),
|
||||
TermKind::Record(_) => unreachable!(),
|
||||
TermKind::RecordRef { .. } => unreachable!(),
|
||||
TermKind::RecordBind { .. } => unreachable!(),
|
||||
TermKind::Declaration(_, ref mut usages, _)
|
||||
| TermKind::RecordField { ref mut usages, .. } => usages.push(usage),
|
||||
};
|
||||
|
@ -2,6 +2,8 @@ use std::collections::HashMap;
|
||||
|
||||
use codespan::ByteIndex;
|
||||
use nickel_lang::{
|
||||
environment::Environment,
|
||||
identifier::Ident,
|
||||
term::MetaValue,
|
||||
typecheck::linearization::{LinearizationState, Scope},
|
||||
};
|
||||
@ -15,6 +17,7 @@ use super::{
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Completed {
|
||||
pub linearization: Vec<LinearizationItem<Resolved>>,
|
||||
pub lin_env: Environment<Ident, usize>,
|
||||
scope: HashMap<Scope, Vec<usize>>,
|
||||
id_to_index: HashMap<ID, usize>,
|
||||
}
|
||||
@ -24,11 +27,13 @@ impl Completed {
|
||||
linearization: Vec<LinearizationItem<Resolved>>,
|
||||
scope: HashMap<Scope, Vec<usize>>,
|
||||
id_to_index: HashMap<ID, usize>,
|
||||
lin_env: Environment<Ident, usize>,
|
||||
) -> Self {
|
||||
Self {
|
||||
linearization,
|
||||
scope,
|
||||
id_to_index,
|
||||
lin_env,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ pub enum TermKind {
|
||||
usages: Vec<ID>,
|
||||
value: ValueState,
|
||||
},
|
||||
RecordRef {
|
||||
RecordBind {
|
||||
ident: Ident,
|
||||
fields: Vec<Ident>,
|
||||
},
|
||||
|
@ -104,23 +104,33 @@ impl Linearizer for AnalysisHost {
|
||||
mut pos: TermPos,
|
||||
ty: TypeWrapper,
|
||||
) {
|
||||
fn get_record_ref_item(
|
||||
// TODO: add support for nested records
|
||||
fn get_record_bind_item(
|
||||
ty: TypeWrapper,
|
||||
scope: Scope,
|
||||
ident: &Ident,
|
||||
id: usize,
|
||||
term: &RichTerm,
|
||||
) -> Option<LinearizationItem<Unresolved>> {
|
||||
let t = term.as_ref();
|
||||
let mut t = term.as_ref();
|
||||
match t {
|
||||
Term::Record(fields, ..) => {
|
||||
Term::MetaValue(MetaValue {
|
||||
value: Some(term), ..
|
||||
}) => {
|
||||
t = term.as_ref();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
match t {
|
||||
Term::Record(fields, ..) | Term::RecRecord(fields, ..) => {
|
||||
// panic!("{:?}", fields);
|
||||
let fields = fields.keys().map(|item| item.clone()).collect();
|
||||
Some(LinearizationItem {
|
||||
id,
|
||||
ty,
|
||||
pos: ident.pos.clone(),
|
||||
scope,
|
||||
kind: TermKind::RecordRef {
|
||||
kind: TermKind::RecordBind {
|
||||
ident: ident.clone(),
|
||||
fields,
|
||||
},
|
||||
@ -223,7 +233,7 @@ impl Linearizer for AnalysisHost {
|
||||
});
|
||||
match term {
|
||||
Term::LetPattern(_, _, body, _) => {
|
||||
get_record_ref_item(ty, self.scope.clone(), ident, id, body)
|
||||
get_record_bind_item(ty, self.scope.clone(), ident, id, body)
|
||||
.map_or((), |item| lin.push(item))
|
||||
}
|
||||
_ => (),
|
||||
@ -255,7 +265,7 @@ impl Linearizer for AnalysisHost {
|
||||
});
|
||||
|
||||
match term {
|
||||
Term::LetPattern(..) => get_record_ref_item(
|
||||
Term::LetPattern(..) => get_record_bind_item(
|
||||
TypeWrapper::Concrete(AbsType::Dyn()),
|
||||
self.scope.clone(),
|
||||
&ident,
|
||||
@ -267,7 +277,8 @@ impl Linearizer for AnalysisHost {
|
||||
}
|
||||
}
|
||||
}
|
||||
Term::Let(ident, ..) | Term::Fun(ident, ..) => {
|
||||
Term::Let(ident, body, ..) | Term::Fun(ident, body) => {
|
||||
// panic!("{:?}",term);
|
||||
let value_ptr = match term {
|
||||
Term::Let(..) => {
|
||||
self.let_binding = Some(id);
|
||||
@ -299,13 +310,14 @@ impl Linearizer for AnalysisHost {
|
||||
meta: self.meta.take(),
|
||||
});
|
||||
|
||||
match term {
|
||||
Term::LetPattern(_, _, body, _) => {
|
||||
get_record_ref_item(ty, self.scope.clone(), ident, id, body)
|
||||
.map_or((), |item| lin.push(item))
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
get_record_bind_item(ty, self.scope.clone(), ident, id, body)
|
||||
.map_or((), |item| lin.push(item))
|
||||
// match term {
|
||||
// Term::Let(_, _, body, _) => {
|
||||
|
||||
// }
|
||||
// _ => (),
|
||||
// }
|
||||
}
|
||||
Term::Var(ident) => {
|
||||
let root_id = id_gen.get_and_advance();
|
||||
@ -483,7 +495,7 @@ impl Linearizer for AnalysisHost {
|
||||
})
|
||||
.collect();
|
||||
|
||||
Linearization::new(Completed::new(lin_, scope, id_mapping))
|
||||
Linearization::new(Completed::new(lin_, scope, id_mapping, self.env))
|
||||
}
|
||||
|
||||
fn scope(&mut self) -> Self {
|
||||
|
@ -1,8 +1,8 @@
|
||||
use codespan::ByteIndex;
|
||||
use codespan_lsp::position_to_byte_index;
|
||||
use log::debug;
|
||||
// use log::debug;
|
||||
use lsp_server::{RequestId, Response, ResponseError};
|
||||
use lsp_types::{CompletionItem, CompletionParams};
|
||||
use lsp_types::{CompletionContext, CompletionItem, CompletionParams};
|
||||
use nickel_lang::{
|
||||
identifier::Ident,
|
||||
term::{Contract, MetaValue},
|
||||
@ -11,7 +11,7 @@ use nickel_lang::{
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
linearization::interface::TermKind,
|
||||
linearization::{completed::Completed, interface::TermKind},
|
||||
server::Server,
|
||||
trace::{Enrich, Trace},
|
||||
};
|
||||
@ -21,11 +21,19 @@ pub fn handle_completion(
|
||||
id: RequestId,
|
||||
server: &mut Server,
|
||||
) -> Result<(), ResponseError> {
|
||||
// let mut file = OpenOptions::new()
|
||||
// .write(true)
|
||||
// .append(false)
|
||||
// .create(true)
|
||||
// .open("/home/ebresafegaga/server.log")
|
||||
// .unwrap();
|
||||
|
||||
let file_id = server
|
||||
.cache
|
||||
.id_of(params.text_document_position.text_document.uri.as_str())
|
||||
.unwrap();
|
||||
|
||||
let text = server.cache.files().source(file_id);
|
||||
let start = position_to_byte_index(
|
||||
server.cache.files(),
|
||||
file_id,
|
||||
@ -33,27 +41,41 @@ pub fn handle_completion(
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let name = text[..start - 1]
|
||||
.chars()
|
||||
.rev()
|
||||
.take_while(|c| c.is_ascii_alphabetic() || *c == '.')
|
||||
.skip_while(|c| *c == '.') // skip . (if any)
|
||||
.collect::<String>()
|
||||
.chars()
|
||||
.rev()
|
||||
.collect::<String>();
|
||||
|
||||
// Make sure you parse the string to support:
|
||||
// 1. Normal Identifiers
|
||||
// 2. Record Index
|
||||
// 3. Record terms - Maybe this can be gotten from the Linearizer
|
||||
|
||||
// This is the environment containing stdlib stuff.
|
||||
// let env = &server.initial_ctxt.type_env;
|
||||
|
||||
// lin_env is a mapping from idents to IDs.
|
||||
// This is from the Linearizer used to build this linearization.
|
||||
// idealy we should get the id and use it instead of comparing strings
|
||||
let Completed { lin_env: _, .. } = &server.lin_cache.get(&file_id).unwrap();
|
||||
|
||||
let locator = (file_id, ByteIndex(start as u32));
|
||||
let linearization = server.lin_cache_get(&file_id)?;
|
||||
|
||||
Trace::enrich(&id, linearization);
|
||||
|
||||
let item = linearization.item_at(&locator);
|
||||
|
||||
if item == None {
|
||||
server.reply(Response::new_ok(id, Value::Null));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let item = item.unwrap().to_owned();
|
||||
|
||||
// How can we get data for record completion based on the TermKind
|
||||
// 1. Record: just list it's fields
|
||||
// 2. RecordRef: list the field it contains if the item we're at corresponds with it's ID
|
||||
// 3. Check the type of the term: If it's a record, list it's fields
|
||||
// 4. Check the MetaValue for it's contract: if it's a record, list it's fields
|
||||
|
||||
// Can we seperate record completion from just normal completion?
|
||||
let trigger = match params.context {
|
||||
Some(CompletionContext {
|
||||
trigger_character: Some(s),
|
||||
..
|
||||
}) => Some(s),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
/// Extract identifiers from a row type which forms a record.
|
||||
/// Ensure that ty is really a row type.
|
||||
@ -66,39 +88,85 @@ pub fn handle_completion(
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
let in_scope: Vec<_> = linearization
|
||||
.get_in_scope(&item)
|
||||
.iter()
|
||||
.filter_map(|i| match i.kind {
|
||||
TermKind::Declaration(ref ident, _, _) => Some((vec![ident.clone()], i.ty.clone())),
|
||||
TermKind::RecordRef { ref fields, .. } if i.id == item.id => {
|
||||
Some((fields.clone(), i.ty.clone()))
|
||||
}
|
||||
TermKind::Record(ref fields) => {
|
||||
Some((fields.keys().map(|i| i.clone()).collect(), i.ty.clone()))
|
||||
}
|
||||
_ if matches!(i.ty, Types(AbsType::StaticRecord(..))) => match &i.ty {
|
||||
Types(AbsType::StaticRecord(row)) => Some((extract_ident(row), i.ty.clone())),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
let in_scope = match trigger {
|
||||
// Record completion
|
||||
Some(s) if s == "." => linearization
|
||||
.linearization
|
||||
.iter()
|
||||
.filter_map(|i| match i.kind {
|
||||
TermKind::RecordBind {
|
||||
ident, ref fields, ..
|
||||
} if ident.label() == name => Some((fields.clone(), i.ty.clone())),
|
||||
|
||||
_ if i.meta.is_some() => match &i.meta {
|
||||
Some(MetaValue { contracts, .. }) => {
|
||||
let fields = contracts
|
||||
.clone()
|
||||
.iter()
|
||||
.map(|Contract { types, .. }| match types {
|
||||
Types(AbsType::StaticRecord(row)) => extract_ident(row),
|
||||
_ => vec![],
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
Some((fields, i.ty.clone()))
|
||||
// Get static type info
|
||||
TermKind::Declaration(ident, ..)
|
||||
if ident.label() == name
|
||||
&& matches!(i.ty, Types(AbsType::StaticRecord(..))) =>
|
||||
{
|
||||
match &i.ty {
|
||||
Types(AbsType::StaticRecord(row)) => {
|
||||
Some((extract_ident(row), i.ty.clone()))
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
_ => None,
|
||||
})
|
||||
|
||||
// Get contract info
|
||||
TermKind::Declaration(ident, ..) if ident.label() == name && i.meta.is_some() => {
|
||||
match &i.meta {
|
||||
Some(MetaValue { contracts, .. }) => {
|
||||
let fields = contracts
|
||||
.clone()
|
||||
.iter()
|
||||
.map(|Contract { types, .. }| match types {
|
||||
Types(AbsType::StaticRecord(row)) => extract_ident(row),
|
||||
_ => vec![],
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
Some((fields, i.ty.clone()))
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect(),
|
||||
|
||||
// variable name completion
|
||||
Some(..) | None => {
|
||||
let item = linearization.item_at(&locator);
|
||||
if item == None {
|
||||
server.reply(Response::new_ok(id, Value::Null));
|
||||
return Ok(());
|
||||
} else {
|
||||
let item = item.unwrap().to_owned();
|
||||
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<_>>()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// How can we get data for record completion based on the TermKind
|
||||
// 1. Record: just list it's fields
|
||||
// 2. RecordRef: list the field it contains if the item we're at corresponds with it's ID
|
||||
// 3. Check the type of the term: If it's a record, list it's fields
|
||||
// 4. Check the MetaValue for it's contract: if it's a record, list it's fields
|
||||
|
||||
// Can we seperate record completion from just normal completion?
|
||||
|
||||
// We also need data form the standard library functions
|
||||
|
||||
let in_scope: Vec<_> = in_scope
|
||||
.iter()
|
||||
.map(|(idents, _)| {
|
||||
idents
|
||||
.iter()
|
||||
@ -113,6 +181,6 @@ pub fn handle_completion(
|
||||
|
||||
server.reply(Response::new_ok(id, in_scope));
|
||||
|
||||
debug!("found closest item: {:?}", item);
|
||||
// debug!("found closest item: {:?}", item);
|
||||
Ok(())
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ use crate::position::TermPos;
|
||||
simple_counter::generate_counter!(GeneratedCounter, usize);
|
||||
static INTERNER: Lazy<interner::Interner> = Lazy::new(interner::Interner::new);
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
|
||||
#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize)]
|
||||
#[serde(into = "String", from = "String")]
|
||||
pub struct Ident {
|
||||
symbol: interner::Symbol,
|
||||
@ -114,7 +114,7 @@ mod interner {
|
||||
use typed_arena::Arena;
|
||||
|
||||
/// A symbol is a correspondance between an [Ident](super::Ident) and its string representation stored in the [Interner].
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct Symbol(u32);
|
||||
|
||||
/// The interner, which serves a double purpose: it pre-allocates space
|
||||
|
Loading…
Reference in New Issue
Block a user