Skip to content

Commit c0cb4aa

Browse files
committed
Improve behavior when both submodules + prompt
1 parent 699848b commit c0cb4aa

File tree

2 files changed

+42
-25
lines changed

2 files changed

+42
-25
lines changed

Lib/_pyrepl/completing_reader.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -188,14 +188,13 @@ def do(self) -> None:
188188
if not r.cmpltn_action:
189189
r.error("no matches")
190190
elif len(completions) == 1:
191-
if not r.cmpltn_action:
192-
if completions_unchangable and len(completions[0]) == len(stem):
193-
r.msg = "[ sole completion ]"
194-
r.dirty = True
195-
r.insert(completions[0][len(stem):])
191+
if completions_unchangable and len(completions[0]) == len(stem):
192+
r.msg = "[ sole completion ]"
193+
r.dirty = True
194+
r.insert(completions[0][len(stem):])
196195
else:
197196
p = prefix(completions, len(stem))
198-
if p and not r.cmpltn_action:
197+
if p:
199198
r.insert(p)
200199
if last_is_completer:
201200
r.cmpltn_menu_visible = True
@@ -214,12 +213,14 @@ def do(self) -> None:
214213
r.dirty = True
215214

216215
if r.cmpltn_action:
217-
if r.msg:
218-
r.msg += "\n" + r.cmpltn_action[0]
216+
if r.msg and r.cmpltn_message_visible:
217+
# There is already a message (eg. [ not unique ]) that
218+
# would conflict for next tab: cancel action
219+
r.cmpltn_action = None
219220
else:
220221
r.msg = r.cmpltn_action[0]
221-
r.cmpltn_message_visible = True
222-
r.dirty = True
222+
r.cmpltn_message_visible = True
223+
r.dirty = True
223224

224225

225226
class self_insert(commands.self_insert):

Lib/test/test_pyrepl/test_pyrepl.py

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,28 +1136,42 @@ def test_attribute_completion_module_on_demand(self):
11361136
dir = pathlib.Path(_dir)
11371137
(dir / "foo.py").write_text("bar = 42")
11381138
(dir / "pack").mkdir()
1139-
(dir / "pack" / "__init__.py").touch()
1139+
(dir / "pack" / "__init__.py").write_text("attr = 42")
1140+
(dir / "pack" / "foo.py").touch()
11401141
(dir / "pack" / "bar.py").touch()
1142+
(dir / "pack" / "baz.py").touch()
11411143
with patch.object(sys, "path", [_dir, *sys.path]):
11421144
cases = (
1143-
("from foo import \t\n", "from foo import ", False),
1144-
("from foo import \t\t\n", "from foo import bar", True),
1145-
("from foo import ba\t\n", "from foo import ba", False),
1146-
("from foo import ba\t\t\n", "from foo import bar", True),
1147-
("from foo import \tb\ta\t\n", "from foo import ba", False),
1148-
# only one suggestion but message: do not complete
1149-
("from pack import \t\n", "from pack import ", False),
1145+
# needs 2 tabs to import (show prompt, then import)
1146+
("from foo import \t\n", "from foo import ", set()),
1147+
("from foo import \t\t\n", "from foo import bar", {"foo"}),
1148+
("from foo import ba\t\n", "from foo import ba", set()),
1149+
("from foo import ba\t\t\n", "from foo import bar", {"foo"}),
1150+
# reset if a character is inserted between tabs
1151+
("from foo import \tb\ta\t\n", "from foo import ba", set()),
1152+
# packages: needs 3 tabs ([ not unique ], prompt, import)
1153+
("from pack import \t\t\n", "from pack import ", set()),
1154+
("from pack import \t\t\t\n", "from pack import ", {"pack"}),
1155+
("from pack import \t\t\ta\t\n", "from pack import attr", {"pack"}),
1156+
# one match: needs 2 tabs (insert + show prompt, import)
1157+
("from pack import f\t\n", "from pack import foo", set()),
1158+
("from pack import f\t\t\n", "from pack import foo", {"pack"}),
1159+
# common prefix: needs 3 tabs (insert + [ not unique ], prompt, import)
1160+
("from pack import b\t\n", "from pack import ba", set()),
1161+
("from pack import b\t\t\n", "from pack import ba", set()),
1162+
("from pack import b\t\t\t\n", "from pack import ba", {"pack"}),
11501163
)
1151-
for code, expected, is_foo_imported in cases:
1152-
with self.subTest(code=code):
1164+
for code, expected, expected_imports in cases:
1165+
with self.subTest(code=code), patch.dict(sys.modules):
1166+
_imported = set(sys.modules.keys())
11531167
events = code_to_events(code)
11541168
reader = self.prepare_reader(events, namespace={})
11551169
output = reader.readline()
11561170
self.assertEqual(output, expected)
1157-
self.assertEqual("foo" in sys.modules, is_foo_imported)
1158-
if is_foo_imported:
1159-
del sys.modules["foo"]
1171+
new_imports = sys.modules.keys() - _imported
1172+
self.assertEqual(new_imports, expected_imports)
11601173

1174+
@patch.dict(sys.modules)
11611175
def test_attribute_completion_error_on_import(self):
11621176
with tempfile.TemporaryDirectory() as _dir:
11631177
dir = pathlib.Path(_dir)
@@ -1175,7 +1189,7 @@ def test_attribute_completion_error_on_import(self):
11751189
output = reader.readline()
11761190
self.assertEqual(output, expected)
11771191
self.assertNotIn("boom", sys.modules)
1178-
del sys.modules["foo"]
1192+
11791193

11801194
def test_attribute_completion_error_on_attributes_access(self):
11811195
class BrokenModule:
@@ -1191,6 +1205,7 @@ def __dir__(self):
11911205
# ignore attributes, just propose submodule
11921206
self.assertEqual(output, "from boom import submodule")
11931207

1208+
@patch.dict(sys.modules)
11941209
def test_attribute_completion_private_and_invalid_names(self):
11951210
with tempfile.TemporaryDirectory() as _dir:
11961211
dir = pathlib.Path(_dir)
@@ -1209,7 +1224,7 @@ def test_attribute_completion_private_and_invalid_names(self):
12091224
reader = self.prepare_reader(events, namespace={})
12101225
output = reader.readline()
12111226
self.assertEqual(output, expected)
1212-
del sys.modules["foo"]
1227+
12131228

12141229
def test_get_path_and_prefix(self):
12151230
cases = (
@@ -1399,6 +1414,7 @@ def test_suggestions_and_messages(self) -> None:
13991414
self.addCleanup(sys.modules.pop, mod)
14001415

14011416
class TestHardcodedSubmodules(TestCase):
1417+
@patch.dict(sys.modules)
14021418
def test_hardcoded_stdlib_submodules_are_importable(self):
14031419
for parent_path, submodules in HARDCODED_SUBMODULES.items():
14041420
for module_name in submodules:

0 commit comments

Comments
 (0)