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.
Comments