diff --git a/src/node/markdown/markdown.ts b/src/node/markdown/markdown.ts index 5050bebd..aca503e2 100644 --- a/src/node/markdown/markdown.ts +++ b/src/node/markdown/markdown.ts @@ -19,9 +19,8 @@ import type { ShikiTransformer, ThemeRegistrationAny } from '@shikijs/types' -import type { Options } from 'markdown-it' -import { MarkdownItAsync } from 'markdown-it-async' import anchorPlugin from 'markdown-it-anchor' +import { MarkdownItAsync, type Options } from 'markdown-it-async' import attrsPlugin from 'markdown-it-attrs' import { full as emojiPlugin } from 'markdown-it-emoji' import type { BuiltinLanguage, BuiltinTheme, Highlighter } from 'shiki' diff --git a/src/node/markdown/plugins/containers.ts b/src/node/markdown/plugins/containers.ts index 03be3abc..6b08fc3d 100644 --- a/src/node/markdown/plugins/containers.ts +++ b/src/node/markdown/plugins/containers.ts @@ -1,4 +1,4 @@ -import type MarkdownIt from 'markdown-it' +import type { MarkdownItAsync } from 'markdown-it-async' import container from 'markdown-it-container' import type { RenderRule } from 'markdown-it/lib/renderer.mjs' import type Token from 'markdown-it/lib/token.mjs' @@ -10,7 +10,7 @@ import { } from './preWrapper' export const containerPlugin = ( - md: MarkdownIt, + md: MarkdownItAsync, options: Options, containerOptions?: ContainerOptions ) => { @@ -54,7 +54,7 @@ type ContainerArgs = [typeof container, string, { render: RenderRule }] function createContainer( klass: string, defaultTitle: string, - md: MarkdownIt + md: MarkdownItAsync ): ContainerArgs { return [ container, @@ -77,7 +77,7 @@ function createContainer( ] } -function createCodeGroup(options: Options, md: MarkdownIt): ContainerArgs { +function createCodeGroup(options: Options, md: MarkdownItAsync): ContainerArgs { return [ container, 'code-group', diff --git a/src/node/markdown/plugins/githubAlerts.ts b/src/node/markdown/plugins/githubAlerts.ts index eab5956d..7e065f7a 100644 --- a/src/node/markdown/plugins/githubAlerts.ts +++ b/src/node/markdown/plugins/githubAlerts.ts @@ -1,11 +1,11 @@ -import type MarkdownIt from 'markdown-it' +import type { MarkdownItAsync } from 'markdown-it-async' import type { ContainerOptions } from './containers' const markerRE = /^\[!(TIP|NOTE|INFO|IMPORTANT|WARNING|CAUTION|DANGER)\]([^\n\r]*)/i export const gitHubAlertsPlugin = ( - md: MarkdownIt, + md: MarkdownItAsync, options?: ContainerOptions ) => { const titleMark = { diff --git a/src/node/markdown/plugins/highlightLines.ts b/src/node/markdown/plugins/highlightLines.ts index f03ae617..a66882ec 100644 --- a/src/node/markdown/plugins/highlightLines.ts +++ b/src/node/markdown/plugins/highlightLines.ts @@ -2,11 +2,11 @@ // Now this plugin is only used to normalize line attrs. // The else part of line highlights logic is in './highlight.ts'. -import type MarkdownIt from 'markdown-it' +import type { MarkdownItAsync } from 'markdown-it-async' const RE = /{([\d,-]+)}/ -export const highlightLinePlugin = (md: MarkdownIt) => { +export const highlightLinePlugin = (md: MarkdownItAsync) => { const fence = md.renderer.rules.fence! md.renderer.rules.fence = (...args) => { const [tokens, idx] = args diff --git a/src/node/markdown/plugins/image.ts b/src/node/markdown/plugins/image.ts index d6f7eb5f..40f0b296 100644 --- a/src/node/markdown/plugins/image.ts +++ b/src/node/markdown/plugins/image.ts @@ -1,6 +1,6 @@ // markdown-it plugin for normalizing image source -import type MarkdownIt from 'markdown-it' +import type { MarkdownItAsync } from 'markdown-it-async' import { EXTERNAL_URL_RE } from '../../shared' export interface Options { @@ -11,7 +11,10 @@ export interface Options { lazyLoading?: boolean } -export const imagePlugin = (md: MarkdownIt, { lazyLoading }: Options = {}) => { +export const imagePlugin = ( + md: MarkdownItAsync, + { lazyLoading }: Options = {} +) => { const imageRule = md.renderer.rules.image! md.renderer.rules.image = (tokens, idx, options, env, self) => { const token = tokens[idx] diff --git a/src/node/markdown/plugins/lineNumbers.ts b/src/node/markdown/plugins/lineNumbers.ts index 15fff082..040963af 100644 --- a/src/node/markdown/plugins/lineNumbers.ts +++ b/src/node/markdown/plugins/lineNumbers.ts @@ -1,9 +1,9 @@ // markdown-it plugin for generating line numbers. // It depends on preWrapper plugin. -import type MarkdownIt from 'markdown-it' +import type { MarkdownItAsync } from 'markdown-it-async' -export const lineNumberPlugin = (md: MarkdownIt, enable = false) => { +export const lineNumberPlugin = (md: MarkdownItAsync, enable = false) => { const fence = md.renderer.rules.fence! md.renderer.rules.fence = (...args) => { const rawCode = fence(...args) diff --git a/src/node/markdown/plugins/link.ts b/src/node/markdown/plugins/link.ts index 4c47eb44..3e48843c 100644 --- a/src/node/markdown/plugins/link.ts +++ b/src/node/markdown/plugins/link.ts @@ -2,7 +2,7 @@ // 1. adding target="_blank" to external links // 2. normalize internal links to end with `.html` -import type MarkdownIt from 'markdown-it' +import type { MarkdownItAsync } from 'markdown-it-async' import { URL } from 'node:url' import { EXTERNAL_URL_RE, @@ -14,7 +14,7 @@ import { const indexRE = /(^|.*\/)index.md(#?.*)$/i export const linkPlugin = ( - md: MarkdownIt, + md: MarkdownItAsync, externalAttrs: Record, base: string ) => { diff --git a/src/node/markdown/plugins/preWrapper.ts b/src/node/markdown/plugins/preWrapper.ts index 906cac2a..bd6b96ba 100644 --- a/src/node/markdown/plugins/preWrapper.ts +++ b/src/node/markdown/plugins/preWrapper.ts @@ -1,11 +1,11 @@ -import type MarkdownIt from 'markdown-it' +import type { MarkdownItAsync } from 'markdown-it-async' export interface Options { codeCopyButtonTitle: string hasSingleTheme: boolean } -export function preWrapperPlugin(md: MarkdownIt, options: Options) { +export function preWrapperPlugin(md: MarkdownItAsync, options: Options) { const fence = md.renderer.rules.fence! md.renderer.rules.fence = (...args) => { const [tokens, idx] = args diff --git a/src/node/markdown/plugins/restoreEntities.ts b/src/node/markdown/plugins/restoreEntities.ts index 07c71e70..0d2bb1d4 100644 --- a/src/node/markdown/plugins/restoreEntities.ts +++ b/src/node/markdown/plugins/restoreEntities.ts @@ -1,9 +1,9 @@ -import type MarkdownIt from 'markdown-it' +import type { MarkdownItAsync } from 'markdown-it-async' import type StateCore from 'markdown-it/lib/rules_core/state_core.mjs' import type Token from 'markdown-it/lib/token.mjs' import { escapeHtml } from '../../shared' -export function restoreEntities(md: MarkdownIt): void { +export function restoreEntities(md: MarkdownItAsync): void { md.core.ruler.at('text_join', text_join) md.renderer.rules.text = (tokens, idx) => escapeHtml(tokens[idx].content) } diff --git a/src/node/markdown/plugins/snippet.ts b/src/node/markdown/plugins/snippet.ts index 05356f72..71e5e76c 100644 --- a/src/node/markdown/plugins/snippet.ts +++ b/src/node/markdown/plugins/snippet.ts @@ -1,5 +1,5 @@ import fs from 'fs-extra' -import type MarkdownIt from 'markdown-it' +import type { MarkdownItAsync } from 'markdown-it-async' import type { RuleBlock } from 'markdown-it/lib/parser_block.mjs' import path from 'node:path' import type { MarkdownEnv } from '../../shared' @@ -51,78 +51,75 @@ export function dedent(text: string): string { return text } +const markers = [ + { + start: /^\s*\/\/\s*#?region\b\s*(.*?)\s*$/, + end: /^\s*\/\/\s*#?endregion\b\s*(.*?)\s*$/ + }, + { + start: /^\s*/, + end: /^\s*/ + }, + { + start: /^\s*\/\*\s*#region\b\s*(.*?)\s*\*\//, + end: /^\s*\/\*\s*#endregion\b\s*(.*?)\s*\*\// + }, + { + start: /^\s*#[rR]egion\b\s*(.*?)\s*$/, + end: /^\s*#[eE]nd ?[rR]egion\b\s*(.*?)\s*$/ + }, + { + start: /^\s*#\s*#?region\b\s*(.*?)\s*$/, + end: /^\s*#\s*#?endregion\b\s*(.*?)\s*$/ + }, + { + start: /^\s*(?:--|::|@?REM)\s*#region\b\s*(.*?)\s*$/, + end: /^\s*(?:--|::|@?REM)\s*#endregion\b\s*(.*?)\s*$/ + }, + { + start: /^\s*#pragma\s+region\b\s*(.*?)\s*$/, + end: /^\s*#pragma\s+endregion\b\s*(.*?)\s*$/ + }, + { + start: /^\s*\(\*\s*#region\b\s*(.*?)\s*\*\)/, + end: /^\s*\(\*\s*#endregion\b\s*(.*?)\s*\*\)/ + } +] + export function findRegion(lines: Array, regionName: string) { - const regionRegexps: [RegExp, RegExp][] = [ - [ - /^[ \t]*\/\/ ?#?(region) ([\w*-]+)$/, - /^[ \t]*\/\/ ?#?(endregion) ?([\w*-]*)$/ - ], // javascript, typescript, java - [ - /^\/\* ?#(region) ([\w*-]+) ?\*\/$/, - /^\/\* ?#(endregion) ?([\w*-]*) ?\*\/$/ - ], // css, less, scss - [/^#pragma (region) ([\w*-]+)$/, /^#pragma (endregion) ?([\w*-]*)$/], // C, C++ - [/^$/, /^$/], // HTML, markdown - [/^[ \t]*#(Region) ([\w*-]+)$/, /^[ \t]*#(End Region) ?([\w*-]*)$/], // Visual Basic - [/^::#(region) ([\w*-]+)$/, /^::#(endregion) ?([\w*-]*)$/], // Bat - [/^[ \t]*# ?(region) ([\w*-]+)$/, /^[ \t]*# ?(endregion) ?([\w*-]*)$/] // C#, PHP, Powershell, Python, perl & misc - ] - - let chosenRegex: [RegExp, RegExp] | null = null - let startLine = -1 + let chosen: { re: (typeof markers)[number]; start: number } | null = null // find the regex pair for a start marker that matches the given region name for (let i = 0; i < lines.length; i++) { - const line = lines[i].trim() - for (const [startRegex, endRegex] of regionRegexps) { - const startMatch = startRegex.exec(line) - if ( - startMatch && - startMatch[2] === regionName && - /^[rR]egion$/.test(startMatch[1]) - ) { - chosenRegex = [startRegex, endRegex] - startLine = i + 1 + for (const re of markers) { + if (re.start.exec(lines[i])?.[1] === regionName) { + chosen = { re, start: i + 1 } break } } - if (chosenRegex) break + if (chosen) break } - if (!chosenRegex) return null + if (!chosen) return null - const [startRegex, endRegex] = chosenRegex let counter = 1 // scan the rest of the lines to find the matching end marker, handling nested markers - for (let i = startLine; i < lines.length; i++) { - const trimmed = lines[i].trim() + for (let i = chosen.start; i < lines.length; i++) { // check for an inner start marker for the same region - const startMatch = startRegex.exec(trimmed) - if ( - startMatch && - startMatch[2] === regionName && - /^[rR]egion$/.test(startMatch[1]) - ) { + if (chosen.re.start.exec(lines[i])?.[1] === regionName) { counter++ continue } // check for an end marker for the same region - const endMatch = endRegex.exec(trimmed) - if ( - endMatch && - // allow empty region name on the end marker as a fallback - (endMatch[2] === regionName || endMatch[2] === '') && - /^[Ee]nd ?[rR]egion$/.test(endMatch[1]) - ) { - counter-- - if (counter === 0) { - return { start: startLine, end: i, regexp: chosenRegex } - } + const endRegion = chosen.re.end.exec(lines[i])?.[1] + // allow empty region name on the end marker as a fallback + if (endRegion === regionName || endRegion === '') { + if (--counter === 0) return { ...chosen, end: i } } } return null } -export const snippetPlugin = (md: MarkdownIt, srcDir: string) => { +export const snippetPlugin = (md: MarkdownItAsync, srcDir: string) => { const parser: RuleBlock = (state, startLine, endLine, silent) => { const CH = '<'.charCodeAt(0) const pos = state.bMarks[startLine] + state.tShift[startLine] @@ -205,13 +202,7 @@ export const snippetPlugin = (md: MarkdownIt, srcDir: string) => { content = dedent( lines .slice(region.start, region.end) - .filter((line) => { - const trimmed = line.trim() - return ( - !region.regexp[0].test(trimmed) && - !region.regexp[1].test(trimmed) - ) - }) + .filter((l) => !(region.re.start.test(l) || region.re.end.test(l))) .join('\n') ) }