@linxiraos/pi-utils 1.1.22 → 1.1.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,13 @@
1
+ import {
2
+ CloserIndexes,
3
+ canCloseDelimiter,
4
+ canOpenDelimiter,
5
+ escapedAt,
6
+ InlineClosers,
7
+ PUNCTUATION,
8
+ } from "./inline-closers";
9
+ import { TextRun, TextStops } from "./inline-text";
10
+
1
11
  /** Token shapes emitted by the Markdown lexer. */
2
12
  export namespace Tokens {
3
13
  /** A block quote. */
@@ -237,6 +247,16 @@ export type TokensList = Token[] & { links: Links };
237
247
  /** Context supplied to extension tokenizers. */
238
248
  export interface TokenizerThis {
239
249
  lexer: Lexer;
250
+ /**
251
+ * For an inline tokenizer, the whole inline source, such as a paragraph's text. The `src` it receives is the part
252
+ * of it before `end` that starts at `end - src.length`: inside a link label or the text of emphasis, `end` is where
253
+ * that text ends, right before its closer, and a token there must not reach past it. Inline tokenizers get one
254
+ * context object per inline source, the same for every call while the lexer works through it and the labels and
255
+ * emphasis inside it, so a tokenizer can keep state for it in a `WeakMap`.
256
+ */
257
+ source?: string;
258
+ /** For an inline tokenizer, where the text the lexer works through ends in `source` (see `source`). */
259
+ end?: number;
240
260
  }
241
261
  /** A tokenizer extension callback. */
242
262
  export type TokenizerExtensionFunction = (
@@ -246,11 +266,25 @@ export type TokenizerExtensionFunction = (
246
266
  ) => Tokens.Generic | undefined;
247
267
  /** A tokenizer extension start hint. */
248
268
  export type TokenizerStartFunction = (this: TokenizerThis, src: string) => number | void;
269
+ /**
270
+ * An offset form of a start hint: the first index at or after `from` in `src` where the extension's tokenizer could
271
+ * match, or `undefined` (not -1) when there is none; anything else below `from` throws. The test at each index must
272
+ * read `src` only from that index on, so the answer for `from` is also the answer for every later offset up to it,
273
+ * and must still pass where `src` goes on past its end: for a link label or the text of emphasis, the lexer asks on
274
+ * the inline source it lies in, then asks again on the label or text at the index it got, if that lies inside.
275
+ */
276
+ export type TokenizerStartFromFunction = (this: TokenizerThis, src: string, from: number) => number | undefined;
249
277
  /** An inline or block tokenizer extension. */
250
278
  export interface TokenizerExtension {
251
279
  name: string;
252
280
  level: "block" | "inline";
253
281
  start?: TokenizerStartFunction;
282
+ /**
283
+ * Replaces `start` for inline text when present. Inline lexing calls it on the whole inline source and reuses
284
+ * the answer until lexing passes it, inside link labels and emphasis too, instead of calling `start` on the rest
285
+ * of the source at every text step, so a hint that stops at its answer keeps a long paragraph linear.
286
+ */
287
+ startFrom?: TokenizerStartFromFunction;
254
288
  tokenizer: TokenizerExtensionFunction;
255
289
  childTokens?: string[];
256
290
  }
@@ -341,7 +375,6 @@ const DEFAULTS: MarkedOptions = {
341
375
  walkTokens: null,
342
376
  extensions: null,
343
377
  };
344
- const PUNCTUATION = /[!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]/;
345
378
 
346
379
  function tokenList(links: Links = Object.create(null)): TokensList {
347
380
  const list = [] as unknown as TokensList;
@@ -379,80 +412,33 @@ function escapeHtml(value: string, encode = true): string {
379
412
  return out;
380
413
  }
381
414
 
382
- function findClosingBracket(src: string, start: number, open: string, close: string): number {
383
- let depth = 0;
384
- for (let i = start; i < src.length; i++) {
385
- if (src[i] === "\\") {
386
- i++;
387
- continue;
388
- }
389
- if (src[i] === open) depth++;
390
- else if (src[i] === close) {
391
- if (depth === 0) return i;
392
- depth--;
393
- }
394
- }
395
- return -1;
396
- }
397
-
398
415
  function findDelimiter(src: string, delimiter: string, from: number): number {
399
416
  let at = src.indexOf(delimiter, from);
400
- while (at !== -1) {
401
- let escapes = 0;
402
- for (let i = at - 1; i >= 0 && src[i] === "\\"; i--) escapes++;
403
- if (escapes % 2 === 0) return at;
404
- at = src.indexOf(delimiter, at + delimiter.length);
405
- }
406
- return -1;
417
+ while (at !== -1 && escapedAt(src, at)) at = src.indexOf(delimiter, at + delimiter.length);
418
+ return at;
407
419
  }
408
420
 
409
- function canOpenDelimiter(src: string, index: number, width: number, marker: string, previous = "\n"): boolean {
410
- const before = index === 0 ? previous : src[index - 1]!;
411
- if (index === 0 && before === marker) return false;
412
- const after = src[index + width];
413
- if (after === undefined || /\s/.test(after)) return false;
414
- if (marker === "_" && /[\p{L}\p{N}]/u.test(before) && /[\p{L}\p{N}]/u.test(after)) return false;
415
- return true;
421
+ /**
422
+ * The autolink at the start of `src`, a suffix of `closers`' source: "<", a URL or an address, ">". A URL runs from its
423
+ * scheme to the first " " or ">", which must be a ">"; an address holds no " ", "<", ">" or second "@". An address
424
+ * that matches after a scheme ends where the URL does, so only the URL is tried there.
425
+ */
426
+ function autolinkPrefix(src: string, closers: InlineClosers): string | undefined {
427
+ const scheme = /^<(?:https?:\/\/|ftp:\/\/)/i.exec(src);
428
+ if (!scheme) return /^<[^ <>@]+@[^ <>@]+>/.exec(src)?.[0];
429
+ const close = closers.closeUrlAutolink(src, scheme[0].length);
430
+ return close === -1 ? undefined : src.slice(0, close + 1);
416
431
  }
417
432
 
418
- function canCloseDelimiter(src: string, index: number, marker: string): boolean {
419
- const before = src[index - 1];
420
- const after = src[index + 1] ?? "\n";
421
- if (before === undefined || /\s/.test(before)) return false;
422
- if (marker === "_" && /[\p{L}\p{N}]/u.test(before) && /[\p{L}\p{N}]/u.test(after)) return false;
423
- return !PUNCTUATION.test(before) || /\s/.test(after) || PUNCTUATION.test(after);
424
- }
425
- function inlineHtmlPrefix(src: string): string | undefined {
433
+ /** The inline HTML (a tag or a comment) at the start of `src`, a suffix of `closers`' source, or `undefined`. */
434
+ function inlineHtmlPrefix(src: string, closers: InlineClosers): string | undefined {
426
435
  if (src.startsWith("<!--")) {
427
- const end = src.indexOf("-->", 4);
436
+ const end = closers.closeComment(src);
428
437
  return end === -1 ? undefined : src.slice(0, end + 3);
429
438
  }
430
439
  if (!/^<\/?[A-Za-z][A-Za-z0-9-]*(?:\s|\/?>)/.test(src)) return undefined;
431
- let quote = "";
432
- for (let i = 1; i < src.length; i++) {
433
- const char = src[i]!;
434
- if (quote !== "") {
435
- if (char === quote) quote = "";
436
- continue;
437
- }
438
- if (char === '"' || char === "'") {
439
- quote = char;
440
- continue;
441
- }
442
- if (char === ">") return src.slice(0, i + 1);
443
- }
444
- return undefined;
445
- }
446
-
447
- function appendText(tokens: Token[], raw: string, text = raw, escaped = false): void {
448
- if (raw === "") return;
449
- const previous = tokens.at(-1);
450
- if (previous?.type === "text" && previous.tokens === undefined && previous.escaped === escaped) {
451
- previous.raw += raw;
452
- previous.text += text;
453
- return;
454
- }
455
- tokens.push({ type: "text", raw, text, escaped });
440
+ const end = closers.closeTag(src);
441
+ return end === -1 ? undefined : src.slice(0, end + 1);
456
442
  }
457
443
 
458
444
  /** Tokenizes the built-in inline Markdown surface. */
@@ -473,15 +459,60 @@ export class Tokenizer {
473
459
  }
474
460
  }
475
461
 
476
- function matchLink(src: string, lexer: Lexer): Tokens.Link | Tokens.Image | undefined {
462
+ // A nested inline source (a link label, the text of emphasis) shares the closers, text stops and tokenizer context of
463
+ // the source it lies in, which are offered to the next inline lex on that lexer. They are offered for the call that
464
+ // lexes the nested source through `lexer.inlineTokens`, which a subclass may override; the first lex that call
465
+ // reaches takes them if it lexes exactly that string, or else starts a root source.
466
+ let offered: { lexer: Lexer; nested: NestedMatch; outer: InlineLex } | undefined;
467
+
468
+ /** Starts the lex of `src` on `lexer`: of the nested source offered for it, else of a root source. Clears the offer. */
469
+ function openInlineSource(src: string, lexer: Lexer, output: Token[]): InlineLex {
470
+ const offer = offered;
471
+ offered = undefined;
472
+ const shared = offer?.lexer === lexer && offer.nested.src === src ? offer : undefined;
473
+ const closers = shared ? shared.nested.closers : new InlineClosers(new CloserIndexes(src), src.length);
474
+ return {
475
+ src,
476
+ lexer,
477
+ output,
478
+ closers,
479
+ stops: shared ? shared.outer.stops.nested(src, closers.end) : new TextStops(src, lexer),
480
+ textRun: new TextRun(src),
481
+ context: shared ? shared.outer.context : { lexer, source: src, end: src.length },
482
+ rest: src,
483
+ };
484
+ }
485
+
486
+ /**
487
+ * Whether any label can resolve in `links`. The map every lexer starts with has no prototype, so it resolves none
488
+ * until a definition is added.
489
+ */
490
+ function hasDefinitions(links: Links): boolean {
491
+ if (Object.getPrototypeOf(links) !== null) return true;
492
+ for (const _label in links) return true;
493
+ return false;
494
+ }
495
+
496
+ /**
497
+ * A link, image, emphasis or strong emphasis token whose `tokens` are still to be lexed from `src`, a part of the
498
+ * source that shares `closers`. `inlineTokens` lexes it once the rule that matched it has returned.
499
+ */
500
+ interface NestedMatch {
501
+ token: Tokens.Link | Tokens.Image | Tokens.Em | Tokens.Strong;
502
+ src: string;
503
+ closers: InlineClosers;
504
+ }
505
+
506
+ /** Matches a link or image at the start of `src`, a suffix of `closers`' source. */
507
+ function matchLink(src: string, lexer: Lexer, closers: InlineClosers): NestedMatch | undefined {
477
508
  const image = src.startsWith("![");
478
509
  if (!(image || src.startsWith("["))) return undefined;
479
510
  const labelStart = image ? 2 : 1;
480
- const labelEnd = findClosingBracket(src, labelStart, "[", "]");
511
+ const labelEnd = closers.closeSquare(src, labelStart);
481
512
  if (labelEnd === -1) return undefined;
482
513
  const label = src.slice(labelStart, labelEnd);
483
514
  if (src[labelEnd + 1] === "(") {
484
- const destinationEnd = findClosingBracket(src, labelEnd + 2, "(", ")");
515
+ const destinationEnd = closers.closeRound(src, labelEnd + 2);
485
516
  if (destinationEnd === -1) return undefined;
486
517
  const inside = src.slice(labelEnd + 2, destinationEnd).trim();
487
518
  let href = inside;
@@ -491,31 +522,92 @@ function matchLink(src: string, lexer: Lexer): Tokens.Link | Tokens.Image | unde
491
522
  href = cleanUrl(titleMatch[1]!);
492
523
  title = titleMatch[2] ?? titleMatch[3] ?? titleMatch[4] ?? null;
493
524
  const raw = src.slice(0, destinationEnd + 1);
494
- const tokens = lexer.inlineTokens(label);
495
- return image
496
- ? { type: "image", raw, href, title, text: unescapeMarkdown(label), tokens }
497
- : { type: "link", raw, href, title, text: label, tokens };
525
+ // An image's text is its label unescaped. Nested images each unescape their label, so a label without an escape
526
+ // is taken as it is instead of read again.
527
+ return {
528
+ token: image
529
+ ? {
530
+ type: "image",
531
+ raw,
532
+ href,
533
+ title,
534
+ text: closers.escapeWithin(src, labelStart, labelEnd) ? unescapeMarkdown(label) : label,
535
+ tokens: [],
536
+ }
537
+ : { type: "link", raw, href, title, text: label, tokens: [] },
538
+ src: label,
539
+ closers: closers.nested(src, labelEnd),
540
+ };
498
541
  }
499
542
  let rawEnd = labelEnd + 1;
500
- let ref = label;
543
+ let refStart = labelStart;
544
+ let refEnd = labelEnd;
501
545
  if (src[rawEnd] === "[") {
502
- const refEnd = findClosingBracket(src, rawEnd + 1, "[", "]");
503
- if (refEnd === -1) return undefined;
504
- ref = src.slice(rawEnd + 1, refEnd) || label;
505
- rawEnd = refEnd + 1;
546
+ const end = closers.closeSquare(src, rawEnd + 1);
547
+ if (end === -1) return undefined;
548
+ if (end > rawEnd + 1) {
549
+ refStart = rawEnd + 1;
550
+ refEnd = end;
551
+ }
552
+ rawEnd = end + 1;
506
553
  }
507
- const def = lexer.tokens.links[ref.replace(/\s+/g, " ").toLowerCase()];
554
+ // Definition labels come from `[^\]]+`, so a reference holding an unescaped "]" names none. Skip normalizing it,
555
+ // and every reference while there are no definitions.
556
+ const links = lexer.tokens.links;
557
+ if (!hasDefinitions(links) || closers.squareCloserWithin(src, refStart, refEnd)) return undefined;
558
+ const def = links[src.slice(refStart, refEnd).replace(/\s+/g, " ").toLowerCase()];
508
559
  if (!def) return undefined;
509
560
  const raw = src.slice(0, rawEnd);
510
- const tokens = lexer.inlineTokens(label);
511
- return image
512
- ? { type: "image", raw, href: def.href, title: def.title ?? null, text: unescapeMarkdown(label), tokens }
513
- : { type: "link", raw, href: def.href, title: def.title ?? null, text: label, tokens };
561
+ return {
562
+ token: image
563
+ ? {
564
+ type: "image",
565
+ raw,
566
+ href: def.href,
567
+ title: def.title ?? null,
568
+ text: closers.escapeWithin(src, labelStart, labelEnd) ? unescapeMarkdown(label) : label,
569
+ tokens: [],
570
+ }
571
+ : { type: "link", raw, href: def.href, title: def.title ?? null, text: label, tokens: [] },
572
+ src: label,
573
+ closers: closers.nested(src, labelEnd),
574
+ };
575
+ }
576
+
577
+ /** Matches emphasis or strong emphasis at the start of `rest`, a suffix of `closers`' source, lexed after `output`. */
578
+ function matchEmphasis(rest: string, output: Token[], closers: InlineClosers): NestedMatch | undefined {
579
+ const marker = rest[0];
580
+ const previous = output.at(-1)?.raw.at(-1) ?? "\n";
581
+ if (marker !== "*" && marker !== "_") return undefined;
582
+ if (rest.startsWith(marker.repeat(3)) && canOpenDelimiter(rest, 0, 3, marker, previous)) {
583
+ const end = findDelimiter(rest, marker.repeat(3), 3);
584
+ if (end !== -1 && canCloseDelimiter(rest, end, marker)) {
585
+ // The text between the delimiters inside the outer two markers of each: `**inner**`.
586
+ const text = rest.slice(1, end + 2);
587
+ return {
588
+ token: { type: "em", raw: rest.slice(0, end + 3), text, tokens: [] },
589
+ src: text,
590
+ closers: closers.nested(rest, end + 2),
591
+ };
592
+ }
593
+ }
594
+ const width = rest[1] === marker ? 2 : 1;
595
+ if (!canOpenDelimiter(rest, 0, width, marker, previous)) return undefined;
596
+ const end = closers.closeEmphasis(rest, marker, width);
597
+ if (end === -1) return undefined;
598
+ const raw = rest.slice(0, end + width);
599
+ const text = rest.slice(width, end);
600
+ return {
601
+ token: width === 2 ? { type: "strong", raw, text, tokens: [] } : { type: "em", raw, text, tokens: [] },
602
+ src: text,
603
+ closers: closers.nested(rest, end),
604
+ };
514
605
  }
515
606
 
516
607
  function trimBareUrl(candidate: string): string {
517
- let out = candidate;
518
- while (/[.,:;!?]$/.test(out)) out = out.slice(0, -1);
608
+ let end = candidate.length;
609
+ while (end > 0 && ".,:;!?".includes(candidate[end - 1]!)) end--;
610
+ let out = candidate.slice(0, end);
519
611
  let opens = 0;
520
612
  let closes = 0;
521
613
  for (const char of out) {
@@ -529,12 +621,43 @@ function trimBareUrl(candidate: string): string {
529
621
  return out;
530
622
  }
531
623
 
532
- function inlineTokens(src: string, lexer: Lexer, output: Token[] = []): Token[] {
533
- let rest = src;
624
+ /** The lex of one inline source, carried across the nested sources lexed inside it. */
625
+ interface InlineLex {
626
+ readonly src: string;
627
+ readonly lexer: Lexer;
628
+ readonly output: Token[];
629
+ readonly closers: InlineClosers;
630
+ readonly stops: TextStops;
631
+ readonly textRun: TextRun;
632
+ readonly context: TokenizerThis;
633
+ /** The part of `src` not lexed yet, always a suffix of it. */
634
+ rest: string;
635
+ }
636
+
637
+ // Each level of nested links or emphasis keeps only this loop's small frame on the stack while the level inside it
638
+ // is lexed: the rules run in `lexToNested`, which returns each link, image or emphasis before its content is lexed.
639
+ function inlineTokens(src: string, lexer: Lexer, output: Token[]): Token[] {
640
+ const lex = openInlineSource(src, lexer, output);
641
+ for (let nested = lexToNested(lex); nested; nested = lexToNested(lex)) {
642
+ offered = { lexer, nested, outer: lex };
643
+ nested.token.tokens = lexer.inlineTokens(nested.src);
644
+ // An override that returned without lexing the text leaves its offer to no later lex of an equal string.
645
+ offered = undefined;
646
+ output.push(nested.token);
647
+ }
648
+ return output;
649
+ }
650
+
651
+ /** Lexes `lex.rest` up to the next link, image or emphasis, which it returns with `lex.rest` past it, or to its end. */
652
+ function lexToNested(lex: InlineLex): NestedMatch | undefined {
653
+ const { src, lexer, output, closers, stops, textRun, context } = lex;
654
+ // The context is shared with the sources nested in this one, which move its end while they are lexed.
655
+ context.end = closers.end;
656
+ let rest = lex.rest;
534
657
  while (rest !== "") {
535
658
  let custom: Tokens.Generic | undefined;
536
659
  for (const extension of lexer.extensions.inline) {
537
- custom = extension.tokenizer.call({ lexer }, rest, output);
660
+ custom = extension.tokenizer.call(context, rest, output);
538
661
  if (custom?.raw) break;
539
662
  }
540
663
  if (custom?.raw) {
@@ -556,81 +679,35 @@ function inlineTokens(src: string, lexer: Lexer, output: Token[] = []): Token[]
556
679
  continue;
557
680
  }
558
681
  if (rest[0] === "`") {
559
- const opener = /^`+/.exec(rest)![0];
560
- const end = findDelimiter(rest, opener, opener.length);
682
+ const width = closers.codeOpenerWidth(rest);
683
+ const end = closers.closeCode(rest, width);
561
684
  if (end !== -1) {
562
- const raw = rest.slice(0, end + opener.length);
563
- let text = rest.slice(opener.length, end).replace(/\n/g, " ");
685
+ const raw = rest.slice(0, end + width);
686
+ let text = rest.slice(width, end).replace(/\n/g, " ");
564
687
  if (/^ .* $/.test(text) && text.trim() !== "") text = text.slice(1, -1);
565
688
  output.push({ type: "codespan", raw, text });
566
689
  rest = rest.slice(raw.length);
567
690
  continue;
568
691
  }
569
692
  }
570
- const auto = /^<((?:https?:\/\/|ftp:\/\/)[^ >]+|[^ <>@]+@[^ <>@]+)>/i.exec(rest);
693
+ const auto = autolinkPrefix(rest, closers);
571
694
  if (auto) {
572
- const text = auto[1]!;
695
+ const text = auto.slice(1, -1);
573
696
  const href = text.includes("@") && !/^[a-z][a-z+.-]*:\/\//i.test(text) ? `mailto:${text}` : text;
574
- output.push({ type: "link", raw: auto[0], text, href, tokens: [{ type: "text", raw: text, text }] });
575
- rest = rest.slice(auto[0].length);
697
+ output.push({ type: "link", raw: auto, text, href, tokens: [{ type: "text", raw: text, text }] });
698
+ rest = rest.slice(auto.length);
576
699
  continue;
577
700
  }
578
- const html = inlineHtmlPrefix(rest);
701
+ const html = inlineHtmlPrefix(rest, closers);
579
702
  if (html) {
580
703
  output.push({ type: "html", raw: html, inLink: false, inRawBlock: false, block: false, text: html });
581
704
  rest = rest.slice(html.length);
582
705
  continue;
583
706
  }
584
- const link = matchLink(rest, lexer);
585
- if (link) {
586
- output.push(link);
587
- rest = rest.slice(link.raw.length);
588
- continue;
589
- }
590
-
591
- const marker = rest[0];
592
- const previous = output.at(-1)?.raw.at(-1) ?? "\n";
593
- if (
594
- (marker === "*" || marker === "_") &&
595
- rest.startsWith(marker.repeat(3)) &&
596
- canOpenDelimiter(rest, 0, 3, marker, previous)
597
- ) {
598
- const end = findDelimiter(rest, marker.repeat(3), 3);
599
- if (end !== -1 && canCloseDelimiter(rest, end, marker)) {
600
- const raw = rest.slice(0, end + 3);
601
- const inner = rest.slice(3, end);
602
- const text = `${marker.repeat(2)}${inner}${marker.repeat(2)}`;
603
- output.push({ type: "em", raw, text, tokens: lexer.inlineTokens(text) });
604
- rest = rest.slice(raw.length);
605
- continue;
606
- }
607
- }
608
- if (
609
- (marker === "*" || marker === "_") &&
610
- canOpenDelimiter(rest, 0, rest[1] === marker ? 2 : 1, marker, previous)
611
- ) {
612
- const width = rest[1] === marker ? 2 : 1;
613
- const delimiter = marker.repeat(width);
614
- let end = findDelimiter(rest, delimiter, width);
615
- let nested = 0;
616
- while (end !== -1) {
617
- if (!canCloseDelimiter(rest, end, marker) && canOpenDelimiter(rest, end, width, marker)) {
618
- nested++;
619
- } else if (canCloseDelimiter(rest, end, marker) && nested > 0) {
620
- nested--;
621
- } else if (canCloseDelimiter(rest, end, marker)) {
622
- break;
623
- }
624
- end = findDelimiter(rest, delimiter, end + width);
625
- }
626
- if (end !== -1) {
627
- const raw = rest.slice(0, end + width);
628
- const text = rest.slice(width, end);
629
- const tokens = lexer.inlineTokens(text);
630
- output.push(width === 2 ? { type: "strong", raw, text, tokens } : { type: "em", raw, text, tokens });
631
- rest = rest.slice(raw.length);
632
- continue;
633
- }
707
+ const nested = matchLink(rest, lexer, closers) ?? matchEmphasis(rest, output, closers);
708
+ if (nested) {
709
+ lex.rest = rest.slice(nested.token.raw.length);
710
+ return nested;
634
711
  }
635
712
  if (rest.startsWith("~~")) {
636
713
  let del: Tokens.Del | undefined | false;
@@ -643,14 +720,17 @@ function inlineTokens(src: string, lexer: Lexer, output: Token[] = []): Token[]
643
720
  continue;
644
721
  }
645
722
  }
723
+ // `rest` is always a suffix of `src`, so its offset follows from the lengths.
724
+ const pos = src.length - rest.length;
646
725
  let url: Tokens.Link | undefined | false;
647
726
  const urlOverride = lexer.tokenizerOverrides.url;
648
727
  if (urlOverride) url = urlOverride.call(lexer.tokenizer, rest);
649
728
  if (!urlOverride || url === false) {
650
- const match =
651
- /^(?:(?:https?:\/\/|ftp:\/\/|www\.)[^\s<]+|[A-Za-z0-9._+-]+@[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)+)/i.exec(
652
- rest,
653
- );
729
+ const match = stops.bareUrlCanStart(pos)
730
+ ? /^(?:(?:https?:\/\/|ftp:\/\/|www\.)[^\s<]+|[A-Za-z0-9._+-]+@[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)+)/i.exec(
731
+ rest,
732
+ )
733
+ : null;
654
734
  if (match) {
655
735
  const text = trimBareUrl(match[0]);
656
736
  const href =
@@ -668,24 +748,12 @@ function inlineTokens(src: string, lexer: Lexer, output: Token[] = []): Token[]
668
748
  continue;
669
749
  }
670
750
 
671
- let next = rest.length;
672
- for (const char of ["\\", "`", "<", "[", "!", "*", "_", "~", "\n"]) {
673
- const at = rest.indexOf(char, 1);
674
- if (at !== -1 && at < next) next = at;
675
- }
676
- const urlAt = /(?:https?:\/\/|ftp:\/\/|www\.|[A-Za-z0-9._+-]+@)/i.exec(rest.slice(1));
677
- if (urlAt && urlAt.index + 1 < next) next = urlAt.index + 1;
678
- const hardBreak = /(?: {2,}|\\)\n/.exec(rest.slice(1));
679
- if (hardBreak && hardBreak.index + 1 < next) next = hardBreak.index + 1;
680
- for (const extension of lexer.extensions.inline) {
681
- const at = extension.start?.call({ lexer }, rest);
682
- if (typeof at === "number" && at > 0 && at < next) next = at;
683
- }
684
- if (next === 0) next = 1;
685
- appendText(output, rest.slice(0, next));
751
+ const next = stops.textLength(rest);
752
+ textRun.append(output, pos, pos + next);
686
753
  rest = rest.slice(next);
687
754
  }
688
- return output;
755
+ lex.rest = rest;
756
+ return undefined;
689
757
  }
690
758
 
691
759
  function splitTableRow(line: string): string[] {