mod analyze;
mod complete;
mod definition;
mod jump;
mod matchers;
mod tooltip;
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::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};
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
}
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();
}
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};
pub struct TestWorld {
pub main: Source,
base: &'static TestBase,
}
impl TestWorld {
pub fn new(text: &str) -> Self {
let main = Source::detached(text);
Self {
main,
base: singleton!(TestBase, TestBase::default()),
}
}
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
}
}
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,
}
}
}
fn library() -> Library {
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
}
}