Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 83d6111

Browse files
committed
FEATURE: History: allow session number without trailing slash
Prompted by #15069
1 parent 23cddf7 commit 83d6111

3 files changed

Lines changed: 51 additions & 4 deletions

File tree

IPython/core/history.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1148,13 +1148,15 @@ def __del__(self) -> None:
11481148
self.stop()
11491149

11501150

1151-
# To match, e.g. ~5/8-~2/3
1151+
# To match, e.g. ~5/8-~2/3, or ~4 (without trailing slash for full session)
1152+
# Session numbers: ~N or N/
1153+
# Line numbers: N (just digits, no ~ and no /)
11521154
range_re = re.compile(
11531155
r"""
1154-
((?P<startsess>~?\d+)/)?
1156+
((?P<startsess>(?:~\d+/?|\d+/)))?
11551157
(?P<start>\d+)?
11561158
((?P<sep>[\-:])
1157-
((?P<endsess>~?\d+)/)?
1159+
((?P<endsess>(?:~\d+/?|\d+/)))?
11581160
(?P<end>\d+))?
11591161
$""",
11601162
re.VERBOSE,
@@ -1171,6 +1173,8 @@ def extract_hist_ranges(ranges_str: str) -> Iterable[tuple[int, int, Optional[in
11711173
--------
11721174
>>> list(extract_hist_ranges("~8/5-~7/4 2"))
11731175
[(-8, 5, None), (-7, 1, 5), (0, 2, 3)]
1176+
>>> list(extract_hist_ranges("~4"))
1177+
[(-4, 1, None)] # Full session 4 (trailing / is optional)
11741178
"""
11751179
if ranges_str == "":
11761180
yield (0, 1, None) # Everything from current session
@@ -1197,6 +1201,9 @@ def extract_hist_ranges(ranges_str: str) -> Iterable[tuple[int, int, Optional[in
11971201
end += 1
11981202
startsess = rmatch.group("startsess") or "0"
11991203
endsess = rmatch.group("endsess") or startsess
1204+
# Strip trailing / from session numbers (e.g., "~4/" -> "~4", "4/" -> "4")
1205+
startsess = startsess.rstrip("/")
1206+
endsess = endsess.rstrip("/")
12001207
startsess = int(startsess.replace("~", "-"))
12011208
endsess = int(endsess.replace("~", "-"))
12021209
assert endsess >= startsess, "start session must be earlier than end session"

IPython/core/magics/history.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,12 @@ def history(self, parameter_s = ''):
121121
Line 4, current session
122122
``4-6``
123123
Lines 4-6, current session
124-
``~2/``
124+
``~2/`` or `~2`
125125
All lines of session 2 before current
126126
``243/1-5``
127127
Lines 1-5, session 243
128+
``~2/7``
129+
Line 7, session 2 before current
128130
``~8/1-~6/5``
129131
From the first line of 8 sessions ago, to the fifth line of 6
130132
sessions ago.

tests/test_history.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,44 @@ def test_extract_hist_ranges_empty_str():
209209
assert actual == expected
210210

211211

212+
@pytest.mark.parametrize(
213+
"instr,expected,description",
214+
[
215+
("~4/", [(-4, 1, None)], "with trailing slash"),
216+
("~4", [(-4, 1, None)], "without trailing slash"),
217+
("~4/1-5", [(-4, 1, 6)], "with line ranges and trailing slash"),
218+
(
219+
"~4 ~5/ ~6/1-3",
220+
[(-4, 1, None), (-5, 1, None), (-6, 1, 4)],
221+
"multiple sessions with mixed syntax",
222+
),
223+
("~10 ~20/", [(-10, 1, None), (-20, 1, None)], "larger session numbers"),
224+
("~1", [(-1, 1, None)], "single digit session without slash"),
225+
("~1/", [(-1, 1, None)], "single digit session with slash"),
226+
("~2", [(-2, 1, None)], "backward compatibility without slash"),
227+
("~2/", [(-2, 1, None)], "backward compatibility with slash"),
228+
],
229+
)
230+
def test_extract_hist_ranges_without_trailing_slash(instr, expected, description):
231+
"""Test that ~N (without trailing slash) works and is backward compatible with ~N/"""
232+
actual = list(extract_hist_ranges(instr))
233+
assert (
234+
actual == expected
235+
), f"Failed for '{instr}' ({description}): expected {expected}, got {actual}"
236+
237+
238+
def test_extract_hist_ranges_backward_compatibility():
239+
"""Test that ~N and ~N/ produce identical results (backward compatibility)"""
240+
test_cases = ["~4", "~5", "~10", "~20"]
241+
for case in test_cases:
242+
result_no_slash = list(extract_hist_ranges(case))
243+
result_with_slash = list(extract_hist_ranges(case + "/"))
244+
assert result_no_slash == result_with_slash, (
245+
f"{case} and {case}/ should produce the same result, "
246+
f"got {result_no_slash} vs {result_with_slash}"
247+
)
248+
249+
212250
def test_magic_rerun():
213251
"""Simple test for %rerun (no args -> rerun last line)"""
214252
ip = get_ipython()

0 commit comments

Comments
 (0)