diff --git a/src/syntax/lexer.rs b/src/syntax/lexer.rs index a5e4a9e0c..748cb0763 100644 --- a/src/syntax/lexer.rs +++ b/src/syntax/lexer.rs @@ -264,16 +264,35 @@ impl Lexer<'_> { } fn link(&mut self) -> SyntaxKind { + let mut bracket_stack = Vec::new(); #[rustfmt::skip] - self.s.eat_while(|c: char| matches!(c, - | '0' ..= '9' - | 'a' ..= 'z' - | 'A' ..= 'Z' - | '~' | '/' | '%' | '?' | '#' | '&' | '+' | '=' - | '\'' | '.' | ',' | ';' - )); + self.s.eat_while(|c: char| { + match c { + | '0' ..= '9' + | 'a' ..= 'z' + | 'A' ..= 'Z' + | '!' | '#' | '$' | '%' | '&' | '*' | '+' + | ',' | '-' | '.' | '/' | ':' | ';' | '=' + | '?' | '@' | '_' | '~' | '\'' => true, + '[' => { + bracket_stack.push(SyntaxKind::LeftBracket); + true + } + '(' => { + bracket_stack.push(SyntaxKind::LeftParen); + true + } + ']' => bracket_stack.pop() == Some(SyntaxKind::LeftBracket), + ')' => bracket_stack.pop() == Some(SyntaxKind::LeftParen), + _ => false, + } + }); + if !bracket_stack.is_empty() { + return self.error_at_end("expected closing bracket in link"); + } - if self.s.scout(-1) == Some('.') { + // Don't include the trailing characters likely to be part of another expression. + if matches!(self.s.scout(-1), Some('!' | ',' | '.' | ':' | ';' | '?' | '\'')) { self.s.uneat(); } diff --git a/tests/ref/meta/link.png b/tests/ref/meta/link.png index 4e182e9bb..075ca6e1a 100644 Binary files a/tests/ref/meta/link.png and b/tests/ref/meta/link.png differ diff --git a/tests/typ/meta/link.typ b/tests/typ/meta/link.typ index de4c91c95..36f88f90f 100644 --- a/tests/typ/meta/link.typ +++ b/tests/typ/meta/link.typ @@ -22,6 +22,22 @@ Wahttp://link \ Nohttps:\//link \ Nohttp\://comment +--- +// Verify that brackets are included in links. +https://[::1]:8080/ \ +https://example.com/(paren) \ +https://example.com/#(((nested))) \ + +--- +// Check that unbalanced brackets are not included in links. +#[https://example.com/] \ +https://example.com/) + +--- +// Verify that opening brackets without closing brackets throw an error. +// Error: 22-22 expected closing bracket in link +https://exam(ple.com/ + --- // Styled with underline and color. #show link: it => underline(text(fill: rgb("283663"), it))