Revision dc5fb9be
Added by Francisco Del Roio about 2 years ago
src/accesspanel/extensions/ansi.py | ||
---|---|---|
26 | 26 |
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
27 | 27 |
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
28 | 28 |
|
29 |
import re |
|
29 |
import re, string
|
|
30 | 30 |
|
31 | 31 |
import wx |
32 | 32 |
import wx.lib.colourdb |
33 | 33 |
|
34 |
from accesspanel.extensions.base import BaseExtension
|
|
34 |
from .base import BaseExtension |
|
35 | 35 |
|
36 | 36 |
## Constants |
37 | 37 |
# Regular expressions to capture ANSI codes |
38 |
RE_ANSI = re.compile(r'\x1b\[([^m]*)m', re.UNICODE) |
|
39 | 38 |
RE_CODE = re.compile(r"^(?:(\d+)(?:\;(\d+)(?:\;(\d+))?)?)?$", re.UNICODE) |
40 | 39 |
|
41 | 40 |
# Brightness |
... | ... | |
80 | 79 |
""" |
81 | 80 |
|
82 | 81 |
def __init__(self, panel): |
83 |
BaseExtension.__init__(self, panel)
|
|
82 |
super().__init__(panel)
|
|
84 | 83 |
wx.lib.colourdb.updateColourDB() |
85 | 84 |
self.brightness = NORMAL |
86 | 85 |
self.foreground = wx.BLACK |
... | ... | |
129 | 128 |
|
130 | 129 |
def OnMessage(self, message): |
131 | 130 |
"""Interpret the ANSI codes.""" |
131 |
|
|
132 |
# Variables |
|
132 | 133 |
point = self.panel.editing_pos |
133 |
pos = [] |
|
134 |
|
|
135 |
# Browse through the ANSI codes |
|
136 |
for match in reversed(list(RE_ANSI.finditer(message))): |
|
137 |
code = match.group(1) |
|
138 |
|
|
139 |
# Extract the brightness, foreground and background |
|
140 |
codes = RE_CODE.search(code) |
|
141 |
if codes: |
|
142 |
brightness, foreground, background = codes.groups() |
|
143 |
|
|
144 |
# All 3 variables can be None |
|
145 |
if brightness == 1: |
|
146 |
brightness = BRIGHT |
|
147 |
elif brightness == 4: |
|
148 |
brightness = UNDERLINE |
|
149 |
else: |
|
150 |
brightness = NORMAL |
|
151 |
|
|
152 |
# Extract the foreground color |
|
153 |
if foreground: |
|
154 |
foreground = int(foreground) |
|
155 |
if brightness == NORMAL: |
|
156 |
foreground = self.normal_colors.get(foreground + 10) |
|
157 |
elif brightness == BRIGHT: |
|
158 |
foreground = self.bright_colors.get(foreground + 10) |
|
159 |
elif brightness == UNDERLINE: |
|
160 |
foreground = self.dark_colors.get(foreground + 10) |
|
161 |
|
|
162 |
# Extract the background color |
|
163 |
if background: |
|
164 |
background = int(background) |
|
165 |
if brightness == NORMAL: |
|
166 |
background = self.normal_colors.get(background) |
|
167 |
elif brightness == BRIGHT: |
|
168 |
background = self.bright_colors.get(background) |
|
169 |
elif brightness == UNDERLINE: |
|
170 |
background = self.dark_colors.get(background) |
|
171 |
|
|
172 |
if foreground is None: |
|
173 |
foreground = self.default_foreground |
|
174 |
|
|
175 |
if brightness: |
|
176 |
self.brightness = brightness |
|
177 |
|
|
178 |
if background is None: |
|
179 |
if self.brightness == BRIGHT: |
|
180 |
background = wx.BLACK |
|
181 |
elif self.brightness == UNDERLINE: |
|
182 |
background = wx.YELLOW |
|
183 |
else: |
|
184 |
background = self.default_background |
|
185 |
|
|
186 |
start = match.start() |
|
187 |
end = match.end() |
|
188 |
pos.append((start, end, foreground, background)) |
|
189 |
|
|
190 |
begin_tag = True |
|
191 |
updated_pos = 0 |
|
192 |
last_mod = None |
|
193 |
for start, end, foreground, background in reversed(pos): |
|
194 |
if begin_tag: |
|
195 |
begin_tag = False |
|
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 |
|
196 | 178 |
else: |
197 |
eol = message.count("\r", 0, start - 1) |
|
198 |
begin_tag = True |
|
199 |
real_start, m_foreground, m_background = last_mod |
|
200 |
real_start += point |
|
201 |
real_start -= eol |
|
202 |
real_end = start - updated_pos + point |
|
203 |
real_end -= eol |
|
204 |
self.modifiers.append((real_start, real_end, m_foreground, |
|
205 |
m_background)) |
|
206 |
|
|
207 |
# If it's not the default color, mark an open tag |
|
208 |
if foreground != self.default_foreground or \ |
|
209 |
background != self.default_background: |
|
210 |
begin_tag = False |
|
211 |
|
|
212 |
last_mod = (start - updated_pos, foreground, background) |
|
213 |
updated_pos += end - start |
|
214 |
|
|
215 |
# Remove the ANSI codes from the message |
|
216 |
for start, end, foreground, background in pos: |
|
217 |
message = message[:start] + message[end:] |
|
218 |
|
|
219 |
return message |
|
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 |
|
220 | 247 |
|
221 | 248 |
def PostMessage(self, message): |
222 |
for start, end, foreground, background in self.modifiers: |
|
223 |
range = self.panel.output.GetRange(start, end) |
|
224 |
self.panel.output.SetStyle(start, end, wx.TextAttr( |
|
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( |
|
225 | 264 |
foreground, background)) |
265 |
|
|
266 |
start = point |
|
267 |
last_mark = style |
|
268 |
|
|
269 |
self.modifiers = [] |
Also available in: Unified diff
Changes:
I think that these changes fixes #144.