Skip to content
2 changes: 1 addition & 1 deletion Lib/_pyrepl/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ class unix_word_rubout(KillCommand):
def do(self) -> None:
r = self.reader
for i in range(r.get_arg()):
self.kill_range(r.bow(), r.pos)
self.kill_range(r.bow_ws(), r.pos)


class kill_word(KillCommand):
Expand Down
17 changes: 17 additions & 0 deletions Lib/_pyrepl/reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,23 @@ def bow(self, p: int | None = None) -> int:
p -= 1
return p + 1

def bow_ws(self, p: int | None = None) -> int:
"""Return the 0-based index of the whitespace-delimited word break
preceding p most immediately.

p defaults to self.pos; only whitespace is considered a word
boundary, matching the behavior of unix-word-rubout in bash/readline.
"""
if p is None:
p = self.pos
b = self.buffer
p -= 1
while p >= 0 and b[p] in " \n\t":
p -= 1
while p >= 0 and b[p] not in " \n\t":
p -= 1
return p + 1

def eow(self, p: int | None = None) -> int:
"""Return the 0-based index of the word break following p most
immediately.
Expand Down
35 changes: 35 additions & 0 deletions Lib/test/test_pyrepl/test_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,41 @@ def test_setpos_from_xy_for_non_printing_char(self):
reader.setpos_from_xy(8, 0)
self.assertEqual(reader.pos, 7)

def test_bow_ws_stops_at_whitespace(self):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are the existing tests for bow()? id there are some, please put those tests next to them

# See https://github.com/python/cpython/issues/146044.
reader = prepare_reader(prepare_console([]))
reader.buffer = list("foo.bar baz")
reader.pos = len(reader.buffer)
self.assertEqual(reader.bow_ws(), 8)

def test_bow_ws_includes_punctuation_in_word(self):
reader = prepare_reader(prepare_console([]))
buf = "foo.bar(baz) qux"
reader.buffer = list(buf)
reader.pos = buf.index(")") + 1
self.assertEqual(reader.bow_ws(), 0)

def test_bow_vs_bow_ws(self):
reader = prepare_reader(prepare_console([]))
reader.buffer = list("foo.bar")
reader.pos = len(reader.buffer)
# bow() stops at '.' so we return the index of 'b' in "bar"
self.assertEqual(reader.bow(), 4)
# bow_ws() treats entire "foo.bar" as one word
self.assertEqual(reader.bow_ws(), 0)

def test_bow_ws_with_tabs(self):
reader = prepare_reader(prepare_console([]))
reader.buffer = list("foo\tbar")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add tests with \n and ensure that you also properly jump lines.

reader.pos = len(reader.buffer)
self.assertEqual(reader.bow_ws(), 4)

Comment on lines +388 to +389
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self.assertEqual(reader.bow_ws(), 4)
self.assertEqual(reader.bow_ws(), 4)

def test_bow_ws_with_newlines(self):
reader = prepare_reader(prepare_console([]))
reader.buffer = list("foo\nbar")
reader.pos = len(reader.buffer)
self.assertEqual(reader.bow_ws(), 4)

@force_colorized_test_class
class TestReaderInColor(ScreenEqualMixin, TestCase):
def test_syntax_highlighting_basic(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix ``unix-word-rubout`` (Ctrl-W) in the REPL to use whitespace-only word
boundaries, matching behavior of the basic REPL. Previously it used
syntax-table boundaries which treated punctuation as word separators.
Loading