KEYBINDINGS = {
'KEY_BACKSPACE': ('window.prompt.backspace',),
'KEY_ENTER': ('prompt_enter',),
+ 'KEY_LEFT': ('window.prompt.move_cursor', 'left'),
+ 'KEY_RIGHT': ('window.prompt.move_cursor', 'right'),
'KEY_UP': ('window.prompt.scroll', 'up'),
'KEY_DOWN': ('window.prompt.scroll', 'down'),
'KEY_PGUP': ('window.log.scroll', 'up'),
OSC52_PREFIX = ']52;c;'
PASTE_DELIMITER = '\007'
+PROMPT_TEMPLATE = '> '
+PROMPT_ELL_IN = '<…'
+PROMPT_ELL_OUT = '…>'
+
IRCSPEC_LINE_SEPARATOR = b'\r\n'
IRCSPEC_TAG_ESCAPES = ((r'\:', ';'),
(r'\s', ' '),
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
+ self._clear()
self._input_buffer = ''
- self._history_idx = 0
def set_geometry(self, measurements: YX) -> None:
self._y, self._width = measurements
def append(self, to_append: str) -> None:
- self._input_buffer += to_append
+ self._cursor_x += len(to_append)
+ self._input_buffer = (self._input_buffer[:self._cursor_x - 2]
+ + to_append
+ + self._input_buffer[self._cursor_x - 2:])
self._history_idx = 0
self.draw()
def draw(self) -> None:
- cursor = '_'
- prompt_template = '> '
- prompt = f'{prompt_template}'
+ prompt = PROMPT_TEMPLATE[:]
+ content = self._input_buffer[:]
+ if self._cursor_x > len(self._input_buffer):
+ content += ' '
+ half_width = self._width // 2
+ to_write = f'{prompt}{content}'
offset = 0
- while True:
- to_write = f'{prompt}{self._input_buffer[offset:]}{cursor}'
- len_too_much = len(to_write) - self._width
- if len_too_much <= 0:
- break
- offset += len_too_much
- prompt = f'<{offset}|{prompt_template}…'
- self._write(to_write[:-1], self._y, padding=False)
- self._write(to_write[-1], attribute='reverse', padding=False)
- self._write()
+ if len(to_write) > self._width and self._cursor_x > half_width:
+ prompt = f'{PROMPT_TEMPLATE}{PROMPT_ELL_IN}'
+ if self._cursor_x > len(content) - half_width:
+ offset = len(content) - self._width + len(prompt)
+ else:
+ offset = self._cursor_x - half_width + len(prompt) // 2
+ cursor_x_to_write = len(prompt) - 1 + self._cursor_x - offset
+ to_write = f'{prompt}{content[offset:]}'
+ if len(to_write) > self._width:
+ to_write = (to_write[:self._width - len(PROMPT_ELL_OUT)]
+ + PROMPT_ELL_OUT)
+ self._write(to_write[:cursor_x_to_write], self._y, padding=False)
+ self._write(to_write[cursor_x_to_write], attribute='reverse',
+ padding=False)
+ self._write(to_write[cursor_x_to_write + 1:])
def _scroll(self, up: bool = True) -> None:
if up and -(self._history_idx) < len(self._history):
else:
return
self._input_buffer = self._history[self._history_idx][:]
- self.draw()
def cmd__backspace(self) -> None:
'Truncate current content by one character, if possible.'
- self._input_buffer = self._input_buffer[:-1]
- self._history_idx = 0
+ if self._cursor_x > 1:
+ self._cursor_x -= 1
+ self._input_buffer = (self._input_buffer[:self._cursor_x - 1]
+ + self._input_buffer[self._cursor_x:])
+ self._history_idx = 0
+ self.draw()
+
+ def cmd__move_cursor(self, direction: str) -> None:
+ 'Move cursor one space into direction ("left" or "right") if possible.'
+ if direction == 'left' and self._cursor_x > 1:
+ self._cursor_x -= 1
+ elif direction == 'right'\
+ and self._cursor_x <= len(self._input_buffer):
+ self._cursor_x += 1
+ else:
+ return
self.draw()
def _clear(self) -> None:
- 'Empty current content.'
self._input_buffer = ''
- self.draw()
+ self._cursor_x = len(self._input_buffer) + 1
def enter(self) -> str:
'Return current content while also clearing and then redrawing.'
self.broadcast(EventType.PROMPT_ADD, yielded)
else:
self.broadcast(EventType.ALERT,
- 'unknown keyboard input: {yielded}')
+ f'unknown keyboard input: {yielded}')
def run() -> None: