diff --git a/src/diag.rs b/src/diag.rs index 5c5d9de9e..054a7b03b 100644 --- a/src/diag.rs +++ b/src/diag.rs @@ -140,8 +140,11 @@ impl Trace for SourceResult { F: Fn() -> Tracepoint, { self.map_err(|mut errors| { + if span.is_detached() { + return errors; + } let range = world.source(span.source()).range(span); - for error in errors.iter_mut() { + for error in errors.iter_mut().filter(|e| !e.span.is_detached()) { // Skip traces that surround the error. let error_range = world.source(error.span.source()).range(error.span); if range.start <= error_range.start && range.end >= error_range.end { diff --git a/src/ide/analyze.rs b/src/ide/analyze.rs index d8925cfca..a1ac5778a 100644 --- a/src/ide/analyze.rs +++ b/src/ide/analyze.rs @@ -22,6 +22,8 @@ pub fn analyze(world: &(dyn World + 'static), node: &LinkedNode) -> Vec { return tracer.finish(); } + Some(ast::Expr::Str(s)) => return vec![Value::Str(s.get().into())], + Some(ast::Expr::FieldAccess(access)) => { if let Some(child) = node.children().next() { return analyze(world, &child) diff --git a/src/ide/complete.rs b/src/ide/complete.rs index f4b9be5e6..9302b552a 100644 --- a/src/ide/complete.rs +++ b/src/ide/complete.rs @@ -3,7 +3,7 @@ use std::collections::{BTreeSet, HashSet}; use if_chain::if_chain; use super::{analyze, plain_docs_sentence, summarize_font_family}; -use crate::model::{CastInfo, Scope, Value}; +use crate::model::{methods_on, CastInfo, Scope, Value}; use crate::syntax::{ast, LinkedNode, Source, SyntaxKind, SyntaxNode}; use crate::util::{format_eco, EcoString}; use crate::World; @@ -271,7 +271,7 @@ fn math_completions(ctx: &mut CompletionContext) { /// Complete field accesses. fn complete_field_accesses(ctx: &mut CompletionContext) -> bool { - // Behind an identifier plus dot: "emoji.|". + // Behind an expression plus dot: "emoji.|". if_chain! { if ctx.leaf.kind() == SyntaxKind::Dot || (matches!(ctx.leaf.kind(), SyntaxKind::Text | SyntaxKind::MathAtom) @@ -325,7 +325,16 @@ fn field_access_completions(ctx: &mut CompletionContext, value: &Value) { ctx.value_completion(Some(name.clone()), value, None); } } - _ => {} + _ => { + for &method in methods_on(value.type_name()) { + ctx.completions.push(Completion { + kind: CompletionKind::Func, + label: method.into(), + apply: Some(format_eco!("{method}(${{}})")), + detail: None, + }) + } + } } } diff --git a/src/model/eval.rs b/src/model/eval.rs index d52b1272f..3e1ccfa61 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -46,7 +46,12 @@ pub fn eval( let route = unsafe { Route::insert(route, id) }; let scopes = Scopes::new(Some(library)); let mut vm = Vm::new(world, route.track(), tracer, id, scopes, 0); - let result = source.ast()?.eval(&mut vm); + let root = match source.root().cast::() { + Some(markup) if vm.traced.is_some() => markup, + _ => source.ast()?, + }; + + let result = root.eval(&mut vm); // Handle control flow. if let Some(flow) = vm.flow { diff --git a/src/model/methods.rs b/src/model/methods.rs index 173b95fea..1671a5c43 100644 --- a/src/model/methods.rs +++ b/src/model/methods.rs @@ -210,3 +210,36 @@ pub fn is_accessor(method: &str) -> bool { fn missing_method(type_name: &str, method: &str) -> String { format!("type {type_name} has no method `{method}`") } + +/// List the available methods for a type. +pub fn methods_on(type_name: &str) -> &[&'static str] { + match type_name { + "color" => &["lighten", "darken", "negate"], + "string" => &[ + "len", + "at", + "contains", + "ends-with", + "find", + "first", + "last", + "match", + "matches", + "position", + "replace", + "slice", + "split", + "starts-with", + "trim", + ], + "array" => &[ + "all", "any", "at", "contains", "filter", "find", "first", "flatten", "fold", + "insert", "join", "last", "len", "map", "pop", "position", "push", "remove", + "rev", "slice", "sorted", + ], + "dictionary" => &["at", "insert", "keys", "len", "pairs", "remove", "values"], + "function" => &["where", "with"], + "arguments" => &["named", "pos"], + _ => &[], + } +} diff --git a/src/model/mod.rs b/src/model/mod.rs index d96a314c5..32b0a0033 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -38,6 +38,7 @@ pub use self::dict::*; pub use self::eval::*; pub use self::func::*; pub use self::library::*; +pub use self::methods::*; pub use self::module::*; pub use self::realize::*; pub use self::scope::*; diff --git a/src/syntax/node.rs b/src/syntax/node.rs index d133fc5d6..a0fa5e1e3 100644 --- a/src/syntax/node.rs +++ b/src/syntax/node.rs @@ -150,6 +150,7 @@ impl SyntaxNode { } /// Convert the child to another kind. + #[track_caller] pub(super) fn convert_to_kind(&mut self, kind: SyntaxKind) { debug_assert!(!kind.is_error()); match &mut self.0 { @@ -295,6 +296,7 @@ struct LeafNode { impl LeafNode { /// Create a new leaf node. + #[track_caller] fn new(kind: SyntaxKind, text: impl Into) -> Self { debug_assert!(!kind.is_error()); Self { kind, text: text.into(), span: Span::detached() } @@ -340,6 +342,7 @@ struct InnerNode { impl InnerNode { /// Create a new inner node with the given kind and children. + #[track_caller] fn new(kind: SyntaxKind, children: Vec) -> Self { debug_assert!(!kind.is_error());