summaryrefslogtreecommitdiff
path: root/src/ide
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-01-29 01:07:20 +0100
committerLaurenz <laurmaedje@gmail.com>2023-01-29 01:07:20 +0100
commit83b28e99aed4a168238148a56a5fe2e0bc91d01f (patch)
tree93cea78af3bac0d146d6f79066225329fbe53f09 /src/ide
parentec21927d08d380c2b760a152c821910d57475ff2 (diff)
Import completions
Diffstat (limited to 'src/ide')
-rw-r--r--src/ide/analyze.rs33
-rw-r--r--src/ide/complete.rs74
-rw-r--r--src/ide/tooltip.rs4
3 files changed, 101 insertions, 10 deletions
diff --git a/src/ide/analyze.rs b/src/ide/analyze.rs
index cfc77f38..357e98f8 100644
--- a/src/ide/analyze.rs
+++ b/src/ide/analyze.rs
@@ -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()
+}
diff --git a/src/ide/complete.rs b/src/ide/complete.rs
index 31e0bddd..5c64d801 100644
--- a/src/ide/complete.rs
+++ b/src/ide/complete.rs
@@ -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],
diff --git a/src/ide/tooltip.rs b/src/ide/tooltip.rs
index 26fa3625..566eb5a3 100644
--- a/src/ide/tooltip.rs
+++ b/src/ide/tooltip.rs
@@ -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() {