11import Prism , { Grammar } from 'prismjs'
22import loadLanguages from 'prismjs/components/'
33import MarkdownIt from 'markdown-it'
4+ import { RenderRule } from 'markdown-it/lib/renderer'
45
56interface Options {
7+ /**
8+ * Prism plugins to load.
9+ */
610 plugins : string [ ]
711 /**
812 * Callback for Prism initialisation. Useful for initialising plugins.
@@ -36,7 +40,7 @@ const DEFAULTS: Options = {
3640
3741
3842/**
39- * Loads the provided { @code lang} into prism.
43+ * Loads the provided ` lang` into prism.
4044 *
4145 * @param lang
4246 * Code of the language to load.
@@ -53,10 +57,10 @@ function loadPrismLang(lang: string): Grammar | undefined {
5357}
5458
5559/**
56- * Loads the provided Prism plugin.a
60+ * Loads the provided Prism plugin.
5761 * @param name
58- * Name of the plugin to load
59- * @throws {Error } If there is no plugin with the provided { @code name}
62+ * Name of the plugin to load.
63+ * @throws {Error } If there is no plugin with the provided ` name`.
6064 */
6165function loadPrismPlugin ( name : string ) : void {
6266 try {
@@ -74,7 +78,7 @@ function loadPrismPlugin(name: string): void {
7478 * The options that were used to initialise the plugin.
7579 * @param lang
7680 * Code of the language to highlight the text in.
77- * @return The name of the language to use and the Prism language object for that language.
81+ * @return The name of the language to use and the Prism language object for that language.
7882 */
7983function selectLanguage ( options : Options , lang : string ) : [ string , Grammar | undefined ] {
8084 let langToUse = lang
@@ -93,21 +97,64 @@ function selectLanguage(options: Options, lang: string): [string, Grammar | unde
9397 * Highlights the provided text using Prism.
9498 *
9599 * @param markdownit
96- * The markdown-it instance
100+ * The markdown-it instance.
97101 * @param options
98102 * The options that have been used to initialise the plugin.
99103 * @param text
100104 * The text to highlight.
101105 * @param lang
102106 * Code of the language to highlight the text in.
103- * @return { @code text } wrapped in { @code <pre>} and { @code <code>}, both equipped with the appropriate class
104- * (markdown-it’s langPrefix + lang). If Prism knows { @code lang}, { @code text} will be highlighted by it .
107+ * @return If Prism knows the language that { @link selectLanguage} returns for `lang`, the `text` highlighted for that language. Otherwise, `text`
108+ * html-escaped .
105109 */
106110function highlight ( markdownit : MarkdownIt , options : Options , text : string , lang : string ) : string {
107111 const [ langToUse , prismLang ] = selectLanguage ( options , lang )
108- const code = prismLang ? Prism . highlight ( text , prismLang , langToUse ) : markdownit . utils . escapeHtml ( text )
109- const classAttribute = langToUse ? ` class="${ markdownit . options . langPrefix } ${ markdownit . utils . escapeHtml ( langToUse ) } "` : ''
110- return `<pre${ classAttribute } ><code${ classAttribute } >${ code } </code></pre>`
112+ return prismLang ? Prism . highlight ( text , prismLang , langToUse ) : markdownit . utils . escapeHtml ( text )
113+ }
114+
115+ /**
116+ * Construct the class name for the provided `lang`.
117+ *
118+ * @param markdownit
119+ * The markdown-it instance.
120+ * @param lang
121+ * The selected language.
122+ * @return the class to use for `lang`.
123+ */
124+ function languageClass ( markdownit : MarkdownIt , lang : string ) : string {
125+ return markdownit . options . langPrefix + markdownit . utils . escapeHtml ( lang )
126+ }
127+
128+ /**
129+ * Patch the `<pre>` and `<code>` tags produced by the `existingRule` for fenced code blocks.
130+ *
131+ * @param markdownit
132+ * The markdown-it instance.
133+ * @param options
134+ * The options that have been used to initialise the plugin.
135+ * @param existingRule
136+ * The currently configured render rule for fenced code blocks.
137+ */
138+ function applyCodeAttributes ( markdownit : MarkdownIt , options : Options , existingRule : RenderRule ) : RenderRule {
139+ return ( tokens , idx , renderOptions , env , self ) => {
140+ const fenceToken = tokens [ idx ]
141+ const info = fenceToken . info ? markdownit . utils . unescapeAll ( fenceToken . info ) . trim ( ) : ''
142+ const lang = info . split ( / ( \s + ) / g) [ 0 ]
143+ const [ langToUse ] = selectLanguage ( options , lang )
144+ if ( ! langToUse ) {
145+ return existingRule ( tokens , idx , renderOptions , env , self )
146+ } else {
147+ fenceToken . info = langToUse
148+ const existingResult = existingRule ( tokens , idx , renderOptions , env , self )
149+ const langClass = languageClass ( markdownit , langToUse )
150+ return existingResult . replace (
151+ / < ( (?: p r e | c o d e ) [ ^ > ] * ?) (?: \s + c l a s s = " ( [ ^ " ] * ) " ( [ ^ > ] * ) ) ? > / g,
152+ ( match , tagStart , existingClasses ?: string , tagEnd ?) =>
153+ existingClasses ?. includes ( langClass ) ? match
154+ : `<${ tagStart } class="${ existingClasses ? `${ existingClasses } ` : '' } ${ langClass } "${ tagEnd || '' } >`
155+ )
156+ }
157+ }
111158}
112159
113160/**
@@ -152,4 +199,5 @@ export default function markdownItPrism(markdownit: MarkdownIt, useroptions: Opt
152199
153200 // register ourselves as highlighter
154201 markdownit . options . highlight = ( text , lang ) => highlight ( markdownit , options , text , lang )
202+ markdownit . renderer . rules . fence = applyCodeAttributes ( markdownit , options , markdownit . renderer . rules . fence || ( ( ) => '' ) )
155203}
0 commit comments