Advertisement
Plugins

Adding a Syntax Highlighter Shortcode Using Prism.js

by

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:

/*
Plugin Name: Syntax Highlighter
Plugin URI: http://wp.tutsplus.com/tutorials/plugins/adding-a-syntax-highlighter-shortcode-using-prism-js
Description: Highlight your code snippets with an easy to use shortcode based on Lea Verou's Prism.js.
Version: 1.0.0
Author: c.bavota
Author URI: http://bavotasan.com
*/

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:

add_filter( 'the_content', 'sh_pre_process_shortcode', 7 );
/**
 * Functionality to set up highlighter shortcode correctly.
 *
 * This function is attached to the 'the_content' filter hook.
 *
 * @since 1.0.0
 */
function sh_pre_process_shortcode( $content ) {
	global $shortcode_tags;

	$orig_shortcode_tags = $shortcode_tags;
	$shortcode_tags = array();

	// New shortcodes
	add_shortcode( 'code', 'sh_syntax_highlighter' );

	$content = do_shortcode( $content );
	$shortcode_tags = $orig_shortcode_tags;

	return $content;
}

/**
 * Code shortcode function
 *
 * This function is attached to the 'code' shortcode hook.
 *
 * @since 1.0.0
 */
function sh_syntax_highlighter( $atts, $content = null ) {
	extract( shortcode_atts( array(
		'type' => 'markup',
		'title' => '',
		'linenums' => '',
	), $atts ) );

	$title = ( $title ) ? ' rel="' . $title . '"' : '';
	$linenums = ( $linenums ) ? ' data-linenums="' . $linenums . '"' : '';
	$find_array = array( '[', ']' );
	$replace_array = array( '[', ']' );
	return '</pre>
<div class="syntax-highlighter" title="">
<pre><code class="language-' . $type . '">' . preg_replace_callback( '|(.*)|isU', 'sh_pre_entities', trim( str_replace( $find_array, $replace_array, $content ) ) ) . '</code></pre>
</div>
<pre>
';
}

/**
 * Helper function for 'sh_syntax_highlighter'
 *
 * @since 1.0.0
 */
function sh_pre_entities( $matches ) {
	return str_replace( $matches[1], htmlentities( $matches[1]), $matches[0] );
}

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.

add_action( 'wp_enqueue_scripts', 'sh_add_js' );
/**
 * Load all JavaScript to header
 *
 * This function is attached to the 'wp_enqueue_scripts' action hook.
 *
 * @uses	is_admin()
 * @uses	is_singular()
 * @uses	wp_enqueue_script()
 * @uses	plugins_url()
 *
 * @since 1.0.0
 */
function sh_add_js() {
	if ( sh_has_shortcode( 'code' ) ) {
		wp_enqueue_script( 'sh_js', plugins_url( 'js/sh.js', __FILE__ ), '', '', true );
		wp_enqueue_style( 'sh_css', plugins_url( 'css/sh.css', __FILE__ ) );
	}
}

/**
 * Check posts to see if shortcode has been used
 *
 * @since 1.0.0
 */function sh_has_shortcode( $shortcode = '' ) {
	global $wp_query;
	foreach( $wp_query->posts as $post ) {
		if ( ! empty( $shortcode ) && stripos($post->post_content, '[' . $shortcode) !== false ) {
			return true;
		}
	}
	return false;
}

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:

add_action( 'admin_enqueue_scripts', 'sh_add_quicktags' );
/**
 * Adds a syntax highlighter quicktag to the post editor
 *
 * This function is attached to the 'admin_print_footer_scripts' action hook.
 *
 * @since 1.0.0
 */
function sh_add_quicktags( $hook ) {
    if( 'post.php' == $hook ||  'post-new.php' == $hook )
		wp_enqueue_script( 'sh_quicktag_js', plugins_url( 'js/quicktag.js', __FILE__ ), array( 'quicktags' ), '', true );
}

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

QTags.SyntaxButton = function() {
	QTags.TagButton.call( this, 'syntax_highlighter', 'syntax highlighter', '', '[/code]' );
};
QTags.SyntaxButton.prototype = new QTags.TagButton();
QTags.SyntaxButton.prototype.callback = function( e, c, ed ) {
	var type, linenums, title, t = this;

	if ( t.isOpen( ed ) === false ) {
		type = prompt( 'Type (markup, php, css, javascript)', 'markup' ),
		title = prompt( 'Title (optional)' ),
		linenums = prompt( 'Line number (optional)' );

		type = ( type ) ? ' type="' + type + '"' : '';
		title = ( title ) ? ' title="' + title + '"' : '';
		linenums = ( linenums ) ? ' linenums="' + linenums + '"' : '';

		if ( type ) {
			t.tagStart = '[code' + type + title + linenums + ']';
			QTags.TagButton.prototype.callback.call( t, e, c, ed );
		}
	} else {
		QTags.TagButton.prototype.callback.call( t, e, c, ed );
	}
};
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:

code[class*="language-"],
pre[class*="language-"] {
	color: #fff;
	text-shadow: 0 1px 1px #000;
	font-family: Menlo, Monaco, "Courier New", monospace;
	direction: ltr;
	text-align: left;
	word-spacing: normal;
	white-space: pre;
	word-wrap: normal;
	line-height: 1.4;
	background: none;
	border: 0;

	-moz-tab-size: 4;
	-o-tab-size: 4;
	tab-size: 4;

	-webkit-hyphens: none;
	-moz-hyphens: none;
	-ms-hyphens: none;
	hyphens: none;
	}

	pre[class*="language-"] code {
		float: left;
		padding: 0 15px 0 0;
		}

pre[class*="language-"],
:not(pre) > code[class*="language-"] {
	background: #222;
	}

.syntax-highlighter[rel] {
	position: relative;
	}

	.syntax-highlighter[rel] pre[class*="language-"] {
		padding-top: 44px;
		}

	.syntax-highlighter[rel]:before {
		content: attr(rel);
		text-align: center;
		text-shadow: 1px 1px 2px rgba(0,0,0,0.6);
		position: absolute;
		top: -1px;
		background: #3A87AD;
		padding: 5px 10px;
		left: 0;
		right: 0;
		font: bold 16px/20px "myriad-pro-1","myriad-pro-2","Lucida Grande",Sans-Serif;
		color: #fff;
		-moz-border-radius: 7px 7px 0 0;
		-webkit-border-radius: 7px 7px 0 0;
		border-radius: 7px 7px 0 0;
		}

/* Code blocks */
pre[class*="language-"] {
	padding: 15px;
	margin: 1em 0;
	overflow: auto;
	-moz-border-radius: 8px;
	-webkit-border-radius: 8px;
	border-radius: 8px;
	}

/* Inline code */
:not(pre) > code[class*="language-"] {
	padding: 5px 10px;
	line-height: 1;
	-moz-border-radius: 3px;
	-webkit-border-radius: 3px;
	border-radius: 3px;
	}

.token.comment,
.token.line-comment,
.token.prolog,
.token.doctype,
.token.cdata {
	color: #797979;
	}

.token.selector,
.token.operator,
.token.punctuation {
	color: #fff;
	}

.namespace {
	opacity: .7;
	}

.token.tag,
.token.boolean {
	color: #ffd893;
	}

.token.atrule,
.token.attr-value,
.token.hex,
.token.string {
	color: #B0C975;
	}

.token.property,
.token.entity,
.token.url,
.token.attr-name,
.token.keyword {
	color: #c27628;
	}

.token.regex {
	color: #9B71C6;
	}

.token.entity {
	cursor: help;
	}

.token.function,
.token.constant {
	color: #e5a638;
	}

.token.variable {
	color: #fdfba8;
	}

.token.number {
	color: #8799B0;
	}

.token.important,
.token.deliminator {
	color: #E45734;
	}

pre[data-line] {
	position: relative;
	padding: 1em 0 1em 3em;
	}

.line-highlight {
	position: absolute;
	left: 0;
	right: 0;
	padding: inherit 0;
	margin-top: 1em; /* Same as .prism’s padding-top */
	background: rgba(255,255,255,.2);
	pointer-events: none;
	line-height: inherit;
	white-space: pre;
	}

	.line-highlight:before,
	.line-highlight[data-end]:after {
		content: attr(data-start);
		position: absolute;
		top: .3em;
		left: .6em;
		min-width: 1em;
		padding: 0 .5em;
		background-color: rgba(255,255,255,.3);
		color: #fff;
		font: bold 65%/1.5 sans-serif;
		text-align: center;
		-moz-border-radius: 8px;
		-webkit-border-radius: 8px;
		border-radius: 8px;
		text-shadow: none;
		}

	.line-highlight[data-end]:after {
		content: attr(data-end);
		top: auto;
		bottom: .4em;
		}

/* for line numbers */
ol.linenums {
	margin: 0;
	padding: 0 0 0 35px;
	}

	.linenums li {
		padding-left: 10px;
		border-left: 3px #d9d336 solid;
		}

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.

Related Posts
  • Code
    Creative Coding
    Using a Custom Post Type to Create a Home Page BannerUsing a custom post type to create a home page banner
    Sometimes it's useful to have a nice obvious banner on your site's home page—for announcements, snippets that aren't long enough to merit a blog post, or links to new content within the site. But you don't want to be editing your homepage content every time you add a new piece of content, nor do you want to be delving into the code to add content.Read More…
  • Code
    HTML5
    HTML5: Battery Status APIPdl54 preview image@2x
    The number of people browsing the web using mobile devices grows every day. It's therefore important to optimize websites and web applications to accommodate mobile visitors. The W3C (World Wide Web Consortium) is well aware of this trend and has introduced a number of APIs that help with this challenge. In this article, I will introduce you to one of these APIs, the Battery Status API.Read More…
  • Web Design
    CMS
    Uber Aesthetics and ResponsivenessGhost rwd retina
    Welcome to the final part of our Ghost theme design tutorial series! In the previous two parts of this series we worked on the first half of styling our theme and laid the groundwork for responsive behavior. This fifth and final part finalizes our styling and completes the task of making our theme fully responsive.Read More…
  • Code
    Plugins
    Creating a Shortcode for Responsive VideoResponsive video shortcode main image 400
    If you're anything like me, you use YouTube to host any video you add to your WordPress site. It saves worrying about browser or device compatibility, it saves space on your servers, and it can be a lot more reliable.Read More…
  • Code
    Plugins
    A Better Forum List Widget for bbPressBbpress
    When bbPress was still a standalone installation, I had tried it out and wasn't really impressed. Things were clunky and it didn't always work the way it was supposed to. After languishing for a few years, Automattic decided to take bbpress and turn it into a plugin, improving the functionality leaps and bounds and making it a strong contender amongst other forum option for WordPress.Read More…
  • Code
    Theme Development
    Using CSS Preprocessors With WordPress - LESS + CodeKitUsing preprocessors with wordpress what are they
    In the first part of this series I gave a quick overview of the popular CSS preprocessors LESS and SASS. I also shared a few of the frameworks in which they are used. I plan on taking a deeper dive into LESS and talking about the parts of the language I use most often. I also plan on showing you how to get started using it with CodeKit. Let's dig in!Read More…