mirror of
https://github.com/typst/typst
synced 2025-05-13 12:36:23 +08:00
Import completions
This commit is contained in:
parent
ec21927d08
commit
83b28e99ae
@ -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()
|
||||
}
|
||||
|
@ -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],
|
||||
|
@ -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() {
|
||||
|
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user