diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-03-14 22:35:31 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-03-14 22:53:54 +0100 |
| commit | 89f44f220de2972452dd816fe59836ba76953d59 (patch) | |
| tree | 2749428e8e10252588bb68c0e8e9bfd150f28a9c /src/ide | |
| parent | 2a86e4db0bb3894d1cc3b94e1a1af31a6cd87b80 (diff) | |
Bibliography and citations
Diffstat (limited to 'src/ide')
| -rw-r--r-- | src/ide/analyze.rs | 37 | ||||
| -rw-r--r-- | src/ide/complete.rs | 32 | ||||
| -rw-r--r-- | src/ide/mod.rs | 11 | ||||
| -rw-r--r-- | src/ide/tooltip.rs | 43 |
4 files changed, 107 insertions, 16 deletions
diff --git a/src/ide/analyze.rs b/src/ide/analyze.rs index 3c46cca1..7338ba57 100644 --- a/src/ide/analyze.rs +++ b/src/ide/analyze.rs @@ -1,8 +1,11 @@ use std::path::PathBuf; use comemo::Track; +use ecow::EcoString; +use crate::doc::Frame; use crate::eval::{eval, Module, Route, Tracer, Value}; +use crate::model::{Introspector, Label}; use crate::syntax::{ast, LinkedNode, Source, SyntaxKind}; use crate::util::PathExt; use crate::World; @@ -64,3 +67,37 @@ pub fn analyze_import( let source = world.source(id); eval(world.track(), route.track(), tracer.track_mut(), source).ok() } + +/// Find all labels and details for them. +pub fn analyze_labels( + world: &(dyn World + 'static), + frames: &[Frame], +) -> (Vec<(Label, Option<EcoString>)>, usize) { + let mut output = vec![]; + let mut introspector = Introspector::new(); + let items = &world.library().items; + introspector.update(frames); + + // Labels in the document. + for node in introspector.iter() { + let Some(label) = node.label() else { continue }; + let details = node + .field("caption") + .or_else(|| node.field("body")) + .and_then(|field| match field { + Value::Content(content) => Some(content), + _ => None, + }) + .and_then(|content| (items.text_str)(content)); + output.push((label.clone(), details)); + } + + let split = output.len(); + + // Bibliography keys. + for (key, detail) in (items.bibliography_keys)(world.track(), introspector.track()) { + output.push((Label(key), detail)); + } + + (output, split) +} diff --git a/src/ide/complete.rs b/src/ide/complete.rs index de6f2b73..66590160 100644 --- a/src/ide/complete.rs +++ b/src/ide/complete.rs @@ -4,7 +4,9 @@ use ecow::{eco_format, EcoString}; use if_chain::if_chain; use unscanny::Scanner; +use super::analyze::analyze_labels; use super::{analyze_expr, analyze_import, plain_docs_sentence, summarize_font_family}; +use crate::doc::Frame; use crate::eval::{methods_on, CastInfo, Library, Scope, Value}; use crate::syntax::{ ast, is_id_continue, is_id_start, is_ident, LinkedNode, Source, SyntaxKind, @@ -21,11 +23,12 @@ use crate::World; /// control and space or something similar. pub fn autocomplete( world: &(dyn World + 'static), + frames: &[Frame], source: &Source, cursor: usize, explicit: bool, ) -> Option<(usize, Vec<Completion>)> { - let mut ctx = CompletionContext::new(world, source, cursor, explicit)?; + let mut ctx = CompletionContext::new(world, frames, source, cursor, explicit)?; let _ = complete_comments(&mut ctx) || complete_field_accesses(&mut ctx) @@ -78,7 +81,10 @@ fn complete_comments(ctx: &mut CompletionContext) -> bool { /// Complete in markup mode. fn complete_markup(ctx: &mut CompletionContext) -> bool { // Bail if we aren't even in markup. - if !matches!(ctx.leaf.parent_kind(), None | Some(SyntaxKind::Markup)) { + if !matches!( + ctx.leaf.parent_kind(), + None | Some(SyntaxKind::Markup) | Some(SyntaxKind::Ref) + ) { return false; } @@ -96,6 +102,13 @@ fn complete_markup(ctx: &mut CompletionContext) -> bool { return true; } + // Start of an reference: "@|" or "@he|". + if ctx.leaf.kind() == SyntaxKind::RefMarker { + ctx.from = ctx.leaf.offset() + 1; + ctx.label_completions(); + return true; + } + // Behind a half-completed binding: "#let x = |". if_chain! { if let Some(prev) = ctx.leaf.prev_leaf(); @@ -850,6 +863,7 @@ fn code_completions(ctx: &mut CompletionContext, hashtag: bool) { /// Context for autocompletion. struct CompletionContext<'a> { world: &'a (dyn World + 'static), + frames: &'a [Frame], library: &'a Library, source: &'a Source, global: &'a Scope, @@ -869,6 +883,7 @@ impl<'a> CompletionContext<'a> { /// Create a new autocompletion context. fn new( world: &'a (dyn World + 'static), + frames: &'a [Frame], source: &'a Source, cursor: usize, explicit: bool, @@ -878,6 +893,7 @@ impl<'a> CompletionContext<'a> { let leaf = LinkedNode::new(source.root()).leaf_at(cursor)?; Some(Self { world, + frames, library, source, global: &library.global.scope(), @@ -955,6 +971,18 @@ impl<'a> CompletionContext<'a> { } } + /// Add completions for all labels. + fn label_completions(&mut self) { + for (label, detail) in analyze_labels(self.world, self.frames).0 { + self.completions.push(Completion { + kind: CompletionKind::Constant, + label: label.0, + apply: None, + detail, + }); + } + } + /// Add a completion for a specific value. fn value_completion( &mut self, diff --git a/src/ide/mod.rs b/src/ide/mod.rs index bee959cd..38bede0b 100644 --- a/src/ide/mod.rs +++ b/src/ide/mod.rs @@ -6,6 +6,7 @@ mod highlight; mod jump; mod tooltip; +pub use self::analyze::analyze_labels; pub use self::complete::*; pub use self::highlight::*; pub use self::jump::*; @@ -13,15 +14,17 @@ pub use self::tooltip::*; use std::fmt::Write; +use ecow::{eco_format, EcoString}; + use self::analyze::*; use crate::font::{FontInfo, FontStyle}; /// Extract the first sentence of plain text of a piece of documentation. /// /// Removes Markdown formatting. -fn plain_docs_sentence(docs: &str) -> String { +fn plain_docs_sentence(docs: &str) -> EcoString { let mut s = unscanny::Scanner::new(docs); - let mut output = String::new(); + let mut output = EcoString::new(); let mut link = false; while let Some(c) = s.eat() { match c { @@ -62,7 +65,7 @@ fn plain_docs_sentence(docs: &str) -> String { } /// Create a short description of a font family. -fn summarize_font_family<'a>(variants: impl Iterator<Item = &'a FontInfo>) -> String { +fn summarize_font_family<'a>(variants: impl Iterator<Item = &'a FontInfo>) -> EcoString { let mut infos: Vec<_> = variants.collect(); infos.sort_by_key(|info| info.variant); @@ -78,7 +81,7 @@ fn summarize_font_family<'a>(variants: impl Iterator<Item = &'a FontInfo>) -> St let count = infos.len(); let s = if count == 1 { "" } else { "s" }; - let mut detail = format!("{count} variant{s}."); + let mut detail = eco_format!("{count} variant{s}."); if min_weight == max_weight { write!(detail, " Weight {min_weight}.").unwrap(); diff --git a/src/ide/tooltip.rs b/src/ide/tooltip.rs index a32dfb0b..0b37b7ca 100644 --- a/src/ide/tooltip.rs +++ b/src/ide/tooltip.rs @@ -1,20 +1,22 @@ use std::fmt::Write; -use ecow::EcoString; +use ecow::{eco_format, EcoString}; use if_chain::if_chain; +use super::analyze::analyze_labels; use super::{analyze_expr, plain_docs_sentence, summarize_font_family}; +use crate::doc::Frame; use crate::eval::{CastInfo, Tracer, Value}; use crate::geom::{round_2, Length, Numeric}; -use crate::syntax::ast; -use crate::syntax::{LinkedNode, Source, SyntaxKind}; +use crate::syntax::{ast, LinkedNode, Source, SyntaxKind}; use crate::util::pretty_comma_list; use crate::World; /// Describe the item under the cursor. pub fn tooltip( world: &(dyn World + 'static), + frames: &[Frame], source: &Source, cursor: usize, ) -> Option<Tooltip> { @@ -22,6 +24,7 @@ pub fn tooltip( named_param_tooltip(world, &leaf) .or_else(|| font_tooltip(world, &leaf)) + .or_else(|| ref_tooltip(world, frames, &leaf)) .or_else(|| expr_tooltip(world, &leaf)) } @@ -29,9 +32,9 @@ pub fn tooltip( #[derive(Debug, Clone)] pub enum Tooltip { /// A string of text. - Text(String), + Text(EcoString), /// A string of Typst code. - Code(String), + Code(EcoString), } /// Tooltip for a hovered expression. @@ -55,7 +58,7 @@ fn expr_tooltip(world: &(dyn World + 'static), leaf: &LinkedNode) -> Option<Tool if let &Value::Length(length) = value { if let Some(tooltip) = length_tooltip(length) { - return Some(Tooltip::Code(tooltip)); + return Some(tooltip); } } } @@ -85,22 +88,42 @@ fn expr_tooltip(world: &(dyn World + 'static), leaf: &LinkedNode) -> Option<Tool } let tooltip = pretty_comma_list(&pieces, false); - (!tooltip.is_empty()).then(|| Tooltip::Code(tooltip)) + (!tooltip.is_empty()).then(|| Tooltip::Code(tooltip.into())) } /// Tooltip text for a hovered length. -fn length_tooltip(length: Length) -> Option<String> { +fn length_tooltip(length: Length) -> Option<Tooltip> { length.em.is_zero().then(|| { - format!( + Tooltip::Code(eco_format!( "{}pt = {}mm = {}cm = {}in", round_2(length.abs.to_pt()), round_2(length.abs.to_mm()), round_2(length.abs.to_cm()), round_2(length.abs.to_inches()) - ) + )) }) } +/// Tooltip for a hovered reference. +fn ref_tooltip( + world: &(dyn World + 'static), + frames: &[Frame], + leaf: &LinkedNode, +) -> Option<Tooltip> { + if leaf.kind() != SyntaxKind::RefMarker { + return None; + } + + let target = leaf.text().trim_start_matches('@'); + for (label, detail) in analyze_labels(world, frames).0 { + if label.0 == target { + return Some(Tooltip::Text(detail?.into())); + } + } + + None +} + /// Tooltips for components of a named parameter. fn named_param_tooltip( world: &(dyn World + 'static), |
