@@ -71,6 +71,18 @@ def do(self) -> None:
7171 r .select_item (r .historyi - 1 )
7272
7373
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+
7486class restore_history (commands .Command ):
7587 def do (self ) -> None :
7688 r = self .reader
@@ -234,6 +246,8 @@ def __post_init__(self) -> None:
234246 isearch_forwards ,
235247 isearch_backwards ,
236248 operate_and_get_next ,
249+ history_search_backward ,
250+ history_search_forward ,
237251 ]:
238252 self .commands [c .__name__ ] = c
239253 self .commands [c .__name__ .replace ("_" , "-" )] = c
@@ -251,8 +265,8 @@ def collect_keymap(self) -> tuple[tuple[KeySpec, CommandName], ...]:
251265 (r"\C-s" , "forward-history-isearch" ),
252266 (r"\M-r" , "restore-history" ),
253267 (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 " ),
256270 )
257271
258272 def select_item (self , i : int ) -> None :
@@ -305,6 +319,59 @@ def get_prompt(self, lineno: int, cursor_on_line: bool) -> str:
305319 else :
306320 return super ().get_prompt (lineno , cursor_on_line )
307321
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+
308375 def isearch_next (self ) -> None :
309376 st = self .isearch_term
310377 p = self .pos
0 commit comments