@@ -157,6 +157,8 @@ def __init__(self, model=None, additional_imports=None):
157157 self .console = InteractiveConsole (locals_dict )
158158 self .buffer = []
159159 self .history : list [ConsoleEntry ] = []
160+ self .history_index = - 1
161+ self .current_input = ""
160162
161163 def execute_code (
162164 self , code_line : str , set_input_text : Callable [[str ], None ]
@@ -282,13 +284,51 @@ def clear_console(self) -> None:
282284 """Clear the console history and reset the console state."""
283285 self .history .clear ()
284286 self .buffer .clear ()
287+ self .history_index = - 1
288+ self .current_input = ""
285289 # Reset the console while maintaining the locals dictionary
286290 self .console = InteractiveConsole (self .locals_dict )
287291
288292 def get_entries (self ) -> list [ConsoleEntry ]:
289293 """Get the list of console entries."""
290294 return self .history
291295
296+ def prev_command (
297+ self , current_text : str , set_input_text : Callable [[str ], None ]
298+ ) -> None :
299+ """Navigate to previous command in history."""
300+ if not self .history :
301+ return
302+
303+ # Save the current input
304+ if self .history_index == - 1 :
305+ self .current_input = current_text
306+
307+ # Move up in history
308+ if self .history_index == - 1 :
309+ self .history_index = len (self .history ) - 1
310+ elif self .history_index > 0 :
311+ self .history_index -= 1
312+
313+ # Set text to the historical command
314+ if 0 <= self .history_index < len (self .history ):
315+ set_input_text (self .history [self .history_index ].command )
316+
317+ def next_command (self , set_input_text : Callable [[str ], None ]) -> None :
318+ """Navigate to next command in history."""
319+ if self .history_index == - 1 :
320+ return # Not in history navigation mode
321+
322+ # Move down in history
323+ self .history_index += 1
324+
325+ # If we've moved past the end of history, restore the saved input
326+ if self .history_index >= len (self .history ):
327+ self .history_index = - 1
328+ set_input_text (self .current_input )
329+ else :
330+ set_input_text (self .history [self .history_index ].command )
331+
292332
293333def format_command_html (entry ):
294334 """Format the command part of a console entry as HTML."""
@@ -338,13 +378,18 @@ def format_output_html(entry):
338378
339379
340380@solara .component
341- def ConsoleInput (on_submit ):
381+ def ConsoleInput (on_submit , on_up , on_down ):
342382 """A solara component for handling console input."""
343383 input_text , set_input_text = solara .use_state ("" )
344384
345385 def handle_submit (* ignore_args ):
346- on_submit (input_text )
347- set_input_text ("" )
386+ on_submit (input_text , set_input_text )
387+
388+ def handle_up (* ignore_args ):
389+ on_up (input_text , set_input_text )
390+
391+ def handle_down (* ignore_args ):
392+ on_down (set_input_text )
348393
349394 input_elem = solara .v .TextField (
350395 v_model = input_text ,
@@ -366,8 +411,12 @@ def handle_submit(*ignore_args):
366411 },
367412 )
368413
369- # Handle Enter key press
370- use_change (input_elem , handle_submit , update_events = ["keyup.enter" ])
414+ # Bind key events with the input element
415+ use_change (input_elem , handle_submit , update_events = ["keypress.enter" ])
416+ use_change (input_elem , handle_up , update_events = ["keyup.38" ]) # 38 -> Up arrow
417+ use_change (
418+ input_elem , handle_down , update_events = ["keydown.40" ]
419+ ) # 40 -> Down arrow
371420
372421 return input_elem
373422
@@ -385,8 +434,16 @@ def CommandConsole(model=None, additional_imports=None):
385434 # State to trigger re-renders
386435 refresh , set_refresh = solara .use_state (0 )
387436
388- def handle_code_execution (code ):
389- console_ref .current .execute_code (code , lambda _ : None )
437+ def handle_code_execution (code , set_input_text ):
438+ console_ref .current .execute_code (code , set_input_text )
439+ set_refresh (refresh + 1 )
440+
441+ def handle_up (current_text , set_input_text ):
442+ console_ref .current .prev_command (current_text , set_input_text )
443+ set_refresh (refresh + 1 )
444+
445+ def handle_down (set_input_text ):
446+ console_ref .current .next_command (set_input_text )
390447 set_refresh (refresh + 1 )
391448
392449 with solara .Column (
@@ -415,7 +472,9 @@ def handle_code_execution(code):
415472 style = {"align-items" : "center" , "margin" : "0" , "width" : "94.5%" }
416473 ):
417474 solara .Text (">>> " , style = {"color" : "#0066cc" })
418- ConsoleInput (on_submit = handle_code_execution )
475+ ConsoleInput (
476+ on_submit = handle_code_execution , on_up = handle_up , on_down = handle_down
477+ )
419478
420479 solara .Markdown (
421480 "*Type 'tips' for usage instructions.*" ,
0 commit comments