aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWaylan Limberg <waylan@gmail.com>2014-01-07 14:48:07 -0800
committerWaylan Limberg <waylan@gmail.com>2014-01-07 14:48:07 -0800
commit809195fb900c8e8bd3ff65a2e69de78075224096 (patch)
tree210eb67b03202906ab375b27d40744365b294fd4
parent3e03dd14b3f47f1482e91da155834afc25d6458f (diff)
parentf6b4f7c74dc4215847008cb8a59c03e63b9268e0 (diff)
downloadmarkdown-809195fb900c8e8bd3ff65a2e69de78075224096.tar.gz
markdown-809195fb900c8e8bd3ff65a2e69de78075224096.tar.bz2
markdown-809195fb900c8e8bd3ff65a2e69de78075224096.zip
Merge pull request #274 from ajdavis/highlight-lines
Add feature for emphasizing some lines in a code block.
-rw-r--r--docs/extensions/code_hilite.txt11
-rw-r--r--docs/extensions/fenced_code_blocks.txt25
-rw-r--r--markdown/extensions/codehilite.py38
-rw-r--r--markdown/extensions/fenced_code.py31
-rw-r--r--tests/test_extensions.py84
5 files changed, 178 insertions, 11 deletions
diff --git a/docs/extensions/code_hilite.txt b/docs/extensions/code_hilite.txt
index ab09742..92f60f8 100644
--- a/docs/extensions/code_hilite.txt
+++ b/docs/extensions/code_hilite.txt
@@ -91,6 +91,17 @@ block contains and each one has a different result.
# Code goes here ...
+ Certain lines can be selected for emphasis with the colon syntax. By
+ default, emphasized lines have a yellow background. This is useful to
+ direct the reader's attention.
+
+ :::python hl_lines="1 3"
+ # This line is emphasized
+ # This line isn't
+ # This line is emphasized
+
+ (`hl_lines` is named for Pygments' "highlighted lines" option.)
+
* ###When No Language is Defined
CodeHilite is completely backward compatible so that if a code block is
diff --git a/docs/extensions/fenced_code_blocks.txt b/docs/extensions/fenced_code_blocks.txt
index c54c5bd..0148c80 100644
--- a/docs/extensions/fenced_code_blocks.txt
+++ b/docs/extensions/fenced_code_blocks.txt
@@ -35,6 +35,8 @@ Fenced code blocks can have a blank line as the first and/or last line of a
code block and they can also come immediately after a list item without becoming
part of the list.
+### Language
+
In addition to PHP Extra's syntax, you can define the language of the code
block for use by syntax highlighters etc. The language will be assigned as a
class attribute of the ``<code>`` element in the output. Therefore, you should
@@ -66,6 +68,29 @@ The above will output:
[Github]: http://github.github.com/github-flavored-markdown/
+### Emphasized Lines
+
+If [Pygments][] is installed, this extension can emphasize certain lines of
+code. By default, emphasized lines have a yellow background. This is useful to
+direct the reader's attention. The lines can be specified with PHP Extra's
+syntax:
+
+ ~~~~{.python hl_lines="1 3"}
+ # This line is emphasized
+ # This line isn't
+ # This line is emphasized
+
+... or with GitHub's:
+
+ ```python hl_lines="1 3"
+ # This line is emphasized
+ # This line isn't
+ # This line is emphasized
+
+(`hl_lines` is named for Pygments' "highlighted lines" option.)
+
+[Pygments]: http://pygments.org/
+
Usage
-----
diff --git a/markdown/extensions/codehilite.py b/markdown/extensions/codehilite.py
index 7a3676b..9f99518 100644
--- a/markdown/extensions/codehilite.py
+++ b/markdown/extensions/codehilite.py
@@ -31,6 +31,22 @@ try:
except ImportError:
pygments = False
+
+def parse_hl_lines(expr):
+ """Support our syntax for emphasizing certain lines of code.
+
+ expr should be like '1 2' to emphasize lines 1 and 2 of a code block.
+ Returns a list of ints, the line numbers to emphasize.
+ """
+ if not expr:
+ return []
+
+ try:
+ return map(int, expr.split())
+ except ValueError:
+ return []
+
+
# ------------------ The Main CodeHilite Class ----------------------
class CodeHilite(object):
"""
@@ -49,6 +65,8 @@ class CodeHilite(object):
* css_class: Set class name of wrapper div ('codehilite' by default).
+ * hl_lines: (List of integers) Lines to emphasize, 1-indexed.
+
Low Level Usage:
>>> code = CodeHilite()
>>> code.src = 'some text' # String or anything with a .readline attr.
@@ -59,7 +77,7 @@ class CodeHilite(object):
def __init__(self, src=None, linenums=None, guess_lang=True,
css_class="codehilite", lang=None, style='default',
- noclasses=False, tab_length=4):
+ noclasses=False, tab_length=4, hl_lines=None):
self.src = src
self.lang = lang
self.linenums = linenums
@@ -68,6 +86,7 @@ class CodeHilite(object):
self.style = style
self.noclasses = noclasses
self.tab_length = tab_length
+ self.hl_lines = hl_lines or []
def hilite(self):
"""
@@ -83,7 +102,7 @@ class CodeHilite(object):
self.src = self.src.strip('\n')
if self.lang is None:
- self._getLang()
+ self._parseHeader()
if pygments:
try:
@@ -99,7 +118,8 @@ class CodeHilite(object):
formatter = HtmlFormatter(linenos=self.linenums,
cssclass=self.css_class,
style=self.style,
- noclasses=self.noclasses)
+ noclasses=self.noclasses,
+ hl_lines=self.hl_lines)
return highlight(self.src, lexer, formatter)
else:
# just escape and build markup usable by JS highlighting libs
@@ -118,7 +138,7 @@ class CodeHilite(object):
return '<pre class="%s"><code%s>%s</code></pre>\n'% \
(self.css_class, class_str, txt)
- def _getLang(self):
+ def _parseHeader(self):
"""
Determines language of a code block from shebang line and whether said
line should be removed or left in place. If the sheband line contains a
@@ -131,6 +151,9 @@ class CodeHilite(object):
(e.i.: :::python), line numbering is left in the current state - off
by default.
+ Also parses optional list of highlight lines, like:
+
+ :::python hl_lines="1 3"
"""
import re
@@ -141,9 +164,12 @@ class CodeHilite(object):
fl = lines.pop(0)
c = re.compile(r'''
- (?:(?:^::+)|(?P<shebang>^[#]!)) # Shebang or 2 or more colons.
+ (?:(?:^::+)|(?P<shebang>^[#]!)) # Shebang or 2 or more colons
(?P<path>(?:/\w+)*[/ ])? # Zero or 1 path
(?P<lang>[\w+-]*) # The language
+ \s* # Arbitrary whitespace
+ # Optional highlight lines, single- or double-quote-delimited
+ (hl_lines=(?P<quot>"|')(?P<hl_lines>.*?)(?P=quot))?
''', re.VERBOSE)
# search first line for shebang
m = c.search(fl)
@@ -159,6 +185,8 @@ class CodeHilite(object):
if self.linenums is None and m.group('shebang'):
# Overridable and Shebang exists - use line numbers
self.linenums = True
+
+ self.hl_lines = parse_hl_lines(m.group('hl_lines'))
else:
# No match
lines.insert(0, fl)
diff --git a/markdown/extensions/fenced_code.py b/markdown/extensions/fenced_code.py
index c5aaa6a..39c6540 100644
--- a/markdown/extensions/fenced_code.py
+++ b/markdown/extensions/fenced_code.py
@@ -59,6 +59,20 @@ Optionally backticks instead of tildes as per how github's code block markdown i
~~~~~ # these tildes will not close the block
</code></pre>
+If the codehighlite extension and Pygments are installed, lines can be highlighted:
+
+ >>> text = '''
+ ... ```hl_lines="1 3"
+ ... line 1
+ ... line 2
+ ... line 3
+ ... ```'''
+ >>> print markdown.markdown(text, extensions=['codehilite', 'fenced_code'])
+ <pre><code><span class="hilight">line 1</span>
+ line 2
+ <span class="hilight">line 3</span>
+ </code></pre>
+
Copyright 2007-2008 [Waylan Limberg](http://achinghead.com/).
Project website: <http://packages.python.org/Markdown/extensions/fenced_code_blocks.html>
@@ -77,7 +91,7 @@ from __future__ import absolute_import
from __future__ import unicode_literals
from . import Extension
from ..preprocessors import Preprocessor
-from .codehilite import CodeHilite, CodeHiliteExtension
+from .codehilite import CodeHilite, CodeHiliteExtension, parse_hl_lines
import re
@@ -93,10 +107,14 @@ class FencedCodeExtension(Extension):
class FencedBlockPreprocessor(Preprocessor):
- FENCED_BLOCK_RE = re.compile( \
- r'(?P<fence>^(?:~{3,}|`{3,}))[ ]*(\{?\.?(?P<lang>[a-zA-Z0-9_+-]*)\}?)?[ ]*\n(?P<code>.*?)(?<=\n)(?P=fence)[ ]*$',
- re.MULTILINE|re.DOTALL
- )
+ FENCED_BLOCK_RE = re.compile(r'''
+(?P<fence>^(?:~{3,}|`{3,}))[ ]* # Opening ``` or ~~~
+(\{?\.?(?P<lang>[a-zA-Z0-9_+-]*))?[ ]* # Optional {, and lang
+# Optional highlight lines, single- or double-quote-delimited
+(hl_lines=(?P<quot>"|')(?P<hl_lines>.*?)(?P=quot))?[ ]*
+}?[ ]*\n # Optional closing }
+(?P<code>.*?)(?<=\n)
+(?P=fence)[ ]*$''', re.MULTILINE | re.DOTALL | re.VERBOSE)
CODE_WRAP = '<pre><code%s>%s</code></pre>'
LANG_TAG = ' class="%s"'
@@ -135,7 +153,8 @@ class FencedBlockPreprocessor(Preprocessor):
css_class=self.codehilite_conf['css_class'][0],
style=self.codehilite_conf['pygments_style'][0],
lang=(m.group('lang') or None),
- noclasses=self.codehilite_conf['noclasses'][0])
+ noclasses=self.codehilite_conf['noclasses'][0],
+ hl_lines=parse_hl_lines(m.group('hl_lines')))
code = highliter.hilite()
else:
diff --git a/tests/test_extensions.py b/tests/test_extensions.py
index add759a..d33feec 100644
--- a/tests/test_extensions.py
+++ b/tests/test_extensions.py
@@ -126,12 +126,37 @@ class TestCodeHilite(unittest.TestCase):
'<pre class="codehilite"><code class="language-python"># A Code Comment'
'</code></pre>')
+ def testHighlightLinesWithColon(self):
+ # Test with hl_lines delimited by single or double quotes.
+ text0 = '\t:::Python hl_lines="2"\n\t#line 1\n\t#line 2\n\t#line 3'
+ text1 = "\t:::Python hl_lines='2'\n\t#line 1\n\t#line 2\n\t#line 3"
+
+ for text in (text0, text1):
+ md = markdown.Markdown(extensions=['codehilite'])
+ if self.has_pygments:
+ self.assertEqual(md.convert(text),
+ '<div class="codehilite"><pre>'
+ '<span class="c">#line 1</span>\n'
+ '<span class="hll"><span class="c">#line 2</span>\n</span>'
+ '<span class="c">#line 3</span>\n'
+ '</pre></div>')
+ else:
+ self.assertEqual(md.convert(text),
+ '<pre class="codehilite">'
+ '<code class="language-python">#line 1\n'
+ '#line 2\n'
+ '#line 3</code></pre>')
class TestFencedCode(unittest.TestCase):
""" Test fenced_code extension. """
def setUp(self):
self.md = markdown.Markdown(extensions=['fenced_code'])
+ self.has_pygments = True
+ try:
+ import pygments
+ except ImportError:
+ self.has_pygments = False
def testBasicFence(self):
""" Test Fenced Code Blocks. """
@@ -191,6 +216,65 @@ Fenced code block
'~~~~~ # these tildes will not close the block\n'
'</code></pre>')
+ def testFencedCodeWithHighlightLines(self):
+ """ Test Fenced Code with Highlighted Lines. """
+
+ text = '''
+```hl_lines="1 3"
+line 1
+line 2
+line 3
+```'''
+ md = markdown.Markdown(extensions=[
+ 'codehilite(linenums=None,guess_lang=False)',
+ 'fenced_code'])
+
+ if self.has_pygments:
+ self.assertEqual(md.convert(text),
+ '<div class="codehilite"><pre>'
+ '<span class="hll">line 1\n</span>'
+ 'line 2\n'
+ '<span class="hll">line 3\n</span>'
+ '</pre></div>')
+ else:
+ self.assertEqual(md.convert(text),
+ '<pre class="codehilite"><code>line 1\n'
+ 'line 2\n'
+ 'line 3</code></pre>')
+
+ def testFencedLanguageAndHighlightLines(self):
+ """ Test Fenced Code with Highlighted Lines. """
+
+ text0 = '''
+```.python hl_lines="1 3"
+#line 1
+#line 2
+#line 3
+```'''
+ text1 = '''
+~~~{.python hl_lines='1 3'}
+#line 1
+#line 2
+#line 3
+~~~'''
+ for text in (text0, text1):
+ md = markdown.Markdown(extensions=[
+ 'codehilite(linenums=None,guess_lang=False)',
+ 'fenced_code'])
+
+ if self.has_pygments:
+ self.assertEqual(md.convert(text),
+ '<div class="codehilite"><pre>'
+ '<span class="hll"><span class="c">#line 1</span>\n</span>'
+ '<span class="c">#line 2</span>\n'
+ '<span class="hll"><span class="c">#line 3</span>\n</span>'
+ '</pre></div>')
+ else:
+ self.assertEqual(md.convert(text),
+ '<pre class="codehilite"><code class="language-python">#line 1\n'
+ '#line 2\n'
+ '#line 3</code></pre>')
+
class TestHeaderId(unittest.TestCase):
""" Test HeaderId Extension. """