New Year, New Ideas, Problems Solved

Well its now 2010, Happy New Year!!!, and my mailserver guide is still not on the site. It has not been forgotten and in fact part of the guide is already hidden away under going editing. One of the issues that was delaying progress was finding the best way to display code on the site. There are alot of plugins for Wordpress that will do syntax highlighting using Geshi. What I have wanted to do is highlight specific lines of highlighted syntax to make it easier to show changes. I did a little research and found that Geshi already supports the functionality I required but it hadn’t been implemented in any Wordpress plugins. So I decided to modify one. I selected CodeColorer to modify as it already had a method of processing arguments within the “cc” tag it uses.

The modification is simple but has a few limitations. Firstly each line that needs to be selectively highlighted needs to be referenced by line number. This is easy for a few lines but is going to mean alot of number typing for large chunks of code. That said for really big chunks of code they are probably best split off from the rest of the file anyway. The line highlight only stretches to the limit of the longest line and not the whole of the code window. Only a single colour can be specified for the selective highlight and it only works if the Geshi style is selected rather the the custom CodeColorer themes.

Lets now have a look at the modification and what it does. I only needed to modify the codecolorer-core.php file in the plugin directory with a few lines. I have highlighted the changes on lines 161-167 using the modification. To achieve this I placed a new argument ‘highlight’ into the tag after the language argument [cc lang=”html” inline=”true”][cc lang=”php” highlight=”161,162,163,164,165,166,167”][/cc]. The ‘highlight’ argument takes a comma separated list of line numbers to highlight. Thats basically it, the modification takes the line numbers, puts them into an array and passes it on to the inbuilt functionality of Geshi which handles the rest.

Note

Since moving to the static site CodeColourer is no longer used. The image below shows it in use on a WordPress site.

../../../_images/CodeColorer.png

Note

Here is is the full code using the static site syntax highlighter which ironically has issues highlighting it.

codecolorer-core.php
  1<?php
  2    /*
  3    CodeColorer plugin core part
  4    https://kpumuk.info/projects/wordpress-plugins/codecolorer
  5    */
  6    /*
  7        Copyright 2006 - 2009  Dmytro Shteflyuk <kpumuk@kpumuk.info>
  8
  9        This program is free software; you can redistribute it and/or modify
 10        it under the terms of the GNU General Public License as published by
 11        the Free Software Foundation; either version 2 of the License, or
 12        (at your option) any later version.
 13
 14        This program is distributed in the hope that it will be useful,
 15        but WITHOUT ANY WARRANTY; without even the implied warranty of
 16        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 17        GNU General Public License for more details.
 18
 19        You should have received a copy of the GNU General Public License
 20        along with this program; if not, write to the Free Software
 21        Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 22    */
 23
 24    class CodeColorer {
 25      var $blocks = array();
 26      var $comments = array();
 27
 28      var $geshiExternal = false;
 29      var $geshiVersion = '1.0.8.4';
 30
 31      var $samplePhpCode = '
 32       [cc_php]
 33       /**
 34        * Comment
 35        */
 36       function hello() {
 37         echo "Hello!";
 38         return null;
 39       }
 40       exit();
 41       [/cc_php]
 42     ';
 43
 44      /** Search content for code tags and replace it */
 45      function BeforeHighlightCodeBlock($content) {
 46        $content = preg_replace('#(\s*)\[cc([^\s\]_]*(?:_[^\s\]]*)?)([^\]]*)\](.*?)\[/cc\2\](\s*)#sie', '$this->PerformHighlightCodeBlock(\'\\4\', \'\\3\', $content, \'\\2\', \'\\1\', \'\\5\');', $content);
 47        $content = preg_replace('#(\s*)\<code (.*?)\>(.*?)\</code>(\s*)#sie', '$this->PerformHighlightCodeBlock(\'\\3\', \'\\2\', $content, \'\', \'\\1\', \'\\4\');', $content);
 48
 49        return $content;
 50      }
 51
 52      function AfterHighlightCodeBlock($content) {
 53        $content = str_replace(array_keys($this->blocks), array_values($this->blocks), $content);
 54
 55        return $content;
 56      }
 57
 58      function BeforeProtectComment($content) {
 59        $content = preg_replace('#(\s*)(\[cc[^\s\]_]*(?:_[^\s\]]*)?[^\]]*\].*?\[/cc\1\])(\s*)#sie', '$this->PerformProtectComment(\'\\2\', $content, \'\\1\', \'\\3\');', $content);
 60        $content = preg_replace('#(\s*)(\<code .*?\>.*?\</code>)(\s*)#sie', '$this->PerformProtectComment(\'\\2\', $content, \'\\1\', \'\\3\');', $content);
 61
 62        return $content;
 63      }
 64
 65      function AfterProtectComment($content) {
 66        $content = str_replace(array_keys($this->comments), array_values($this->comments), $content);
 67        $this->comments = array();
 68
 69        return $content;
 70      }
 71
 72      /**
 73       *Perform code highlightning
 74       */
 75      function PerformHighlightCodeBlock($text, $opts, $content, $suffix = '', $before = '', $after = '') {
 76        // Preprocess source text
 77        $text = str_replace(array("\\"", "\\'"), array (""", "\'"), $text);
 78        $text = preg_replace('/(< \?php)/i', '<?php', $text);
 79        $text = preg_replace('/(?:^(?:\s*[\r\n])+|\s+$)/', '', $text);
 80
 81       // Parse options
 82       $options = CodeColorerOptions::ParseOptions($opts, $suffix);
 83
 84       if ($options['escaped']) {
 85         $text = html_entity_decode($text, ENT_QUOTES);
 86         $text = preg_replace('~&#x0*([0-9a-f]+);~ei', 'chr(hexdec("\\1"))', $text);
 87         $text = preg_replace('~&#0*([0-9]+);~e', 'chr(\\1)', $text);
 88        }
 89
 90        $result = '';
 91        // Check if CodeColorer has been disabled for this particular block
 92        if (!$options['enabled']) {
 93          $result = '<code>' . $text . '';
 94        } else {
 95          // See if we should force a height
 96          $num_lines = count(explode("\n", $text));
 97
 98          $result = $this->PerformHighlightGeshi($text, $options);
 99
100          $result = $this->AddContainer($result, $options, $num_lines);
101        }
102
103        if ($options['inline']) {
104          $blockID = $this->GetBlockID($content, false, '<span>', '</span>');
105        } else {
106          $blockID = $this->GetBlockID($content);
107        }
108        $this->blocks[$blockID] = $result;
109
110        if ($options['inline']) {
111          $result = $before . $blockID . $after;
112        } else {
113          $result = "\n\n$blockID\n\n";
114        }
115
116        return $result;
117      }
118
119      /**
120       * Perform code protecting from mangling by Wordpress (used in Comments)
121       */
122      function PerformProtectComment($text, $content, $before, $after) {
123        $text = str_replace(array("\\"", "\\'"), array (""", "\'"), $text);
124
125       $blockID = $this->GetBlockID($content, true, '', '');
126       $this->comments[$blockID] = $text;
127
128       return $before . $blockID . $after;
129     }
130
131     /**
132      * Perform code highlighting using GESHi engine
133      */
134     function PerformHighlightGeshi($content, $options) {
135       /* Geshi configuration */
136       if (!$this->geshi) {
137         $this->geshi = new GeSHi();
138         $this->geshi->enable_line_numbers(GESHI_NO_LINE_NUMBERS, 1);
139         if (is_feed()) {
140           $this->geshi->set_overall_style('padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap');
141         }
142       }
143
144       $geshi = $this->geshi;
145       $geshi->set_source($content);
146       $geshi->set_language($options['lang']);
147       $geshi->set_overall_class('codecolorer');
148       $geshi->set_tab_width($options['tab_size']);
149       if (!is_feed()) {
150         $geshi->enable_classes($options['theme'] != 'geshi');
151         if ($options['nowrap']) {
152           $geshi->set_overall_style('white-space:nowrap');
153         } else {
154           $geshi->set_overall_style('');
155         }
156       } else {
157         $geshi->enable_classes(false);
158       }
159       if (!is_null($options['strict'])) $geshi->enable_strict_mode($options['strict']);
160       if ($options['no_links']) $geshi->enable_keyword_links(false);
161       /* My Custom Bit */
162        if ($options['highlight']){
163           $myarray = explode(",",$options['highlight']);
164           $geshi->highlight_lines_extra($myarray);
165           $geshi->set_highlight_lines_extra_style('background-color: #ffff66;'); /* Select the colour of the highlight */
166       }
167        /* End My Custom Bit */
168        if ($options['inline']) {
169          $geshi->set_header_type(GESHI_HEADER_NONE);
170        } else {
171          $geshi->set_header_type(GESHI_HEADER_DIV);
172        }
173
174        $result = $geshi->parse_code();
175
176        if ($geshi->error()) {
177          return $geshi->error();
178        }
179
180        if ($options['line_numbers'] && !$options['inline']) {
181          $table = '<table cellspacing="0" cellpadding="0"><tbody><tr><td ';
182          if (is_feed()) {
183            $table .= 'style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"';
184          } else {
185            $table .= 'class="line-numbers"';
186          }
187          $table .= '><div>';
188          for ($i = 0, $count = substr_count($result, '<br />') + 1; $i < $count; $i++) {
189            $table .= ($i + $options['first_line']) . '<br />';
190          }
191          $result = $table . '</div></td><td>' . $result . '</td></tr></tbody></table>';
192        }
193
194        return $result;
195      }
196
197      function AddContainer($html, $options, $num_lines) {
198        $custom_css_class = empty($options['class']) ? '' : ' ' . $options['class'];
199        if ($options['inline']) {
200          $theme = empty($options['inline_theme']) ? 'default' : $options['inline_theme'];
201          $result  = '<code class="codecolorer ' . $options['lang'] . ' ' . $theme . $custom_css_class . '">';
202          $result .= '<span class="' . $options['lang'] . '">' . $html . '</span>';
203          $result .= '</code>';
204        } else {
205          $theme = empty($options['theme']) ? 'default' : $options['theme'];
206          $style = 'style="';
207          if ($options['nowrap']) $style .= 'overflow:auto;white-space:nowrap;';
208          if (is_feed()) $style .= 'border: 1px solid #9F9F9F;';
209          $style .= $this->GetDimensionRule('width', is_feed() ? $options['rss_width'] : $options['width']);
210          if($num_lines > $options['lines'] && $options['lines'] > 0) {
211            $style .= $this->GetDimensionRule('height', $options['height']);
212          }
213          $style .= '"';
214
215          $css_class = 'codecolorer-container ' . $options['lang'] . ' ' . $theme . $custom_css_class;
216          if ($options['noborder']) $css_class .= ' codecolorer-noborder';
217          $result = '<div class="' . $css_class . '" ' . $style . '>' . $html . '</div>';
218        }
219        return $result;
220      }
221
222      /**
223       * Generate a block ID that will be replaced at the end (after all that
224       * crazy WP text work!) with the right code
225       */
226      function GetBlockID($content, $comment = false, $before = '<div>', $after = '</div>') {
227        static $num = 0;
228
229        $block = $comment ? 'COMMENT' : 'BLOCK';
230        $before = $before . '::CODECOLORER_' . $block . '_';
231        $after = '::' . $after;
232
233        // Just do a check to make sure the user
234        // hasn't (however unlikely) input block replacements
235        // as legit text
236        do {
237          ++$num;
238          $blockID = $before . $num . $after;
239        } while (strpos($content, $blockID) !== false);
240
241        return $blockID;
242      }
243
244      function GetDimensionRule($dimension, $value) {
245        $rule = '';
246        if (!empty($value)) $rule = "$dimension:$value" . (is_numeric($value) ? 'px;' : ';');
247        return $rule;
248      }
249
250      function ShowWarning($type, $title, $message) {
251        $disable = ' <a href="options-general.php?page=codecolorer.php&amp;disable=' . $type . '">' . __('Close', 'codecolorer') . '</a>';
252        echo '<div id="codecolorer-' . $type . '" class="updated fade"><p><strong>' . $title . "</strong> " . $message . $disable . "</p></div>\n";
253      }
254
255      function ShowGeshiWarning() {
256        if ($this->geshiExternal) {
257          $this->ShowWarning('concurrent', __('CodeColorer has detected a problem.', 'codecolorer'), sprintf(__('We found another plugin based on GeSHi library in your system. CodeColorer will work, but our version of GeSHi contain some patches, so we can\'t guarantee an ideal code highlighting now. Please review your <a href="%1$s">plugins</a>, maybe you don\'t need them all.', 'codecolorer'), "plugins.php"));
258        }
259      }
260
261      function ShowOptionsPage() {
262        $page = $this->GetOptionsPage();
263        $page->Show();
264      }
265
266      function GetOptionsPage() {
267        if (!$this->optionsPage) {
268          if (!class_exists('CodeColorerAdmin')) {
269            $path = dirname(__FILE__);
270            if (!file_exists("$path/codecolorer-admin.php")) return false;
271            require_once("$path/codecolorer-admin.php");
272          }
273          $this->optionsPage = new CodeColorerAdmin($this);
274        }
275        return $this->optionsPage;
276      }
277
278      function GetCodeHighlighted($code) {
279        $content = $this->BeforeHighlightCodeBlock($code);
280        return $this->AfterHighlightCodeBlock($content);
281      }
282
283      function GetSampleCodeHighlighted() {
284        return $this->GetCodeHighlighted($this->samplePhpCode);
285      }
286
287      function &GetInstance() {
288        static $instance = null;
289
290        if (null === $instance) {
291          $path = dirname(__FILE__);
292          if (!class_exists('CodeColorerOptions')) {
293            if (!file_exists("$path/codecolorer-options.php")) return null;
294            require_once("$path/codecolorer-options.php");
295          }
296
297          $instance = new CodeColorer();
298
299          # Maybe GeSHi has been loaded by some another plugin?
300         if (!class_exists('GeSHi')) {
301            if (!file_exists("$path/lib/geshi.php")) return null;
302            require_once("$path/lib/geshi.php");
303          } else {
304            $instance->geshiExternal = true;
305          }
306        }
307
308        return $instance;
309      }
310    }
311?>
Amine27
amineroukh@gmail.com
41.201.109.149
2010-04-01
20:59:08
Hi, Thanks for the addition, can add an option to chose a range of lines (ig: from 161 to 167) like this for example: [cc lang='php' highlight='161...167'] Best regards, Amine
Quantum
quantum@deltanova.co.uk
78.86.91.0
2010-04-05
01:06:13
The underlying Geshi engine which highlights the code takes an array of numbers eg 1,3,5,7 to highlight. Whilst it should be possible to parse the highlight instruction to determine the lines to be highlighted, it still needs to be converted back to an array of individual lines for Geshi. I'll have a think on the modification.
DELTA NOVA » Plugin Enhancement
80.82.120.200
2010-04-05
19:10:36
[...] syntax highlighting plugin for Wordpress has been integrated into the official 0.9.8 release. I received a request to enhance the modification furthur. Instead of having a long comma separated list of individual [...]
codecolorerc???? « ????
121.207.250.120
2012-02-08
09:29:10
[...] Amine Roukh). * GeSHi updated to 1.0.8.6. * Added ability to highlight specified lines (thanks to DELTA NOVA). * Added Czech translation (thanks to Lelkoun). * Added Georgian translation (thanks to Nika [...]
CodeColorer?? – mikko's blog
121.43.37.23
2016-11-20
10:03:41
... Added ability to highlight specified lines (thanks to DELTA NOVA). ...