def test_undo(keys, expected_edit_pos, expected_text): edit = ReadlineEdit() for key in keys: edit.keypress(edit.size, key) edit.undo() assert edit.edit_pos == expected_edit_pos assert edit.text == expected_text
def test_enable_autocomplete_reverse(start_text, start_pos, edits, autocomplete_key, autocomplete_key_reverse): source = ["start", "stop", "next"] keypress = autocomplete_key if autocomplete_key else "tab" keypress_reverse = (autocomplete_key_reverse if autocomplete_key_reverse else "shift tab") def compl(text, state): tmp = ([c for c in source if c and c.startswith(text)] if text else source) try: return tmp[state] except (IndexError, TypeError): return None edit = ReadlineEdit(edit_text=start_text, edit_pos=start_pos) kwargs = {} if autocomplete_key: kwargs["key"] = autocomplete_key if autocomplete_key_reverse: kwargs["key_reverse"] = autocomplete_key_reverse edit.enable_autocomplete(compl, **kwargs) for direction, expected_text, expected_pos in edits: if direction == "forward": edit.keypress(None, keypress) else: edit.keypress(None, keypress_reverse) assert edit.edit_text == expected_text assert edit.edit_pos == expected_pos
def test_enable_autocomplete_reverse( completion_func_for_source, start_text, start_pos, edits, autocomplete_key, autocomplete_key_reverse, ): source = ["start", "stop", "next"] keypress = autocomplete_key if autocomplete_key else "tab" keypress_reverse = (autocomplete_key_reverse if autocomplete_key_reverse else "shift tab") compl = completion_func_for_source(source) edit = ReadlineEdit(edit_text=start_text, edit_pos=start_pos) kwargs = {} if autocomplete_key: kwargs["key"] = autocomplete_key if autocomplete_key_reverse: kwargs["key_reverse"] = autocomplete_key_reverse edit.enable_autocomplete(compl, **kwargs) for direction, expected_text, expected_pos in edits: if direction == "forward": edit.keypress(None, keypress) else: edit.keypress(None, keypress_reverse) assert edit.edit_text == expected_text assert edit.edit_pos == expected_pos
def test_enable_autocomplete(start_text, start_pos, source, positions): def compl(text, state): tmp = ([c for c in source if c and c.startswith(text)] if text else source) try: return tmp[state] except IndexError: return None edit = ReadlineEdit(edit_text=start_text, edit_pos=start_pos) edit.enable_autocomplete(compl) for position in positions: expected_text, expected_pos = position edit.keypress(None, "tab") assert edit.edit_text == expected_text assert edit.edit_pos == expected_pos
def test_autocomplete_delimiters( completion_func_for_source, autocomplete_delimiters, word_separator, final_phrase, word1="firstw", word2="secondw", ): phrase = word_separator.join([word1, word2]) phrase_length = len(phrase) source = [phrase] compl = completion_func_for_source(source) edit = ReadlineEdit(edit_text=word1, edit_pos=len(word1)) edit.enable_autocomplete(compl) if autocomplete_delimiters is not None: edit.set_completer_delims(autocomplete_delimiters) # Completion from word1 to phrase edit.keypress(edit.size, "tab") assert edit.edit_text == phrase assert edit.edit_pos == phrase_length # Backspace to after word1 + space for _ in range(len(word2)): edit.keypress(edit.size, "backspace") assert edit.edit_text == word1 + word_separator assert edit.edit_pos == len(word1) + 1 # Completion from word1 + word_separator edit.keypress(edit.size, "tab") assert edit.edit_text == final_phrase assert edit.edit_pos == len(final_phrase)
def test_enable_autocomplete(start_text, start_pos, source, positions, autocomplete_key): keypress = autocomplete_key if autocomplete_key else "tab" def compl(text, state): tmp = ([c for c in source if c and c.startswith(text)] if text else source) try: return tmp[state] except (IndexError, TypeError): return None edit = ReadlineEdit(edit_text=start_text, edit_pos=start_pos) kwargs = {} if autocomplete_key: kwargs["key"] = autocomplete_key edit.enable_autocomplete(compl, **kwargs) for position in positions: expected_text, expected_pos = position edit.keypress(None, keypress) assert edit.edit_text == expected_text assert edit.edit_pos == expected_pos
def test_enable_autocomplete( completion_func_for_source, start_text, start_pos, source, positions, autocomplete_key, ): keypress = autocomplete_key if autocomplete_key else "tab" compl = completion_func_for_source(source) edit = ReadlineEdit(edit_text=start_text, edit_pos=start_pos) kwargs = {} if autocomplete_key: kwargs["key"] = autocomplete_key edit.enable_autocomplete(compl, **kwargs) for position in positions: expected_text, expected_pos = position edit.keypress(None, keypress) assert edit.edit_text == expected_text assert edit.edit_pos == expected_pos
def test_enable_autocomplete_clear_state(completion_func_for_source): source = ["start", "stop", "next"] compl = completion_func_for_source(source) edit = ReadlineEdit(edit_text="s", edit_pos=1) edit.enable_autocomplete(compl) edit.keypress(edit.size, "tab") assert edit.edit_text == "start" assert edit.edit_pos == 5 edit.keypress(edit.size, "home") edit.keypress(edit.size, "right") assert edit.edit_pos == 1 edit.keypress(edit.size, "tab") assert edit.edit_text == "starttart" assert edit.edit_pos == 5
def test_enable_autocomplete_clear_state(): source = ["start", "stop", "next"] def compl(text, state): tmp = ([c for c in source if c and c.startswith(text)] if text else source) try: return tmp[state] except IndexError: return None edit = ReadlineEdit(edit_text="s", edit_pos=1) edit.enable_autocomplete(compl) edit.keypress(edit.size, "tab") assert edit.edit_text == "start" assert edit.edit_pos == 5 edit.keypress(edit.size, "home") edit.keypress(edit.size, "right") assert edit.edit_pos == 1 edit.keypress(edit.size, "tab") assert edit.edit_text == "starttart" assert edit.edit_pos == 5
class FuzzyInput(urwid.ListBox): signals = ["accept"] def __init__( self, plugin: PluginBase, main_loop: urwid.MainLoop, is_tag_used: Callable[[str], bool], ) -> None: self._plugin = plugin self._main_loop = main_loop self._is_tag_used = is_tag_used self._focus = -1 self._matches: List[Tuple[str, int]] = [] self._update_id = 0 self._update_alarm = None self._input_box = ReadlineEdit("", wrap=urwid.CLIP) urwid.signals.connect_signal( self._input_box, "change", self._on_text_change ) super().__init__(urwid.SimpleListWalker([])) self._update_widgets() def selectable(self) -> bool: return True def keypress(self, size: Tuple[int, int], key: str) -> Optional[str]: keymap: Dict[str, Callable[[Tuple[int, int]], None]] = { "enter": self._accept, "tab": self._select_prev, "shift tab": self._select_next, } if key in keymap: keymap[key](size) return None return self._input_box.keypress((size[0],), key) def _accept(self, _size: Tuple[int, int]) -> None: text = self._input_box.text.strip() if not text: return self._input_box.set_edit_text("") self._matches = [] self._focus = -1 self._update_widgets() urwid.signals.emit_signal(self, "accept", self, text) self._invalidate() def _select_next(self, _size: Tuple[int, int]) -> None: if self._focus > 0: self._focus -= 1 self._on_results_focus_change() self._update_widgets() def _select_prev(self, size: Tuple[int, int]) -> None: if self._focus + 1 < min(len(self._matches), size[1] - 1): self._focus += 1 self._on_results_focus_change() self._update_widgets() def _on_text_change(self, *_args: Any, **_kwargs: Any) -> None: if self._update_alarm: self._main_loop.remove_alarm(self._update_alarm) self._update_alarm = self._main_loop.set_alarm_in( 0.05, lambda *_: self._schedule_update_matches() ) def _on_results_focus_change(self, *_args: Any, **_kwargs: Any) -> None: urwid.signals.disconnect_signal( self._input_box, "change", self._on_text_change ) self._input_box.set_edit_text( common.box_to_ui(self._matches[self._focus][0]) ) self._input_box.set_edit_pos(len(self._input_box.text)) urwid.signals.connect_signal( self._input_box, "change", self._on_text_change ) def _schedule_update_matches(self) -> None: asyncio.ensure_future(self._update_matches()) async def _update_matches(self) -> None: text = common.unbox_from_ui(self._input_box.text) self._update_id += 1 update_id = self._update_id tag_names = await self._plugin.find_tags(text) if self._update_id > update_id: return matches = [] for tag_name in tag_names: tag_usage_count = await self._plugin.get_tag_usage_count(tag_name) matches.append((tag_name, tag_usage_count)) self._matches = matches self._focus = util.clamp(self._focus, -1, len(self._matches) - 1) self._update_widgets() def _update_widgets(self) -> None: new_list: List[urwid.Widget] = [self._input_box] for i, (tag_name, tag_usage_count) in enumerate(self._matches): attr_name = "match" if self._is_tag_used(tag_name): attr_name = "e-" + attr_name if i == self._focus: attr_name = "f-" + attr_name columns_widget = urwid.Columns( [ ( urwid.Text( common.box_to_ui(tag_name), align=urwid.LEFT, wrap=urwid.CLIP, layout=EllipsisTextLayout(), ) ), (urwid.PACK, urwid.Text(str(tag_usage_count))), ], dividechars=1, ) new_list.append(urwid.AttrWrap(columns_widget, attr_name)) list.clear(self.body) self.body.extend(new_list) self.body.set_focus(0)