Commit ad34ed5a63
Changed files (2)
lib
docs
wasm
markdown
lib/docs/wasm/markdown/Parser.zig
@@ -988,6 +988,9 @@ const InlineParser = struct {
'<' => try ip.parseAutolink(),
'*', '_' => try ip.parseEmphasis(),
'`' => try ip.parseCodeSpan(),
+ 'h' => if (ip.pos == 0 or isPreTextAutolink(ip.content[ip.pos - 1])) {
+ try ip.parseTextAutolink();
+ },
else => {},
}
}
@@ -1123,6 +1126,115 @@ const InlineParser = struct {
ip.pos = start;
}
+ /// Parses a plain text autolink (not delimited by `<>`), starting at the
+ /// first character in the link (an `h`). `ip.pos` is left at the last
+ /// character of the link, or remains unchanged if there is no valid link.
+ fn parseTextAutolink(ip: *InlineParser) !void {
+ const start = ip.pos;
+ var state: union(enum) {
+ /// Inside `http`. Contains the rest of the text to be matched.
+ http: []const u8,
+ after_http,
+ after_https,
+ /// Inside `://`. Contains the rest of the text to be matched.
+ authority: []const u8,
+ /// Inside link content.
+ content: struct {
+ start: usize,
+ paren_nesting: usize,
+ },
+ } = .{ .http = "http" };
+
+ while (ip.pos < ip.content.len) : (ip.pos += 1) {
+ switch (state) {
+ .http => |rest| {
+ if (ip.content[ip.pos] != rest[0]) break;
+ if (rest.len > 1) {
+ state = .{ .http = rest[1..] };
+ } else {
+ state = .after_http;
+ }
+ },
+ .after_http => switch (ip.content[ip.pos]) {
+ 's' => state = .after_https,
+ ':' => state = .{ .authority = "//" },
+ else => break,
+ },
+ .after_https => switch (ip.content[ip.pos]) {
+ ':' => state = .{ .authority = "//" },
+ else => break,
+ },
+ .authority => |rest| {
+ if (ip.content[ip.pos] != rest[0]) break;
+ if (rest.len > 1) {
+ state = .{ .authority = rest[1..] };
+ } else {
+ state = .{ .content = .{
+ .start = ip.pos + 1,
+ .paren_nesting = 0,
+ } };
+ }
+ },
+ .content => |*content| switch (ip.content[ip.pos]) {
+ ' ', '\t', '\n' => break,
+ '(' => content.paren_nesting += 1,
+ ')' => if (content.paren_nesting == 0) {
+ break;
+ } else {
+ content.paren_nesting -= 1;
+ },
+ else => {},
+ },
+ }
+ }
+
+ switch (state) {
+ .http, .after_http, .after_https, .authority => {
+ ip.pos = start;
+ },
+ .content => |content| {
+ while (ip.pos > content.start and isPostTextAutolink(ip.content[ip.pos - 1])) {
+ ip.pos -= 1;
+ }
+ if (ip.pos == content.start) {
+ ip.pos = start;
+ return;
+ }
+
+ const target = try ip.parent.addString(ip.content[start..ip.pos]);
+ const node = try ip.parent.addNode(.{
+ .tag = .autolink,
+ .data = .{ .text = .{
+ .content = target,
+ } },
+ });
+ try ip.completed_inlines.append(ip.parent.allocator, .{
+ .node = node,
+ .start = start,
+ .len = ip.pos - start,
+ });
+ ip.pos -= 1;
+ },
+ }
+ }
+
+ /// Returns whether `c` may appear before a text autolink is recognized.
+ fn isPreTextAutolink(c: u8) bool {
+ return switch (c) {
+ ' ', '\t', '\n', '*', '_', '(' => true,
+ else => false,
+ };
+ }
+
+ /// Returns whether `c` is punctuation that may appear after a text autolink
+ /// and not be considered part of it.
+ fn isPostTextAutolink(c: u8) bool {
+ return switch (c) {
+ '?', '!', '.', ',', ':', '*', '_' => true,
+ else => false,
+ };
+ }
+
/// Parses emphasis, starting at the beginning of a run of `*` or `_`
/// characters. `ip.pos` is left at the last character in the run after
/// parsing.
lib/docs/wasm/markdown.zig
@@ -81,6 +81,11 @@
//! escapes). `target` is expected to be an absolute URI: an autolink will not
//! be recognized unless `target` starts with a URI scheme followed by a `:`.
//!
+//! For convenience, autolinks may also be recognized in plain text without
+//! any `<>` delimiters. Such autolinks are restricted to start with `http://`
+//! or `https://` followed by at least one other character, not including any
+//! trailing punctuation after the link.
+//!
//! - **Image** - a link directly preceded by a `!`. The link text is
//! interpreted as the alt text of the image.
//!
@@ -740,6 +745,26 @@ test "autolinks" {
);
}
+test "text autolinks" {
+ try testRender(
+ \\Text autolinks must start with http:// or https://.
+ \\This doesn't count: ftp://example.com.
+ \\Example: https://ziglang.org.
+ \\Here is an important link: **http://example.com**
+ \\(Links may be in parentheses: https://example.com/?q=(parens))
+ \\Escaping a link so it's plain text: https\://example.com
+ \\
+ ,
+ \\<p>Text autolinks must start with http:// or https://.
+ \\This doesn't count: ftp://example.com.
+ \\Example: <a href="https://ziglang.org">https://ziglang.org</a>.
+ \\Here is an important link: <strong><a href="http://example.com">http://example.com</a></strong>
+ \\(Links may be in parentheses: <a href="https://example.com/?q=(parens)">https://example.com/?q=(parens)</a>)
+ \\Escaping a link so it's plain text: https://example.com</p>
+ \\
+ );
+}
+
test "images" {
try testRender(
\\