diff options
| author | Laurenz <laurmaedje@gmail.com> | 2024-11-13 12:01:38 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2024-11-13 12:03:47 +0100 |
| commit | 8dbbe68527e0855b87910fe367ef29f96a670408 (patch) | |
| tree | b80403da30012f623fe1d2d298dbd1def19f90a8 /crates/typst-ide/src/lib.rs | |
| parent | 737895d769188f6fc154523e67a9102bc24c872e (diff) | |
Backport IDE improvements0.12.0-with-extras
Diffstat (limited to 'crates/typst-ide/src/lib.rs')
| -rw-r--r-- | crates/typst-ide/src/lib.rs | 219 |
1 files changed, 34 insertions, 185 deletions
diff --git a/crates/typst-ide/src/lib.rs b/crates/typst-ide/src/lib.rs index 63ba6f75..c0edcce9 100644 --- a/crates/typst-ide/src/lib.rs +++ b/crates/typst-ide/src/lib.rs @@ -6,199 +6,48 @@ mod definition; mod jump; mod matchers; mod tooltip; +mod utils; pub use self::analyze::{analyze_expr, analyze_import, analyze_labels}; pub use self::complete::{autocomplete, Completion, CompletionKind}; -pub use self::definition::{definition, Definition, DefinitionKind}; +pub use self::definition::{definition, Definition}; pub use self::jump::{jump_from_click, jump_from_cursor, Jump}; pub use self::matchers::{deref_target, named_items, DerefTarget, NamedItem}; pub use self::tooltip::{tooltip, Tooltip}; -use std::fmt::Write; - -use ecow::{eco_format, EcoString}; -use typst::text::{FontInfo, FontStyle}; - -/// Extract the first sentence of plain text of a piece of documentation. -/// -/// Removes Markdown formatting. -fn plain_docs_sentence(docs: &str) -> EcoString { - let mut s = unscanny::Scanner::new(docs); - let mut output = EcoString::new(); - let mut link = false; - while let Some(c) = s.eat() { - match c { - '`' => { - let mut raw = s.eat_until('`'); - if (raw.starts_with('{') && raw.ends_with('}')) - || (raw.starts_with('[') && raw.ends_with(']')) - { - raw = &raw[1..raw.len() - 1]; - } - - s.eat(); - output.push('`'); - output.push_str(raw); - output.push('`'); - } - '[' => link = true, - ']' if link => { - if s.eat_if('(') { - s.eat_until(')'); - s.eat(); - } else if s.eat_if('[') { - s.eat_until(']'); - s.eat(); - } - link = false - } - '*' | '_' => {} - '.' => { - output.push('.'); - break; - } - _ => output.push(c), - } - } - - output -} - -/// Create a short description of a font family. -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); - - let mut has_italic = false; - let mut min_weight = u16::MAX; - let mut max_weight = 0; - for info in &infos { - let weight = info.variant.weight.to_number(); - has_italic |= info.variant.style == FontStyle::Italic; - min_weight = min_weight.min(weight); - max_weight = min_weight.max(weight); - } - - let count = infos.len(); - let mut detail = eco_format!("{count} variant{}.", if count == 1 { "" } else { "s" }); - - if min_weight == max_weight { - write!(detail, " Weight {min_weight}.").unwrap(); - } else { - write!(detail, " Weights {min_weight}–{max_weight}.").unwrap(); +use ecow::EcoString; +use typst::syntax::package::PackageSpec; +use typst::syntax::FileId; +use typst::World; + +/// Extends the `World` for IDE functionality. +pub trait IdeWorld: World { + /// Turn this into a normal [`World`]. + /// + /// This is necessary because trait upcasting is experimental in Rust. + /// See <https://github.com/rust-lang/rust/issues/65991>. + /// + /// Implementors can simply return `self`. + fn upcast(&self) -> &dyn World; + + /// A list of all available packages and optionally descriptions for them. + /// + /// This function is **optional** to implement. It enhances the user + /// experience by enabling autocompletion for packages. Details about + /// packages from the `@preview` namespace are available from + /// `https://packages.typst.org/preview/index.json`. + fn packages(&self) -> &[(PackageSpec, Option<EcoString>)] { + &[] + } + + /// Returns a list of all known files. + /// + /// This function is **optional** to implement. It enhances the user + /// experience by enabling autocompletion for file paths. + fn files(&self) -> Vec<FileId> { + vec![] } - - if has_italic { - detail.push_str(" Has italics."); - } - - detail } #[cfg(test)] -mod tests { - use typst::diag::{FileError, FileResult}; - use typst::foundations::{Bytes, Datetime, Smart}; - use typst::layout::{Abs, Margin, PageElem}; - use typst::syntax::{FileId, Source}; - use typst::text::{Font, FontBook, TextElem, TextSize}; - use typst::utils::{singleton, LazyHash}; - use typst::{Library, World}; - - /// A world for IDE testing. - pub struct TestWorld { - pub main: Source, - base: &'static TestBase, - } - - impl TestWorld { - /// Create a new world for a single test. - /// - /// This is cheap because the shared base for all test runs is lazily - /// initialized just once. - pub fn new(text: &str) -> Self { - let main = Source::detached(text); - Self { - main, - base: singleton!(TestBase, TestBase::default()), - } - } - - /// The ID of the main file in a `TestWorld`. - pub fn main_id() -> FileId { - *singleton!(FileId, Source::detached("").id()) - } - } - - impl World for TestWorld { - fn library(&self) -> &LazyHash<Library> { - &self.base.library - } - - fn book(&self) -> &LazyHash<FontBook> { - &self.base.book - } - - fn main(&self) -> FileId { - self.main.id() - } - - fn source(&self, id: FileId) -> FileResult<Source> { - if id == self.main.id() { - Ok(self.main.clone()) - } else { - Err(FileError::NotFound(id.vpath().as_rootless_path().into())) - } - } - - fn file(&self, id: FileId) -> FileResult<Bytes> { - Err(FileError::NotFound(id.vpath().as_rootless_path().into())) - } - - fn font(&self, index: usize) -> Option<Font> { - Some(self.base.fonts[index].clone()) - } - - fn today(&self, _: Option<i64>) -> Option<Datetime> { - None - } - } - - /// Shared foundation of all test worlds. - struct TestBase { - library: LazyHash<Library>, - book: LazyHash<FontBook>, - fonts: Vec<Font>, - } - - impl Default for TestBase { - fn default() -> Self { - let fonts: Vec<_> = typst_assets::fonts() - .chain(typst_dev_assets::fonts()) - .flat_map(|data| Font::iter(Bytes::from_static(data))) - .collect(); - - Self { - library: LazyHash::new(library()), - book: LazyHash::new(FontBook::from_fonts(&fonts)), - fonts, - } - } - } - - /// The extended standard library for testing. - fn library() -> Library { - // Set page width to 120pt with 10pt margins, so that the inner page is - // exactly 100pt wide. Page height is unbounded and font size is 10pt so - // that it multiplies to nice round numbers. - let mut lib = Library::default(); - lib.styles - .set(PageElem::set_width(Smart::Custom(Abs::pt(120.0).into()))); - lib.styles.set(PageElem::set_height(Smart::Auto)); - lib.styles.set(PageElem::set_margin(Margin::splat(Some(Smart::Custom( - Abs::pt(10.0).into(), - ))))); - lib.styles.set(TextElem::set_size(TextSize(Abs::pt(10.0).into()))); - lib - } -} +mod tests; |
