@@ -71,6 +71,18 @@ def do(self) -> None:
71
71
r .select_item (r .historyi - 1 )
72
72
73
73
74
+ class history_search_backward (commands .Command ):
75
+ def do (self ) -> None :
76
+ r = self .reader
77
+ r .search_next (forwards = False )
78
+
79
+
80
+ class history_search_forward (commands .Command ):
81
+ def do (self ) -> None :
82
+ r = self .reader
83
+ r .search_next (forwards = True )
84
+
85
+
74
86
class restore_history (commands .Command ):
75
87
def do (self ) -> None :
76
88
r = self .reader
@@ -234,6 +246,8 @@ def __post_init__(self) -> None:
234
246
isearch_forwards ,
235
247
isearch_backwards ,
236
248
operate_and_get_next ,
249
+ history_search_backward ,
250
+ history_search_forward ,
237
251
]:
238
252
self .commands [c .__name__ ] = c
239
253
self .commands [c .__name__ .replace ("_" , "-" )] = c
@@ -251,8 +265,8 @@ def collect_keymap(self) -> tuple[tuple[KeySpec, CommandName], ...]:
251
265
(r"\C-s" , "forward-history-isearch" ),
252
266
(r"\M-r" , "restore-history" ),
253
267
(r"\M-." , "yank-arg" ),
254
- (r"\<page down>" , "last- history" ),
255
- (r"\<page up>" , "first- history" ),
268
+ (r"\<page down>" , "history-search-forward " ),
269
+ (r"\<page up>" , "history-search-backward " ),
256
270
)
257
271
258
272
def select_item (self , i : int ) -> None :
@@ -305,6 +319,59 @@ def get_prompt(self, lineno: int, cursor_on_line: bool) -> str:
305
319
else :
306
320
return super ().get_prompt (lineno , cursor_on_line )
307
321
322
+ def search_next (self , * , forwards : bool ) -> None :
323
+ """Search history for the current line contents up to the cursor.
324
+
325
+ Selects the first item found. If nothing is under the cursor, any next
326
+ item in history is selected.
327
+ """
328
+ pos = self .pos
329
+ s = self .get_unicode ()
330
+ history_index = self .historyi
331
+
332
+ # In multiline contexts, we're only interested in the current line.
333
+ nl_index = s .rfind ('\n ' , 0 , pos )
334
+ prefix = s [nl_index + 1 :pos ]
335
+ pos = len (prefix )
336
+
337
+ match_prefix = len (prefix )
338
+ len_item = 0
339
+ if history_index < len (self .history ):
340
+ len_item = len (self .get_item (history_index ))
341
+ if len_item and pos == len_item :
342
+ match_prefix = False
343
+ elif not pos :
344
+ match_prefix = False
345
+
346
+ while 1 :
347
+ if forwards :
348
+ out_of_bounds = history_index >= len (self .history ) - 1
349
+ else :
350
+ out_of_bounds = history_index == 0
351
+ if out_of_bounds :
352
+ if forwards and not match_prefix :
353
+ self .pos = 0
354
+ self .buffer = []
355
+ self .dirty = True
356
+ else :
357
+ self .error ("not found" )
358
+ return
359
+
360
+ history_index += 1 if forwards else - 1
361
+ s = self .get_item (history_index )
362
+
363
+ if not match_prefix :
364
+ self .select_item (history_index )
365
+ return
366
+
367
+ len_acc = 0
368
+ for i , line in enumerate (s .splitlines (keepends = True )):
369
+ if line .startswith (prefix ):
370
+ self .select_item (history_index )
371
+ self .pos = pos + len_acc
372
+ return
373
+ len_acc += len (line )
374
+
308
375
def isearch_next (self ) -> None :
309
376
st = self .isearch_term
310
377
p = self .pos
0 commit comments