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 #2
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)
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_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
Example #5
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
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
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 #8
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 #9
0
class WriteBox(urwid.Pile):
    def __init__(self, view: Any) -> None:
        super().__init__(self.main_view(True))
        self.model = view.model
        self.view = view
        self.msg_edit_id = None  # type: Optional[int]

    def main_view(self, new: bool) -> Any:
        if new:
            return []
        else:
            self.contents.clear()

    def set_editor_mode(self) -> None:
        # if not in the editor mode already set editor_mode to True.
        if not self.view.controller.editor_mode:
            self.view.controller.editor_mode = True
            self.view.controller.editor = self

    def private_box_view(self, button: Any=None, email: str='') -> None:
        self.set_editor_mode()
        if email == '' and button is not None:
            email = button.email
        self.to_write_box = ReadlineEdit(u"To: ", edit_text=email)
        self.msg_write_box = ReadlineEdit(multiline=True)
        self.msg_write_box.enable_autocomplete(
            func=self.generic_autocomplete,
            key=keys_for_command('AUTOCOMPLETE').pop(),
            key_reverse=keys_for_command('AUTOCOMPLETE_REVERSE').pop()
        )
        to_write_box = urwid.LineBox(
            self.to_write_box, tlcorner=u'─', tline=u'─', lline=u'',
            trcorner=u'─', blcorner=u'─', rline=u'',
            bline=u'─', brcorner=u'─'
        )
        self.contents = [
            (to_write_box, self.options()),
            (self.msg_write_box, self.options()),
        ]
        self.focus_position = 1

    def stream_box_view(self, caption: str='', title: str='') -> None:
        self.set_editor_mode()
        self.to_write_box = None
        self.msg_write_box = ReadlineEdit(multiline=True)
        self.msg_write_box.enable_autocomplete(
            func=self.generic_autocomplete,
            key=keys_for_command('AUTOCOMPLETE').pop(),
            key_reverse=keys_for_command('AUTOCOMPLETE_REVERSE').pop()
        )
        self.stream_write_box = ReadlineEdit(
            caption=u"Stream:  ",
            edit_text=caption
        )
        self.title_write_box = ReadlineEdit(caption=u"Topic:  ",
                                            edit_text=title)

        header_write_box = urwid.Columns([
            urwid.LineBox(
                self.stream_write_box, tlcorner=u'─', tline=u'─', lline=u'',
                trcorner=u'┬', blcorner=u'─', rline=u'│',
                bline=u'─', brcorner=u'┴'
            ),
            urwid.LineBox(
                self.title_write_box, tlcorner=u'─', tline=u'─', lline=u'',
                trcorner=u'─', blcorner=u'─', rline=u'',
                bline=u'─', brcorner=u'─'
            ),
        ])
        write_box = [
            (header_write_box, self.options()),
            (self.msg_write_box, self.options()),
        ]
        self.contents = write_box

    def generic_autocomplete(self, text: str, state: int) -> Optional[str]:
        if text.startswith('@_'):
            typeahead = self.autocomplete_mentions(text, '@_')
        elif text.startswith('@'):
            typeahead = self.autocomplete_mentions(text, '@')
        elif text.startswith('#'):
            typeahead = self.autocomplete_streams(text)
        elif text.startswith(':'):
            typeahead = self.autocomplete_emojis(text)
        else:
            return text

        try:
            return typeahead[state]
        except (IndexError, TypeError):
            return None

    def autocomplete_mentions(self, text: str, prefix_string: str
                              ) -> List[str]:
        # Handles user mentions (@ mentions and silent mentions)
        # and group mentions.
        group_typeahead = ['@*{}*'.format(group_name)
                           for group_name in self.model.user_group_names
                           if match_group(group_name, text[1:])]

        users_list = self.view.users
        user_typeahead = [prefix_string+'**{}**'.format(user['full_name'])
                          for user in users_list
                          if match_user(user, text[len(prefix_string):])]
        combined_typeahead = group_typeahead + user_typeahead

        return combined_typeahead

    def autocomplete_streams(self, text: str) -> List[str]:
        streams_list = self.view.pinned_streams + self.view.unpinned_streams
        stream_typeahead = ['#**{}**'.format(stream[0])
                            for stream in streams_list
                            if match_stream(stream, text[1:])]
        return stream_typeahead

    def autocomplete_emojis(self, text: str) -> List[str]:
        emoji_list = emoji_names.EMOJI_NAMES
        emoji_typeahead = [':{}:'.format(emoji)
                           for emoji in emoji_list
                           if match_emoji(emoji, text[1:])]
        return emoji_typeahead

    def keypress(self, size: urwid_Size, key: str) -> Optional[str]:
        if is_command_key('SEND_MESSAGE', key):
            if self.msg_edit_id:
                if not self.to_write_box:
                    success = self.model.update_stream_message(
                        topic=self.title_write_box.edit_text,
                        content=self.msg_write_box.edit_text,
                        msg_id=self.msg_edit_id,
                    )
                else:
                    success = self.model.update_private_message(
                        content=self.msg_write_box.edit_text,
                        msg_id=self.msg_edit_id,
                    )
            else:
                if not self.to_write_box:
                    success = self.model.send_stream_message(
                        stream=self.stream_write_box.edit_text,
                        topic=self.title_write_box.edit_text,
                        content=self.msg_write_box.edit_text
                    )
                else:
                    success = self.model.send_private_message(
                        recipients=self.to_write_box.edit_text,
                        content=self.msg_write_box.edit_text
                    )
            if success:
                self.msg_write_box.edit_text = ''
                if self.msg_edit_id:
                    self.msg_edit_id = None
                    self.keypress(size, 'esc')
        elif is_command_key('GO_BACK', key):
            self.msg_edit_id = None
            self.view.controller.editor_mode = False
            self.main_view(False)
            self.view.middle_column.set_focus('body')
        elif is_command_key('TAB', key):
            if len(self.contents) == 0:
                return key
            # toggle focus position
            if self.focus_position == 0 and self.to_write_box is None:
                if self.contents[0][0].focus_col == 0:
                    self.contents[0][0].focus_col = 1
                    return key
                else:
                    self.contents[0][0].focus_col = 0
            self.focus_position = self.focus_position == 0
            self.contents[0][0].focus_col = 0

        key = super().keypress(size, key)
        return key
Example #10
0
class WriteBox(urwid.Pile):
    def __init__(self, view: Any) -> None:
        super().__init__(self.main_view(True))
        self.model = view.model
        self.view = view
        self.msg_edit_id = None  # type: Optional[int]
        self.is_in_typeahead_mode = False

    def main_view(self, new: bool) -> Any:
        if new:
            return []
        else:
            self.contents.clear()

    def set_editor_mode(self) -> None:
        self.view.controller.enter_editor_mode_with(self)

    def private_box_view(self, button: Any = None, email: str = '') -> None:
        self.set_editor_mode()
        if email == '' and button is not None:
            email = button.email
        self.to_write_box = ReadlineEdit("To: ", edit_text=email)
        self.msg_write_box = ReadlineEdit(multiline=True)
        self.msg_write_box.enable_autocomplete(
            func=self.generic_autocomplete,
            key=keys_for_command('AUTOCOMPLETE').pop(),
            key_reverse=keys_for_command('AUTOCOMPLETE_REVERSE').pop())
        to_write_box = urwid.LineBox(self.to_write_box,
                                     tlcorner='─',
                                     tline='─',
                                     lline='',
                                     trcorner='─',
                                     blcorner='─',
                                     rline='',
                                     bline='─',
                                     brcorner='─')
        self.contents = [
            (to_write_box, self.options()),
            (self.msg_write_box, self.options()),
        ]
        self.focus_position = 1

    def stream_box_view(self, caption: str = '', title: str = '') -> None:
        self.set_editor_mode()
        self.to_write_box = None
        self.msg_write_box = ReadlineEdit(multiline=True)
        self.msg_write_box.enable_autocomplete(
            func=self.generic_autocomplete,
            key=keys_for_command('AUTOCOMPLETE').pop(),
            key_reverse=keys_for_command('AUTOCOMPLETE_REVERSE').pop())
        self.stream_write_box = ReadlineEdit(caption="Stream:  ",
                                             edit_text=caption)
        self.title_write_box = ReadlineEdit(caption="Topic:  ",
                                            edit_text=title)

        header_write_box = urwid.Columns([
            urwid.LineBox(self.stream_write_box,
                          tlcorner='─',
                          tline='─',
                          lline='',
                          trcorner='┬',
                          blcorner='─',
                          rline='│',
                          bline='─',
                          brcorner='┴'),
            urwid.LineBox(self.title_write_box,
                          tlcorner='─',
                          tline='─',
                          lline='',
                          trcorner='─',
                          blcorner='─',
                          rline='',
                          bline='─',
                          brcorner='─'),
        ])
        write_box = [
            (header_write_box, self.options()),
            (self.msg_write_box, self.options()),
        ]
        self.contents = write_box

    def generic_autocomplete(self, text: str,
                             state: Optional[int]) -> Optional[str]:
        num_suggestions = 10
        autocomplete_map = OrderedDict([
            ('@_', self.autocomplete_mentions),
            ('@', self.autocomplete_mentions),
            ('#', self.autocomplete_streams),
            (':', self.autocomplete_emojis),
        ])

        for prefix, autocomplete_func in autocomplete_map.items():
            if text.startswith(prefix):
                self.is_in_typeahead_mode = True
                typeaheads, suggestions = autocomplete_func(text, prefix)
                fewer_typeaheads = typeaheads[:num_suggestions]
                reduced_suggestions = suggestions[:num_suggestions]
                is_truncated = len(fewer_typeaheads) != len(typeaheads)

                if (state is not None and state < len(fewer_typeaheads)
                        and state >= -len(fewer_typeaheads)):
                    typeahead = fewer_typeaheads[state]  # type: Optional[str]
                else:
                    typeahead = None
                    state = None
                self.view.set_typeahead_footer(reduced_suggestions, state,
                                               is_truncated)
                return typeahead

        return text

    def autocomplete_mentions(
            self, text: str,
            prefix_string: str) -> Tuple[List[str], List[str]]:
        # Handles user mentions (@ mentions and silent mentions)
        # and group mentions.
        groups = [
            group_name for group_name in self.model.user_group_names
            if match_group(group_name, text[1:])
        ]
        group_typeahead = format_string(groups, '@*{}*')

        users_list = self.view.users
        users = [
            user['full_name'] for user in users_list
            if match_user(user, text[len(prefix_string):])
        ]
        user_typeahead = format_string(users, prefix_string + '**{}**')

        combined_typeahead = group_typeahead + user_typeahead
        combined_names = groups + users

        return combined_typeahead, combined_names

    def autocomplete_streams(
            self, text: str,
            prefix_string: str) -> Tuple[List[str], List[str]]:
        streams_list = self.view.pinned_streams + self.view.unpinned_streams
        streams = [stream[0] for stream in streams_list]
        stream_typeahead = format_string(streams, '#**{}**')
        stream_data = list(zip(stream_typeahead, streams))

        matched_data = match_stream(stream_data, text[1:],
                                    self.view.pinned_streams)
        return matched_data

    def autocomplete_emojis(self, text: str,
                            prefix_string: str) -> Tuple[List[str], List[str]]:
        emoji_list = emoji_names.EMOJI_NAMES
        emojis = [
            emoji for emoji in emoji_list if match_emoji(emoji, text[1:])
        ]
        emoji_typeahead = format_string(emojis, ':{}:')

        return emoji_typeahead, emojis

    def keypress(self, size: urwid_Size, key: str) -> Optional[str]:
        if self.is_in_typeahead_mode:
            if not (is_command_key('AUTOCOMPLETE', key)
                    or is_command_key('AUTOCOMPLETE_REVERSE', key)):
                # set default footer when done with autocomplete
                self.is_in_typeahead_mode = False
                self.view.set_footer_text()

        if is_command_key('SEND_MESSAGE', key):
            if self.msg_edit_id:
                if not self.to_write_box:
                    success = self.model.update_stream_message(
                        topic=self.title_write_box.edit_text,
                        content=self.msg_write_box.edit_text,
                        msg_id=self.msg_edit_id,
                    )
                else:
                    success = self.model.update_private_message(
                        content=self.msg_write_box.edit_text,
                        msg_id=self.msg_edit_id,
                    )
            else:
                if not self.to_write_box:
                    success = self.model.send_stream_message(
                        stream=self.stream_write_box.edit_text,
                        topic=self.title_write_box.edit_text,
                        content=self.msg_write_box.edit_text)
                else:
                    success = self.model.send_private_message(
                        recipients=self.to_write_box.edit_text,
                        content=self.msg_write_box.edit_text)
            if success:
                self.msg_write_box.edit_text = ''
                if self.msg_edit_id:
                    self.msg_edit_id = None
                    self.keypress(size, 'esc')
        elif is_command_key('GO_BACK', key):
            self.msg_edit_id = None
            self.view.controller.exit_editor_mode()
            self.main_view(False)
            self.view.middle_column.set_focus('body')
        elif is_command_key('TAB', key):
            if len(self.contents) == 0:
                return key
            # toggle focus position
            if self.focus_position == 0 and self.to_write_box is None:
                if self.contents[0][0].focus_col == 0:
                    self.contents[0][0].focus_col = 1
                    return key
                else:
                    self.contents[0][0].focus_col = 0
            self.focus_position = self.focus_position == 0
            self.contents[0][0].focus_col = 0

        key = super().keypress(size, key)
        return key