Advertisement
  1. Code
  2. WordPress
  3. Plugin Development

Adding a Syntax Highlighter Shortcode Using Prism.js

Scroll to top
8 min read

Syntax highlighting has become pretty standard on most tutorial sites (as you can see below) and there are many options available, all depending on what languages you use and how you want your code snippets to be displayed.

For the longest time I have been using Google's Prettify since it was easy to set up. The only real issue I had was that it didn't capture all the appropriate elements within my code and highlight them accordingly. I tried re-working it, but it wasn't the easiest code to navigate.

Thankfully I recently stumbled upon a new lightweight syntax highlighter by Lea Verou called Prism.js that offers the ability to expand upon the base HTML and CSS markup highlighting with some simple plugin hooks.

I took her core JavaScript code and added the option to include line numbering and PHP highlighting. I also modified a few of her base regex patterns to make her original highlight code capture a few more elements for each language.


Putting Together the Plugin

All we need to do to is add a shortcode function in WordPress so that we can easily include syntax highlighting to our code snippets using my modified Prism.js script. The easiest way to incorporate everything is to make it all into a plugin. Our finished plugin folder will look like this:

So let's start our plugin with the required fields:

1
2
/*

3
Plugin Name: Syntax Highlighter

4
Plugin URI: https://code.tutsplus.com/tutorials/adding-a-syntax-highlighter-shortcode-using-prism-js--wp-28718

5
Description: Highlight your code snippets with an easy to use shortcode based on Lea Verou's Prism.js.

6
Version: 1.0.0

7
Author: c.bavota

8
Author URI: http://bavotasan.com

9
*/

The next thing we want to add is our actual shortcode hook and a pre-process fix to make sure it fires at the right time:

1
2
add_filter( 'the_content', 'sh_pre_process_shortcode', 7 );
3
/**

4
 * Functionality to set up highlighter shortcode correctly.

5
 *

6
 * This function is attached to the 'the_content' filter hook.

7
 *

8
 * @since 1.0.0

9
 */
10
function sh_pre_process_shortcode( $content ) {
11
	global $shortcode_tags;
12
13
	$orig_shortcode_tags = $shortcode_tags;
14
	$shortcode_tags = array();
15
16
	// New shortcodes

17
	add_shortcode( 'code', 'sh_syntax_highlighter' );
18
19
	$content = do_shortcode( $content );
20
	$shortcode_tags = $orig_shortcode_tags;
21
22
	return $content;
23
}
24
25
/**

26
 * Code shortcode function

27
 *

28
 * This function is attached to the 'code' shortcode hook.

29
 *

30
 * @since 1.0.0

31
 */
32
function sh_syntax_highlighter( $atts, $content = null ) {
33
	extract( shortcode_atts( array(
34
		'type' => 'markup',
35
		'title' => '',
36
		'linenums' => '',
37
	), $atts ) );
38
39
	$title = ( $title ) ? ' rel="' . $title . '"' : '';
40
	$linenums = ( $linenums ) ? ' data-linenums="' . $linenums . '"' : '';
41
	$find_array = array( '[', ']' );
42
	$replace_array = array( '[', ']' );
43
	return '</pre>

44
<div class="syntax-highlighter" title="">

45
<pre><code class="language-' . $type . '">' . preg_replace_callback( '|(.*)|isU', 'sh_pre_entities', trim( str_replace( $find_array, $replace_array, $content ) ) ) . '</code></pre>

46
</div>

47
<pre>

48
';
49
}
50
51
/**

52
 * Helper function for 'sh_syntax_highlighter'

53
 *

54
 * @since 1.0.0

55
 */
56
function sh_pre_entities( $matches ) {
57
	return str_replace( $matches[1], htmlentities( $matches[1]), $matches[0] );
58
}

The sh_pre_process_shortcode() function is required so that our shortcode syntax is processed before all of the content filters start to clean up the text by default in WordPress. The helper function will filter our code and replace HTML entities with their appropriate entity value.

Enqueuing Scripts and Styles

In order for our plugin to work properly though, we also need to add in a few more functions to load up the CSS and JS files.

1
2
add_action( 'wp_enqueue_scripts', 'sh_add_js' );
3
/**

4
 * Load all JavaScript to header

5
 *

6
 * This function is attached to the 'wp_enqueue_scripts' action hook.

7
 *

8
 * @uses	is_admin()

9
 * @uses	is_singular()

10
 * @uses	wp_enqueue_script()

11
 * @uses	plugins_url()

12
 *

13
 * @since 1.0.0

14
 */
15
function sh_add_js() {
16
	if ( sh_has_shortcode( 'code' ) ) {
17
		wp_enqueue_script( 'sh_js', plugins_url( 'js/sh.js', __FILE__ ), '', '', true );
18
		wp_enqueue_style( 'sh_css', plugins_url( 'css/sh.css', __FILE__ ) );
19
	}
20
}
21
22
/**

23
 * Check posts to see if shortcode has been used

24
 *

25
 * @since 1.0.0

26
 */function sh_has_shortcode( $shortcode = '' ) {
27
	global $wp_query;
28
	foreach( $wp_query->posts as $post ) {
29
		if ( ! empty( $shortcode ) && stripos($post->post_content, '[' . $shortcode) !== false ) {
30
			return true;
31
		}
32
	}
33
	return false;
34
}

We need to enqueue our script and styles, but only if the shortcode has been used within our post content. Hence why we need that little conditional function to check is the shortcode is present.

The Quicktag

Adding a quicktag for our shortcode isn't very hard so we might as well do it:

1
2
add_action( 'admin_enqueue_scripts', 'sh_add_quicktags' );
3
/**

4
 * Adds a syntax highlighter quicktag to the post editor

5
 *

6
 * This function is attached to the 'admin_print_footer_scripts' action hook.

7
 *

8
 * @since 1.0.0

9
 */
10
function sh_add_quicktags( $hook ) {
11
    if( 'post.php' == $hook ||  'post-new.php' == $hook )
12
		wp_enqueue_script( 'sh_quicktag_js', plugins_url( 'js/quicktag.js', __FILE__ ), array( 'quicktags' ), '', true );
13
}

This is all we need in our quicktag.js file:

1
2
QTags.SyntaxButton = function() {
3
	QTags.TagButton.call( this, 'syntax_highlighter', 'syntax highlighter', '', '[/code]' );
4
};
5
QTags.SyntaxButton.prototype = new QTags.TagButton();
6
QTags.SyntaxButton.prototype.callback = function( e, c, ed ) {
7
	var type, linenums, title, t = this;
8
9
	if ( t.isOpen( ed ) === false ) {
10
		type = prompt( 'Type (markup, php, css, javascript)', 'markup' ),
11
		title = prompt( 'Title (optional)' ),
12
		linenums = prompt( 'Line number (optional)' );
13
14
		type = ( type ) ? ' type="' + type + '"' : '';
15
		title = ( title ) ? ' title="' + title + '"' : '';
16
		linenums = ( linenums ) ? ' linenums="' + linenums + '"' : '';
17
18
		if ( type ) {
19
			t.tagStart = '[code' + type + title + linenums + ']';
20
			QTags.TagButton.prototype.callback.call( t, e, c, ed );
21
		}
22
	} else {
23
		QTags.TagButton.prototype.callback.call( t, e, c, ed );
24
	}
25
};
26
edButtons[150] = new QTags.SyntaxButton();

The CSS

For my syntax highlighting, I prefer a dark theme, so I created my highlights using the following CSS:

1
2
code[class*="language-"],
3
pre[class*="language-"] {
4
	color: #fff;
5
	text-shadow: 0 1px 1px #000;
6
	font-family: Menlo, Monaco, "Courier New", monospace;
7
	direction: ltr;
8
	text-align: left;
9
	word-spacing: normal;
10
	white-space: pre;
11
	word-wrap: normal;
12
	line-height: 1.4;
13
	background: none;
14
	border: 0;
15
16
	-moz-tab-size: 4;
17
	-o-tab-size: 4;
18
	tab-size: 4;
19
20
	-webkit-hyphens: none;
21
	-moz-hyphens: none;
22
	-ms-hyphens: none;
23
	hyphens: none;
24
	}
25
26
	pre[class*="language-"] code {
27
		float: left;
28
		padding: 0 15px 0 0;
29
		}
30
31
pre[class*="language-"],
32
:not(pre) > code[class*="language-"] {
33
	background: #222;
34
	}
35
36
.syntax-highlighter[rel] {
37
	position: relative;
38
	}
39
40
	.syntax-highlighter[rel] pre[class*="language-"] {
41
		padding-top: 44px;
42
		}
43
44
	.syntax-highlighter[rel]:before {
45
		content: attr(rel);
46
		text-align: center;
47
		text-shadow: 1px 1px 2px rgba(0,0,0,0.6);
48
		position: absolute;
49
		top: -1px;
50
		background: #3A87AD;
51
		padding: 5px 10px;
52
		left: 0;
53
		right: 0;
54
		font: bold 16px/20px "myriad-pro-1","myriad-pro-2","Lucida Grande",Sans-Serif;
55
		color: #fff;
56
		-moz-border-radius: 7px 7px 0 0;
57
		-webkit-border-radius: 7px 7px 0 0;
58
		border-radius: 7px 7px 0 0;
59
		}
60
61
/* Code blocks */
62
pre[class*="language-"] {
63
	padding: 15px;
64
	margin: 1em 0;
65
	overflow: auto;
66
	-moz-border-radius: 8px;
67
	-webkit-border-radius: 8px;
68
	border-radius: 8px;
69
	}
70
71
/* Inline code */
72
:not(pre) > code[class*="language-"] {
73
	padding: 5px 10px;
74
	line-height: 1;
75
	-moz-border-radius: 3px;
76
	-webkit-border-radius: 3px;
77
	border-radius: 3px;
78
	}
79
80
.token.comment,
81
.token.line-comment,
82
.token.prolog,
83
.token.doctype,
84
.token.cdata {
85
	color: #797979;
86
	}
87
88
.token.selector,
89
.token.operator,
90
.token.punctuation {
91
	color: #fff;
92
	}
93
94
.namespace {
95
	opacity: .7;
96
	}
97
98
.token.tag,
99
.token.boolean {
100
	color: #ffd893;
101
	}
102
103
.token.atrule,
104
.token.attr-value,
105
.token.hex,
106
.token.string {
107
	color: #B0C975;
108
	}
109
110
.token.property,
111
.token.entity,
112
.token.url,
113
.token.attr-name,
114
.token.keyword {
115
	color: #c27628;
116
	}
117
118
.token.regex {
119
	color: #9B71C6;
120
	}
121
122
.token.entity {
123
	cursor: help;
124
	}
125
126
.token.function,
127
.token.constant {
128
	color: #e5a638;
129
	}
130
131
.token.variable {
132
	color: #fdfba8;
133
	}
134
135
.token.number {
136
	color: #8799B0;
137
	}
138
139
.token.important,
140
.token.deliminator {
141
	color: #E45734;
142
	}
143
144
pre[data-line] {
145
	position: relative;
146
	padding: 1em 0 1em 3em;
147
	}
148
149
.line-highlight {
150
	position: absolute;
151
	left: 0;
152
	right: 0;
153
	padding: inherit 0;
154
	margin-top: 1em; /* Same as .prism’s padding-top */
155
	background: rgba(255,255,255,.2);
156
	pointer-events: none;
157
	line-height: inherit;
158
	white-space: pre;
159
	}
160
161
	.line-highlight:before,
162
	.line-highlight[data-end]:after {
163
		content: attr(data-start);
164
		position: absolute;
165
		top: .3em;
166
		left: .6em;
167
		min-width: 1em;
168
		padding: 0 .5em;
169
		background-color: rgba(255,255,255,.3);
170
		color: #fff;
171
		font: bold 65%/1.5 sans-serif;
172
		text-align: center;
173
		-moz-border-radius: 8px;
174
		-webkit-border-radius: 8px;
175
		border-radius: 8px;
176
		text-shadow: none;
177
		}
178
179
	.line-highlight[data-end]:after {
180
		content: attr(data-end);
181
		top: auto;
182
		bottom: .4em;
183
		}
184
185
/* for line numbers */
186
ol.linenums {
187
	margin: 0;
188
	padding: 0 0 0 35px;
189
	}
190
191
	.linenums li {
192
		padding-left: 10px;
193
		border-left: 3px #d9d336 solid;
194
		}

Using the Shortcode

The code shortcode for our syntax highlighter has three attributes: type, title and linenums.

type: there are four language types that works with our highlighter: markup, css, php, and javascript
title: you can include a title which will appear above the syntax highlighter box (optional)
linenums: add line numbers to your code, starting at whatever number you set (optional)

The only required attribute is "type", though it will default to "markup".


Conclusion

Putting together a plugin to give you the ability to use a syntax highlighter shortcode has a few steps, but thankfully you can always just download the plugin and install it without having to really know how it was put together. Though some of the fun in using WordPress is understanding how everything works so you can customize things to really get it working for you. That way, if you aren't a fan of my dark theme you can easily play around with the CSS to modify the styles of your syntax highlighter so that it matches your design.

If you have any comments or feedback on anything you read above, please feel free to discuss it below.

Advertisement
Advertisement