'use strict'; const hljs = require('highlight.js'); const stripIndent = require('strip-indent'); const alias = require('../highlight_alias.json'); function highlightUtil(str, options = {}) { if (typeof str !== 'string') throw new TypeError('str must be a string!'); str = stripIndent(str); const useHljs = Object.prototype.hasOwnProperty.call(options, 'hljs') ? options.hljs : false; const { gutter = true, firstLine = 1, caption, mark = [], languageAttr = false, tab } = options; let { wrap = true } = options; hljs.configure({ classPrefix: useHljs ? 'hljs-' : ''}); const data = highlight(str, options); const lang = options.lang || data.language || ''; const classNames = (useHljs ? 'hljs' : 'highlight') + (lang ? ` ${lang}` : ''); if (gutter && !wrap) wrap = true; // arbitrate conflict ("gutter:true" takes priority over "wrap:false") const before = useHljs ? `
` : '
';
  const after = useHljs ? '
' : '
'; const lines = data.value.split('\n'); let numbers = ''; let content = ''; for (let i = 0, len = lines.length; i < len; i++) { let line = lines[i]; if (tab) line = replaceTabs(line, tab); numbers += `${Number(firstLine) + i}
`; content += formatLine(line, Number(firstLine) + i, mark, options, wrap); } let codeCaption = ''; if (caption) { codeCaption = wrap ? `
${caption}
` : `
${caption}
`; } if (!wrap) { // if original content has one trailing newline, replace it only once, else remove all trailing newlines content = /\r?\n$/.test(data.value) ? content.replace(/\n$/, '') : content.trimEnd(); return `
${codeCaption}${content}
`; } let result = `
`; result += codeCaption; result += ''; if (gutter) { result += ``; } result += ``; result += '
${numbers}
${before}${content}${after}
'; return result; } function formatLine(line, lineno, marked, options, wrap) { const useHljs = (options.hljs || false) || !wrap; const br = wrap ? '
' : '\n'; let res = useHljs ? '' : '${line}`; } else { res += useHljs ? line : `">${line}`; } res += br; return res; } function replaceTabs(str, tab) { return str.replace(/\t+/, match => tab.repeat(match.length)); } function highlight(str, options) { let { lang } = options; const { autoDetect = false } = options; if (lang) { lang = lang.toLowerCase(); } else if (autoDetect) { const result = hljs.highlightAuto(str); return closeTags(result); } if (!lang || !alias.aliases[lang]) { lang = 'plaintext'; } const res = hljs.highlight(str, { language: lang, ignoreIllegals: true }); return closeTags(res); } // https://github.com/hexojs/hexo-util/issues/10 function closeTags(res) { const tokenStack = []; res.value = res.value.split('\n').map(line => { const prepend = tokenStack.map(token => ``).join(''); const matches = line.matchAll(/(|<\/span>)/g); for (const match of matches) { if (match[0] === '') tokenStack.shift(); else tokenStack.unshift(match[2]); } const append = ''.repeat(tokenStack.length); return prepend + line + append; }).join('\n'); return res; } module.exports = highlightUtil;