diff --git a/_test/tests/inc/parser/parser_code.test.php b/_test/tests/inc/parser/parser_code.test.php
index c50d2d328dd05e2fc5fe1f615528793ae5400567..f4db5e184681dbbba2dc040def2a5e03ade1ba66 100644
--- a/_test/tests/inc/parser/parser_code.test.php
+++ b/_test/tests/inc/parser/parser_code.test.php
@@ -1,6 +1,11 @@
 <?php
 require_once 'parser.inc.php';
 
+/**
+ * Tests to ensure functionality of the <code> syntax tag.
+ *
+ * @group parser_code
+ */
 class TestOfDoku_Parser_Code extends TestOfDoku_Parser {
 
     function setUp() {
@@ -68,5 +73,281 @@ class TestOfDoku_Parser_Code extends TestOfDoku_Parser {
         );
         $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
     }
+
+    function testCodeOptionsArray_OneOption() {
+        $this->P->parse('Foo <code C [enable_line_numbers]>Test</code> Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('p_close',array()),
+            array('code',array('Test','C', null,
+                               array('enable_line_numbers' => 1)
+                               )),
+            array('p_open',array()),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testCodeOptionsArray_TwoOptions() {
+        $this->P->parse('Foo <code C [enable_line_numbers, highlight_lines_extra="3"]>Test</code> Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('p_close',array()),
+            array('code',array('Test','C', null,
+                               array('enable_line_numbers' => 1,
+                                     'highlight_lines_extra' => array(3)
+                               ))),
+            array('p_open',array()),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testCodeOptionsArray_UnknownOption() {
+        $this->P->parse('Foo <code C [unknown="I will be deleted/ignored!"]>Test</code> Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('p_close',array()),
+            array('code',array('Test','C', null, null)),
+            array('p_open',array()),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testCodeOptionsArray_EnableLineNumbers1() {
+        $this->P->parse('Foo <code C [enable_line_numbers]>Test</code> Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('p_close',array()),
+            array('code',array('Test','C', null,
+                               array('enable_line_numbers' => 1)
+                               )),
+            array('p_open',array()),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testCodeOptionsArray_EnableLineNumbers2() {
+        $this->P->parse('Foo <code C [enable_line_numbers="1"]>Test</code> Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('p_close',array()),
+            array('code',array('Test','C', null,
+                               array('enable_line_numbers' => 1)
+                               )),
+            array('p_open',array()),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testCodeOptionsArray_EnableLineNumbers3() {
+        $this->P->parse('Foo <code C [enable_line_numbers="0"]>Test</code> Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('p_close',array()),
+            array('code',array('Test','C', null,
+                               array('enable_line_numbers' => 0)
+                               )),
+            array('p_open',array()),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testCodeOptionsArray_EnableLineNumbers4() {
+        $this->P->parse('Foo <code C [enable_line_numbers=""]>Test</code> Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('p_close',array()),
+            array('code',array('Test','C', null,
+                               array('enable_line_numbers' => 1)
+                               )),
+            array('p_open',array()),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testCodeOptionsArray_HighlightLinesExtra1() {
+        $this->P->parse('Foo <code C [enable_line_numbers, highlight_lines_extra="42, 123, 456, 789"]>Test</code> Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('p_close',array()),
+            array('code',array('Test','C', null,
+                               array('enable_line_numbers' => 1,
+                                     'highlight_lines_extra' => array(42, 123, 456, 789)
+                               ))),
+            array('p_open',array()),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testCodeOptionsArray_HighlightLinesExtra2() {
+        $this->P->parse('Foo <code C [enable_line_numbers, highlight_lines_extra]>Test</code> Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('p_close',array()),
+            array('code',array('Test','C', null,
+                               array('enable_line_numbers' => 1,
+                                     'highlight_lines_extra' => array(1))
+                               )),
+            array('p_open',array()),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testCodeOptionsArray_HighlightLinesExtra3() {
+        $this->P->parse('Foo <code C [enable_line_numbers, highlight_lines_extra=""]>Test</code> Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('p_close',array()),
+            array('code',array('Test','C', null,
+                               array('enable_line_numbers' => 1,
+                                     'highlight_lines_extra' => array(1))
+                               )),
+            array('p_open',array()),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testCodeOptionsArray_StartLineNumbersAt1() {
+        $this->P->parse('Foo <code C [enable_line_numbers, [enable_line_numbers, start_line_numbers_at="42"]]>Test</code> Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('p_close',array()),
+            array('code',array('Test','C', null,
+                               array('enable_line_numbers' => 1,
+                                     'start_line_numbers_at' => 42)
+                               )),
+            array('p_open',array()),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testCodeOptionsArray_StartLineNumbersAt2() {
+        $this->P->parse('Foo <code C [enable_line_numbers, [enable_line_numbers, start_line_numbers_at]]>Test</code> Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('p_close',array()),
+            array('code',array('Test','C', null,
+                               array('enable_line_numbers' => 1,
+                                     'start_line_numbers_at' => 1)
+                               )),
+            array('p_open',array()),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testCodeOptionsArray_StartLineNumbersAt3() {
+        $this->P->parse('Foo <code C [enable_line_numbers, [enable_line_numbers, start_line_numbers_at=""]]>Test</code> Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('p_close',array()),
+            array('code',array('Test','C', null,
+                               array('enable_line_numbers' => 1,
+                                     'start_line_numbers_at' => 1)
+                               )),
+            array('p_open',array()),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testCodeOptionsArray_EnableKeywordLinks1() {
+        $this->P->parse('Foo <code C [enable_keyword_links="false"]>Test</code> Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('p_close',array()),
+            array('code',array('Test','C', null,
+                               array('enable_keyword_links' => false)
+                               )),
+            array('p_open',array()),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testCodeOptionsArray_EnableKeywordLinks2() {
+        $this->P->parse('Foo <code C [enable_keyword_links="true"]>Test</code> Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('p_close',array()),
+            array('code',array('Test','C', null,
+                               array('enable_keyword_links' => true)
+                               )),
+            array('p_open',array()),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
 }
 
diff --git a/inc/parser/handler.php b/inc/parser/handler.php
index e31bf05cdd3e2ff6730fe9fd4c283d1c209fa86b..8fa1ab6140d4aef4c4d94766a6a7881426e7e34a 100644
--- a/inc/parser/handler.php
+++ b/inc/parser/handler.php
@@ -363,6 +363,71 @@ class Doku_Handler {
         return true;
     }
 
+    /**
+     * Internal function for parsing highlight options.
+     * $options is parsed for key value pairs separated by commas.
+     * A value might also be missing in which case the value will simple
+     * be set to true. Commas in strings are ignored, e.g. option="4,56"
+     * will work as expected and will only create one entry.
+     *
+     * @param string $options Comma separated list of key-value pairs,
+     *                        e.g. option1=123, option2="456"
+     * @return array|null     Array of key-value pairs $array['key'] = 'value';
+     *                        or null if no entries found
+     */
+    protected function parse_highlight_options ($options) {
+        $result = array();
+        preg_match_all('/(\w+(?:="[^"]*"))|(\w+[^=,\]])(?:,*)/', $options, $matches, PREG_SET_ORDER);
+        foreach ($matches as $match) {
+            $equal_sign = strpos($match [0], '=');
+            if ($equal_sign === false) {
+                $key = trim($match[0],',');
+                $result [$key] = 1;
+            } else {
+                $key = substr($match[0], 0, $equal_sign);
+                $value = substr($match[0], $equal_sign+1);
+                $value = trim($value, '"');
+                if (strlen($value) > 0) {
+                    $result [$key] = $value;
+                } else {
+                    $result [$key] = 1;
+                }
+            }
+        }
+
+        // Check for supported options
+        $result = array_intersect_key(
+            $result,
+            array_flip(array(
+                'enable_line_numbers',
+                'start_line_numbers_at',
+                'highlight_lines_extra',
+                'enable_keyword_links')
+            )
+        );
+
+        // Sanitize values
+        if(isset($result['enable_line_numbers'])) {
+            $result['enable_line_numbers'] = (bool) $result['enable_line_numbers'];
+        }
+        if(isset($result['highlight_lines_extra'])) {
+            $result['highlight_lines_extra'] = array_map('intval', explode(',', $result['highlight_lines_extra']));
+            $result['highlight_lines_extra'] = array_filter($result['highlight_lines_extra']);
+            $result['highlight_lines_extra'] = array_unique($result['highlight_lines_extra']);
+        }        
+        if(isset($result['start_line_numbers_at'])) {
+            $result['start_line_numbers_at'] = (int) $result['start_line_numbers_at'];
+        }
+        if(isset($result['enable_keyword_links'])) {
+            $result['enable_keyword_links'] = ($result['enable_keyword_links'] !== 'false');
+        }
+        if (count($result) == 0) {
+            return null;
+        }
+
+        return $result;
+    }
+
     function file($match, $state, $pos) {
         return $this->code($match, $state, $pos, 'file');
     }
@@ -370,15 +435,20 @@ class Doku_Handler {
     function code($match, $state, $pos, $type='code') {
         if ( $state == DOKU_LEXER_UNMATCHED ) {
             $matches = explode('>',$match,2);
-
+            // Cut out variable options enclosed in []
+            preg_match('/\[.*\]/', $matches[0], $options);
+            if (!empty($options[0])) {
+                $matches[0] = str_replace($options[0], '', $matches[0]);
+            }
             $param = preg_split('/\s+/', $matches[0], 2, PREG_SPLIT_NO_EMPTY);
             while(count($param) < 2) array_push($param, null);
-
             // We shortcut html here.
             if ($param[0] == 'html') $param[0] = 'html4strict';
             if ($param[0] == '-') $param[0] = null;
             array_unshift($param, $matches[1]);
-
+            if (!empty($options[0])) {
+                $param [] = $this->parse_highlight_options ($options[0]);
+            }
             $this->_addCall($type, $param, $pos);
         }
         return true;
diff --git a/inc/parser/xhtml.php b/inc/parser/xhtml.php
index e60d7d38b991f3c1b50f846a741a62ca35726912..2ff12f0d914906c1365b2124ac41d1fc81027f5e 100644
--- a/inc/parser/xhtml.php
+++ b/inc/parser/xhtml.php
@@ -601,9 +601,10 @@ class Doku_Renderer_xhtml extends Doku_Renderer {
      * @param string $text     text to show
      * @param string $language programming language to use for syntax highlighting
      * @param string $filename file path label
+     * @param array  $options  assoziative array with additional geshi options
      */
-    function file($text, $language = null, $filename = null) {
-        $this->_highlight('file', $text, $language, $filename);
+    function file($text, $language = null, $filename = null, $options=null) {
+        $this->_highlight('file', $text, $language, $filename, $options);
     }
 
     /**
@@ -612,9 +613,10 @@ class Doku_Renderer_xhtml extends Doku_Renderer {
      * @param string $text     text to show
      * @param string $language programming language to use for syntax highlighting
      * @param string $filename file path label
+     * @param array  $options  assoziative array with additional geshi options
      */
-    function code($text, $language = null, $filename = null) {
-        $this->_highlight('code', $text, $language, $filename);
+    function code($text, $language = null, $filename = null, $options=null) {
+        $this->_highlight('code', $text, $language, $filename, $options);
     }
 
     /**
@@ -625,8 +627,9 @@ class Doku_Renderer_xhtml extends Doku_Renderer {
      * @param string $text     text to show
      * @param string $language programming language to use for syntax highlighting
      * @param string $filename file path label
+     * @param array  $options  assoziative array with additional geshi options
      */
-    function _highlight($type, $text, $language = null, $filename = null) {
+    function _highlight($type, $text, $language = null, $filename = null, $options = null) {
         global $ID;
         global $lang;
 
@@ -655,7 +658,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer {
             $class = 'code'; //we always need the code class to make the syntax highlighting apply
             if($type != 'code') $class .= ' '.$type;
 
-            $this->doc .= "<pre class=\"$class $language\">".p_xhtml_cached_geshi($text, $language, '').'</pre>'.DOKU_LF;
+            $this->doc .= "<pre class=\"$class $language\">".p_xhtml_cached_geshi($text, $language, '', $options).'</pre>'.DOKU_LF;
         }
 
         if($filename) {
diff --git a/inc/parserutils.php b/inc/parserutils.php
index 92a5047730f31784be497484bf8b6df6e1ff9503..7933adaf3dde235fc0c89540b09806bd37e7a430 100644
--- a/inc/parserutils.php
+++ b/inc/parserutils.php
@@ -745,20 +745,20 @@ function p_get_first_heading($id, $render=METADATA_RENDER_USING_SIMPLE_CACHE){
  * @author Christopher Smith <chris@jalakai.co.uk>
  * @author Andreas Gohr <andi@splitbrain.org>
  */
-function p_xhtml_cached_geshi($code, $language, $wrapper='pre') {
+function p_xhtml_cached_geshi($code, $language, $wrapper='pre', array $options=null) {
     global $conf, $config_cascade, $INPUT;
     $language = strtolower($language);
 
     // remove any leading or trailing blank lines
     $code = preg_replace('/^\s*?\n|\s*?\n$/','',$code);
 
-    $cache = getCacheName($language.$code,".code");
+    $optionsmd5 = md5(serialize($options));
+    $cache = getCacheName($language.$code.$optionsmd5,".code");
     $ctime = @filemtime($cache);
     if($ctime && !$INPUT->bool('purge') &&
             $ctime > filemtime(DOKU_INC.'vendor/composer/installed.json') &&  // libraries changed
             $ctime > filemtime(reset($config_cascade['main']['default']))){ // dokuwiki changed
         $highlighted_code = io_readFile($cache, false);
-
     } else {
 
         $geshi = new GeSHi($code, $language);
@@ -766,6 +766,13 @@ function p_xhtml_cached_geshi($code, $language, $wrapper='pre') {
         $geshi->enable_classes();
         $geshi->set_header_type(GESHI_HEADER_PRE);
         $geshi->set_link_target($conf['target']['extern']);
+        if($options !== null) {
+            foreach ($options as $function => $params) {
+                if(is_callable(array($geshi, $function))) {
+                    $geshi->$function($params);
+                }
+            }
+        }
 
         // remove GeSHi's wrapper element (we'll replace it with our own later)
         // we need to use a GeSHi wrapper to avoid <BR> throughout the highlighted text
diff --git a/lib/styles/geshi.less b/lib/styles/geshi.less
index 5551eae00c15816e5bef7587b14742ecc6a41e59..69749e8329dcead2048363c0a2afdd0fab2b5d47 100644
--- a/lib/styles/geshi.less
+++ b/lib/styles/geshi.less
@@ -132,4 +132,13 @@
     .re1, .st0, .st_h {
         color: #ff0000;
     }
+
+    li, .li1 {
+        font-weight: normal;
+        vertical-align:top;
+    }
+
+    .ln-xtra {
+        background-color: #ffc;
+    }
 }