[WordPress][PrismJS]Luxeritasのシンタックスハイライターの対応言語を増やす方法

経緯

WordPressのテーマの1つであるLuxeritasには、カスタムブロック(Luxeritas Blocks)として、シンタックスハイライトをしてくれるコードブロック(シンタックスハイライター)が備わっています。本ホームページにVBAの記事が増えてきたため、このソースコードにシンタックスハイライトを適用しようかと思いましたが、どうやらVBAは未対応のようです(2020年05月04日時点、Luxeritas Ver.3.8.1.2)。

しかし、シンタックスハイライターの実体はPrism.jsでしたので、Visual Basicを対応させてみました。なお、この方法は基本的にLuxeritasが対応していない全ての言語に対して適用可能です。応用的には、追加する言語にPrism.js内の依存関係がある場合は、もう一工夫必要です。

概要

Luxeritasの親テーマで定義されている2つの関数を、子テーマのfunctions.pspで定義することで、親テーマの関数が定義されるのを防止(要は上書き)し、子テーマ内のディレクトリに保存したPrismJSのコードをロードします。そのため、Luxeritasの親テーマがアップデートした際、上書きした関数の定義が変更されたとしても、子テーマのfunctions.phpから関数を削除しない限りは、アップデートの影響を受けませんのでご注意ください。

想定する読者

これから紹介する方法は、WordPressに新しい機能を追加します。そのため、下記の全てに当てはまる方のみ、この方法の採用を検討して下さい。

  • FTPなどを使用して、サーバー上のWordPress内にファイルを保存したり、ディレクトリを作成することができる方
  • JavaScriptやPHPがプログラミング言語であることを理解しており、ソースコードの1文字の違いが与える影響を理解している方
  • この方法を採用した結果、将来的に不具合が起こっても復旧できる方

言語の追加方法

言語毎のprism.jsのダウンロード

まずは追加したい言語をPrism.jsのホームページからダウンロードします。この際、「Select/unselect all」をクリックして、全ての言語のチェックを外しておいて下さい。



次に、追加したい言語を選択してください。今回はVBAを追加するので「Visual Basic」を選択します。



チェックが出来たら、JavaScriptをダウンロードします(CSSは不要です)。



ダウンロードしたJavaScriptのファイル名は「prism.js」になっていると思いますので、これを言語の略名称に変更します。今回は「vb.js」にします。

「vb.js」をテキストエディタで開き、1~3行目を削除します。他の言語でも同様に1~3行目を削除します。削除結果は下記の通りです。

削除前

/* PrismJS 1.20.0
https://prismjs.com/download.html#themes=prism-okaidia&languages=visual-basic */
var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(u){var c=/\blang(?:uage)?-([\w-]+)\b/i,n=0,C={manual:u.Prism&&u.Prism.manual,disableWorkerMessageHandler:u.Prism&&u.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof _?new _(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/</g,"<").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).slice(8,-1)},objId:function(e){return e.__id||Object.defineProperty(e,"__id",{value:++n}),e.__id},clone:function t(e,r){var a,n,l=C.util.type(e);switch(r=r||{},l){case"Object":if(n=C.util.objId(e),r[n])return r[n];for(var i in a={},r[n]=a,e)e.hasOwnProperty(i)&&(a[i]=t(e[i],r));return a;case"Array":return n=C.util.objId(e),r[n]?r[n]:(a=[],r[n]=a,e.forEach(function(e,n){a[n]=t(e,r)}),a);default:return e}},getLanguage:function(e){for(;e&&!c.test(e.className);)e=e.parentElement;return e?(e.className.match(c)||[,"none"])[1].toLowerCase():"none"},currentScript:function(){if("undefined"==typeof document)return null;if("currentScript"in document)return document.currentScript;try{throw new Error}catch(e){var n=(/at [^(\r\n]*\((.*):.+:.+\)$/i.exec(e.stack)||[])[1];if(n){var t=document.getElementsByTagName("script");for(var r in t)if(t[r].src==n)return t[r]}return null}}},languages:{extend:function(e,n){var t=C.util.clone(C.languages[e]);for(var r in n)t[r]=n[r];return t},insertBefore:function(t,e,n,r){var a=(r=r||C.languages)[t],l={};for(var i in a)if(a.hasOwnProperty(i)){if(i==e)for(var o in n)n.hasOwnProperty(o)&&(l[o]=n[o]);n.hasOwnProperty(i)||(l[i]=a[i])}var s=r[t];return r[t]=l,C.languages.DFS(C.languages,function(e,n){n===s&&e!=t&&(this[e]=l)}),l},DFS:function e(n,t,r,a){a=a||{};var l=C.util.objId;for(var i in n)if(n.hasOwnProperty(i)){t.call(n,i,n[i],r||i);var o=n[i],s=C.util.type(o);"Object"!==s||a[l(o)]?"Array"!==s||a[l(o)]||(a[l(o)]=!0,e(o,t,i,a)):(a[l(o)]=!0,e(o,t,null,a))}}},plugins:{},highlightAll:function(e,n){C.highlightAllUnder(document,e,n)},highlightAllUnder:function(e,n,t){var r={callback:t,container:e,selector:'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'};C.hooks.run("before-highlightall",r),r.elements=Array.prototype.slice.apply(r.container.querySelectorAll(r.selector)),C.hooks.run("before-all-elements-highlight",r);for(var a,l=0;a=r.elements[l++];)C.highlightElement(a,!0===n,r.callback)},highlightElement:function(e,n,t){var r=C.util.getLanguage(e),a=C.languages[r];e.className=e.className.replace(c,"").replace(/\s+/g," ")+" language-"+r;var l=e.parentNode;l&&"pre"===l.nodeName.toLowerCase()&&(l.className=l.className.replace(c,"").replace(/\s+/g," ")+" language-"+r);var i={element:e,language:r,grammar:a,code:e.textContent};function o(e){i.highlightedCode=e,C.hooks.run("before-insert",i),i.element.innerHTML=i.highlightedCode,C.hooks.run("after-highlight",i),C.hooks.run("complete",i),t&&t.call(i.element)}if(C.hooks.run("before-sanity-check",i),!i.code)return C.hooks.run("complete",i),void(t&&t.call(i.element));if(C.hooks.run("before-highlight",i),i.grammar)if(n&&u.Worker){var s=new Worker(C.filename);s.onmessage=function(e){o(e.data)},s.postMessage(JSON.stringify({language:i.language,code:i.code,immediateClose:!0}))}else o(C.highlight(i.code,i.grammar,i.language));else o(C.util.encode(i.code))},highlight:function(e,n,t){var r={code:e,grammar:n,language:t};return C.hooks.run("before-tokenize",r),r.tokens=C.tokenize(r.code,r.grammar),C.hooks.run("after-tokenize",r),_.stringify(C.util.encode(r.tokens),r.language)},tokenize:function(e,n){var t=n.rest;if(t){for(var r in t)n[r]=t[r];delete n.rest}var a=new l;return M(a,a.head,e),function e(n,t,r,a,l,i,o){for(var s in r)if(r.hasOwnProperty(s)&&r[s]){var u=r[s];u=Array.isArray(u)?u:[u];for(var c=0;c<u.length;++c){if(o&&o==s+","+c)return;var g=u[c],f=g.inside,h=!!g.lookbehind,d=!!g.greedy,v=0,p=g.alias;if(d&&!g.pattern.global){var m=g.pattern.toString().match(/[imsuy]*$/)[0];g.pattern=RegExp(g.pattern.source,m+"g")}g=g.pattern||g;for(var y=a.next,k=l;y!==t.tail;k+=y.value.length,y=y.next){var b=y.value;if(t.length>n.length)return;if(!(b instanceof _)){var x=1;if(d&&y!=t.tail.prev){g.lastIndex=k;var w=g.exec(n);if(!w)break;var A=w.index+(h&&w[1]?w[1].length:0),P=w.index+w[0].length,S=k;for(S+=y.value.length;S<=A;)y=y.next,S+=y.value.length;if(S-=y.value.length,k=S,y.value instanceof _)continue;for(var O=y;O!==t.tail&&(S<P||"string"==typeof O.value&&!O.prev.value.greedy);O=O.next)x++,S+=O.value.length;x--,b=n.slice(k,S),w.index-=k}else{g.lastIndex=0;var w=g.exec(b)}if(w){h&&(v=w[1]?w[1].length:0);var A=w.index+v,w=w[0].slice(v),P=A+w.length,E=b.slice(0,A),N=b.slice(P),j=y.prev;E&&(j=M(t,j,E),k+=E.length),W(t,j,x);var L=new _(s,f?C.tokenize(w,f):w,p,w,d);if(y=M(t,j,L),N&&M(t,y,N),1<x&&e(n,t,r,y.prev,k,!0,s+","+c),i)break}else if(i)break}}}}}(e,a,n,a.head,0),function(e){var n=[],t=e.head.next;for(;t!==e.tail;)n.push(t.value),t=t.next;return n}(a)},hooks:{all:{},add:function(e,n){var t=C.hooks.all;t[e]=t[e]||[],t[e].push(n)},run:function(e,n){var t=C.hooks.all[e];if(t&&t.length)for(var r,a=0;r=t[a++];)r(n)}},Token:_};function _(e,n,t,r,a){this.type=e,this.content=n,this.alias=t,this.length=0|(r||"").length,this.greedy=!!a}function l(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function M(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function W(e,n,t){for(var r=n.next,a=0;a<t&&r!==e.tail;a++)r=r.next;(n.next=r).prev=n,e.length-=a}if(u.Prism=C,_.stringify=function n(e,t){if("string"==typeof e)return e;if(Array.isArray(e)){var r="";return e.forEach(function(e){r+=n(e,t)}),r}var a={type:e.type,content:n(e.content,t),tag:"span",classes:["token",e.type],attributes:{},language:t},l=e.alias;l&&(Array.isArray(l)?Array.prototype.push.apply(a.classes,l):a.classes.push(l)),C.hooks.run("wrap",a);var i="";for(var o in a.attributes)i+=" "+o+'="'+(a.attributes[o]||"").replace(/"/g,""")+'"';return"<"+a.tag+' class="'+a.classes.join(" ")+'"'+i+">"+a.content+"</"+a.tag+">"},!u.document)return u.addEventListener&&(C.disableWorkerMessageHandler||u.addEventListener("message",function(e){var n=JSON.parse(e.data),t=n.language,r=n.code,a=n.immediateClose;u.postMessage(C.highlight(r,C.languages[t],t)),a&&u.close()},!1)),C;var e=C.util.currentScript();function t(){C.manual||C.highlightAll()}if(e&&(C.filename=e.src,e.hasAttribute("data-manual")&&(C.manual=!0)),!C.manual){var r=document.readyState;"loading"===r||"interactive"===r&&e&&e.defer?document.addEventListener("DOMContentLoaded",t):window.requestAnimationFrame?window.requestAnimationFrame(t):window.setTimeout(t,16)}return C}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism);
Prism.languages["visual-basic"]={comment:{pattern:/(?:['‘’]|REM\b)(?:[^\r\n_]|_(?:\r\n?|\n)?)*/i,inside:{keyword:/^REM/i}},directive:{pattern:/#(?:Const|Else|ElseIf|End|ExternalChecksum|ExternalSource|If|Region)(?:[^\S\r\n]_[^\S\r\n]*(?:\r\n?|\n)|.)+/i,alias:"comment",greedy:!0},string:{pattern:/\$?["“”](?:["“”]{2}|[^"“”])*["“”]C?/i,greedy:!0},date:{pattern:/#[^\S\r\n]*(?:\d+([/-])\d+\1\d+(?:[^\S\r\n]+(?:\d+[^\S\r\n]*(?:AM|PM)|\d+:\d+(?::\d+)?(?:[^\S\r\n]*(?:AM|PM))?))?|(?:\d+[^\S\r\n]*(?:AM|PM)|\d+:\d+(?::\d+)?(?:[^\S\r\n]*(?:AM|PM))?))[^\S\r\n]*#/i,alias:"builtin"},number:/(?:(?:\b\d+(?:\.\d+)?|\.\d+)(?:E[+-]?\d+)?|&[HO][\dA-F]+)(?:U?[ILS]|[FRD])?/i,boolean:/\b(?:True|False|Nothing)\b/i,keyword:/\b(?:AddHandler|AddressOf|Alias|And(?:Also)?|As|Boolean|ByRef|Byte|ByVal|Call|Case|Catch|C(?:Bool|Byte|Char|Date|Dbl|Dec|Int|Lng|Obj|SByte|Short|Sng|Str|Type|UInt|ULng|UShort)|Char|Class|Const|Continue|Date|Decimal|Declare|Default|Delegate|Dim|DirectCast|Do|Double|Each|Else(?:If)?|End(?:If)?|Enum|Erase|Error|Event|Exit|Finally|For|Friend|Function|Get(?:Type|XMLNamespace)?|Global|GoSub|GoTo|Handles|If|Implements|Imports|In|Inherits|Integer|Interface|Is|IsNot|Let|Lib|Like|Long|Loop|Me|Mod|Module|Must(?:Inherit|Override)|My(?:Base|Class)|Namespace|Narrowing|New|Next|Not(?:Inheritable|Overridable)?|Object|Of|On|Operator|Option(?:al)?|Or(?:Else)?|Out|Overloads|Overridable|Overrides|ParamArray|Partial|Private|Property|Protected|Public|RaiseEvent|ReadOnly|ReDim|RemoveHandler|Resume|Return|SByte|Select|Set|Shadows|Shared|short|Single|Static|Step|Stop|String|Structure|Sub|SyncLock|Then|Throw|To|Try|TryCast|TypeOf|U(?:Integer|Long|Short)|Using|Variant|Wend|When|While|Widening|With(?:Events)?|WriteOnly|Xor)\b/i,operator:[/[+\-*/\\^<=>&#@$%!]/,{pattern:/([^\S\r\n])_(?=[^\S\r\n]*[\r\n])/,lookbehind:!0}],punctuation:/[{}().,:?]/},Prism.languages.vb=Prism.languages["visual-basic"];

削除後

Prism.languages["visual-basic"]={comment:{pattern:/(?:['‘’]|REM\b)(?:[^\r\n_]|_(?:\r\n?|\n)?)*/i,inside:{keyword:/^REM/i}},directive:{pattern:/#(?:Const|Else|ElseIf|End|ExternalChecksum|ExternalSource|If|Region)(?:[^\S\r\n]_[^\S\r\n]*(?:\r\n?|\n)|.)+/i,alias:"comment",greedy:!0},string:{pattern:/\$?["“”](?:["“”]{2}|[^"“”])*["“”]C?/i,greedy:!0},date:{pattern:/#[^\S\r\n]*(?:\d+([/-])\d+\1\d+(?:[^\S\r\n]+(?:\d+[^\S\r\n]*(?:AM|PM)|\d+:\d+(?::\d+)?(?:[^\S\r\n]*(?:AM|PM))?))?|(?:\d+[^\S\r\n]*(?:AM|PM)|\d+:\d+(?::\d+)?(?:[^\S\r\n]*(?:AM|PM))?))[^\S\r\n]*#/i,alias:"builtin"},number:/(?:(?:\b\d+(?:\.\d+)?|\.\d+)(?:E[+-]?\d+)?|&[HO][\dA-F]+)(?:U?[ILS]|[FRD])?/i,boolean:/\b(?:True|False|Nothing)\b/i,keyword:/\b(?:AddHandler|AddressOf|Alias|And(?:Also)?|As|Boolean|ByRef|Byte|ByVal|Call|Case|Catch|C(?:Bool|Byte|Char|Date|Dbl|Dec|Int|Lng|Obj|SByte|Short|Sng|Str|Type|UInt|ULng|UShort)|Char|Class|Const|Continue|Date|Decimal|Declare|Default|Delegate|Dim|DirectCast|Do|Double|Each|Else(?:If)?|End(?:If)?|Enum|Erase|Error|Event|Exit|Finally|For|Friend|Function|Get(?:Type|XMLNamespace)?|Global|GoSub|GoTo|Handles|If|Implements|Imports|In|Inherits|Integer|Interface|Is|IsNot|Let|Lib|Like|Long|Loop|Me|Mod|Module|Must(?:Inherit|Override)|My(?:Base|Class)|Namespace|Narrowing|New|Next|Not(?:Inheritable|Overridable)?|Object|Of|On|Operator|Option(?:al)?|Or(?:Else)?|Out|Overloads|Overridable|Overrides|ParamArray|Partial|Private|Property|Protected|Public|RaiseEvent|ReadOnly|ReDim|RemoveHandler|Resume|Return|SByte|Select|Set|Shadows|Shared|short|Single|Static|Step|Stop|String|Structure|Sub|SyncLock|Then|Throw|To|Try|TryCast|TypeOf|U(?:Integer|Long|Short)|Using|Variant|Wend|When|While|Widening|With(?:Events)?|WriteOnly|Xor)\b/i,operator:[/[+\-*/\\^<=>&#@$%!]/,{pattern:/([^\S\r\n])_(?=[^\S\r\n]*[\r\n])/,lookbehind:!0}],punctuation:/[{}().,:?]/},Prism.languages.vb=Prism.languages["visual-basic"];

prism.js(vb.js)を子テーマ内に格納

この「vb.js」を、Luxeritasの子テーマに保存します。保存方法はFTPなど、任意の方法で構いません。保存場所も特に決まりはありませんが、今回は親テーマと同じ構造のディレクトリを作成し、そこに保存します。Luxeritasの子テーマのディレクトリ名は既定で「luxech」ですので、ドキュメントルートを「public_html」とすると、保存場所は下記の場所となります。

/public_html/wp-content/themes/luxech/js/prism/vb.js

なお、「js」ディレクトリと「prism」ディレクトリはFTPなどを使用して手動で作成して下さい。また、ドキュメントルートは「htdocs」や「www」という名前の場合もありますので注意してください。

確認のため、WordPressのダッシュボードから、「外観>テーマエディタ」を開いてください。「編集するテーマを選択」でLuxeritasの子テーマが選択されている状態で、テーマファイルに「js/prism/vb.js」が表示されていれば、正しく保存されています。

prism.js(vb.js)のロード

次に、保存した「vb.js」を読み込むコードを入力します。現在開いている「テーマの編集」画面の「テーマファイル」から「functions.php」を開きます。ここに、下記のコードを貼り付けて下さい。

//Luxeritasのシンタックスハイライトの対応言語を子テーマから増やす関数(1/2)
function thk_highlighter_load( $loads, $list, $active ) {
	global $luxe, $post;
	foreach( $list as $key => $val ) {
		if( strpos( $post->post_content, '<code class="language-' . str_replace( 'highlight_', '', $key ) . '"' ) !== false ) {
			$active = true;
			break;
		}
	}
	if( $active === true ) {
		$jsdir  = TPATH . DSEP . 'js' . DSEP . 'prism' . DSEP;
		$cssdir = TPATH . DSEP . 'css' . DSEP . 'prism' . DSEP;
		
		$jsdir_ch = SPATH . DSEP . 'js' . DSEP . 'prism' . DSEP; //【言語追加のため追記:子テーマに保存した言語毎のPrismJSを読み込むためのパス】
		
		if( !isset( $loads[1]['prism'] ) ) {
			$loads[0] .= thk_fgc( $jsdir . 'prism.js' );
			$loads[1]['prism'] = true;
		}
		// CSS
		if( !isset( $luxe['highlighter_css_loaded'] ) ) {
			if( isset( $luxe['highlighter_css'] ) && $luxe['highlighter_css'] !== 'none' ) {
				$highlighter_css = trim( thk_fgc( $cssdir . 'prism-' . $luxe['highlighter_css'] . '.min.css' ) );
				$highlighter_css .= 'pre[class*="language-"]{margin:20px 0 30px 0}';
				if( TPATH !== SPATH ) {
					wp_add_inline_style( 'luxech', $highlighter_css );
					$luxe['highlighter_css_loaded'] = true;
				}
				else {
					wp_add_inline_style( 'luxe', $highlighter_css );
					$luxe['highlighter_css_loaded'] = true;
				}
			}
		}
		// Javascript
		foreach( $list as $key => $val ) {
			if( strpos( $post->post_content, '<code class="language-' . str_replace( 'highlight_', '', $key ) . '"' ) !== false ) {
				$lang = str_replace( 'highlight_', '', $key );
				if( !isset( $loads[1][$lang] ) ) {
					// markup の場合
					if( $lang === 'markup' ) {
						// 言語ごとの読み込み
						$loads[0] .= thk_fgc( $jsdir . $lang . '.js' );
						$loads[1][$lang] = true;
					}
					/*
					 * 他言語の依存チェック
					 */
					// markup
					if(
						!isset( $loads[1]['markup'] ) &&
						( $lang === 'php' || $lang === 'aspnet' )
					) {
						$loads[0] .= thk_fgc( $jsdir . 'markup.js' );
						$loads[1]['markup'] = true;
					}
					// css
					if(
						!isset( $loads[1]['css'] ) &&
						( $lang === 'markup' || $lang === 'php' || $lang === 'aspnet' || $lang === 'sass' )
					) {
						$loads[0] .= thk_fgc( $jsdir . 'css.js' );
						$loads[1]['css'] = true;
					}
					// clike
					if(
						!isset( $loads[1]['clike'] ) &&
						( $lang === 'markup' || $lang === 'javascript' || $lang === 'java' || $lang === 'php' || $lang === 'aspnet' || $lang === 'c' || $lang === 'cpp' || $lang === 'csharp' || $lang === 'ruby' || $lang === 'nginx' )
					) {
						$loads[0] .= thk_fgc( $jsdir . 'clike.js' );
						$loads[1]['clike'] = true;
					}
					// javascript
					if(
						!isset( $loads[1]['javascript'] ) &&
						( $lang === 'markup' || $lang === 'php' || $lang === 'aspnet' )
					) {
						$loads[0] .= thk_fgc( $jsdir . 'javascript.js' );
						$loads[1]['javascript'] = true;
					}
					// c
					if(
						!isset( $loads[1]['c'] ) &&
						$lang === 'cpp'
					) {
						$loads[0] .= thk_fgc( $jsdir . 'c.js' );
						$loads[1]['c'] = true;
					}
					// basic
					if(
						!isset( $loads[1]['basic'] ) &&
						$lang === 'vbnet' 
					) {
						$loads[0] .= thk_fgc( $jsdir . 'basic.js' );
						$loads[1]['basic'] = true;
					}
					// sql
					if(
						!isset( $loads[1]['sql'] ) &&
						$lang === 'plsql' 
					) {
						$loads[0] .= thk_fgc( $jsdir . 'sql.js' );
						$loads[1]['sql'] = true;
					}
					// markup 以外の場合
					if( $lang !== 'markup' ) {
						// 言語ごとの読み込み
						//【言語追加のため追記:子テーマに保存した言語毎のPrismJSを読み込む処理を追加】
						$js_contents = thk_fgc( $jsdir . $lang . '.js' );
						if($js_contents === false) { $js_contents = thk_fgc( $jsdir_ch . $lang . '.js' ); } 
						$loads[0] .= $js_contents;
						$loads[1][$lang] = true;
						
						//【追記前の処理は下記の通り】
						// $loads[0] .= thk_fgc( $jsdir . $lang . '.js' );
						// $loads[1][$lang] = true;
					}
				}
			}
		}
		if( !isset( $loads[1]['options'] ) ) {
			$loads[0] .= thk_fgc( $jsdir . 'prism-options.js' );
			$loads[1]['options'] = true;
		}
	}
	return $loads;
}
//Luxeritasのシンタックスハイライトの対応言語を子テーマから増やす関数(2/2)
function thk_syntax_highlighter_list() {
	return array(
		'highlight_markup'	=> 'HTML / XHTML',
		'highlight_apacheconf'	=> 'Apache Config',
		'highlight_aspnet'	=> 'ASP.NET',
		'highlight_autohotkey'	=> 'autoHotkey',
		'highlight_bash'	=> 'Bash',
		'highlight_basic'	=> 'Basic',
		'highlight_clike'	=> 'Clike',
		'highlight_c'		=> 'C',
		'highlight_cpp'		=> 'C++',
		'highlight_csharp'	=> 'C#',
		'highlight_css'		=> 'CSS',
		'highlight_diff'	=> 'Diff',
		'highlight_git'		=> 'Git',
		'highlight_java'	=> 'Java',
		'highlight_javascript'	=> 'Javascript',
		'highlight_json'	=> 'JSON',
		'highlight_nginx'	=> 'nginx',
		'highlight_nim'		=> 'Nim',
		'highlight_perl'	=> 'Perl',
		'highlight_php'		=> 'PHP',
		'highlight_plsql'	=> 'PL/SQL',
		'highlight_powershell'	=> 'PowerShell',
		'highlight_python'	=> 'Python',
		'highlight_r'		=> 'R',
		'highlight_ruby'	=> 'Ruby',
		'highlight_rust'	=> 'Rust',
		'highlight_sass'	=> 'Sass',
		'highlight_sql'		=> 'SQL',
		'highlight_vbnet'	=> 'VB.NET',
		'highlight_vb'		=> 'Visual Basic', //【言語追加のため追記】
		'highlight_vim'		=> 'Vim',
	);
}

この関数は、Luxeritasの親テーマで定義されている関数をコピーして、一部追記したものです。追記個所は、14行目、108~112行目、及び160行目です。子テーマのfunctions.phpで、親テーマに含まれる関数と同名の関数を定義することで、実質的に親テーマの関数を上書きしています(親テーマ内で「function_exists()」により重複定義を防止している場合にのみ上書き可能)。そのため、Luxeritasの親テーマがアップデートした際、thk_highlighter_load()とthk_syntax_highlighter_list()の定義が変更されたとしても、functions.phpからこれらの関数を削除しない限りは、アップデートの影響を受けません。そのため、その辺を理解した上で、この方法の採用を検討して下さい。

補足

コードの補足として、DSEP、TPATH、SPATH及びthk_fgc()について説明ておきます。まず、それぞれの定義は下記の通りです。

// 親テーマのfunctions.phpで定義
//パス:/public_html/wp-content/themes/luxeritas/functions.php
define( 'TPATH', get_template_directory() );
define( 'SPATH', get_stylesheet_directory() );
// 親テーマのindex.phpで定義
//パス:/public_html/wp-content/themes/luxeritas/index.php
define( 'DSEP', DIRECTORY_SEPARATOR );
// 親テーマのthk-mod-class.phpで定義
//パス:/public_html/wp-content/themes/luxeritas/inc/thk-mod-class.php
if( function_exists( 'thk_fgc' ) === false ):
function thk_fgc( $load ) {
	$gcon = strrev( 'stnetnoc' . '_teg' . '_elif' );
	if( is_readable( $load ) === true && is_dir( $load ) === false ) {
		return $gcon( $load );
	}
	return false;
}
endif;

DSEPはディレクトリセパレータ(Linuxサーバなら '/’ )、TPATHは親テーマのディレクトリパス、SPATHは子テーマのディレクトリパスを表しています。thk_fgc()は、引数で指定したファイルパスのファイルが読み取り可能な状態であれば、そのファイルの内容を返し、関数が失敗した場合はfalseを返します(少なくともLuxeritas Ver.3.8.1.2では)。

そのため、子テーマに保存した「vb.js」のパスは「 SPATH . DSEP . 'js’ . DSEP . 'prism’ . DSEP . 'vb.js’;」となります。この「 SPATH . DSEP . 'js’ . DSEP . 'prism’ . DSEP 」までの部分を14行目で「$jsdir_ch」に入れておきます。そして、109行目では、「$js_contents = thk_fgc( $jsdir . $lang . '.js’ );」で親テーマに保存された言語毎のprism.jsを読み込んでいますが、これが失敗すると「$js_contents」にはfalseが入りますので、falseの場合は子テーマのディレクトリから同様に言語毎のprism.jsを読み込みます。この処理が109行目の「if($js_contents === false) { $js_contents = thk_fgc( $jsdir_ch . $lang . '.js’ ); }」です。

別の言語を追加したい場合は、追加したい言語のprism.jsファイルを適当にリネームして、「vb.js」と同じディレクトリに保存した後、「thk_syntax_highlighter_list()」が返す配列に、追加したい言語を追記すればOKです。この際の書式は、「’highlight_(jsファイル名)’ => '(言語名)’,」となります。なお、「(jsファイル名)」は拡張子は不要です。また、「(言語名)」に入力した文字列は、シンタックスハイライターにマウスカーソルを重ねた際に表示される言語名となります。

これで言語の追加は完了です。ちなみに、50~104行目は、PrismJSの依存関係の処理を行っています。PHPやC言語系が読めれば何をしているのか分かると思いますので、説明は割愛しますが、追加したい言語のprism.jsが、別の言語のprism.jsに依存している場合、この辺に追記が必要になります。

動作確認

試しにVBAのシンタックスハイライトを行ってみました。いい感じにハイライトされています。しかし、#がコメント扱いになっているのか、#If…Then…#Else ディレクティブがコメントのような色合いになっています(PrismJSの仕様です)。それを差し引いても見やすいです。

Option Explicit
#If Win64 Then
   Declare PtrSafe Function MyMathFunc Lib "User32" (ByVal N As LongLong) As LongLong
#Else
   Declare Function MyMathFunc Lib "User32" (ByVal N As Long) As Long
#End If
#If VBA7 Then
   Declare PtrSafe Sub MessageBeep Lib "User32" (ByVal N As Long)
#Else
   Declare Sub MessageBeep Lib "User32" (ByVal N AS Long)
#End If
'コメント
Private Sub Hoge()
End Sub
Public Function Foo(ByVal Arg1 As Long, ByRef Arg2 As Variant, ParamArray Arg3() As Variant) As String
On Error GoTo Try
    If (Arg1 = 0) Then
        GoSub Sub1
    ElseIf (IsEmpty(Arg2)) Then
        GoTo Try
    Else
        Call Err.Raise(65535)
    End If
    
    Dim i As Long
    Static Ary() As Long
    ReDim Ary(0 To 10)
    
    For i = LBound(Ary) To UBound(Ary)
        Exit For
    Next
    
    Do While (False)
    Loop
    
    Do Until (False)
        Exit Do
    Loop
    
    Foo = "Result"
    
    Exit Function
Sub1:
    Return
Try:
    
End Function