Skip to content

refactor highlight: add extend api for highlight #5095

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Dec 14, 2022
21 changes: 21 additions & 0 deletions lib/extend/filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ class Filter {
if (index !== -1) list.splice(index, 1);
}

queryCount(type) {
const filters = this.list(type);
return filters.length;
}

exec(type, data, options = {}) {
const filters = this.list(type);
if (filters.length === 0) return Promise.resolve(data);
Expand Down Expand Up @@ -87,6 +92,22 @@ class Filter {

return args[0];
}

execFirstSync(type, data, options = {}) {
const filters = this.list(type);
const filtersLen = filters.length;
if (filtersLen === 0) return null;

const ctx = options.context;
const args = options.args || [];

args.unshift(data);

const result = Reflect.apply(filters[0], ctx, args);
args[0] = result == null ? args[0] : result;

return args[0];
}
}

module.exports = Filter;
11 changes: 10 additions & 1 deletion lib/hexo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,8 @@ class Hexo extends EventEmitter {
'update_package', // Update package.json
'load_config', // Load config
'load_theme_config', // Load alternate theme config
'load_plugins' // Load external plugins & scripts
'load_plugins', // Load external plugins & scripts
'load_highlight' // Load highlight.js or prism.js
], name => require(`./${name}`)(this)).then(() => this.execFilter('after_init', null, { context: this })).then(() => {
// Ready to go!
this.emit('ready');
Expand Down Expand Up @@ -478,13 +479,21 @@ class Hexo extends EventEmitter {
});
}

queryFilterCount(type) {
return this.extend.filter.queryCount(type);
}

execFilter(type, data, options) {
return this.extend.filter.exec(type, data, options);
}

execFilterSync(type, data, options) {
return this.extend.filter.execSync(type, data, options);
}

execFirstFilterSync(type, data, options) {
return this.extend.filter.execFirstSync(type, data, options);
}
}

Hexo.lib_dir = libDir + sep;
Expand Down
3 changes: 3 additions & 0 deletions lib/hexo/load_highlight.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
'use strict';

module.exports = require('../plugins/filter/highlight');
129 changes: 44 additions & 85 deletions lib/plugins/filter/before_post_render/backtick_code_block.js
Original file line number Diff line number Diff line change
@@ -1,111 +1,70 @@
'use strict';

let highlight, prismHighlight;

const rBacktick = /^((?:[^\S\r\n]*>){0,3}[^\S\r\n]*)(`{3,}|~{3,})[^\S\r\n]*((?:.*?[^`\s])?)[^\S\r\n]*\n((?:[\s\S]*?\n)?)(?:(?:[^\S\r\n]*>){0,3}[^\S\r\n]*)\2[^\S\r\n]?(\n+|$)/gm;
const rAllOptions = /([^\s]+)\s+(.+?)\s+(https?:\/\/\S+|\/\S+)\s*(.+)?/;
const rLangCaption = /([^\s]+)\s*(.+)?/;

const escapeSwigTag = str => str.replace(/{/g, '{').replace(/}/g, '}');

function backtickCodeBlock(data) {
const dataContent = data.content;

const hljsCfg = this.config.highlight || {};
const prismCfg = this.config.prismjs || {};
module.exports = ctx => {
return function backtickCodeBlock(data) {
const dataContent = data.content;

if ((!dataContent.includes('```') && !dataContent.includes('~~~')) || (!hljsCfg.enable && !prismCfg.enable)) return;
if ((!dataContent.includes('```') && !dataContent.includes('~~~')) || !ctx.queryFilterCount('highlight')) return;

data.content = dataContent.replace(rBacktick, ($0, start, $2, _args, _content, end) => {
let content = _content.replace(/\n$/, '');
data.content = dataContent.replace(rBacktick, ($0, start, $2, _args, _content, end) => {
let content = _content.replace(/\n$/, '');

// neither highlight or prismjs is enabled, return escaped content directly.
if (!hljsCfg.enable && !prismCfg.enable) return escapeSwigTag($0);
// neither highlight or prismjs is enabled, return escaped content directly.
if (!ctx.queryFilterCount('highlight')) return escapeSwigTag($0);

// Extract language and caption of code blocks
const args = _args.split('=').shift();
let lang, caption;
// Extract language and caption of code blocks
const args = _args.split('=').shift();
let lang, caption;

if (args) {
const match = rAllOptions.exec(args) || rLangCaption.exec(args);
if (args) {
const match = rAllOptions.exec(args) || rLangCaption.exec(args);

if (match) {
lang = match[1];
if (match) {
lang = match[1];

if (match[2]) {
caption = `<span>${match[2]}</span>`;
if (match[2]) {
caption = `<span>${match[2]}</span>`;

if (match[3]) {
caption += `<a href="${match[3]}">${match[4] ? match[4] : 'link'}</a>`;
if (match[3]) {
caption += `<a href="${match[3]}">${match[4] ? match[4] : 'link'}</a>`;
}
}
}
}
}

// PR #3765
if (start.includes('>')) {
// heading of last line is already removed by the top RegExp "rBacktick"
const depth = start.split('>').length - 1;
const regexp = new RegExp(`^([^\\S\\r\\n]*>){0,${depth}}([^\\S\\r\\n]|$)`, 'mg');
content = content.replace(regexp, '');
}

// Since prismjs have better performance, so prismjs should have higher priority.
if (prismCfg.enable) {
if (!prismHighlight) prismHighlight = require('hexo-util').prismHighlight;

const options = {
lineNumber: prismCfg.line_number,
tab: prismCfg.tab_replace,
isPreprocess: prismCfg.preprocess,
lang,
caption
};

content = prismHighlight(content, options);
} else if (hljsCfg.enable) {
if (!highlight) highlight = require('hexo-util').highlight;
// PR #3765
if (start.includes('>')) {
// heading of last line is already removed by the top RegExp "rBacktick"
const depth = start.split('>').length - 1;
const regexp = new RegExp(`^([^\\S\\r\\n]*>){0,${depth}}([^\\S\\r\\n]|$)`, 'mg');
content = content.replace(regexp, '');
}

const options = {
hljs: hljsCfg.hljs,
autoDetect: hljsCfg.auto_detect,
gutter: hljsCfg.line_number,
tab: hljsCfg.tab_replace,
wrap: hljsCfg.wrap,
lang,
languageAttr: hljsCfg.language_attr,
caption
caption,
lines_length: content.split('\n').length
};
// setup line number by inline
_args = _args.replace('=+', '=');

if (options.gutter) {
hljsCfg.first_line_number = hljsCfg.first_line_number || 'always1';
if (hljsCfg.first_line_number === 'inline') {

// setup line number by inline
_args = _args.replace('=+', '=');
options.gutter = _args.includes('=');

// setup firstLineNumber;
options.firstLine = options.gutter ? _args.split('=')[1] || 1 : 0;
}
}

if (Array.isArray(hljsCfg.exclude_languages) && hljsCfg.exclude_languages.includes(options.lang)) {
// Only wrap with <pre><code class="lang"></code></pre>
options.wrap = false;
options.gutter = false;
options.autoDetect = false;
// setup firstLineNumber;
if (_args.includes('=')) {
options.firstLineNumber = _args.split('=')[1] || 1;
}

content = highlight(content, options);
}

return start
+ '<hexoPostRenderCodeBlock>'
+ escapeSwigTag(content)
+ '</hexoPostRenderCodeBlock>'
+ end;
});
}

module.exports = backtickCodeBlock;
content = ctx.execFirstFilterSync('highlight', content, { args: [options] });

return start
+ '<hexoPostRenderCodeBlock>'
+ escapeSwigTag(content)
+ '</hexoPostRenderCodeBlock>'
+ end;
});
};
};
2 changes: 1 addition & 1 deletion lib/plugins/filter/before_post_render/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
module.exports = ctx => {
const { filter } = ctx.extend;

filter.register('before_post_render', require('./backtick_code_block'));
filter.register('before_post_render', require('./backtick_code_block')(ctx));
filter.register('before_post_render', require('./titlecase'));
};
46 changes: 46 additions & 0 deletions lib/plugins/filter/highlight/highlight.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use strict';

// Lazy require highlight.js
let highlight;

module.exports = ctx => {
return function highlightFilter(code, options) {
const hljsCfg = ctx.config.highlight || {};
const line_threshold = options.line_threshold || hljsCfg.line_threshold || 0;
const shouldUseLineNumbers = typeof options.line_number === 'undefined' ? hljsCfg.line_number : options.line_number;
const surpassesLineThreshold = options.lines_length > line_threshold;
const gutter = shouldUseLineNumbers && surpassesLineThreshold;
const languageAttr = typeof options.language_attr === 'undefined' ? hljsCfg.language_attr : options.language_attr;

const hljsOptions = {
autoDetect: hljsCfg.auto_detect,
caption: options.caption,
firstLine: options.firstLine,
gutter,
hljs: hljsCfg.hljs,
lang: options.lang,
languageAttr,
mark: options.mark,
tab: hljsCfg.tab_replace,
wrap: hljsCfg.wrap
};
if (hljsCfg.first_line_number === 'inline') {
if (typeof options.firstLineNumber !== 'undefined') {
hljsOptions.firstLine = options.firstLineNumber;
} else {
hljsOptions.gutter = false;
}
}

if (Array.isArray(hljsCfg.exclude_languages) && hljsCfg.exclude_languages.includes(hljsOptions.lang)) {
// Only wrap with <pre><code class="lang"></code></pre>
hljsOptions.wrap = false;
hljsOptions.gutter = false;
hljsOptions.autoDetect = false;
}

if (!highlight) highlight = require('hexo-util').highlight;

return highlight(code, hljsOptions);
};
};
14 changes: 14 additions & 0 deletions lib/plugins/filter/highlight/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use strict';

module.exports = ctx => {
const { filter } = ctx.extend;
const hljsCfg = ctx.config.highlight || {};
const prismCfg = ctx.config.prismjs || {};

// Since prismjs have better performance, so prismjs should have higher priority.
if (prismCfg.enable) {
filter.register('highlight', require('./prism')(ctx));
} else if (hljsCfg.enable) {
filter.register('highlight', require('./highlight')(ctx));
}
};
28 changes: 28 additions & 0 deletions lib/plugins/filter/highlight/prism.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use strict';

// Lazy require prismjs
let prismHighlight;

module.exports = ctx => {
return function(code, options) {
const prismjsCfg = ctx.config.prismjs || {};
const line_threshold = options.line_threshold || prismjsCfg.line_threshold || 0;
const shouldUseLineNumbers = typeof options.line_number === 'undefined' ? prismjsCfg.line_number : options.line_number;
const surpassesLineThreshold = options.lines_length > line_threshold;
const lineNumber = shouldUseLineNumbers && surpassesLineThreshold;

const prismjsOptions = {
caption: options.caption,
firstLine: options.firstLine,
isPreprocess: prismjsCfg.preprocess,
lang: options.lang,
lineNumber,
mark: options.mark,
tab: prismjsCfg.tab_replace
};

if (!prismHighlight) prismHighlight = require('hexo-util').prismHighlight;

return prismHighlight(code, prismjsOptions);
};
};
1 change: 1 addition & 0 deletions lib/plugins/filter/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module.exports = ctx => {
require('./before_exit')(ctx);
require('./before_generate')(ctx);
require('./template_locals')(ctx);
require('./highlight')(ctx);

filter.register('new_post_path', require('./new_post_path'));
filter.register('post_permalink', require('./post_permalink'));
Expand Down
Loading