Project

Profile

Help

How to connect?
Download (8.72 KB) Statistics View on GitHub Reload from mirrored respository
| Branch: | Tag: | Revision:

github / src / accesspanel / extensions / ansi.py @ dc5fb9be

1
# Copyright (c) 2016, LE GOFF Vincent
2
# All rights reserved.
3

    
4
# Redistribution and use in source and binary forms, with or without
5
# modification, are permitted provided that the following conditions are met:
6

    
7
# * Redistributions of source code must retain the above copyright notice, this
8
#   list of conditions and the following disclaimer.
9

    
10
# * Redistributions in binary form must reproduce the above copyright notice,
11
#   this list of conditions and the following disclaimer in the documentation
12
#   and/or other materials provided with the distribution.
13

    
14
# * Neither the name of ytranslate nor the names of its
15
#   contributors may be used to endorse or promote products derived from
16
#   this software without specific prior written permission.
17

    
18
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28

    
29
import re, string
30

    
31
import wx
32
import wx.lib.colourdb
33

    
34
from .base import BaseExtension
35

    
36
## Constants
37
# Regular expressions to capture ANSI codes
38
RE_CODE = re.compile(r"^(?:(\d+)(?:\;(\d+)(?:\;(\d+))?)?)?$", re.UNICODE)
39

    
40
# Brightness
41
NORMAL = 1
42
BRIGHT = 2
43
UNDERLINE = 3
44

    
45
class ANSI(BaseExtension):
46

    
47
    """Manage the ANSI codes to have color in the text.
48

49
    The AccessPanel can support color codes. This extension allows
50
    to write ANSI color codes and put the text in correct formatting.
51

52
    Brief reminder:
53
        ANSI codes are placed in the text, between a '\x1b[' sequence
54
        and a final 'm'. In between are numbers separated with a
55
        semicolon.  The first number represents the level of
56
        brightness (0 for normal, 1 for bright, 4 for underline,
57
        7 for negative).  The second number represents the foreground
58
        color and should be between 30 and 37 (see the list below).
59
        The third number represents the background color and should be
60
        between 40 and 47 (the same list of color applies).
61

62
    List of colors:
63
        | Color      | Foreground | Background |
64
        | Black      | 30         | 40         |
65
        | Red        | 31         | 41         |
66
        | Green      | 32         | 42         |
67
        | Yellow     | 33         | 43         |
68
        | Blue       | 34         | 44         |
69
        | Magenta    | 35         | 45         |
70
        | Cyan       | 36         | 46         |
71
        | White      | 37         | 47         |
72

73
    Examples:
74
        "\x1b[0;31;47m" means red on white.
75
        "\x1b[1;33m" means bright yellow on default background.
76
        "\x1b[4;36m" means underline cyan on default background.
77
        "\x1b[0m" means back to default colors.
78

79
    """
80

    
81
    def __init__(self, panel):
82
        super().__init__(panel)
83
        wx.lib.colourdb.updateColourDB()
84
        self.brightness = NORMAL
85
        self.foreground = wx.BLACK
86
        self.background = wx.WHITE
87
        self.default_foreground = wx.BLACK
88
        self.default_background = wx.WHITE
89
        self.modifiers = []
90

    
91
        # Color codes
92
        self.normal_colors = {
93
            40: wx.NamedColour("dark grey"),
94
            41: wx.RED,
95
            42: wx.GREEN,
96
            43: wx.YELLOW,
97
            44: wx.BLUE,
98
            45: wx.NamedColour("magenta"),
99
            46: wx.CYAN,
100
            47: wx.WHITE,
101
        }
102

    
103
        self.bright_colors = {
104
            40: wx.NamedColour("grey"),
105
            41: wx.NamedColour("deep pink"),
106
            42: wx.NamedColour("light green"),
107
            43: wx.NamedColour("light yellow"),
108
            44: wx.NamedColour("light blue"),
109
            45: wx.NamedColour("light magenta"),
110
            46: wx.NamedColour("light cyan"),
111
            47: wx.WHITE,
112
        }
113

    
114
        self.dark_colors = {
115
            40: wx.BLACK,
116
            41: wx.NamedColour("dark red"),
117
            42: wx.NamedColour("dark green"),
118
            43: wx.NamedColour("orange"),
119
            44: wx.NamedColour("dark blue"),
120
            45: wx.NamedColour("dark magenta"),
121
            46: wx.NamedColour("dark cyan"),
122
            47: wx.WHITE,
123
        }
124

    
125
    def OnClearOutput(self):
126
        """The output has been cleared."""
127
        self.modifiers = []
128

    
129
    def OnMessage(self, message):
130
        """Interpret the ANSI codes."""
131

    
132
        # Variables
133
        point = self.panel.editing_pos
134
        ansi_stage = 1
135
        ansi_buffer = ""
136
        clean_buffer = ""
137
        char_index = 0
138

    
139
        def select_colors(code):
140
            """
141
            Transforms ANSI sequences in a format specifier
142
            """
143

    
144
            match = RE_CODE.match(code)
145

    
146
            # ANSI style sequences have tree parts
147
            p1, p2, p3 = match.groups()
148
            brightness, foreground, background = (None, None, None)
149

    
150
            if p1:
151
                p1 = int(p1.strip())
152

    
153
                if p1 in (0, 1, 4, 7):
154
                    brightness = p1
155
                elif p1 in range(30, 38):
156
                    foreground = p1
157
                elif p1 in range(40, 48):
158
                    background = p1
159

    
160
            if p2:
161
                p2 = int(p2.strip())
162

    
163
                if p2 in range(30, 40):
164
                    foreground = p2
165
                elif p2 in range(40, 50):
166
                    background = p2
167

    
168
            if p3:
169
                p3 = int(p3.strip())
170

    
171
                if p3 in range(40, 50):
172
                    background = p3
173

    
174
            if brightness == 1:
175
                brightness = BRIGHT
176
            elif brightness == 4:
177
                brightness = UNDERLINE
178
            else:
179
                brightness = NORMAL
180

    
181

    
182
            # Now the colors
183
            colorlist = None
184

    
185
            if brightness == BRIGHT:
186
                colorlist = self.bright_colors
187
            elif brightness == UNDERLINE:
188
                colorlist = self.dark_colors
189
            else:
190
                colorlist = self.normal_colors
191

    
192
            if foreground:
193
                foreground = colorlist[foreground+10]
194
            else:
195
                foreground = self.default_foreground
196

    
197
            if background:
198
                background = colorlist[background]
199
            else:
200

    
201
                if brightness == BRIGHT:
202
                    background = wx.BLACK
203
                elif brightness == UNDERLINE:
204
                    background = wx.YELLOW
205
                else:
206
                    background = self.default_background
207

    
208
            return (foreground, background)
209

    
210
        # We must iterate over the entire message string
211
        while char_index < len(message):
212

    
213
            # First stage: look for a 0x1B character and switch to second stage
214
            if ansi_stage == 1:
215
                if message[char_index] == "\x1B":
216
                    ansi_stage = 2
217
                else:
218
                    clean_buffer += message[char_index]
219

    
220
            # Second stage: look for a validation character (an '[')
221
            elif ansi_stage == 2:
222
                if message[char_index] == "[":
223
                    ansi_stage = 3
224
                    ansi_buffer = ""
225
                else:
226
                    ansi_stage = 1
227

    
228
                    # Just add the two last characters
229
                    clean_buffer += message[char_index-1:char_index]
230

    
231
            # Last stage: process ANSI command with arguments
232
            elif ansi_stage == 3:
233
                if message[char_index] in string.digits+";":
234
                    ansi_buffer += message[char_index]
235

    
236
                # Now we have all arguments and the command (too generic detection for now...)
237
                else:
238
                    ansi_stage = 1
239
                    self.modifiers.append((point+len(clean_buffer), select_colors(ansi_buffer)))
240

    
241
                    ansi_buffer = ""
242

    
243
            char_index += 1
244

    
245

    
246
        return clean_buffer
247

    
248
    def PostMessage(self, message):
249
        """Applies ANSI style to text"""
250

    
251
        start = None
252
        last_mark = None
253

    
254
        for point, style in self.modifiers:
255
            if not last_mark:
256
                last_mark = style
257
                start = point
258
                continue
259

    
260
            # Unpack foreground and background from style tuple
261
            foreground, background = style
262

    
263
            self.panel.output.SetStyle(start, point, wx.TextAttr(
264
                    foreground, background))
265

    
266
            start = point
267
            last_mark = style
268

    
269
        self.modifiers = []
(2-2/5)