# -*- coding: utf-8 -*-
"""
See :class:`UIProcessKeyPressedMixin`.
"""
import typing as T
from . import keyboard
from . import exc
if T.TYPE_CHECKING:
from .item import T_ITEM
from .ui import UI
[docs]class UIProcessKeyPressedMixin:
"""
This class implements the key pressed event processing logics.
**Important**
All methods whose names start with ``process_`` are abstract methods
that handle specific key press events. They implement the Default behavior:.
Experienced developers can override them to customize the behavior.
"""
[docs] def cursor_up_and_down(self: "UI"):
"""
A helper method should be called after the cursor moves up or down.
- need_run_handler: False, the user input query is not changed, so we
don't need to run the handler again.
- need_move_to_end: True, the dropdown menu may change, so we need to
move the cursor to the end of the line in the next event loop
- need_clear_items: True, the dropdown menu may change, so we need to
clear the dropdown menu.
- need_clear_query: False, the user input query is not changed, so we
don't need to clear the user input query.
- need_print_query: False, the user input query is not changed, so we
don't need to print the user input query.
- need_print_items: True, the dropdown menu may change, so we need to
print the dropdown menu.
- need_process_input: True, we need to process the user input next time.
"""
self.need_run_handler: bool = False
self.need_clear_query: bool = False
self.need_print_query: bool = False
[docs] def cursor_left_and_right(self: "UI"):
"""
A helper method should be called after the cursor moves left and right.
- need_run_handler: False, the user input query is not changed, so we
don't need to run the handler again.
- need_move_to_end: False, the dropdown menu is not changed, so we
don't need to move the cursor to the end of the line.
- need_clear_items: False, the dropdown menu is not changed, so we
don't need to clear the dropdown menu.
- need_clear_query: False, the user input query is not changed, so we
don't need to clear the user input query.
- need_print_query: False, the user input query is not changed, so we
don't need to print the user input query.
- need_print_items: False, the dropdown menu is not change, so we
don't need to print the dropdown menu.
- need_process_input: True, we need to process the user input next time.
"""
self.need_run_handler = False
self.need_move_to_end = False
self.need_clear_items = False
self.need_clear_query = False
self.need_print_query = False
self.need_print_items = False
[docs] def process_up(self: "UI"):
"""
Default behavior:
select the previous item in dropdown menu.
"""
self.cursor_up_and_down()
self.dropdown.press_up()
[docs] def process_down(self: "UI"):
"""
Default behavior:
select the next item in dropdown menu.
"""
self.cursor_up_and_down()
self.dropdown.press_down()
[docs] def process_ctrl_e(self: "UI"):
"""
Default behavior:
select the previous item in dropdown menu.
"""
self.cursor_up_and_down()
self.dropdown.press_up()
[docs] def process_ctrl_d(self: "UI"):
"""
Default behavior:
select the next item in dropdown menu.
"""
self.cursor_up_and_down()
self.dropdown.press_down()
[docs] def process_ctrl_r(self: "UI"):
"""
Default behavior:
scroll up the dropdown menu (press UP 5 times).
"""
self.cursor_up_and_down()
self.dropdown.scroll_up()
[docs] def process_ctrl_f(self: "UI"):
"""
Default behavior:
scroll down the dropdown menu (press DOWN 5 times).
"""
self.cursor_up_and_down()
self.dropdown.scroll_down()
[docs] def process_left(self: "UI"):
"""
Default behavior:
move user input cursor to the previous character.
"""
self.cursor_left_and_right()
self.line_editor.press_left()
[docs] def process_right(self: "UI"):
"""
Default behavior:
move user input cursor to the next character.
"""
self.cursor_left_and_right()
self.line_editor.press_right()
[docs] def process_alt_left(self: "UI"):
"""
Default behavior:
Move cursor to the beginning of previous word.
"""
self.cursor_left_and_right()
self.line_editor.move_word_backward()
[docs] def process_alt_right(self: "UI"):
"""
Default behavior:
Move cursor to the beginning of next word.
"""
self.cursor_left_and_right()
self.line_editor.move_word_forward()
[docs] def process_home(self: "UI"):
"""
Default behavior:
Move cursor to the beginning of the line.
"""
self.cursor_left_and_right()
self.line_editor.press_home()
[docs] def process_end(self: "UI"):
"""
Default behavior:
Move cursor to the end of the line.
"""
self.cursor_left_and_right()
self.line_editor.press_end()
[docs] def press_backspace(self: "UI"):
"""
Default behavior:
Delete character backwards in the line editor. Also move cursor to the left.
"""
self.line_editor.press_backspace()
[docs] def press_delete(self: "UI"):
"""
Default behavior:
Delete character forwards in the line editor. Also, the cursor stays.
"""
self.line_editor.press_delete()
[docs] def process_ctrl_k(self: "UI"):
"""
Default behavior:
Delete the previous word.
"""
self.line_editor.delete_word_backward()
[docs] def process_ctrl_l(self: "UI"):
"""
Default behavior:
Delete the next word.
"""
self.line_editor.delete_word_forward()
[docs] def process_tab(self: "UI"):
"""
Default behavior:
Auto complete.
"""
self.line_editor.clear_line()
selected_item = self.dropdown.selected_item
if selected_item.autocomplete:
self.line_editor.enter_text(selected_item.autocomplete)
[docs] def process_ctrl_x(self: "UI"):
"""
Default behavior:
Clear the user input.
"""
self.line_editor.clear_line()
[docs] def process_ctrl_c(self: "UI"):
"""
Default behavior:
Keyboard interrupt.
"""
raise KeyboardInterrupt()
def validate_selected_item(self: "UI") -> T.Optional["T_ITEM"]:
if self.dropdown.n_items == 0:
raise exc.EndOfInputError(
selection="select nothing",
)
else:
self.move_to_end()
if self.dropdown.items:
return self.dropdown.selected_item
return None
[docs] def process_enter(self: "UI"):
"""
Default behavior:
User action for enter.
"""
selected_item = self.validate_selected_item()
if selected_item is not None:
selected_item.enter_handler(ui=self)
selected_item.post_enter_handler(ui=self)
[docs] def process_ctrl_a(self: "UI"):
"""
Default behavior:
User action for CTRL A.
"""
selected_item = self.validate_selected_item()
if selected_item is not None:
selected_item.ctrl_a_handler(ui=self)
selected_item.post_ctrl_a_handler(ui=self)
[docs] def process_ctrl_w(self: "UI"):
"""
Default behavior:
User action for CTRL W.
"""
selected_item = self.validate_selected_item()
if selected_item is not None:
selected_item.ctrl_w_handler(ui=self)
selected_item.post_ctrl_w_handler(ui=self)
[docs] def process_ctrl_u(self: "UI"):
"""
Default behavior:
User action for CTRL U.
"""
selected_item = self.validate_selected_item()
if selected_item is not None:
selected_item.ctrl_u_handler(ui=self)
selected_item.post_ctrl_u_handler(ui=self)
[docs] def process_ctrl_p(self: "UI"):
"""
Default behavior:
User action for CTRL P.
"""
selected_item = self.validate_selected_item()
if selected_item is not None:
selected_item.ctrl_p_handler(ui=self)
selected_item.post_ctrl_p_handler(ui=self)
[docs] def process_f1(self: "UI"):
"""
Default behavior:
Jump out the sub-session, return to the previous view.
"""
raise exc.JumpOutSessionError
[docs] def process_ctrl_t(self: "UI"):
"""
Default behavior:
Do nothing. This is reserved for user custom shortcut.
"""
self.wait_next_user_input()
[docs] def process_ctrl_g(self: "UI"):
"""
Default behavior:
Do nothing. This is reserved for user custom shortcut.
"""
self.wait_next_user_input()
[docs] def process_ctrl_b(self: "UI"):
"""
Default behavior:
Do nothing. This is reserved for user custom shortcut.
"""
self.wait_next_user_input()
[docs] def process_ctrl_n(self: "UI"):
"""
Default behavior:
Do nothing. This is reserved for user custom shortcut.
"""
self.wait_next_user_input()
def _create_key_processor_mapper(self):
"""
Create a key processor mapper. So we can map the key to the corresponding
processor.
"""
self._key_processor_mapper = {
# dropdown menu
keyboard.UP: self.process_up,
keyboard.DOWN: self.process_down,
keyboard.CTRL_E: self.process_ctrl_e,
keyboard.CTRL_D: self.process_ctrl_d,
keyboard.CTRL_R: self.process_ctrl_r,
keyboard.CTRL_F: self.process_ctrl_f,
# line editor - move cursor
keyboard.LEFT: self.process_left,
keyboard.RIGHT: self.process_right,
keyboard.ALT_LEFT: self.process_alt_left,
keyboard.ALT_RIGHT: self.process_alt_right,
keyboard.HOME: self.process_home,
keyboard.END: self.process_end,
# line editor - change text
keyboard.BACKSPACE: self.press_backspace,
keyboard.DELETE: self.press_delete,
keyboard.CTRL_K: self.process_ctrl_k,
keyboard.CTRL_L: self.process_ctrl_l,
keyboard.TAB: self.process_tab,
keyboard.CTRL_X: self.process_ctrl_x,
# special
keyboard.CTRL_C: self.process_ctrl_c,
keyboard.F1: self.process_f1,
# User defined (customizable) item action
keyboard.ENTER: self.process_enter,
keyboard.CTRL_A: self.process_ctrl_a,
keyboard.CTRL_W: self.process_ctrl_w,
keyboard.CTRL_U: self.process_ctrl_u,
keyboard.CTRL_P: self.process_ctrl_p,
# User defined UI keybinding:
keyboard.CTRL_T: self.process_ctrl_t,
keyboard.CTRL_G: self.process_ctrl_g,
keyboard.CTRL_B: self.process_ctrl_b,
keyboard.CTRL_N: self.process_ctrl_n,
}
def _process_key_pressed_input(self: "UI", key: str):
"""
Try to process the key pressed
:return: a boolean flag indicating whether the key pressed is processed
"""
if key in self._key_processor_mapper:
self._key_processor_mapper[key]()
else:
self.line_editor.press_key(key)