diff --git a/docs/hotkeys.md b/docs/hotkeys.md index 1272bfc519..d1cd722db5 100644 --- a/docs/hotkeys.md +++ b/docs/hotkeys.md @@ -8,7 +8,6 @@ |Show/hide Help Menu|?| |Show/hide Markdown Help Menu|Meta + m| |Show/hide About Menu|Meta + ?| -|Go back|Esc| |Open draft message saved in this session|d| |Copy information from About Menu to clipboard|c| |Redraw screen|Ctrl + l| @@ -18,6 +17,7 @@ ## Navigation |Command|Key Combination| | :--- | :---: | +|Close popup|Esc| |Go up / Previous message|Up / k| |Go down / Next message|Down / j| |Go left|Left / h| @@ -42,6 +42,7 @@ |Search topics in a stream|q| |Search emojis from emoji picker|p| |Submit search and browse results|Enter| +|Clear search in current panel|Esc| ## Message actions |Command|Key Combination| @@ -85,6 +86,7 @@ |Autocomplete @mentions, #stream_names, :emoji: and topics|Ctrl + f| |Cycle through autocomplete suggestions in reverse|Ctrl + r| |Narrow to compose box message recipient|Meta + .| +|Exit message compose box|Esc| |Insert new line|Enter| ## Editor: Navigation diff --git a/tests/ui/test_ui_tools.py b/tests/ui/test_ui_tools.py index 18d2ef27b3..0a4d9ec030 100644 --- a/tests/ui/test_ui_tools.py +++ b/tests/ui/test_ui_tools.py @@ -559,8 +559,8 @@ def test_keypress_SEARCH_STREAMS(self, mocker, stream_view, key, widget_size): stream_view.stream_search_box ) - @pytest.mark.parametrize("key", keys_for_command("GO_BACK")) - def test_keypress_GO_BACK(self, mocker, stream_view, key, widget_size): + @pytest.mark.parametrize("key", keys_for_command("CLEAR_SEARCH")) + def test_keypress_CLEAR_SEARCH(self, mocker, stream_view, key, widget_size): size = widget_size(stream_view) mocker.patch.object(stream_view, "set_focus") mocker.patch(VIEWS + ".urwid.Frame.keypress") @@ -725,8 +725,8 @@ def test_keypress_SEARCH_TOPICS(self, mocker, topic_view, key, widget_size): topic_view.topic_search_box ) - @pytest.mark.parametrize("key", keys_for_command("GO_BACK")) - def test_keypress_GO_BACK(self, mocker, topic_view, key, widget_size): + @pytest.mark.parametrize("key", keys_for_command("CLEAR_SEARCH")) + def test_keypress_CLEAR_SEARCH(self, mocker, topic_view, key, widget_size): size = widget_size(topic_view) mocker.patch(VIEWS + ".TopicsView.set_focus") mocker.patch(VIEWS + ".urwid.Frame.keypress") @@ -1105,8 +1105,8 @@ def test_keypress_SEARCH_PEOPLE(self, right_col_view, mocker, key, widget_size): right_col_view.user_search ) - @pytest.mark.parametrize("key", keys_for_command("GO_BACK")) - def test_keypress_GO_BACK(self, right_col_view, mocker, key, widget_size): + @pytest.mark.parametrize("key", keys_for_command("CLEAR_SEARCH")) + def test_keypress_CLEAR_SEARCH(self, right_col_view, mocker, key, widget_size): size = widget_size(right_col_view) mocker.patch(VIEWS + ".UsersView") mocker.patch(VIEWS + ".RightColumnView.set_focus") diff --git a/tests/ui_tools/test_boxes.py b/tests/ui_tools/test_boxes.py index b6c763c0cb..4b28a1e02b 100644 --- a/tests/ui_tools/test_boxes.py +++ b/tests/ui_tools/test_boxes.py @@ -235,7 +235,7 @@ def test_not_calling_send_private_message_without_recipients( assert not write_box.model.send_private_message.called - @pytest.mark.parametrize("key", keys_for_command("GO_BACK")) + @pytest.mark.parametrize("key", keys_for_command("EXIT_COMPOSE")) def test__compose_attributes_reset_for_private_compose( self, key: str, @@ -256,7 +256,7 @@ def test__compose_attributes_reset_for_private_compose( assert write_box.msg_write_box.edit_text == "" assert write_box.compose_box_status == "closed" - @pytest.mark.parametrize("key", keys_for_command("GO_BACK")) + @pytest.mark.parametrize("key", keys_for_command("EXIT_COMPOSE")) def test__compose_attributes_reset_for_stream_compose( self, key: str, @@ -1516,7 +1516,7 @@ def test_keypress_SEND_MESSAGE_no_topic( (primary_key_for_command("AUTOCOMPLETE"), True, True, False), (primary_key_for_command("AUTOCOMPLETE_REVERSE"), True, True, False), # footer resets - (primary_key_for_command("GO_BACK"), True, False, True), + (primary_key_for_command("EXIT_COMPOSE"), True, False, True), ("space", True, False, True), ("k", True, False, True), ], @@ -1858,8 +1858,8 @@ def test_keypress_ENTER( panel_view.set_focus.assert_not_called() panel_view.body.set_focus.assert_not_called() - @pytest.mark.parametrize("back_key", keys_for_command("GO_BACK")) - def test_keypress_GO_BACK( + @pytest.mark.parametrize("back_key", keys_for_command("CLEAR_SEARCH")) + def test_keypress_CLEAR_SEARCH( self, panel_search_box: PanelSearchBox, back_key: str, diff --git a/tests/ui_tools/test_popups.py b/tests/ui_tools/test_popups.py index ee412f65a1..d58b6c3353 100644 --- a/tests/ui_tools/test_popups.py +++ b/tests/ui_tools/test_popups.py @@ -78,8 +78,8 @@ def test_exit_popup_no( self.callback.assert_not_called() assert self.controller.exit_popup.called - @pytest.mark.parametrize("key", keys_for_command("GO_BACK")) - def test_exit_popup_GO_BACK( + @pytest.mark.parametrize("key", keys_for_command("EXIT_POPUP")) + def test_exit_popup_EXIT_POPUP( self, popup_view: PopUpConfirmationView, key: str, @@ -133,8 +133,8 @@ def test_init(self, mocker: MockerFixture) -> None: self.pop_up_view.body, header=mocker.ANY, footer=mocker.ANY ) - @pytest.mark.parametrize("key", keys_for_command("GO_BACK")) - def test_keypress_GO_BACK( + @pytest.mark.parametrize("key", keys_for_command("EXIT_POPUP")) + def test_keypress_EXIT_POPUP( self, key: str, widget_size: Callable[[Widget], urwid_Size], @@ -210,7 +210,7 @@ def mock_external_classes(self, mocker: MockerFixture) -> None: ) @pytest.mark.parametrize( - "key", {*keys_for_command("GO_BACK"), *keys_for_command("ABOUT")} + "key", {*keys_for_command("EXIT_POPUP"), *keys_for_command("ABOUT")} ) def test_keypress_exit_popup( self, key: str, widget_size: Callable[[Widget], urwid_Size] @@ -467,7 +467,7 @@ def test__fetch_user_data_USER_NOT_FOUND(self, mocker: MockerFixture) -> None: assert custom_profile_data == {} @pytest.mark.parametrize( - "key", {*keys_for_command("GO_BACK"), *keys_for_command("USER_INFO")} + "key", {*keys_for_command("EXIT_POPUP"), *keys_for_command("USER_INFO")} ) def test_keypress_exit_popup( self, key: str, widget_size: Callable[[Widget], urwid_Size] @@ -540,7 +540,7 @@ def test_keypress_exit_popup_invalid_key( "key", { *keys_for_command("FULL_RENDERED_MESSAGE"), - *keys_for_command("GO_BACK"), + *keys_for_command("EXIT_POPUP"), }, ) def test_keypress_show_msg_info( @@ -616,7 +616,7 @@ def test_keypress_exit_popup_invalid_key( "key", { *keys_for_command("FULL_RAW_MESSAGE"), - *keys_for_command("GO_BACK"), + *keys_for_command("EXIT_POPUP"), }, ) def test_keypress_show_msg_info( @@ -688,7 +688,7 @@ def test_keypress_exit_popup_invalid_key( assert not self.controller.exit_popup.called @pytest.mark.parametrize( - "key", {*keys_for_command("EDIT_HISTORY"), *keys_for_command("GO_BACK")} + "key", {*keys_for_command("EDIT_HISTORY"), *keys_for_command("EXIT_POPUP")} ) def test_keypress_show_msg_info( self, key: str, widget_size: Callable[[Widget], urwid_Size] @@ -911,7 +911,7 @@ def test_keypress_any_key( assert not self.controller.exit_popup.called @pytest.mark.parametrize( - "key", {*keys_for_command("GO_BACK"), *keys_for_command("MARKDOWN_HELP")} + "key", {*keys_for_command("EXIT_POPUP"), *keys_for_command("MARKDOWN_HELP")} ) def test_keypress_exit_popup( self, key: str, widget_size: Callable[[Widget], urwid_Size] @@ -942,7 +942,7 @@ def test_keypress_any_key( assert not self.controller.exit_popup.called @pytest.mark.parametrize( - "key", {*keys_for_command("GO_BACK"), *keys_for_command("HELP")} + "key", {*keys_for_command("EXIT_POPUP"), *keys_for_command("HELP")} ) def test_keypress_exit_popup( self, key: str, widget_size: Callable[[Widget], urwid_Size] @@ -1113,7 +1113,7 @@ def test_keypress_full_raw_message( ) @pytest.mark.parametrize( - "key", {*keys_for_command("GO_BACK"), *keys_for_command("MSG_INFO")} + "key", {*keys_for_command("EXIT_POPUP"), *keys_for_command("MSG_INFO")} ) def test_keypress_exit_popup( self, key: str, widget_size: Callable[[Widget], urwid_Size] @@ -1550,7 +1550,7 @@ def test_footlinks( assert footlinks_width == expected_footlinks_width @pytest.mark.parametrize( - "key", {*keys_for_command("GO_BACK"), *keys_for_command("STREAM_INFO")} + "key", {*keys_for_command("EXIT_POPUP"), *keys_for_command("STREAM_INFO")} ) def test_keypress_exit_popup( self, key: str, widget_size: Callable[[Widget], urwid_Size] @@ -1615,7 +1615,7 @@ def mock_external_classes(self, mocker: MockerFixture) -> None: self.stream_members_view = StreamMembersView(self.controller, stream_id) @pytest.mark.parametrize( - "key", {*keys_for_command("GO_BACK"), *keys_for_command("STREAM_MEMBERS")} + "key", {*keys_for_command("EXIT_POPUP"), *keys_for_command("STREAM_MEMBERS")} ) def test_keypress_exit_popup( self, key: str, widget_size: Callable[[Widget], urwid_Size] @@ -1730,7 +1730,7 @@ def test_keypress_search_emoji( assert self.emoji_picker_view.get_focus() == "header" @pytest.mark.parametrize( - "key", {*keys_for_command("GO_BACK"), *keys_for_command("ADD_REACTION")} + "key", {*keys_for_command("EXIT_POPUP"), *keys_for_command("ADD_REACTION")} ) def test_keypress_exit_called( self, key: str, widget_size: Callable[[Widget], urwid_Size] diff --git a/tools/lint-hotkeys b/tools/lint-hotkeys index 503a024e98..83a7db068e 100755 --- a/tools/lint-hotkeys +++ b/tools/lint-hotkeys @@ -23,7 +23,7 @@ SCRIPT_NAME = PurePath(__file__).name HELP_TEXT_STYLE = re.compile(r"^[a-zA-Z /()',&@#:_-]*$") # Exclude keys from duplicate keys checking -KEYS_TO_EXCLUDE = ["q", "e", "m", "r"] +KEYS_TO_EXCLUDE = ["q", "e", "m", "r", "Esc"] def main(fix: bool) -> None: diff --git a/zulipterminal/config/keys.py b/zulipterminal/config/keys.py index 3306f682de..254778a12b 100644 --- a/zulipterminal/config/keys.py +++ b/zulipterminal/config/keys.py @@ -46,12 +46,6 @@ class KeyBinding(TypedDict): 'help_text': 'Show/hide About Menu', 'key_category': 'general', }, - 'GO_BACK': { - 'keys': ['esc'], - 'help_text': 'Go back', - 'excluded_from_random_tips': False, - 'key_category': 'general', - }, 'OPEN_DRAFT': { 'keys': ['d'], 'help_text': 'Open draft message saved in this session', @@ -62,6 +56,11 @@ class KeyBinding(TypedDict): 'help_text': 'Copy information from About Menu to clipboard', 'key_category': 'general', }, + 'EXIT_POPUP': { + 'keys': ['esc'], + 'help_text': 'Close popup', + 'key_category': 'navigation', + }, 'GO_UP': { 'keys': ['up', 'k'], 'help_text': 'Go up / Previous message', @@ -183,6 +182,11 @@ class KeyBinding(TypedDict): 'help_text': 'Narrow to compose box message recipient', 'key_category': 'msg_compose', }, + 'EXIT_COMPOSE': { + 'keys': ['esc'], + 'help_text': 'Exit message compose box', + 'key_category': 'msg_compose', + }, 'TOGGLE_NARROW': { 'keys': ['z'], 'help_text': @@ -260,6 +264,11 @@ class KeyBinding(TypedDict): 'help_text': 'Submit search and browse results', 'key_category': 'searching', }, + 'CLEAR_SEARCH': { + 'keys': ['esc'], + 'help_text': 'Clear search in current panel', + 'key_category': 'searching', + }, 'TOGGLE_MUTE_STREAM': { 'keys': ['m'], 'help_text': 'Mute/unmute streams', diff --git a/zulipterminal/model.py b/zulipterminal/model.py index f917a56254..1d7688cdeb 100644 --- a/zulipterminal/model.py +++ b/zulipterminal/model.py @@ -1714,7 +1714,7 @@ def _handle_message_event(self, event: Event) -> None: "Press '{}' to close this window." ) notice = notice_template.format( - failed_command, primary_display_key_for_command("GO_BACK") + failed_command, primary_display_key_for_command("EXIT_POPUP") ) self.controller.popup_with_message(notice, width=50) self.controller.update_screen() diff --git a/zulipterminal/ui_tools/boxes.py b/zulipterminal/ui_tools/boxes.py index 91e2f80966..fa3dc1ffd6 100644 --- a/zulipterminal/ui_tools/boxes.py +++ b/zulipterminal/ui_tools/boxes.py @@ -775,7 +775,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: if success: self.msg_write_box.edit_text = "" if self.msg_edit_state is not None: - self.keypress(size, primary_key_for_command("GO_BACK")) + self.keypress(size, primary_key_for_command("EXIT_COMPOSE")) assert self.msg_edit_state is None elif is_command_key("NARROW_MESSAGE_RECIPIENT", key): if self.compose_box_status == "open_with_stream": @@ -798,7 +798,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: self.view.controller.report_error( "Cannot narrow to message without specifying recipients." ) - elif is_command_key("GO_BACK", key): + elif is_command_key("EXIT_COMPOSE", key): self.send_stop_typing_status() self._set_compose_attributes_to_defaults() self.view.controller.exit_editor_mode() @@ -948,7 +948,7 @@ def main_view(self) -> Any: def keypress(self, size: urwid_Size, key: str) -> Optional[str]: if ( is_command_key("EXECUTE_SEARCH", key) and self.text_box.edit_text == "" - ) or is_command_key("GO_BACK", key): + ) or is_command_key("CLEAR_SEARCH", key): self.text_box.set_edit_text("") self.controller.exit_editor_mode() self.controller.view.middle_column.set_focus("body") @@ -1004,13 +1004,13 @@ def valid_char(self, ch: str) -> bool: def keypress(self, size: urwid_Size, key: str) -> Optional[str]: if ( is_command_key("EXECUTE_SEARCH", key) and self.get_edit_text() == "" - ) or is_command_key("GO_BACK", key): + ) or is_command_key("CLEAR_SEARCH", key): self.panel_view.view.controller.exit_editor_mode() self.reset_search_text() self.panel_view.set_focus("body") # Don't call 'Esc' when inside a popup search-box. if not self.panel_view.view.controller.is_any_popup_open(): - self.panel_view.keypress(size, primary_key_for_command("GO_BACK")) + self.panel_view.keypress(size, primary_key_for_command("CLEAR_SEARCH")) elif is_command_key("EXECUTE_SEARCH", key) and not self.panel_view.empty_search: self.panel_view.view.controller.exit_editor_mode() self.set_caption([("filter_results", " Search Results "), " "]) diff --git a/zulipterminal/ui_tools/views.py b/zulipterminal/ui_tools/views.py index ddb8e8d01a..c2034b3ef7 100644 --- a/zulipterminal/ui_tools/views.py +++ b/zulipterminal/ui_tools/views.py @@ -398,7 +398,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: self.stream_search_box.set_caption(" ") self.view.controller.enter_editor_mode_with(self.stream_search_box) return key - elif is_command_key("GO_BACK", key): + elif is_command_key("CLEAR_SEARCH", key): self.stream_search_box.reset_search_text() self.log.clear() self.log.extend(self.streams_btn_list) @@ -518,7 +518,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: self.topic_search_box.set_caption(" ") self.view.controller.enter_editor_mode_with(self.topic_search_box) return key - elif is_command_key("GO_BACK", key): + elif is_command_key("CLEAR_SEARCH", key): self.topic_search_box.reset_search_text() self.log.clear() self.log.extend(self.topics_btn_list) @@ -759,7 +759,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: self.user_search.set_caption(" ") self.view.controller.enter_editor_mode_with(self.user_search) return key - elif is_command_key("GO_BACK", key): + elif is_command_key("CLEAR_SEARCH", key): self.user_search.reset_search_text() self.allow_update_user_list = True self.body = UsersView(self.view.controller, self.users_btn_list) @@ -1058,7 +1058,7 @@ def make_table_with_categories( return widgets def keypress(self, size: urwid_Size, key: str) -> str: - if is_command_key("GO_BACK", key) or is_command_key(self.command, key): + if is_command_key("EXIT_POPUP", key) or is_command_key(self.command, key): self.controller.exit_popup() return super().keypress(size, key) @@ -1073,7 +1073,7 @@ def __init__( urwid.Padding(urwid.Text(notice_text), left=1, right=1), urwid.Divider(), ] - super().__init__(controller, widgets, "GO_BACK", width, title) + super().__init__(controller, widgets, "EXIT_POPUP", width, title) class AboutView(PopUpView): @@ -1351,7 +1351,7 @@ def exit_popup_no(self, args: Any) -> None: self.controller.exit_popup() def keypress(self, size: urwid_Size, key: str) -> str: - if is_command_key("GO_BACK", key): + if is_command_key("EXIT_POPUP", key): self.controller.exit_popup() return super().keypress(size, key) @@ -1561,7 +1561,7 @@ def __init__(self, controller: Any, stream_id: int) -> None: super().__init__(controller, widgets, "STREAM_INFO", popup_width, title) def keypress(self, size: urwid_Size, key: str) -> str: - if is_command_key("GO_BACK", key) or is_command_key("STREAM_MEMBERS", key): + if is_command_key("EXIT_POPUP", key) or is_command_key("STREAM_MEMBERS", key): self.controller.show_stream_info(stream_id=self.stream_id) return key return super().keypress(size, key) @@ -1895,7 +1895,7 @@ def _get_author_prefix(snapshot: Dict[str, Any], tag: EditHistoryTag) -> str: return author_prefix def keypress(self, size: urwid_Size, key: str) -> str: - if is_command_key("GO_BACK", key) or is_command_key("EDIT_HISTORY", key): + if is_command_key("EXIT_POPUP", key) or is_command_key("EDIT_HISTORY", key): self.controller.show_msg_info( msg=self.message, topic_links=self.topic_links, @@ -1937,7 +1937,7 @@ def __init__( ) def keypress(self, size: urwid_Size, key: str) -> str: - if is_command_key("GO_BACK", key) or is_command_key( + if is_command_key("EXIT_POPUP", key) or is_command_key( "FULL_RENDERED_MESSAGE", key ): self.controller.show_msg_info( @@ -1989,7 +1989,7 @@ def __init__( ) def keypress(self, size: urwid_Size, key: str) -> str: - if is_command_key("GO_BACK", key) or is_command_key("FULL_RAW_MESSAGE", key): + if is_command_key("EXIT_POPUP", key) or is_command_key("FULL_RAW_MESSAGE", key): self.controller.show_msg_info( msg=self.message, topic_links=self.topic_links, @@ -2144,7 +2144,7 @@ def keypress(self, size: urwid_Size, key: str) -> str: self.emoji_search.set_caption(" ") self.controller.enter_editor_mode_with(self.emoji_search) return key - elif is_command_key("GO_BACK", key) or is_command_key("ADD_REACTION", key): + elif is_command_key("EXIT_POPUP", key) or is_command_key("ADD_REACTION", key): for emoji_code, emoji_name in self.selected_emojis.items(): self.controller.model.toggle_message_reaction(self.message, emoji_name) self.emoji_search.reset_search_text()