Plugin Enhancement¶
My previous modification to the CodeColorer syntax highlighting plugin for Wordpress has been integrated into the official 0.9.8 release. I received a comment request to enhance the modification furthur.
Instead of having a long comma separated list of individual line numbers to be highlighted it would be beneficial if ranges of numbers could be specified. I spent a few hours today playing about with PHP again and I have the following solution.
The lines are highlighted using [cc lang=”html” inline=”true”][cc lang=”php” highlight=”161-180”][/cc] rather than listing individual line numbers. You can specify a list of comma separated line numbers, number ranges or a combination of both.
Note
See the section of modified code below. Since moving to a static site this is not shown with CodeColorer.
1<?php
2/*
3CodeColorer plugin core part
4https://kpumuk.info/projects/wordpress-plugins/codecolorer
5*/
6/*
7 Copyright 2006 - 2010 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
24class CodeColorer {
25 var $blocks = array();
26 var $comments = array();
27
28 var $geshiExternal = false;
29 var $geshiVersion = '1.0.8.6';
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('~�*([0-9a-f]+);~ei', 'chr(hexdec("\\1"))', $text);
87 $text = preg_replace('~�*([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 /* Modification */
162 if ($options['highlight']){
163 $hlines = explode(',',$options['highlight']);
164 $b = array(); /* Empty array to store processed line numbers*/
165 foreach($hlines as $v) {
166 if (strstr($v,"-")) {
167 $c = explode("-",$v);
168
169 for ($i=$c[0]; $i< =$c[1]; $i++) {
170 array_push($b,$i);
171 }
172 }else{
173 array_push($b,$v);
174 }
175 }
176 sort($b); /* Sort the array in ascending numerical order */
177 $geshi->highlight_lines_extra($b);
178 $geshi->set_highlight_lines_extra_style('background-color: #ffff66;');
179 }
180 /* End Modification */
181
182
183
184 if ($options['inline']) {
185 $geshi->set_header_type(GESHI_HEADER_NONE);
186 } else {
187 $geshi->set_header_type(GESHI_HEADER_DIV);
188 }
189
190 $result = $geshi->parse_code();
191
192 if ($geshi->error()) {
193 return $geshi->error();
194 }
195
196 if ($options['line_numbers'] && !$options['inline']) {
197 $table = '<table cellspacing="0" cellpadding="0"><tbody><tr><td ';
198 if (is_feed()) {
199 $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;"';
200 } else {
201 $table .= 'class="line-numbers"';
202 }
203 $table .= '><div>';
204 for ($i = 0, $count = substr_count($result, '<br />') + 1; $i < $count; $i++) {
205 $table .= ($i + $options['first_line']) . '<br />';
206 }
207 $result = $table . '</div></td><td>' . $result . '</td></tr></tbody></table>';
208 }
209
210 return $result;
211 }
212
213 function AddContainer($html, $options, $num_lines) {
214 $custom_css_class = empty($options['class']) ? '' : ' ' . $options['class'];
215 if ($options['inline']) {
216 $theme = empty($options['inline_theme']) ? 'default' : $options['inline_theme'];
217 $result = '<code class="codecolorer ' . $options['lang'] . ' ' . $theme . $custom_css_class . '">';
218 $result .= '<span class="' . $options['lang'] . '">' . $html . '</span>';
219 $result .= '</code>';
220 } else {
221 $theme = empty($options['theme']) ? 'default' : $options['theme'];
222 $style = 'style="';
223 if ($options['nowrap']) $style .= 'overflow:auto;white-space:nowrap;';
224 if (is_feed()) $style .= 'border:1px solid #9F9F9F;';
225 $style .= $this->GetDimensionRule('width', is_feed() ? $options['rss_width'] : $options['width']);
226 if($num_lines > $options['lines'] && $options['lines'] > 0) {
227 $style .= $this->GetDimensionRule('height', $options['height']);
228 }
229 $style .= '"';
230
231 $css_class = 'codecolorer-container ' . $options['lang'] . ' ' . $theme . $custom_css_class;
232 if ($options['noborder']) $css_class .= ' codecolorer-noborder';
233 $result = '<div class="' . $css_class . '" ' . $style . '>' . $html . '</div>';
234 }
235 return $result;
236 }
237
238 /**
239 * Generate a block ID that will be replaced at the end (after all that
240 * crazy WP text work!) with the right code
241 */
242 function GetBlockID($content, $comment = false, $before = '<div>', $after = '</div>') {
243 static $num = 0;
244
245 $block = $comment ? 'COMMENT' : 'BLOCK';
246 $before = $before . '::CODECOLORER_' . $block . '_';
247 $after = '::' . $after;
248
249 // Just do a check to make sure the user
250 // hasn't (however unlikely) input block replacements
251 // as legit text
252 do {
253 ++$num;
254 $blockID = $before . $num . $after;
255 } while (strpos($content, $blockID) !== false);
256
257 return $blockID;
258 }
259
260 function GetDimensionRule($dimension, $value) {
261 $rule = '';
262 if (!empty($value)) $rule = "$dimension:$value" . (is_numeric($value) ? 'px;' : ';');
263 return $rule;
264 }
265
266 function ShowWarning($type, $title, $message) {
267 $disable = ' <a href="options-general.php?page=codecolorer.php&disable=' . $type . '">' . __('Close', 'codecolorer') . '</a>';
268 echo '<div id="codecolorer-' . $type . '" class="updated fade"><p><strong>' . $title . "</strong> " . $message . $disable . "</p></div>\n";
269 }
270
271 function ShowGeshiWarning() {
272 if ($this->geshiExternal) {
273 $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"));
274 }
275 }
276
277 function ShowOptionsPage() {
278 $page = $this->GetOptionsPage();
279 $page->Show();
280 }
281
282 function GetOptionsPage() {
283 if (!$this->optionsPage) {
284 if (!class_exists('CodeColorerAdmin')) {
285 $path = dirname(__FILE__);
286 if (!file_exists("$path/codecolorer-admin.php")) return false;
287 require_once("$path/codecolorer-admin.php");
288 }
289 $this->optionsPage = new CodeColorerAdmin($this);
290 }
291 return $this->optionsPage;
292 }
293
294 function GetCodeHighlighted($code) {
295 $content = $this->BeforeHighlightCodeBlock($code);
296 return $this->AfterHighlightCodeBlock($content);
297 }
298
299 function GetSampleCodeHighlighted() {
300 return $this->GetCodeHighlighted($this->samplePhpCode);
301 }
302
303 function &GetInstance() {
304 static $instance = null;
305
306 if (null === $instance) {
307 $path = dirname(__FILE__);
308 if (!class_exists('CodeColorerOptions')) {
309 if (!file_exists("$path/codecolorer-options.php")) return null;
310 require_once("$path/codecolorer-options.php");
311 }
312
313 $instance = new CodeColorer();
314
315 # Maybe GeSHi has been loaded by some another plugin?
316 if (!class_exists('GeSHi')) {
317 if (!file_exists("$path/lib/geshi.php")) return null;
318 require_once("$path/lib/geshi.php");
319 } else {
320 $instance->geshiExternal = true;
321 }
322 }
323
324 return $instance;
325 }
326}