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_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_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_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(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
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
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
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