summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2024-09-30 14:45:44 +0200
committerGitHub <noreply@github.com>2024-09-30 12:45:44 +0000
commitd94acd615e5bde7f6d131be351e145477e515721 (patch)
tree7ceb5d3ab6705f71c67d5cd8392c98a5bc45c541
parent788ae10a07619278d8d9e8e31bc0f40635b1dc68 (diff)
Add internal URL type (#5074)
-rw-r--r--crates/typst-ide/src/jump.rs5
-rw-r--r--crates/typst/src/eval/markup.rs7
-rw-r--r--crates/typst/src/model/bibliography.rs78
-rw-r--r--crates/typst/src/model/link.rs50
4 files changed, 94 insertions, 46 deletions
diff --git a/crates/typst-ide/src/jump.rs b/crates/typst-ide/src/jump.rs
index d7f2e1d2..b798defa 100644
--- a/crates/typst-ide/src/jump.rs
+++ b/crates/typst-ide/src/jump.rs
@@ -1,8 +1,7 @@
use std::num::NonZeroUsize;
-use ecow::EcoString;
use typst::layout::{Frame, FrameItem, Point, Position, Size};
-use typst::model::{Destination, Document};
+use typst::model::{Destination, Document, Url};
use typst::syntax::{FileId, LinkedNode, Side, Source, Span, SyntaxKind};
use typst::visualize::Geometry;
use typst::World;
@@ -13,7 +12,7 @@ pub enum Jump {
/// Jump to a position in a source file.
Source(FileId, usize),
/// Jump to an external URL.
- Url(EcoString),
+ Url(Url),
/// Jump to a point on a page.
Position(Position),
}
diff --git a/crates/typst/src/eval/markup.rs b/crates/typst/src/eval/markup.rs
index a735bb8e..42fede1c 100644
--- a/crates/typst/src/eval/markup.rs
+++ b/crates/typst/src/eval/markup.rs
@@ -1,4 +1,4 @@
-use crate::diag::{warning, SourceResult};
+use crate::diag::{warning, At, SourceResult};
use crate::eval::{Eval, Vm};
use crate::foundations::{
Content, Label, NativeElement, Repr, Smart, Unlabellable, Value,
@@ -6,7 +6,7 @@ use crate::foundations::{
use crate::math::EquationElem;
use crate::model::{
EmphElem, EnumItem, HeadingElem, LinkElem, ListItem, ParbreakElem, RefElem,
- StrongElem, Supplement, TermItem,
+ StrongElem, Supplement, TermItem, Url,
};
use crate::symbols::Symbol;
use crate::syntax::ast::{self, AstNode};
@@ -195,7 +195,8 @@ impl Eval for ast::Link<'_> {
type Output = Content;
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
- Ok(LinkElem::from_url(self.get().clone()).pack())
+ let url = Url::new(self.get().clone()).at(self.span())?;
+ Ok(LinkElem::from_url(url).pack())
}
}
diff --git a/crates/typst/src/model/bibliography.rs b/crates/typst/src/model/bibliography.rs
index 11454804..d6b81a20 100644
--- a/crates/typst/src/model/bibliography.rs
+++ b/crates/typst/src/model/bibliography.rs
@@ -34,6 +34,7 @@ use crate::layout::{
};
use crate::model::{
CitationForm, CiteGroup, Destination, FootnoteElem, HeadingElem, LinkElem, ParElem,
+ Url,
};
use crate::syntax::{Span, Spanned};
@@ -741,8 +742,8 @@ impl<'a> Generator<'a> {
/// Displays hayagriva's output as content for the citations and references.
fn display(&mut self, rendered: &hayagriva::Rendered) -> StrResult<Works> {
- let citations = self.display_citations(rendered);
- let references = self.display_references(rendered);
+ let citations = self.display_citations(rendered)?;
+ let references = self.display_references(rendered)?;
let hanging_indent =
rendered.bibliography.as_ref().is_some_and(|b| b.hanging_indent);
Ok(Works { citations, references, hanging_indent })
@@ -752,7 +753,7 @@ impl<'a> Generator<'a> {
fn display_citations(
&mut self,
rendered: &hayagriva::Rendered,
- ) -> HashMap<Location, SourceResult<Content>> {
+ ) -> StrResult<HashMap<Location, SourceResult<Content>>> {
// Determine for each citation key where in the bibliography it is,
// so that we can link there.
let mut links = HashMap::new();
@@ -779,7 +780,7 @@ impl<'a> Generator<'a> {
Content::empty()
} else {
let mut content =
- renderer.display_elem_children(&citation.citation, &mut None);
+ renderer.display_elem_children(&citation.citation, &mut None)?;
if info.footnote {
content = FootnoteElem::with_content(content).pack();
@@ -791,15 +792,16 @@ impl<'a> Generator<'a> {
output.insert(info.location, Ok(content));
}
- output
+ Ok(output)
}
/// Display the bibliography references.
+ #[allow(clippy::type_complexity)]
fn display_references(
&self,
rendered: &hayagriva::Rendered,
- ) -> Option<Vec<(Option<Content>, Content)>> {
- let rendered = rendered.bibliography.as_ref()?;
+ ) -> StrResult<Option<Vec<(Option<Content>, Content)>>> {
+ let Some(rendered) = &rendered.bibliography else { return Ok(None) };
// Determine for each citation key where it first occurred, so that we
// can link there.
@@ -829,18 +831,22 @@ impl<'a> Generator<'a> {
let backlink = location.variant(k + 1);
// Render the first field.
- let mut prefix = item.first_field.as_ref().map(|elem| {
- let mut content = renderer.display_elem_child(elem, &mut None);
- if let Some(location) = first_occurrences.get(item.key.as_str()) {
- let dest = Destination::Location(*location);
- content = content.linked(dest);
- }
- content
- });
+ let mut prefix = item
+ .first_field
+ .as_ref()
+ .map(|elem| {
+ let mut content = renderer.display_elem_child(elem, &mut None)?;
+ if let Some(location) = first_occurrences.get(item.key.as_str()) {
+ let dest = Destination::Location(*location);
+ content = content.linked(dest);
+ }
+ StrResult::Ok(content)
+ })
+ .transpose()?;
// Render the main reference content.
let mut reference =
- renderer.display_elem_children(&item.content, &mut prefix);
+ renderer.display_elem_children(&item.content, &mut prefix)?;
// Attach a backlink to either the prefix or the reference so that
// we can link to the bibliography entry.
@@ -849,7 +855,7 @@ impl<'a> Generator<'a> {
output.push((prefix, reference));
}
- Some(output)
+ Ok(Some(output))
}
}
@@ -874,10 +880,14 @@ impl ElemRenderer<'_> {
&self,
elems: &hayagriva::ElemChildren,
prefix: &mut Option<Content>,
- ) -> Content {
- Content::sequence(
- elems.0.iter().map(|elem| self.display_elem_child(elem, prefix)),
- )
+ ) -> StrResult<Content> {
+ Ok(Content::sequence(
+ elems
+ .0
+ .iter()
+ .map(|elem| self.display_elem_child(elem, prefix))
+ .collect::<StrResult<Vec<_>>>()?,
+ ))
}
/// Display a rendered hayagriva element.
@@ -885,16 +895,16 @@ impl ElemRenderer<'_> {
&self,
elem: &hayagriva::ElemChild,
prefix: &mut Option<Content>,
- ) -> Content {
- match elem {
+ ) -> StrResult<Content> {
+ Ok(match elem {
hayagriva::ElemChild::Text(formatted) => self.display_formatted(formatted),
- hayagriva::ElemChild::Elem(elem) => self.display_elem(elem, prefix),
+ hayagriva::ElemChild::Elem(elem) => self.display_elem(elem, prefix)?,
hayagriva::ElemChild::Markup(markup) => self.display_math(markup),
- hayagriva::ElemChild::Link { text, url } => self.display_link(text, url),
+ hayagriva::ElemChild::Link { text, url } => self.display_link(text, url)?,
hayagriva::ElemChild::Transparent { cite_idx, format } => {
self.display_transparent(*cite_idx, format)
}
- }
+ })
}
/// Display a block-level element.
@@ -902,7 +912,7 @@ impl ElemRenderer<'_> {
&self,
elem: &hayagriva::Elem,
prefix: &mut Option<Content>,
- ) -> Content {
+ ) -> StrResult<Content> {
use citationberg::Display;
let block_level = matches!(elem.display, Some(Display::Block | Display::Indent));
@@ -911,7 +921,7 @@ impl ElemRenderer<'_> {
let mut content = self.display_elem_children(
&elem.children,
if block_level { &mut suf_prefix } else { prefix },
- );
+ )?;
if let Some(prefix) = suf_prefix {
const COLUMN_GUTTER: Em = Em::new(0.65);
@@ -941,7 +951,7 @@ impl ElemRenderer<'_> {
}
Some(Display::LeftMargin) => {
*prefix.get_or_insert_with(Default::default) += content;
- return Content::empty();
+ return Ok(Content::empty());
}
_ => {}
}
@@ -953,7 +963,7 @@ impl ElemRenderer<'_> {
}
}
- content
+ Ok(content)
}
/// Display math.
@@ -964,11 +974,11 @@ impl ElemRenderer<'_> {
}
/// Display a link.
- fn display_link(&self, text: &hayagriva::Formatted, url: &str) -> Content {
- let dest = Destination::Url(url.into());
- LinkElem::new(dest.into(), self.display_formatted(text))
+ fn display_link(&self, text: &hayagriva::Formatted, url: &str) -> StrResult<Content> {
+ let dest = Destination::Url(Url::new(url)?);
+ Ok(LinkElem::new(dest.into(), self.display_formatted(text))
.pack()
- .spanned(self.span)
+ .spanned(self.span))
}
/// Display transparent pass-through content.
diff --git a/crates/typst/src/model/link.rs b/crates/typst/src/model/link.rs
index 107b0d9a..b583a6fd 100644
--- a/crates/typst/src/model/link.rs
+++ b/crates/typst/src/model/link.rs
@@ -1,7 +1,9 @@
+use std::ops::Deref;
+
use ecow::{eco_format, EcoString};
use smallvec::SmallVec;
-use crate::diag::{At, SourceResult};
+use crate::diag::{bail, At, SourceResult, StrResult};
use crate::engine::Engine;
use crate::foundations::{
cast, elem, Content, Label, Packed, Repr, Show, Smart, StyleChain,
@@ -89,7 +91,7 @@ pub struct LinkElem {
impl LinkElem {
/// Create a link element from a URL with its bare text.
- pub fn from_url(url: EcoString) -> Self {
+ pub fn from_url(url: Url) -> Self {
let body = body_from_url(&url);
Self::new(LinkTarget::Dest(Destination::Url(url)), body)
}
@@ -112,13 +114,13 @@ impl Show for Packed<LinkElem> {
}
}
-fn body_from_url(url: &EcoString) -> Content {
+fn body_from_url(url: &Url) -> Content {
let mut text = url.as_str();
for prefix in ["mailto:", "tel:"] {
text = text.trim_start_matches(prefix);
}
let shorter = text.len() < url.len();
- TextElem::packed(if shorter { text.into() } else { url.clone() })
+ TextElem::packed(if shorter { text.into() } else { (**url).clone() })
}
/// A target where a link can go.
@@ -148,13 +150,15 @@ impl From<Destination> for LinkTarget {
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum Destination {
/// A link to a URL.
- Url(EcoString),
+ Url(Url),
/// A link to a point on a page.
Position(Position),
/// An unresolved link to a location in the document.
Location(Location),
}
+impl Destination {}
+
impl Repr for Destination {
fn repr(&self) -> EcoString {
eco_format!("{self:?}")
@@ -168,7 +172,41 @@ cast! {
Self::Position(v) => v.into_value(),
Self::Location(v) => v.into_value(),
},
- v: EcoString => Self::Url(v),
+ v: Url => Self::Url(v),
v: Position => Self::Position(v),
v: Location => Self::Location(v),
}
+
+/// A uniform resource locator with a maximum length.
+#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub struct Url(EcoString);
+
+impl Url {
+ /// Create an URL from a string, checking the maximum length.
+ pub fn new(url: impl Into<EcoString>) -> StrResult<Self> {
+ let url = url.into();
+ if url.len() > 8000 {
+ bail!("URL is too long")
+ }
+ Ok(Self(url))
+ }
+
+ /// Extract the underlying [`EcoString`].
+ pub fn into_inner(self) -> EcoString {
+ self.0
+ }
+}
+
+impl Deref for Url {
+ type Target = EcoString;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+cast! {
+ Url,
+ self => self.0.into_value(),
+ v: EcoString => Self::new(v)?,
+}