mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +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 comemo::Track;
|
||||||
|
|
||||||
use crate::model::{eval, Route, Tracer, Value};
|
use crate::model::{eval, Module, Route, Tracer, Value};
|
||||||
use crate::syntax::{ast, LinkedNode, SyntaxKind};
|
use crate::syntax::{ast, LinkedNode, Source, SyntaxKind};
|
||||||
|
use crate::util::PathExt;
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
/// Try to determine a set of possible values for an expression.
|
/// 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>() {
|
match node.cast::<ast::Expr>() {
|
||||||
Some(ast::Expr::None(_)) => vec![Value::None],
|
Some(ast::Expr::None(_)) => vec![Value::None],
|
||||||
Some(ast::Expr::Auto(_)) => vec![Value::Auto],
|
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)) => {
|
Some(ast::Expr::FieldAccess(access)) => {
|
||||||
let Some(child) = node.children().next() else { return vec![] };
|
let Some(child) = node.children().next() else { return vec![] };
|
||||||
analyze(world, &child)
|
analyze_expr(world, &child)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|target| target.field(&access.field()).ok())
|
.filter_map(|target| target.field(&access.field()).ok())
|
||||||
.collect()
|
.collect()
|
||||||
@ -26,7 +29,7 @@ pub fn analyze(world: &(dyn World + 'static), node: &LinkedNode) -> Vec<Value> {
|
|||||||
Some(_) => {
|
Some(_) => {
|
||||||
if let Some(parent) = node.parent() {
|
if let Some(parent) = node.parent() {
|
||||||
if parent.kind() == SyntaxKind::FieldAccess && node.index() > 0 {
|
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![],
|
_ => 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 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::model::{methods_on, CastInfo, Scope, Value};
|
||||||
use crate::syntax::{ast, LinkedNode, Source, SyntaxKind};
|
use crate::syntax::{ast, LinkedNode, Source, SyntaxKind};
|
||||||
use crate::util::{format_eco, EcoString};
|
use crate::util::{format_eco, EcoString};
|
||||||
@ -24,6 +24,7 @@ pub fn autocomplete(
|
|||||||
let mut ctx = CompletionContext::new(world, source, cursor, explicit)?;
|
let mut ctx = CompletionContext::new(world, source, cursor, explicit)?;
|
||||||
|
|
||||||
let _ = complete_field_accesses(&mut ctx)
|
let _ = complete_field_accesses(&mut ctx)
|
||||||
|
|| complete_imports(&mut ctx)
|
||||||
|| complete_rules(&mut ctx)
|
|| complete_rules(&mut ctx)
|
||||||
|| complete_params(&mut ctx)
|
|| complete_params(&mut ctx)
|
||||||
|| complete_markup(&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 ctx.leaf.range().end == ctx.cursor;
|
||||||
if let Some(prev) = ctx.leaf.prev_sibling();
|
if let Some(prev) = ctx.leaf.prev_sibling();
|
||||||
if prev.is::<ast::Expr>();
|
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 {
|
then {
|
||||||
ctx.from = ctx.cursor;
|
ctx.from = ctx.cursor;
|
||||||
field_access_completions(ctx, &value);
|
field_access_completions(ctx, &value);
|
||||||
@ -294,7 +295,7 @@ fn complete_field_accesses(ctx: &mut CompletionContext) -> bool {
|
|||||||
if prev.kind() == SyntaxKind::Dot;
|
if prev.kind() == SyntaxKind::Dot;
|
||||||
if let Some(prev_prev) = prev.prev_sibling();
|
if let Some(prev_prev) = prev.prev_sibling();
|
||||||
if prev_prev.is::<ast::Expr>();
|
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 {
|
then {
|
||||||
ctx.from = ctx.leaf.offset();
|
ctx.from = ctx.leaf.offset();
|
||||||
field_access_completions(ctx, &value);
|
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.
|
/// Complete set and show rules.
|
||||||
fn complete_rules(ctx: &mut CompletionContext) -> bool {
|
fn complete_rules(ctx: &mut CompletionContext) -> bool {
|
||||||
// We don't want to complete directly behind the keyword.
|
// 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.
|
/// Context for autocompletion.
|
||||||
struct CompletionContext<'a> {
|
struct CompletionContext<'a> {
|
||||||
world: &'a (dyn World + 'static),
|
world: &'a (dyn World + 'static),
|
||||||
|
source: &'a Source,
|
||||||
global: &'a Scope,
|
global: &'a Scope,
|
||||||
math: &'a Scope,
|
math: &'a Scope,
|
||||||
before: &'a str,
|
before: &'a str,
|
||||||
@ -775,6 +842,7 @@ impl<'a> CompletionContext<'a> {
|
|||||||
let leaf = LinkedNode::new(source.root()).leaf_at(cursor)?;
|
let leaf = LinkedNode::new(source.root()).leaf_at(cursor)?;
|
||||||
Some(Self {
|
Some(Self {
|
||||||
world,
|
world,
|
||||||
|
source,
|
||||||
global: &world.library().global.scope(),
|
global: &world.library().global.scope(),
|
||||||
math: &world.library().math.scope(),
|
math: &world.library().math.scope(),
|
||||||
before: &text[..cursor],
|
before: &text[..cursor],
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use if_chain::if_chain;
|
use if_chain::if_chain;
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
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::geom::{round_2, Length, Numeric};
|
||||||
use crate::model::{CastInfo, Tracer, Value};
|
use crate::model::{CastInfo, Tracer, Value};
|
||||||
use crate::syntax::ast;
|
use crate::syntax::ast;
|
||||||
@ -42,7 +42,7 @@ fn expr_tooltip(world: &(dyn World + 'static), leaf: &LinkedNode) -> Option<Tool
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let values = analyze(world, ancestor);
|
let values = analyze_expr(world, ancestor);
|
||||||
|
|
||||||
if let [value] = values.as_slice() {
|
if let [value] = values.as_slice() {
|
||||||
if let Some(docs) = value.docs() {
|
if let Some(docs) = value.docs() {
|
||||||
|
@ -1696,7 +1696,7 @@ node! {
|
|||||||
impl ModuleImport {
|
impl ModuleImport {
|
||||||
/// The module or path from which the items should be imported.
|
/// The module or path from which the items should be imported.
|
||||||
pub fn source(&self) -> Expr {
|
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.
|
/// The items to be imported.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user