summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorg Brandl <georg@python.org>2016-02-14 19:07:16 +0100
committerGeorg Brandl <georg@python.org>2016-02-14 19:07:16 +0100
commitf8e9795479bdace4e822641567d806538a345f4f (patch)
tree1f7b1d99f35d28a1ef2a27a49046797e48179186
parente71840a35ffb2aa453542ecd6f770ffbaa7db439 (diff)
parent064edec39bc9075dad066450c9a5bab254f4a581 (diff)
downloadpygments-f8e9795479bdace4e822641567d806538a345f4f.tar.gz
Merged in Carreau/pygments-main/256ansi (pull request #531)
Allow ansi escape sequence as color format
-rw-r--r--doc/docs/styles.rst59
-rw-r--r--pygments/formatters/terminal256.py38
-rw-r--r--pygments/style.py43
-rw-r--r--tests/test_lexers_other.py23
-rw-r--r--tests/test_terminal_formatter.py78
5 files changed, 232 insertions, 9 deletions
diff --git a/doc/docs/styles.rst b/doc/docs/styles.rst
index d56db0db..0076d062 100644
--- a/doc/docs/styles.rst
+++ b/doc/docs/styles.rst
@@ -143,3 +143,62 @@ a way to iterate over all styles:
>>> from pygments.styles import get_all_styles
>>> styles = list(get_all_styles())
+
+
+.. _AnsiTerminalStyle:
+
+Terminal Styles
+===============
+
+.. versionadded:: 2.2
+
+Custom styles used with `Terminal256` formatter can also defines colors using
+ansi-color. To do so use the `#ansigreen`, `#ansired` or any other colors
+defined in ``pygments.style.ansilist``. Foreground ANSI colors will be mapped
+to the corresponding `escape codes 30 to 37
+<https://en.wikipedia.org/wiki/ANSI_escape_code#Colors>`_ thus respecting any
+custom color mapping and themes provided by many terminal emulators. Light
+variant are treated for foreground color with and extra bold flag.
+`bg:#ansi<color>` will also be respected, except the light variant will be the
+same shade as their light variant.
+
+See following example where the color of the string `"hello world"` is governed
+by the escape sequence `\x1b34;01m` (Ansi Blue, Bold, `41` beeing red background)
+instead of an extended foreground & background color.
+
+.. sourcecode:: pycon
+
+ >>> from pygments import highlight
+ >>> from pygments.style import Style
+ >>> from pygments.token import Token
+ >>> from pygments.lexers import Python3Lexer
+ >>> from pygments.formatters import Terminal256Formatter
+
+ >>> class MyStyle(Style):
+ styles = {
+ Token.String: '#ansiblue bg:#ansired',
+ }
+
+ >>> code = 'print("Hello World")'
+ >>> result = highlight(code, Python3Lexer(), Terminal256Formatter(style=MyStyle))
+ >>> print(result.encode())
+ b'print(\x1b[34;41;01m"\x1b[39;49;00m\x1b[34;41;01mHello World\x1b[39;49;00m\x1b[34;41;01m"\x1b[39;49;00m)\n'
+
+Style that use `#ansi*` colors might not correctly work with
+formatters others than ``Terminal256``. `HtmlFormatter` is capable of handling
+some `#ansi*` code and will map to a fixed HTML/CSS color. For example,
+`#ansiblue` will be converted to `color:#0000ff` , `#ansired` to `color:#ff0000`.
+
+By definition of Ansi color the following color are considered "light" colors,
+and will be rendered by most terminal as bold:
+
+ - "darkgray", "red", "green", "yellow", "blue", "fuchsia", "turquoise",
+ "white"
+
+
+The following are considered "dark" color and will be rendered as non-bold:
+
+ - "black", "darkred", "darkgreen", "brown", "darkblue", "purple", "teal",
+ "lightgray"
+
+Exact behavior might depends on the terminal emulator you are using, and its settings.
diff --git a/pygments/formatters/terminal256.py b/pygments/formatters/terminal256.py
index af311955..1aa19f25 100644
--- a/pygments/formatters/terminal256.py
+++ b/pygments/formatters/terminal256.py
@@ -27,6 +27,8 @@
import sys
from pygments.formatter import Formatter
+from pygments.console import codes
+from pygments.style import ansilist
__all__ = ['Terminal256Formatter', 'TerminalTrueColorFormatter']
@@ -47,9 +49,21 @@ class EscapeSequence:
def color_string(self):
attrs = []
if self.fg is not None:
- attrs.extend(("38", "5", "%i" % self.fg))
+ if self.fg in ansilist:
+ esc = codes[self.fg[5:]]
+ if ';01m' in esc:
+ self.bold = True
+ # extract fg color code.
+ attrs.append(esc[2:4])
+ else :
+ attrs.extend(("38", "5", "%i" % self.fg))
if self.bg is not None:
- attrs.extend(("48", "5", "%i" % self.bg))
+ if self.bg in ansilist:
+ esc = codes[self.bg[5:]]
+ # extract fg color code, add 10 for bg.
+ attrs.append(str(int(esc[2:4])+10))
+ else :
+ attrs.extend(("48", "5", "%i" % self.bg))
if self.bold:
attrs.append("01")
if self.underline:
@@ -89,6 +103,13 @@ class Terminal256Formatter(Formatter):
and converts them to nearest ANSI 256-color escape sequences. Bold and
underline attributes from the style are preserved (and displayed).
+ .. versionadded:: 2.2
+
+ If the used style defined foreground colors in the form `#ansi*`, then
+ `Terminal256Formatter` will map these to non extended foreground color.
+
+ See AnsiTerminalStyle_ for more informations.
+
.. versionadded:: 0.9
Options accepted:
@@ -169,6 +190,10 @@ class Terminal256Formatter(Formatter):
def _color_index(self, color):
index = self.best_match.get(color, None)
+ if color in ansilist:
+ # strip the `#ansi` part an look up code
+ index = color
+ self.best_match[color] = index
if index is None:
try:
rgb = int(str(color), 16)
@@ -185,9 +210,14 @@ class Terminal256Formatter(Formatter):
def _setup_styles(self):
for ttype, ndef in self.style:
escape = EscapeSequence()
- if ndef['color']:
+ # get foreground from ansicolor if set
+ if ndef['ansicolor']:
+ escape.fg = self._color_index(ndef['ansicolor'])
+ elif ndef['color']:
escape.fg = self._color_index(ndef['color'])
- if ndef['bgcolor']:
+ if ndef['bgansicolor']:
+ escape.bg = self._color_index(ndef['bgansicolor'])
+ elif ndef['bgcolor']:
escape.bg = self._color_index(ndef['bgcolor'])
if self.usebold and ndef['bold']:
escape.bold = True
diff --git a/pygments/style.py b/pygments/style.py
index b2b990ea..8c7de528 100644
--- a/pygments/style.py
+++ b/pygments/style.py
@@ -13,6 +13,29 @@ from pygments.token import Token, STANDARD_TYPES
from pygments.util import add_metaclass
+
+_ansimap = {
+ ##
+ '#ansiblack': '000000',
+ '#ansidarkred': '7f0000',
+ '#ansidarkgreen': '007f00',
+ '#ansibrown': '7f7fe0',
+ '#ansidarkblue': '00007f',
+ '#ansipurple': '7f007f',
+ '#ansiteal': '007f7f',
+ '#ansilightgray': 'e5e5e5',
+ ### normal
+ '#ansidarkgray': '555555',
+ '#ansired': 'ff0000',
+ '#ansigreen': '00ff00',
+ '#ansiyellow': 'ffff00',
+ '#ansiblue': '0000ff',
+ '#ansifuchsia': 'ff00ff',
+ '#ansiturquoise': '00ffff',
+ '#ansiwhite': 'ffffff',
+ }
+ansilist = set(_ansimap.keys())
+
class StyleMeta(type):
def __new__(mcs, name, bases, dct):
@@ -22,6 +45,8 @@ class StyleMeta(type):
obj.styles[token] = ''
def colorformat(text):
+ if text in ansilist:
+ return text
if text[0:1] == '#':
col = text[1:]
if len(col) == 6:
@@ -79,16 +104,30 @@ class StyleMeta(type):
def style_for_token(cls, token):
t = cls._styles[token]
+ ansicolor = None
+ color = t[0]
+ if color.startswith('#ansi'):
+ ansicolor = color
+ color = _ansimap[color]
+ bgansicolor = None
+ bgcolor = t[4]
+ if bgcolor.startswith('#ansi'):
+ bgansicolor = bgcolor
+ bgcolor = _ansimap[bgcolor]
+
return {
- 'color': t[0] or None,
+ 'color': color or None,
'bold': bool(t[1]),
'italic': bool(t[2]),
'underline': bool(t[3]),
- 'bgcolor': t[4] or None,
+ 'bgcolor': bgcolor or None,
'border': t[5] or None,
'roman': bool(t[6]) or None,
'sans': bool(t[7]) or None,
'mono': bool(t[8]) or None,
+ 'ansicolor': ansicolor,
+ 'bgansicolor': bgansicolor,
+
}
def list_styles(cls):
diff --git a/tests/test_lexers_other.py b/tests/test_lexers_other.py
index bb667c05..4c5132ad 100644
--- a/tests/test_lexers_other.py
+++ b/tests/test_lexers_other.py
@@ -16,6 +16,26 @@ from pygments.lexers.scripting import EasytrieveLexer, JclLexer, RexxLexer
def _exampleFilePath(filename):
return os.path.join(os.path.dirname(__file__), 'examplefiles', filename)
+_MAX_LENGTH = 80
+
+def safe_repr(obj, short=False):
+ try:
+ result = repr(obj)
+ except Exception:
+ result = object.__repr__(obj)
+ if not short or len(result) < _MAX_LENGTH:
+ return result
+ return result[:_MAX_LENGTH] + ' [truncated]...'
+
+
+class MyTestCase(unittest.TestCase):
+ ### Assert less is 2.7+ only.
+ def assertLess(self, a, b, msg=None):
+ """Just like self.assertTrue(a < b), but with a nicer default message."""
+ if not a < b:
+ standardMsg = '%s not less than %s' % (safe_repr(a), safe_repr(b))
+ self.fail(self._formatMessage(msg, standardMsg))
+
class AnalyseTextTest(unittest.TestCase):
def _testCanRecognizeAndGuessExampleFiles(self, lexer):
@@ -42,8 +62,7 @@ class AnalyseTextTest(unittest.TestCase):
for lexerToTest in LEXERS_TO_TEST:
self._testCanRecognizeAndGuessExampleFiles(lexerToTest)
-
-class EasyTrieveLexerTest(unittest.TestCase):
+class EasyTrieveLexerTest(MyTestCase):
def testCanGuessFromText(self):
self.assertLess(0, EasytrieveLexer.analyse_text('MACRO'))
self.assertLess(0, EasytrieveLexer.analyse_text('\nMACRO'))
diff --git a/tests/test_terminal_formatter.py b/tests/test_terminal_formatter.py
index 07337cd5..84373790 100644
--- a/tests/test_terminal_formatter.py
+++ b/tests/test_terminal_formatter.py
@@ -14,7 +14,12 @@ import re
from pygments.util import StringIO
from pygments.lexers.sql import PlPgsqlLexer
-from pygments.formatters import TerminalFormatter
+from pygments.formatters import TerminalFormatter,Terminal256Formatter, HtmlFormatter, LatexFormatter
+
+from pygments.style import Style
+from pygments.token import Token
+from pygments.lexers import Python3Lexer
+from pygments import highlight
DEMO_TEXT = '''\
-- comment
@@ -49,3 +54,74 @@ class TerminalFormatterTest(unittest.TestCase):
for a, b in zip(DEMO_TEXT.splitlines(), plain.splitlines()):
self.assertTrue(a in b)
+
+
+
+
+
+
+class MyStyle(Style):
+
+ styles = {
+ Token.Comment: '#ansidarkgray',
+ Token.String: '#ansiblue bg:#ansidarkred',
+ Token.Number : '#ansigreen bg:#ansidarkgreen',
+ Token.Number.Hex: '#ansidarkgreen bg:#ansired',
+ }
+
+
+
+code = '''
+# this should be a comment
+print("Hello World")
+async def function(a,b,c, *d, **kwarg:Bool)->Bool:
+ pass
+ return 123, 0xb3e3
+
+'''
+_MAX_LENGTH = 80
+
+def safe_repr(obj, short=False):
+ try:
+ result = repr(obj)
+ except Exception:
+ result = object.__repr__(obj)
+ if not short or len(result) < _MAX_LENGTH:
+ return result
+ return result[:_MAX_LENGTH] + ' [truncated]...'
+
+class MyTest(unittest.TestCase):
+
+ def assertIn(self, member, container, msg=None):
+ """Just like self.assertTrue(a in b), but with a nicer default message."""
+ if member not in container:
+ standardMsg = '%s not found in %s' % (safe_repr(member),
+ safe_repr(container))
+ self.fail(self._formatMessage(msg, standardMsg))
+
+
+termtest = lambda x: highlight(x, Python3Lexer(), Terminal256Formatter(style=MyStyle))
+class Terminal256FormatterTest(MyTest):
+
+
+ def test_style_html(self):
+ style = HtmlFormatter(style=MyStyle).get_style_defs()
+ self.assertIn('#555555',style, "ansigray for comment not html css style")
+
+ def test_tex_works(self):
+ """check tex Formatter don't crash"""
+ highlight(code, Python3Lexer(), LatexFormatter(style=MyStyle))
+
+ def test_html_works(self):
+ highlight(code, Python3Lexer(), HtmlFormatter(style=MyStyle))
+
+ def test_256esc_seq(self):
+ """
+ test that a few escape sequences are actualy used when using #ansi<> color codes
+ """
+ self.assertIn('32;41',termtest('0x123'))
+ self.assertIn('32;42',termtest('123'))
+ self.assertIn('30;01',termtest('#comment'))
+ self.assertIn('34;41',termtest('"String"'))
+
+