Skip to content

Commit 0b29824

Browse files
committed
fixup! Add news entry
1 parent 04b5d42 commit 0b29824

4 files changed

Lines changed: 67 additions & 36 deletions

File tree

Lib/_pyrepl/_module_completer.py

Lines changed: 47 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -72,32 +72,43 @@ def __init__(self, namespace: Mapping[str, Any] | None = None) -> None:
7272
self._curr_sys_path: list[str] = sys.path[:]
7373
self._stdlib_path = os.path.dirname(importlib.__path__[0])
7474

75-
def get_completions(self, line: str) -> tuple[list[str], list[Any], CompletionAction | None] | None:
75+
def get_completions(
76+
self, line: str, *, include_values: bool = True
77+
) -> tuple[list[str], list[Any], CompletionAction | None] | None:
7678
"""Return the next possible import completions for 'line'.
7779
7880
For attributes completion, if the module to complete from is not
7981
imported, also return an action (prompt + callback to run if the
8082
user press TAB again) to import the module.
83+
84+
If *include_values* is false, the returned values list is empty and
85+
attribute values are not resolved.
8186
"""
8287
result = ImportParser(line).parse()
8388
if not result:
8489
return None
8590
try:
86-
return self.complete(*result)
91+
return self.complete(*result, include_values=include_values)
8792
except Exception:
8893
# Some unexpected error occurred, make it look like
8994
# no completions are available
9095
return [], [], None
9196

92-
def complete(self, from_name: str | None, name: str | None) -> tuple[list[str], list[Any], CompletionAction | None]:
97+
def complete(
98+
self,
99+
from_name: str | None,
100+
name: str | None,
101+
*,
102+
include_values: bool = True,
103+
) -> tuple[list[str], list[Any], CompletionAction | None]:
93104
if from_name is None:
94105
# import x.y.z<tab>
95106
assert name is not None
96107
path, prefix = self.get_path_and_prefix(name)
97108
modules = self.find_modules(path, prefix)
98109
names = [self.format_completion(path, module) for module in modules]
99110
# These are always modules, use dummy values to get the right color
100-
values = [sys] * len(names)
111+
values = [sys] * len(names) if include_values else []
101112
return names, values, None
102113

103114
if name is None:
@@ -106,18 +117,22 @@ def complete(self, from_name: str | None, name: str | None) -> tuple[list[str],
106117
modules = self.find_modules(path, prefix)
107118
names = [self.format_completion(path, module) for module in modules]
108119
# These are always modules, use dummy values to get the right color
109-
values = [sys] * len(names)
120+
values = [sys] * len(names) if include_values else []
110121
return names, values, None
111122

112123
# from x.y import z<tab>
113124
submodules = self.find_modules(from_name, name)
114-
attr_names, attr_values, action = self.find_attributes(from_name, name)
125+
attr_names, attr_module, action = self._find_attributes(from_name, name)
115126
all_names = sorted({*submodules, *attr_names})
127+
if not include_values:
128+
return all_names, [], action
129+
116130
# Build values list matching the sorted order:
117131
# submodules use `sys` as a dummy value so they get the 'module' color,
118132
# attributes use their actual value.
119-
submodule_set = set(submodules)
120-
attr_map = dict(zip(attr_names, attr_values))
133+
attr_map = {}
134+
if attr_module is not None:
135+
attr_map = {n: safe_getattr(attr_module, n) for n in attr_names}
121136
all_values = [attr_map[n] if n in attr_map else sys for n in all_names]
122137
return all_names, all_values, action
123138

@@ -180,43 +195,43 @@ def _is_stdlib_module(self, module_info: pkgutil.ModuleInfo) -> bool:
180195
return (isinstance(module_info.module_finder, FileFinder)
181196
and module_info.module_finder.path == self._stdlib_path)
182197

183-
def find_attributes(self, path: str, prefix: str) -> tuple[list[str], list[Any], CompletionAction | None]:
198+
def find_attributes(
199+
self, path: str, prefix: str
200+
) -> tuple[list[str], list[Any], CompletionAction | None]:
184201
"""Find all attributes of module 'path' that start with 'prefix'."""
185-
attributes, values, action = self._find_attributes(path, prefix)
186-
# Filter out invalid attribute names
187-
# (for example those containing dashes that cannot be imported with 'import')
188-
filtered_names = []
189-
filtered_values = []
190-
for attr, val in zip(attributes, values):
191-
if attr.isidentifier():
192-
filtered_names.append(attr)
193-
filtered_values.append(val)
194-
return filtered_names, filtered_values, action
195-
196-
def _find_attributes(self, path: str, prefix: str) -> tuple[list[str], list[Any], CompletionAction | None]:
202+
attributes, module, action = self._find_attributes(path, prefix)
203+
if module is not None:
204+
values = [safe_getattr(module, attr) for attr in attributes]
205+
else:
206+
values = []
207+
return attributes, values, action
208+
209+
def _find_attributes(
210+
self, path: str, prefix: str
211+
) -> tuple[list[str], ModuleType | None, CompletionAction | None]:
197212
path = self._resolve_relative_path(path) # type: ignore[assignment]
198213
if path is None:
199-
return [], [], None
214+
return [], None, None
200215

201216
imported_module = sys.modules.get(path)
202217
if not imported_module:
203218
if path in self._failed_imports: # Do not propose to import again
204-
return [], [], None
219+
return [], None, None
205220
imported_module = self._maybe_import_module(path)
206221
if not imported_module:
207-
return [], [], self._get_import_completion_action(path)
222+
return [], None, self._get_import_completion_action(path)
208223
try:
209224
module_attributes = dir(imported_module)
210225
except Exception:
211226
module_attributes = []
212-
names = []
213-
values = []
214-
for attr_name in module_attributes:
215-
if not self.is_suggestion_match(attr_name, prefix):
216-
continue
217-
names.append(attr_name)
218-
values.append(safe_getattr(imported_module, attr_name))
219-
return names, values, None
227+
# Filter out invalid attribute names, such as dashes that cannot be
228+
# imported with 'import'.
229+
names = [
230+
attr_name for attr_name in module_attributes
231+
if (self.is_suggestion_match(attr_name, prefix)
232+
and attr_name.isidentifier())
233+
]
234+
return names, imported_module, None
220235

221236
def is_suggestion_match(self, module_name: str, prefix: str) -> bool:
222237
if prefix:

Lib/_pyrepl/fancycompleter.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
#
44
# All Rights Reserved
55
"""Colorful tab completion for Python prompt"""
6+
from __future__ import annotations
7+
68
from _colorize import ANSIColors, get_colors, get_theme
79
import rlcompleter
810
import keyword

Lib/_pyrepl/readline.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,12 +171,15 @@ def get_completions(self, stem: str) -> tuple[list[str], CompletionAction | None
171171

172172
def get_module_completions(self) -> tuple[list[str], CompletionAction | None] | None:
173173
line = stripcolor(self.get_line())
174-
result = self.config.module_completer.get_completions(line)
174+
colorize_completions = self.config.colorize_completions
175+
result = self.config.module_completer.get_completions(
176+
line, include_values=bool(colorize_completions)
177+
)
175178
if result is None:
176179
return None
177180
names, values, action = result
178-
if self.config.colorize_completions:
179-
names = self.config.colorize_completions(names, values)
181+
if colorize_completions:
182+
names = colorize_completions(names, values)
180183
return names, action
181184

182185
def get_trimmed_history(self, maxlength: int) -> list[str]:

Lib/test/test_pyrepl/test_fancycompleter.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
import importlib
2+
import inspect
23
import os
34
import types
45
import unittest
56

67
from _colorize import ANSIColors, get_theme
78
from _pyrepl.completing_reader import stripcolor
8-
from _pyrepl.fancycompleter import Completer, commonprefix, _color_for_obj
9+
from _pyrepl.fancycompleter import (
10+
Completer,
11+
colorize_matches,
12+
commonprefix,
13+
_color_for_obj,
14+
)
915
from test.support.import_helper import ready_to_import
1016

1117
class MockPatch:
@@ -36,6 +42,11 @@ def test_commonprefix(self):
3642
self.assertEqual(commonprefix(['isalpha', 'isdigit']), 'is')
3743
self.assertEqual(commonprefix([]), '')
3844

45+
def test_colorize_matches_signature(self):
46+
signature = inspect.signature(colorize_matches)
47+
48+
self.assertEqual(list(signature.parameters), ["names", "values", "theme"])
49+
3950
def test_complete_attribute(self):
4051
compl = Completer({'a': None}, use_colors=False)
4152
self.assertEqual(compl.attr_matches('a.'), ['a.__'])

0 commit comments

Comments
 (0)