feat: improve region regexes for snippet plugin

pull/4620/head
Divyansh Singh 6 months ago
parent 1a2f81de4d
commit 1a6684cf10

@ -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'

@ -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',

@ -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 = {

@ -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

@ -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]

@ -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)

@ -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<string, string>,
base: string
) => {

@ -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

@ -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)
}

@ -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*<!--\s*#?region\b\s*(.*?)\s*-->/,
end: /^\s*<!--\s*#?endregion\b\s*(.*?)\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<string>, 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++
[/^<!-- #?(region) ([\w*-]+) -->$/, /^<!-- #?(endregion) ?([\w*-]*) -->$/], // 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')
)
}

Loading…
Cancel
Save