names = [f'{expr}.{name}' for name in names]
if self.use_colors:
return self.colorize_matches(names, values)
-
- if prefix:
- names.append(' ')
return names
def _attr_matches(self, text):
return expr, attr, names, values
def colorize_matches(self, names, values):
- matches = [self._color_for_obj(i, name, obj)
- for i, (name, obj)
- in enumerate(zip(names, values))]
- # We add a space at the end to prevent the automatic completion of the
- # common prefix, which is the ANSI escape sequence.
- matches.append(' ')
- return matches
-
- def _color_for_obj(self, i, name, value):
+ return [
+ self._color_for_obj(name, obj)
+ for name, obj in zip(names, values)
+ ]
+
+ def _color_for_obj(self, name, value):
t = type(value)
color = self._color_by_type(t)
- # Encode the match index into a fake escape sequence that
- # stripcolor() can still remove once i reaches four digits.
- N = f"\x1b[{i // 100:03d};{i % 100:02d}m"
- return f"{N}{color}{name}{ANSIColors.RESET}"
+ return f"{color}{name}{ANSIColors.RESET}"
def _color_by_type(self, t):
typename = t.__name__
from rlcompleter import Completer as RLCompleter
from . import commands, historical_reader
-from .completing_reader import CompletingReader
+from .completing_reader import CompletingReader, stripcolor
from .console import Console as ConsoleType
from ._module_completer import ModuleCompleter, make_default_module_completer
from .fancycompleter import Completer as FancyCompleter
break
result.append(next)
state += 1
- # emulate the behavior of the standard readline that sorts
- # the completions before displaying them.
- result.sort()
+ # Emulate readline's sorting using the visible text rather than
+ # the raw ANSI escape sequences used for colorized matches.
+ result.sort(key=stripcolor)
return result, None
def get_module_completions(self) -> tuple[list[str], CompletionAction | None] | None:
self.assertEqual(compl.attr_matches('a.'), ['a.attr', 'a.mro'])
self.assertEqual(
compl.attr_matches('a._'),
- ['a._C__attr__attr', 'a._attr', ' '],
+ ['a._C__attr__attr', 'a._attr'],
)
matches = compl.attr_matches('a.__')
self.assertNotIn('__class__', matches)
break
else:
self.assertFalse(True, matches)
- self.assertIn(' ', matches)
+ self.assertNotIn(' ', matches)
def test_preserves_callable_postfix_for_single_attribute_match(self):
compl = Completer({'os': os}, use_colors=False)
self.assertEqual(compl.global_matches('foo'), ['fooba'])
matches = compl.global_matches('fooba')
- # these are the fake escape sequences which are needed so that
- # readline displays the matches in the proper order
- N0 = f"\x1b[000;00m"
- N1 = f"\x1b[000;01m"
int_color = theme.fancycompleter.int
- self.assertEqual(set(matches), {
- ' ',
- f'{N0}{int_color}foobar{ANSIColors.RESET}',
- f'{N1}{int_color}foobazzz{ANSIColors.RESET}',
- })
+ self.assertEqual(matches, [
+ f'{int_color}foobar{ANSIColors.RESET}',
+ f'{int_color}foobazzz{ANSIColors.RESET}',
+ ])
self.assertEqual(compl.global_matches('foobaz'), ['foobazzz'])
self.assertEqual(compl.global_matches('nothing'), [])
- def test_large_color_sort_prefix_is_stripped(self):
+ def test_colorized_match_is_stripped(self):
compl = Completer({'a': 42}, use_colors=True)
- match = compl._color_for_obj(1000, 'spam', 1)
+ match = compl._color_for_obj('spam', 1)
self.assertEqual(stripcolor(match), 'spam')
def test_complete_with_indexer(self):
compl = Completer({'A': A}, use_colors=False)
#
# In this case, we want to display all attributes which start with
- # 'a'. Moreover, we also include a space to prevent readline to
- # automatically insert the common prefix (which will the the ANSI escape
- # sequence if we use colors).
+ # 'a'.
matches = compl.attr_matches('A.a')
self.assertEqual(
sorted(matches),
- [' ', 'A.aaa', 'A.abc_1', 'A.abc_2', 'A.abc_3'],
+ ['A.aaa', 'A.abc_1', 'A.abc_2', 'A.abc_3'],
)
#
# If there is an actual common prefix, we return just it, so that readline
matches = compl.attr_matches('A.ab')
self.assertEqual(matches, ['A.abc_'])
#
- # Finally, at the next tab, we display again all the completions available
- # for this common prefix. Again, we insert a spurious space to prevent the
- # automatic completion of ANSI sequences.
+ # Finally, at the next tab, we display again all the completions
+ # available for this common prefix.
matches = compl.attr_matches('A.abc_')
self.assertEqual(
sorted(matches),
- [' ', 'A.abc_1', 'A.abc_2', 'A.abc_3'],
+ ['A.abc_1', 'A.abc_2', 'A.abc_3'],
)
def test_complete_exception(self):
code_to_events,
)
from _pyrepl.console import Event
+from _pyrepl.completing_reader import stripcolor
from _pyrepl._module_completer import (
ImportParser,
ModuleCompleter,
self.assertNotIn("banana", menu)
self.assertNotIn("mro", menu)
+ def test_get_completions_sorts_colored_matches_by_visible_text(self):
+ console = FakeConsole(iter(()))
+ config = ReadlineConfig()
+ config.readline_completer = FancyCompleter(
+ {
+ "foo_str": "value",
+ "foo_int": 1,
+ "foo_none": None,
+ },
+ use_colors=True,
+ ).complete
+ reader = ReadlineAlikeReader(console=console, config=config)
+
+ matches, action = reader.get_completions("foo_")
+
+ self.assertIsNone(action)
+ self.assertEqual(
+ [stripcolor(match) for match in matches],
+ ["foo_int", "foo_none", "foo_str"],
+ )
+
class TestPyReplReadlineSetup(TestCase):
def test_setup_ignores_basic_completer_env_when_env_is_disabled(self):