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
Example #3
0
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
Example #4
0
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
Example #5
0
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
Example #7
0
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
Example #8
0
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
Example #10
0
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)