Skip to content

Commit 4c3ad9c

Browse files
committed
now that we have themes, we can kill the config object
1 parent 38e8a08 commit 4c3ad9c

File tree

3 files changed

+74
-68
lines changed

3 files changed

+74
-68
lines changed

Lib/_colorize.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,30 @@ class Unittest(ThemeSection):
219219
reset: str = ANSIColors.RESET
220220

221221

222+
@dataclass(frozen=True, kw_only=True)
223+
class FancyCompleter(ThemeSection):
224+
# functions and methods
225+
function: str = ANSIColors.BOLD_BLUE
226+
builtin_function_or_method: str = ANSIColors.BOLD_BLUE
227+
method: str = ANSIColors.BOLD_CYAN
228+
method_wrapper: str = ANSIColors.BOLD_CYAN
229+
wrapper_descriptor: str = ANSIColors.BOLD_CYAN
230+
method_descriptor: str = ANSIColors.BOLD_CYAN
231+
232+
# numbers
233+
int: str = ANSIColors.BOLD_YELLOW
234+
float: str = ANSIColors.BOLD_YELLOW
235+
complex: str = ANSIColors.BOLD_YELLOW
236+
bool: str = ANSIColors.BOLD_YELLOW
237+
238+
# others
239+
type: str = ANSIColors.BOLD_MAGENTA
240+
module: str = ANSIColors.CYAN
241+
NoneType: str = ANSIColors.GREY
242+
str: str = ANSIColors.BOLD_GREEN
243+
bytes: str = ANSIColors.BOLD_GREEN
244+
245+
222246
@dataclass(frozen=True, kw_only=True)
223247
class Theme:
224248
"""A suite of themes for all sections of Python.
@@ -231,6 +255,7 @@ class Theme:
231255
syntax: Syntax = field(default_factory=Syntax)
232256
traceback: Traceback = field(default_factory=Traceback)
233257
unittest: Unittest = field(default_factory=Unittest)
258+
fancycompleter: FancyCompleter = field(default_factory=FancyCompleter)
234259

235260
def copy_with(
236261
self,
@@ -240,6 +265,7 @@ def copy_with(
240265
syntax: Syntax | None = None,
241266
traceback: Traceback | None = None,
242267
unittest: Unittest | None = None,
268+
fancycompleter: FancyCompleter | None = None,
243269
) -> Self:
244270
"""Return a new Theme based on this instance with some sections replaced.
245271
@@ -252,6 +278,7 @@ def copy_with(
252278
syntax=syntax or self.syntax,
253279
traceback=traceback or self.traceback,
254280
unittest=unittest or self.unittest,
281+
fancycompleter=fancycompleter or self.fancycompleter,
255282
)
256283

257284
@classmethod
@@ -268,6 +295,7 @@ def no_colors(cls) -> Self:
268295
syntax=Syntax.no_colors(),
269296
traceback=Traceback.no_colors(),
270297
unittest=Unittest.no_colors(),
298+
fancycompleter=FancyCompleter.no_colors(),
271299
)
272300

273301

Lib/_pyrepl/fancycompleter.py

Lines changed: 27 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,44 +5,11 @@
55
"""
66
Colorful tab completion for Python prompt
77
"""
8-
from _colorize import ANSIColors, get_colors
8+
from _colorize import ANSIColors, get_colors, get_theme
99
import rlcompleter
1010
import types
1111
import keyword
1212

13-
class DefaultConfig:
14-
15-
consider_getitems = True
16-
use_colors = 'auto'
17-
18-
color_by_type = {
19-
types.BuiltinMethodType: ANSIColors.BOLD_CYAN,
20-
types.MethodType: ANSIColors.BOLD_CYAN,
21-
type((42).__add__): ANSIColors.BOLD_CYAN,
22-
type(int.__add__): ANSIColors.BOLD_CYAN,
23-
type(str.replace): ANSIColors.BOLD_CYAN,
24-
25-
types.FunctionType: ANSIColors.BOLD_BLUE,
26-
types.BuiltinFunctionType: ANSIColors.BOLD_BLUE,
27-
28-
type: ANSIColors.BOLD_MAGENTA,
29-
30-
types.ModuleType: ANSIColors.CYAN,
31-
types.NoneType: ANSIColors.GREY,
32-
str: ANSIColors.BOLD_GREEN,
33-
bytes: ANSIColors.BOLD_GREEN,
34-
int: ANSIColors.BOLD_YELLOW,
35-
float: ANSIColors.BOLD_YELLOW,
36-
complex: ANSIColors.BOLD_YELLOW,
37-
bool: ANSIColors.BOLD_YELLOW,
38-
}
39-
40-
def setup(self):
41-
if self.use_colors == 'auto':
42-
colors = get_colors()
43-
self.use_colors = colors.RED != ""
44-
45-
4613
class Completer(rlcompleter.Completer):
4714
"""
4815
When doing someting like a.b.<tab>, display only the attributes of
@@ -51,15 +18,24 @@ class Completer(rlcompleter.Completer):
5118
Optionally, display the various completions in different colors
5219
depending on the type.
5320
"""
54-
def __init__(self, namespace=None, Config=DefaultConfig):
21+
def __init__(
22+
self,
23+
namespace=None,
24+
*,
25+
use_colors='auto',
26+
consider_getitems=True,
27+
):
5528
from _pyrepl import readline
5629
rlcompleter.Completer.__init__(self, namespace)
57-
self.config = Config()
58-
self.config.setup()
30+
if use_colors == 'auto':
31+
# use colors only if we can
32+
use_colors = get_colors().RED != ""
33+
self.use_colors = use_colors
34+
self.consider_getitems = consider_getitems
5935

60-
if self.config.use_colors:
36+
if self.use_colors:
6137
readline.parse_and_bind('set dont-escape-ctrl-chars on')
62-
if self.config.consider_getitems:
38+
if self.consider_getitems:
6339
delims = readline.get_completer_delims()
6440
delims = delims.replace('[', '')
6541
delims = delims.replace(']', '')
@@ -94,7 +70,7 @@ def global_matches(self, text):
9470
values.append(eval(name, self.namespace))
9571
except Exception as exc:
9672
values.append(None)
97-
if self.config.use_colors and names:
73+
if self.use_colors and names:
9874
return self.colorize_matches(names, values)
9975
return names
10076

@@ -153,7 +129,7 @@ def attr_matches(self, text):
153129
if prefix and prefix != attr:
154130
return [f'{expr}.{prefix}'] # autocomplete prefix
155131

156-
if self.config.use_colors:
132+
if self.use_colors:
157133
return self.colorize_matches(names, values)
158134

159135
if prefix:
@@ -170,12 +146,21 @@ def colorize_matches(self, names, values):
170146

171147
def color_for_obj(self, i, name, value):
172148
t = type(value)
173-
color = self.config.color_by_type.get(t, ANSIColors.RESET)
149+
color = self.color_by_type(t)
174150
# hack: prepend an (increasing) fake escape sequence,
175151
# so that readline can sort the matches correctly.
176152
N = f"\x1b[{i:03d};00m"
177153
return f"{N}{color}{name}{ANSIColors.RESET}"
178154

155+
def color_by_type(self, t):
156+
theme = get_theme()
157+
typename = t.__name__
158+
# this is needed e.g. to turn method-wrapper into method_wrapper,
159+
# because if we want _colorize.FancyCompleter to be "dataclassable"
160+
# our keys need to be valid identifiers.
161+
typename = typename.replace('-', '_').replace('.', '_')
162+
return getattr(theme.fancycompleter, typename, ANSIColors.RESET)
163+
179164

180165
def commonprefix(names, base=''):
181166
"""Return the common prefix of all 'names' starting with 'base'"""

Lib/test/test_pyrepl/test_fancycompleter.py

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,7 @@
11
import unittest
22

3-
from _colorize import ANSIColors
4-
from _pyrepl.fancycompleter import Completer, DefaultConfig, commonprefix
5-
6-
7-
class ConfigForTest(DefaultConfig):
8-
use_colors = False
9-
10-
class ColorConfig(DefaultConfig):
11-
use_colors = True
3+
from _colorize import ANSIColors, get_theme
4+
from _pyrepl.fancycompleter import Completer, commonprefix
125

136
class MockPatch:
147
def __init__(self):
@@ -41,7 +34,7 @@ def test_commonprefix(self):
4134
self.assertEqual(commonprefix(['aaa', 'bbb'], base='x'), '')
4235

4336
def test_complete_attribute(self):
44-
compl = Completer({'a': None}, ConfigForTest)
37+
compl = Completer({'a': None}, use_colors=False)
4538
self.assertEqual(compl.attr_matches('a.'), ['a.__'])
4639
matches = compl.attr_matches('a.__')
4740
self.assertNotIn('a.__class__', matches)
@@ -53,23 +46,23 @@ class C(object):
5346
attr = 1
5447
_attr = 2
5548
__attr__attr = 3
56-
compl = Completer({'a': C}, ConfigForTest)
49+
compl = Completer({'a': C}, use_colors=False)
5750
self.assertEqual(compl.attr_matches('a.'), ['attr', 'mro'])
5851
self.assertEqual(compl.attr_matches('a._'), ['_C__attr__attr', '_attr', ' '])
5952
matches = compl.attr_matches('a.__')
6053
self.assertNotIn('a.__class__', matches)
6154
self.assertIn('__class__', matches)
6255
self.assertEqual(compl.attr_matches('a.__class'), ['a.__class__'])
6356

64-
compl = Completer({'a': None}, ConfigForTest)
57+
compl = Completer({'a': None}, use_colors=False)
6558
self.assertEqual(compl.attr_matches('a._'), ['a.__'])
6659

6760
def test_complete_attribute_colored(self):
68-
compl = Completer({'a': 42}, ColorConfig)
61+
theme = get_theme()
62+
compl = Completer({'a': 42}, use_colors=True)
6963
matches = compl.attr_matches('a.__')
7064
self.assertGreater(len(matches), 2)
71-
expected_color = compl.config.color_by_type.get(type(compl.__class__))
72-
self.assertEqual(expected_color, ANSIColors.BOLD_MAGENTA)
65+
expected_color = theme.fancycompleter.type
7366
expected_part = f'{expected_color}__class__{ANSIColors.RESET}'
7467
for match in matches:
7568
if expected_part in match:
@@ -80,28 +73,28 @@ def test_complete_attribute_colored(self):
8073

8174
def test_complete_colored_single_match(self):
8275
"""No coloring, via commonprefix."""
83-
compl = Completer({'foobar': 42}, ColorConfig)
76+
compl = Completer({'foobar': 42}, use_colors=True)
8477
matches = compl.global_matches('foob')
8578
self.assertEqual(matches, ['foobar'])
8679

8780
def test_does_not_color_single_match(self):
8881
class obj:
8982
msgs = []
9083

91-
compl = Completer({'obj': obj}, ColorConfig)
84+
compl = Completer({'obj': obj}, use_colors=True)
9285
matches = compl.attr_matches('obj.msgs')
9386
self.assertEqual(matches, ['obj.msgs'])
9487

9588
def test_complete_global(self):
96-
compl = Completer({'foobar': 1, 'foobazzz': 2}, ConfigForTest)
89+
compl = Completer({'foobar': 1, 'foobazzz': 2}, use_colors=False)
9790
self.assertEqual(compl.global_matches('foo'), ['fooba'])
9891
matches = compl.global_matches('fooba')
9992
self.assertEqual(set(matches), set(['foobar', 'foobazzz']))
10093
self.assertEqual(compl.global_matches('foobaz'), ['foobazzz'])
10194
self.assertEqual(compl.global_matches('nothing'), [])
10295

10396
def test_complete_global_colored(self):
104-
compl = Completer({'foobar': 1, 'foobazzz': 2}, ColorConfig)
97+
compl = Completer({'foobar': 1, 'foobazzz': 2}, use_colors=True)
10598
self.assertEqual(compl.global_matches('foo'), ['fooba'])
10699
matches = compl.global_matches('fooba')
107100

@@ -119,7 +112,7 @@ def test_complete_global_colored(self):
119112
self.assertEqual(compl.global_matches('nothing'), [])
120113

121114
def test_complete_global_colored_exception(self):
122-
compl = Completer({'tryme': 42}, ColorConfig)
115+
compl = Completer({'tryme': 42}, use_colors=True)
123116
N0 = f"\x1b[000;00m"
124117
N1 = f"\x1b[001;00m"
125118
self.assertEqual(compl.global_matches('try'), [
@@ -129,7 +122,7 @@ def test_complete_global_colored_exception(self):
129122
])
130123

131124
def test_complete_with_indexer(self):
132-
compl = Completer({'lst': [None, 2, 3]}, ConfigForTest)
125+
compl = Completer({'lst': [None, 2, 3]}, use_colors=False)
133126
self.assertEqual(compl.attr_matches('lst[0].'), ['lst[0].__'])
134127
matches = compl.attr_matches('lst[0].__')
135128
self.assertNotIn('lst[0].__class__', matches)
@@ -143,7 +136,7 @@ class A:
143136
abc_2 = None
144137
abc_3 = None
145138
bbb = None
146-
compl = Completer({'A': A}, ConfigForTest)
139+
compl = Completer({'A': A}, use_colors=False)
147140
#
148141
# In this case, we want to display all attributes which start with
149142
# 'a'. Moreover, we also include a space to prevent readline to
@@ -164,23 +157,23 @@ class A:
164157
self.assertEqual(sorted(matches), [' ', 'abc_1', 'abc_2', 'abc_3'])
165158

166159
def test_complete_exception(self):
167-
compl = Completer({}, ConfigForTest)
160+
compl = Completer({}, use_colors=False)
168161
self.assertEqual(compl.attr_matches('xxx.'), [])
169162

170163
def test_complete_invalid_attr(self):
171-
compl = Completer({'str': str}, ConfigForTest)
164+
compl = Completer({'str': str}, use_colors=False)
172165
self.assertEqual(compl.attr_matches('str.xx'), [])
173166

174167
def test_complete_function_skipped(self):
175-
compl = Completer({'str': str}, ConfigForTest)
168+
compl = Completer({'str': str}, use_colors=False)
176169
self.assertEqual(compl.attr_matches('str.split().'), [])
177170

178171
def test_unicode_in___dir__(self):
179172
class Foo(object):
180173
def __dir__(self):
181174
return ['hello', 'world']
182175

183-
compl = Completer({'a': Foo()}, ConfigForTest)
176+
compl = Completer({'a': Foo()}, use_colors=False)
184177
matches = compl.attr_matches('a.')
185178
self.assertEqual(matches, ['hello', 'world'])
186179
self.assertIs(type(matches[0]), str)

0 commit comments

Comments
 (0)