]> jfr.im git - irc/weechat/qweechat.git/blame - qweechat/weechat/color.py
Update copyright dates
[irc/weechat/qweechat.git] / qweechat / weechat / color.py
CommitLineData
7dcf23b1
SH
1# -*- coding: utf-8 -*-
2#
e836cfb0
SH
3# color.py - remove/replace colors in WeeChat strings
4#
8335009d 5# Copyright (C) 2011-2021 Sébastien Helleu <flashcode@flashtux.org>
7dcf23b1
SH
6#
7# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
8#
9# QWeeChat is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation; either version 3 of the License, or
12# (at your option) any later version.
13#
14# QWeeChat is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with QWeeChat. If not, see <http://www.gnu.org/licenses/>.
21#
22
7dcf23b1
SH
23import re
24
25RE_COLOR_ATTRS = r'[*!/_|]*'
26RE_COLOR_STD = r'(?:%s\d{2})' % RE_COLOR_ATTRS
27RE_COLOR_EXT = r'(?:@%s\d{5})' % RE_COLOR_ATTRS
28RE_COLOR_ANY = r'(?:%s|%s)' % (RE_COLOR_STD, RE_COLOR_EXT)
3a5ec0c1 29# \x19: color code, \x1A: set attribute, \x1B: remove attribute, \x1C: reset
77df9d06
SH
30RE_COLOR = re.compile(
31 r'(\x19(?:\d{2}|F%s|B\d{2}|B@\d{5}|E|\\*%s(,%s)?|@\d{5}|b.|\x1C))|\x1A.|'
32 r'\x1B.|\x1C'
33 % (RE_COLOR_ANY, RE_COLOR_ANY, RE_COLOR_ANY))
7dcf23b1 34
3a5ec0c1 35TERMINAL_COLORS = \
77df9d06
SH
36 '000000cd000000cd00cdcd000000cdcd00cd00cdcde5e5e5' \
37 '4d4d4dff000000ff00ffff000000ffff00ff00ffffffffff' \
38 '00000000002a0000550000800000aa0000d4002a00002a2a' \
39 '002a55002a80002aaa002ad400550000552a005555005580' \
40 '0055aa0055d400800000802a0080550080800080aa0080d4' \
41 '00aa0000aa2a00aa5500aa8000aaaa00aad400d40000d42a' \
42 '00d45500d48000d4aa00d4d42a00002a002a2a00552a0080' \
43 '2a00aa2a00d42a2a002a2a2a2a2a552a2a802a2aaa2a2ad4' \
44 '2a55002a552a2a55552a55802a55aa2a55d42a80002a802a' \
45 '2a80552a80802a80aa2a80d42aaa002aaa2a2aaa552aaa80' \
46 '2aaaaa2aaad42ad4002ad42a2ad4552ad4802ad4aa2ad4d4' \
47 '55000055002a5500555500805500aa5500d4552a00552a2a' \
48 '552a55552a80552aaa552ad455550055552a555555555580' \
49 '5555aa5555d455800055802a5580555580805580aa5580d4' \
50 '55aa0055aa2a55aa5555aa8055aaaa55aad455d40055d42a' \
51 '55d45555d48055d4aa55d4d480000080002a800055800080' \
52 '8000aa8000d4802a00802a2a802a55802a80802aaa802ad4' \
53 '80550080552a8055558055808055aa8055d480800080802a' \
54 '8080558080808080aa8080d480aa0080aa2a80aa5580aa80' \
55 '80aaaa80aad480d40080d42a80d45580d48080d4aa80d4d4' \
56 'aa0000aa002aaa0055aa0080aa00aaaa00d4aa2a00aa2a2a' \
57 'aa2a55aa2a80aa2aaaaa2ad4aa5500aa552aaa5555aa5580' \
58 'aa55aaaa55d4aa8000aa802aaa8055aa8080aa80aaaa80d4' \
59 'aaaa00aaaa2aaaaa55aaaa80aaaaaaaaaad4aad400aad42a' \
60 'aad455aad480aad4aaaad4d4d40000d4002ad40055d40080' \
61 'd400aad400d4d42a00d42a2ad42a55d42a80d42aaad42ad4' \
62 'd45500d4552ad45555d45580d455aad455d4d48000d4802a' \
63 'd48055d48080d480aad480d4d4aa00d4aa2ad4aa55d4aa80' \
64 'd4aaaad4aad4d4d400d4d42ad4d455d4d480d4d4aad4d4d4' \
65 '0808081212121c1c1c2626263030303a3a3a4444444e4e4e' \
66 '5858586262626c6c6c7676768080808a8a8a9494949e9e9e' \
67 'a8a8a8b2b2b2bcbcbcc6c6c6d0d0d0dadadae4e4e4eeeeee'
7dcf23b1 68
3a5ec0c1 69# WeeChat basic colors (color name, index in terminal colors)
77df9d06
SH
70WEECHAT_BASIC_COLORS = (
71 ('default', 0), ('black', 0), ('darkgray', 8), ('red', 1),
72 ('lightred', 9), ('green', 2), ('lightgreen', 10), ('brown', 3),
73 ('yellow', 11), ('blue', 4), ('lightblue', 12), ('magenta', 5),
74 ('lightmagenta', 13), ('cyan', 6), ('lightcyan', 14), ('gray', 7),
75 ('white', 0))
76
3a5ec0c1
SH
77
78class Color():
79 def __init__(self, color_options, debug=False):
80 self.color_options = color_options
81 self.debug = debug
82
83 def _rgb_color(self, index):
84 color = TERMINAL_COLORS[index*6:(index*6)+6]
85 r = int(color[0:2], 16) * 0.85
86 g = int(color[2:4], 16) * 0.85
87 b = int(color[4:6], 16) * 0.85
88 return '%02x%02x%02x' % (r, g, b)
89
90 def _convert_weechat_color(self, color):
91 try:
92 index = int(color)
93 return '\x01(Fr%s)' % self.color_options[index]
524efe3c 94 except: # noqa: E722
a6630f78 95 print('Error decoding WeeChat color "%s"' % color)
3a5ec0c1
SH
96 return ''
97
98 def _convert_terminal_color(self, fg_bg, attrs, color):
99 try:
100 index = int(color)
101 return '\x01(%s%s#%s)' % (fg_bg, attrs, self._rgb_color(index))
524efe3c 102 except: # noqa: E722
a6630f78 103 print('Error decoding terminal color "%s"' % color)
3a5ec0c1
SH
104 return ''
105
106 def _convert_color_attr(self, fg_bg, color):
107 extended = False
108 if color[0].startswith('@'):
109 extended = True
110 color = color[1:]
111 attrs = ''
77df9d06 112 # keep_attrs = False
3a5ec0c1 113 while color.startswith(('*', '!', '/', '_', '|')):
77df9d06
SH
114 # TODO: manage the "keep attributes" flag
115 # if color[0] == '|':
116 # keep_attrs = True
3a5ec0c1
SH
117 attrs += color[0]
118 color = color[1:]
119 if extended:
120 return self._convert_terminal_color(fg_bg, attrs, color)
121 try:
122 index = int(color)
77df9d06
SH
123 return self._convert_terminal_color(fg_bg, attrs,
124 WEECHAT_BASIC_COLORS[index][1])
524efe3c 125 except: # noqa: E722
a6630f78 126 print('Error decoding color "%s"' % color)
3a5ec0c1
SH
127 return ''
128
129 def _attrcode_to_char(self, code):
890d4fa2
SH
130 codes = {
131 '\x01': '*',
132 '\x02': '!',
133 '\x03': '/',
134 '\x04': '_',
135 }
3a5ec0c1
SH
136 return codes.get(code, '')
137
138 def _convert_color(self, match):
139 color = match.group(0)
140 if color[0] == '\x19':
141 if color[1] == 'b':
142 # bar code, ignored
143 return ''
144 elif color[1] == '\x1C':
145 # reset
146 return '\x01(Fr)\x01(Br)'
147 elif color[1] in ('F', 'B'):
148 # foreground or background
149 return self._convert_color_attr(color[1], color[2:])
150 elif color[1] == '*':
151 # foreground with optional background
152 items = color[2:].split(',')
153 s = self._convert_color_attr('F', items[0])
154 if len(items) > 1:
155 s += self._convert_color_attr('B', items[1])
156 return s
157 elif color[1] == '@':
158 # direct ncurses pair number, ignored
159 return ''
0e4ce9c9
SH
160 elif color[1] == 'E':
161 # text emphasis, ignored
162 return ''
3a5ec0c1
SH
163 if color[1:].isdigit():
164 return self._convert_weechat_color(int(color[1:]))
165 # color code
166 pass
167 elif color[0] == '\x1A':
168 # set attribute
169 return '\x01(+%s)' % self._attrcode_to_char(color[1])
170 elif color[0] == '\x1B':
171 # remove attribute
172 return '\x01(-%s)' % self._attrcode_to_char(color[1])
173 elif color[0] == '\x1C':
174 # reset
175 return '\x01(Fr)\x01(Br)'
176 # should never be executed!
177 return match.group(0)
178
179 def _convert_color_debug(self, match):
180 group = match.group(0)
181 for code in (0x01, 0x02, 0x03, 0x04, 0x19, 0x1A, 0x1B):
182 group = group.replace(chr(code), '<x%02X>' % code)
183 return group
184
185 def convert(self, text):
186 if not text:
187 return ''
188 if self.debug:
189 return RE_COLOR.sub(self._convert_color_debug, text)
190 else:
191 return RE_COLOR.sub(self._convert_color, text)
192
77df9d06 193
54bcac1f
SH
194def remove(text):
195 """Remove colors in a WeeChat string."""
196 if not text:
197 return ''
198 return re.sub(RE_COLOR, '', text)