Skip to content

Commit e29fc80

Browse files
Feat: Added arrow key navigation (#2725)
* feat: added arrow key navigation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * added pre-commit suggestions --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent e2fddbb commit e29fc80

File tree

1 file changed

+67
-8
lines changed

1 file changed

+67
-8
lines changed

mesa/visualization/command_console.py

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -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

293333
def 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

Comments
 (0)