Import completions

This commit is contained in:
Laurenz 2023-01-29 01:07:20 +01:00
parent ec21927d08
commit 83b28e99ae
4 changed files with 102 additions and 11 deletions

View File

@ -1,11 +1,14 @@
use std::path::PathBuf;
use comemo::Track;
use crate::model::{eval, Route, Tracer, Value};
use crate::syntax::{ast, LinkedNode, SyntaxKind};
use crate::model::{eval, Module, Route, Tracer, Value};
use crate::syntax::{ast, LinkedNode, Source, SyntaxKind};
use crate::util::PathExt;
use crate::World;
/// Try to determine a set of possible values for an expression.
pub fn analyze(world: &(dyn World + 'static), node: &LinkedNode) -> Vec<Value> {
pub fn analyze_expr(world: &(dyn World + 'static), node: &LinkedNode) -> Vec<Value> {
match node.cast::<ast::Expr>() {
Some(ast::Expr::None(_)) => vec![Value::None],
Some(ast::Expr::Auto(_)) => vec![Value::Auto],
@ -17,7 +20,7 @@ pub fn analyze(world: &(dyn World + 'static), node: &LinkedNode) -> Vec<Value> {
Some(ast::Expr::FieldAccess(access)) => {
let Some(child) = node.children().next() else { return vec![] };
analyze(world, &child)
analyze_expr(world, &child)
.into_iter()
.filter_map(|target| target.field(&access.field()).ok())
.collect()
@ -26,7 +29,7 @@ pub fn analyze(world: &(dyn World + 'static), node: &LinkedNode) -> Vec<Value> {
Some(_) => {
if let Some(parent) = node.parent() {
if parent.kind() == SyntaxKind::FieldAccess && node.index() > 0 {
return analyze(world, parent);
return analyze_expr(world, parent);
}
}
@ -41,3 +44,23 @@ pub fn analyze(world: &(dyn World + 'static), node: &LinkedNode) -> Vec<Value> {
_ => vec![],
}
}
/// Try to load a module from the current source file.
pub fn analyze_import(
world: &(dyn World + 'static),
source: &Source,
path: &str,
) -> Option<Module> {
let full: PathBuf = if let Some(path) = path.strip_prefix('/') {
world.root().join(path).normalize()
} else if let Some(dir) = source.path().parent() {
dir.join(path).normalize()
} else {
path.into()
};
let route = Route::default();
let mut tracer = Tracer::default();
let id = world.resolve(&full).ok()?;
let source = world.source(id);
eval(world.track(), route.track(), tracer.track_mut(), source).ok()
}

View File

@ -2,7 +2,7 @@ use std::collections::{BTreeSet, HashSet};
use if_chain::if_chain;
use super::{analyze, plain_docs_sentence, summarize_font_family};
use super::{analyze_expr, analyze_import, plain_docs_sentence, summarize_font_family};
use crate::model::{methods_on, CastInfo, Scope, Value};
use crate::syntax::{ast, LinkedNode, Source, SyntaxKind};
use crate::util::{format_eco, EcoString};
@ -24,6 +24,7 @@ pub fn autocomplete(
let mut ctx = CompletionContext::new(world, source, cursor, explicit)?;
let _ = complete_field_accesses(&mut ctx)
|| complete_imports(&mut ctx)
|| complete_rules(&mut ctx)
|| complete_params(&mut ctx)
|| complete_markup(&mut ctx)
@ -279,7 +280,7 @@ fn complete_field_accesses(ctx: &mut CompletionContext) -> bool {
if ctx.leaf.range().end == ctx.cursor;
if let Some(prev) = ctx.leaf.prev_sibling();
if prev.is::<ast::Expr>();
if let Some(value) = analyze(ctx.world, &prev).into_iter().next();
if let Some(value) = analyze_expr(ctx.world, &prev).into_iter().next();
then {
ctx.from = ctx.cursor;
field_access_completions(ctx, &value);
@ -294,7 +295,7 @@ fn complete_field_accesses(ctx: &mut CompletionContext) -> bool {
if prev.kind() == SyntaxKind::Dot;
if let Some(prev_prev) = prev.prev_sibling();
if prev_prev.is::<ast::Expr>();
if let Some(value) = analyze(ctx.world, &prev_prev).into_iter().next();
if let Some(value) = analyze_expr(ctx.world, &prev_prev).into_iter().next();
then {
ctx.from = ctx.leaf.offset();
field_access_completions(ctx, &value);
@ -347,6 +348,71 @@ fn field_access_completions(ctx: &mut CompletionContext, value: &Value) {
}
}
/// Complete imports.
fn complete_imports(ctx: &mut CompletionContext) -> bool {
// Behind an import list:
// "#import "path.typ": |",
// "#import "path.typ": a, b, |".
if_chain! {
if let Some(prev) = ctx.leaf.prev_sibling();
if let Some(ast::Expr::Import(import)) = prev.cast();
if let Some(ast::Imports::Items(items)) = import.imports();
if let Some(source) = prev.children().find(|child| child.is::<ast::Expr>());
if let Some(value) = analyze_expr(ctx.world, &source).into_iter().next();
then {
ctx.from = ctx.cursor;
import_completions(ctx, &items, &value);
return true;
}
}
// Behind a half-started identifier in an import list:
// "#import "path.typ": thi|",
if_chain! {
if ctx.leaf.kind() == SyntaxKind::Ident;
if let Some(parent) = ctx.leaf.parent();
if parent.kind() == SyntaxKind::ImportItems;
if let Some(grand) = parent.parent();
if let Some(ast::Expr::Import(import)) = grand.cast();
if let Some(ast::Imports::Items(items)) = import.imports();
if let Some(source) = grand.children().find(|child| child.is::<ast::Expr>());
if let Some(value) = analyze_expr(ctx.world, &source).into_iter().next();
then {
ctx.from = ctx.leaf.offset();
import_completions(ctx, &items, &value);
return true;
}
}
false
}
/// Add completions for all exports of a module.
fn import_completions(
ctx: &mut CompletionContext,
existing: &[ast::Ident],
value: &Value,
) {
let module = match value {
Value::Str(path) => match analyze_import(ctx.world, ctx.source, path) {
Some(module) => module,
None => return,
},
Value::Module(module) => module.clone(),
_ => return,
};
if existing.is_empty() {
ctx.snippet_completion("*", "*", "Import everything.");
}
for (name, value) in module.scope().iter() {
if existing.iter().all(|ident| ident.as_str() != name.as_str()) {
ctx.value_completion(Some(name.clone()), value, None);
}
}
}
/// Complete set and show rules.
fn complete_rules(ctx: &mut CompletionContext) -> bool {
// We don't want to complete directly behind the keyword.
@ -752,6 +818,7 @@ fn code_completions(ctx: &mut CompletionContext, hashtag: bool) {
/// Context for autocompletion.
struct CompletionContext<'a> {
world: &'a (dyn World + 'static),
source: &'a Source,
global: &'a Scope,
math: &'a Scope,
before: &'a str,
@ -775,6 +842,7 @@ impl<'a> CompletionContext<'a> {
let leaf = LinkedNode::new(source.root()).leaf_at(cursor)?;
Some(Self {
world,
source,
global: &world.library().global.scope(),
math: &world.library().math.scope(),
before: &text[..cursor],

View File

@ -1,7 +1,7 @@
use if_chain::if_chain;
use unicode_segmentation::UnicodeSegmentation;
use super::{analyze, plain_docs_sentence, summarize_font_family};
use super::{analyze_expr, plain_docs_sentence, summarize_font_family};
use crate::geom::{round_2, Length, Numeric};
use crate::model::{CastInfo, Tracer, Value};
use crate::syntax::ast;
@ -42,7 +42,7 @@ fn expr_tooltip(world: &(dyn World + 'static), leaf: &LinkedNode) -> Option<Tool
return None;
}
let values = analyze(world, ancestor);
let values = analyze_expr(world, ancestor);
if let [value] = values.as_slice() {
if let Some(docs) = value.docs() {

View File

@ -1696,7 +1696,7 @@ node! {
impl ModuleImport {
/// The module or path from which the items should be imported.
pub fn source(&self) -> Expr {
self.0.cast_last_match().unwrap_or_default()
self.0.cast_first_match().unwrap_or_default()
}
/// The items to be imported.