123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156 |
- #!/usr/bin/env python
- # License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
- from typing import Callable, Tuple
- from kitty.fast_data_types import truncate_point_for_length, wcswidth
- from kitty.key_encoding import EventType, KeyEvent
- from .operations import RESTORE_CURSOR, SAVE_CURSOR, move_cursor_by, set_cursor_shape
- class LineEdit:
- def __init__(self, is_password: bool = False) -> None:
- self.clear()
- self.is_password = is_password
- def clear(self) -> None:
- self.current_input = ''
- self.cursor_pos = 0
- self.pending_bell = False
- def split_at_cursor(self, delta: int = 0) -> Tuple[str, str]:
- pos = max(0, self.cursor_pos + delta)
- x = truncate_point_for_length(self.current_input, pos) if pos else 0
- before, after = self.current_input[:x], self.current_input[x:]
- return before, after
- def write(self, write: Callable[[str], None], prompt: str = '', screen_cols: int = 0) -> None:
- if self.pending_bell:
- write('\a')
- self.pending_bell = False
- ci = self.current_input
- if self.is_password:
- ci = '*' * wcswidth(ci)
- text = prompt + ci
- cursor_pos = self.cursor_pos + wcswidth(prompt)
- if screen_cols:
- write(SAVE_CURSOR + text + RESTORE_CURSOR)
- used_lines, last_line_cursor_pos = divmod(cursor_pos, screen_cols)
- if used_lines == 0:
- if last_line_cursor_pos:
- write(move_cursor_by(last_line_cursor_pos, 'right'))
- else:
- if used_lines:
- write(move_cursor_by(used_lines, 'down'))
- if last_line_cursor_pos:
- write(move_cursor_by(last_line_cursor_pos, 'right'))
- else:
- write(text)
- write('\r')
- if cursor_pos:
- write(move_cursor_by(cursor_pos, 'right'))
- write(set_cursor_shape('beam'))
- def add_text(self, text: str) -> None:
- if self.current_input:
- x = truncate_point_for_length(self.current_input, self.cursor_pos) if self.cursor_pos else 0
- self.current_input = self.current_input[:x] + text + self.current_input[x:]
- else:
- self.current_input = text
- self.cursor_pos += wcswidth(text)
- def on_text(self, text: str, in_bracketed_paste: bool) -> None:
- self.add_text(text)
- def backspace(self, num: int = 1) -> bool:
- before, after = self.split_at_cursor()
- nbefore = before[:-num]
- if nbefore != before:
- self.current_input = nbefore + after
- self.cursor_pos = wcswidth(nbefore)
- return True
- self.pending_bell = True
- return False
- def delete(self, num: int = 1) -> bool:
- before, after = self.split_at_cursor()
- nafter = after[num:]
- if nafter != after:
- self.current_input = before + nafter
- self.cursor_pos = wcswidth(before)
- return True
- self.pending_bell = True
- return False
- def _left(self) -> None:
- if not self.current_input:
- self.cursor_pos = 0
- return
- if self.cursor_pos:
- before, after = self.split_at_cursor(-1)
- self.cursor_pos = wcswidth(before)
- def _right(self) -> None:
- if not self.current_input:
- self.cursor_pos = 0
- return
- max_pos = wcswidth(self.current_input)
- if self.cursor_pos >= max_pos:
- self.cursor_pos = max_pos
- return
- before, after = self.split_at_cursor(1)
- self.cursor_pos += 1 + int(wcswidth(before) == self.cursor_pos)
- def _move_loop(self, func: Callable[[], None], num: int) -> bool:
- before = self.cursor_pos
- changed = False
- while num > 0:
- func()
- changed = self.cursor_pos != before
- if not changed:
- break
- num -= 1
- if not changed:
- self.pending_bell = True
- return changed
- def left(self, num: int = 1) -> bool:
- return self._move_loop(self._left, num)
- def right(self, num: int = 1) -> bool:
- return self._move_loop(self._right, num)
- def home(self) -> bool:
- if self.cursor_pos:
- self.cursor_pos = 0
- return True
- return False
- def end(self) -> bool:
- orig = self.cursor_pos
- self.cursor_pos = wcswidth(self.current_input)
- return self.cursor_pos != orig
- def on_key(self, key_event: KeyEvent) -> bool:
- if key_event.type is EventType.RELEASE:
- return False
- if key_event.matches('home') or key_event.matches('ctrl+a'):
- return self.home()
- if key_event.matches('end') or key_event.matches('ctrl+e'):
- return self.end()
- if key_event.matches('backspace'):
- self.backspace()
- return True
- if key_event.matches('delete') or key_event.matches('ctrl+d'):
- self.delete()
- return True
- if key_event.matches('left') or key_event.matches('ctrl+b'):
- self.left()
- return True
- if key_event.matches('right') or key_event.matches('ctrl+f'):
- self.right()
- return True
- return False
|