diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c5aa61cc0..f4bbb88134 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Zulip-terminal Changelog + + +### Changes in the terminology from 'Stream' to 'channel' +- **Terminology Update**: The term "Stream" has been replaced with "Channel" in the UI for servers supporting the updated terminology. Older servers will continue to display "Stream" to maintain consistency with the server's terminology. + +### User Documentation Updates +- Updated all references to "Stream" in the documentation to "Channel." +- Added a FAQ entry to explain the terminology change and its behavior with older servers. + ## 0.7.0 - 20 May 2022 This long-awaited release includes contributions from our four 2021 diff --git a/docs/FAQ.md b/docs/FAQ.md index 04a5d160d9..6ab309da66 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -22,6 +22,7 @@ - [Hotkeys don't work as described](#hotkeys-dont-work-as-described) - [Zulip-term crashed!](#zulip-term-crashed) - [Something looks wrong! Where's this feature? There's a bug!](#something-looks-wrong-wheres-this-feature-theres-a-bug) + - [Why does the UI show "Channel" instead of "Stream"?](#why-does-the-ui-show-channel-instead-of-stream) ## What Python implementations are supported? @@ -497,3 +498,9 @@ Come meet us on the [#zulip-terminal](https://chat.zulip.org/#narrow/stream/206-zulip-terminal) stream on *chat.zulip.org*. +## Why does the UI show "Channel" instead of "Stream"? + +Starting from a specific Zulip feature level, the term "Stream" has been renamed to "Channel" to align with modern terminology. If you are using an older Zulip server, the UI will still display "Stream" to match the server's terminology. This ensures consistency between the Zulip Terminal and the web or mobile apps. + +If you encounter any issues or confusion, feel free to reach out on the [#zulip-terminal](https://chat.zulip.org/#narrow/stream/206-zulip-terminal) stream on *chat.zulip.org*. + diff --git a/docs/hotkeys.md b/docs/hotkeys.md index 43bbf6125a..a73c7ca519 100644 --- a/docs/hotkeys.md +++ b/docs/hotkeys.md @@ -30,7 +30,7 @@ ## Switching Messages View |Command|Key Combination| | :--- | :---: | -|View the stream of the current message|s| +|View the of the current message|s| |View the topic of the current message|S| |Zoom in/out the message's conversation context|z| |Switch message view to the compose box target|Meta + .| @@ -46,8 +46,8 @@ | :--- | :---: | |Search users|w| |Search messages|/| -|Search streams|q| -|Search topics in a stream|q| +|Search s|q| +|Search topics in a |q| |Search emojis from emoji picker|p| |Submit search and browse results|Enter| |Clear search in current panel|Esc| @@ -63,12 +63,12 @@ |Show/hide message information|i| |Show/hide message sender information|u| -## Stream list actions +## list actions |Command|Key Combination| | :--- | :---: | -|Toggle topics in a stream|t| -|Mute/unmute streams|m| -|Show/hide stream information & modify settings|i| +|Toggle topics in a |t| +|Mute/unmute s|m| +|Show/hide information & modify settings|i| ## User list actions |Command|Key Combination| @@ -84,7 +84,7 @@ |Reply mentioning the sender of the current message|@| |Reply quoting the current message text|>| |Reply directly to the sender of the current message|R| -|New message to a stream|c| +|New message to a |c| |New message to a person or group of people|x| ## Writing a message @@ -93,7 +93,7 @@ |Cycle through recipient and content boxes|Tab| |Send a message|Ctrl + d / Meta + Enter| |Save current message as a draft|Meta + s| -|Autocomplete @mentions, #stream_names, :emoji: and topics|Ctrl + f| +|Autocomplete @mentions, #_names, :emoji: and topics|Ctrl + f| |Cycle through autocomplete suggestions in reverse|Ctrl + r| |Exit message compose box|Esc| |Insert new line|Enter| @@ -123,11 +123,11 @@ |Delete previous character|Ctrl + h| |Swap with previous character|Ctrl + t| -## Stream information (press i to view info of a stream) +## information (press i to view info of a ) |Command|Key Combination| | :--- | :---: | -|Show/hide stream members|m| -|Copy stream email to clipboard|c| +|Show/hide members|m| +|Copy email to clipboard|c| ## Message information (press i to view info of a message) |Command|Key Combination| diff --git a/pyproject.toml b/pyproject.toml index f01695735c..53e4527fd8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -206,3 +206,4 @@ select = [ # N803: Allow upper-case in test function argument names, eg. ZFL, API # N806: Allow upper-case in test function variables "tests/*" = ["N802", "N803", "N806"] + diff --git a/tests/config/test_keys.py b/tests/config/test_keys.py index 752e69af29..93fd2578e7 100644 --- a/tests/config/test_keys.py +++ b/tests/config/test_keys.py @@ -6,12 +6,14 @@ from zulipterminal.config import keys -AVAILABLE_COMMANDS = list(keys.KEY_BINDINGS.keys()) +AVAILABLE_COMMANDS = list(keys.key_config.KEY_BINDINGS.keys()) -USED_KEYS = {key for values in keys.KEY_BINDINGS.values() for key in values["keys"]} +USED_KEYS = { + key for values in keys.key_config.KEY_BINDINGS.values() for key in values["keys"] +} -@pytest.fixture(params=keys.KEY_BINDINGS.keys()) +@pytest.fixture(params=keys.key_config.KEY_BINDINGS.keys()) def valid_command(request: Any) -> str: return request.param @@ -22,20 +24,20 @@ def invalid_command(request: Any) -> str: def test_keys_for_command(valid_command: str) -> None: - assert keys.KEY_BINDINGS[valid_command]["keys"] == keys.keys_for_command( - valid_command - ) + assert keys.key_config.KEY_BINDINGS[valid_command][ + "keys" + ] == keys.key_config.keys_for_command(valid_command) def test_primary_key_for_command(valid_command: str) -> None: - assert keys.KEY_BINDINGS[valid_command]["keys"][0] == keys.primary_key_for_command( - valid_command - ) + assert keys.key_config.KEY_BINDINGS[valid_command]["keys"][ + 0 + ] == keys.key_config.primary_key_for_command(valid_command) def test_keys_for_command_invalid_command(invalid_command: str) -> None: with pytest.raises(keys.InvalidCommand): - keys.keys_for_command(invalid_command) + keys.key_config.keys_for_command(invalid_command) def test_keys_for_command_identity(valid_command: str) -> None: @@ -44,30 +46,33 @@ def test_keys_for_command_identity(valid_command: str) -> None: new list which validates that the original keys don't get altered elsewhere unintentionally. """ - assert id(keys.KEY_BINDINGS[valid_command]["keys"]) != id( - keys.keys_for_command(valid_command) + assert id(keys.key_config.KEY_BINDINGS[valid_command]["keys"]) != id( + keys.key_config.keys_for_command(valid_command) ) def test_is_command_key_matching_keys(valid_command: str) -> None: - for key in keys.keys_for_command(valid_command): - assert keys.is_command_key(valid_command, key) + for key in keys.key_config.keys_for_command(valid_command): + assert keys.key_config.is_command_key(valid_command, key) def test_is_command_key_nonmatching_keys(valid_command: str) -> None: - keys_to_test = USED_KEYS - set(keys.keys_for_command(valid_command)) + keys_to_test = USED_KEYS - set(keys.key_config.keys_for_command(valid_command)) for key in keys_to_test: - assert not keys.is_command_key(valid_command, key) + assert not keys.key_config.is_command_key(valid_command, key) def test_is_command_key_invalid_command(invalid_command: str) -> None: with pytest.raises(keys.InvalidCommand): - keys.is_command_key(invalid_command, "esc") # key doesn't matter + keys.key_config.is_command_key(invalid_command, "esc") # key doesn't matter def test_HELP_is_not_allowed_as_tip() -> None: - assert keys.KEY_BINDINGS["HELP"]["excluded_from_random_tips"] is True - assert keys.KEY_BINDINGS["HELP"] not in keys.commands_for_random_tips() + assert keys.key_config.KEY_BINDINGS["HELP"]["excluded_from_random_tips"] is True + assert ( + keys.key_config.KEY_BINDINGS["HELP"] + not in keys.key_config.commands_for_random_tips() + ) def test_commands_for_random_tips(mocker: MockerFixture) -> None: @@ -96,20 +101,22 @@ def test_commands_for_random_tips(mocker: MockerFixture) -> None: "excluded_from_random_tips": True, }, } - mocker.patch.dict(keys.KEY_BINDINGS, new_key_bindings, clear=True) - result = keys.commands_for_random_tips() + mocker.patch.dict(keys.key_config.KEY_BINDINGS, new_key_bindings, clear=True) + result = keys.key_config.commands_for_random_tips() assert len(result) == 2 assert new_key_bindings["BETA"] in result assert new_key_bindings["GAMMA"] in result def test_updated_urwid_command_map() -> None: - urwid_to_zt_mapping = {v: k for k, v in keys.ZT_TO_URWID_CMD_MAPPING.items()} + urwid_to_zt_mapping = { + v: k for k, v in keys.key_config.ZT_TO_URWID_CMD_MAPPING.items() + } # Check if keys in command map are actually the ones in KEY_BINDINGS for key, urwid_cmd in keys.command_map._command.items(): try: zt_cmd = urwid_to_zt_mapping[urwid_cmd] - assert key in keys.keys_for_command(zt_cmd) + assert key in keys.key_config.keys_for_command(zt_cmd) except KeyError: pass @@ -138,7 +145,7 @@ def test_updated_urwid_command_map() -> None: ], ) def test_display_key_for_urwid_key(urwid_key: str, display_key: str) -> None: - assert keys.display_key_for_urwid_key(urwid_key) == display_key + assert keys.key_config.display_key_for_urwid_key(urwid_key) == display_key COMMAND_TO_DISPLAY_KEYS = [ @@ -150,14 +157,14 @@ def test_display_key_for_urwid_key(urwid_key: str, display_key: str) -> None: @pytest.mark.parametrize("command, display_keys", COMMAND_TO_DISPLAY_KEYS) def test_display_keys_for_command(command: str, display_keys: List[str]) -> None: - assert keys.display_keys_for_command(command) == display_keys + assert keys.key_config.display_keys_for_command(command) == display_keys @pytest.mark.parametrize("command, display_keys", COMMAND_TO_DISPLAY_KEYS) def test_primary_display_key_for_command(command: str, display_keys: List[str]) -> None: - assert keys.primary_display_key_for_command(command) == display_keys[0] + assert keys.key_config.primary_display_key_for_command(command) == display_keys[0] def test_display_keys_for_command_invalid_command(invalid_command: str) -> None: with pytest.raises(keys.InvalidCommand): - keys.display_keys_for_command(invalid_command) + keys.key_config.display_keys_for_command(invalid_command) diff --git a/tests/conftest.py b/tests/conftest.py index ad92c4cc71..ac02739842 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,11 +12,7 @@ Message, MessageType, ) -from zulipterminal.config.keys import ( - ZT_TO_URWID_CMD_MAPPING, - keys_for_command, - primary_key_for_command, -) +from zulipterminal.config.keys import key_config from zulipterminal.helper import ( CustomProfileData, Index, @@ -1495,8 +1491,8 @@ def compose_box_is_open(request: Any) -> bool: @pytest.fixture( params=[ - ("mouse press", 4, primary_key_for_command("GO_UP")), - ("mouse press", 5, primary_key_for_command("GO_DOWN")), + ("mouse press", 4, key_config.primary_key_for_command("GO_UP")), + ("mouse press", 5, key_config.primary_key_for_command("GO_DOWN")), ], ids=[ "mouse_scroll_up", @@ -1511,7 +1507,11 @@ def mouse_scroll_event(request: Any) -> Tuple[Any]: @pytest.fixture( - params=[key for cmd in ZT_TO_URWID_CMD_MAPPING for key in keys_for_command(cmd)], + params=[ + key + for cmd in key_config.ZT_TO_URWID_CMD_MAPPING + for key in key_config.keys_for_command(cmd) + ], ids=lambda param: "nav-key:{}".format(*param), ) def navigation_key(request: Any) -> str: diff --git a/tests/helper/test_helper.py b/tests/helper/test_helper.py index 2e32e5c10f..63658ff8c3 100644 --- a/tests/helper/test_helper.py +++ b/tests/helper/test_helper.py @@ -5,7 +5,7 @@ from pytest_mock import MockerFixture from zulipterminal.api_types import Composition -from zulipterminal.config.keys import primary_display_key_for_command +from zulipterminal.config.keys import key_config from zulipterminal.helper import ( Index, canonicalize_color, @@ -469,7 +469,7 @@ def test_notify_if_message_sent_outside_narrow( notify_if_message_sent_outside_narrow(req, controller) if footer_updated: - key = primary_display_key_for_command("NARROW_MESSAGE_RECIPIENT") + key = key_config.primary_display_key_for_command("NARROW_MESSAGE_RECIPIENT") report_success.assert_called_once_with( [ "Message is sent outside of current narrow." diff --git a/tests/ui/test_ui.py b/tests/ui/test_ui.py index d017f4697a..38a8b264f8 100644 --- a/tests/ui/test_ui.py +++ b/tests/ui/test_ui.py @@ -5,7 +5,7 @@ from urwid import Widget from zulipterminal.api_types import Composition -from zulipterminal.config.keys import keys_for_command +from zulipterminal.config.keys import key_config from zulipterminal.ui import LEFT_WIDTH, RIGHT_WIDTH, TAB_WIDTH, View from zulipterminal.urwid_types import urwid_Box @@ -299,7 +299,7 @@ def test_keypress_normal_mode_navigation( super_keypress.assert_called_once_with(size, navigation_key) - @pytest.mark.parametrize("key", keys_for_command("ALL_MENTIONS")) + @pytest.mark.parametrize("key", key_config.keys_for_command("ALL_MENTIONS")) def test_keypress_ALL_MENTIONS( self, view: View, @@ -316,7 +316,7 @@ def test_keypress_ALL_MENTIONS( view.mentioned_button.activate.assert_called_once_with(key) - @pytest.mark.parametrize("key", keys_for_command("STREAM_MESSAGE")) + @pytest.mark.parametrize("key", key_config.keys_for_command("STREAM_MESSAGE")) @pytest.mark.parametrize("autohide", [True, False], ids=["autohide", "no_autohide"]) def test_keypress_STREAM_MESSAGE( self, @@ -341,7 +341,7 @@ def test_keypress_STREAM_MESSAGE( assert returned_key == key assert view.body.focus_col == 1 - @pytest.mark.parametrize("key", keys_for_command("NEW_HINT")) + @pytest.mark.parametrize("key", key_config.keys_for_command("NEW_HINT")) def test_keypress_NEW_HINT( self, view: View, @@ -358,7 +358,7 @@ def test_keypress_NEW_HINT( set_footer_text.assert_called_once_with() assert returned_key == key - @pytest.mark.parametrize("key", keys_for_command("SEARCH_PEOPLE")) + @pytest.mark.parametrize("key", key_config.keys_for_command("SEARCH_PEOPLE")) @pytest.mark.parametrize("autohide", [True, False], ids=["autohide", "no_autohide"]) def test_keypress_autohide_users( self, @@ -383,7 +383,7 @@ def test_keypress_autohide_users( mocked_users_view.keypress.assert_called_once_with(size, key) assert view.body.focus_position == 2 - @pytest.mark.parametrize("key", keys_for_command("SEARCH_STREAMS")) + @pytest.mark.parametrize("key", key_config.keys_for_command("SEARCH_STREAMS")) @pytest.mark.parametrize("autohide", [True, False], ids=["autohide", "no_autohide"]) def test_keypress_autohide_streams( self, @@ -430,7 +430,7 @@ def test_keypress_autohide_streams( "no_draft_composition", ], ) - @pytest.mark.parametrize("key", keys_for_command("OPEN_DRAFT")) + @pytest.mark.parametrize("key", key_config.keys_for_command("OPEN_DRAFT")) @pytest.mark.parametrize("autohide", [True, False], ids=["autohide", "no_autohide"]) def test_keypress_OPEN_DRAFT( self, @@ -479,7 +479,7 @@ def test_keypress_OPEN_DRAFT( ["No draft message was saved in this session."] ) - @pytest.mark.parametrize("key", keys_for_command("SEARCH_PEOPLE")) + @pytest.mark.parametrize("key", key_config.keys_for_command("SEARCH_PEOPLE")) def test_keypress_edit_mode( self, view: View, diff --git a/tests/ui/test_ui_tools.py b/tests/ui/test_ui_tools.py index 0a4d9ec030..d667713899 100644 --- a/tests/ui/test_ui_tools.py +++ b/tests/ui/test_ui_tools.py @@ -5,7 +5,7 @@ from pytest import param as case from urwid import Divider -from zulipterminal.config.keys import keys_for_command, primary_key_for_command +from zulipterminal.config.keys import key_config from zulipterminal.config.symbols import STATUS_ACTIVE from zulipterminal.helper import powerset from zulipterminal.ui_tools.views import ( @@ -273,7 +273,7 @@ def test_mouse_event(self, mocker, msg_view, mouse_scroll_event, widget_size): msg_view.mouse_event(size, event, button, 0, 0, mocker.Mock()) msg_view.keypress.assert_called_once_with(size, keypress) - @pytest.mark.parametrize("key", keys_for_command("GO_DOWN")) + @pytest.mark.parametrize("key", key_config.keys_for_command("GO_DOWN")) def test_keypress_GO_DOWN(self, mocker, msg_view, key, widget_size): size = widget_size(msg_view) msg_view.new_loading = False @@ -286,7 +286,7 @@ def test_keypress_GO_DOWN(self, mocker, msg_view, key, widget_size): msg_view.set_focus_valign.assert_called_once_with("middle") @pytest.mark.parametrize("view_is_focused", [True, False]) - @pytest.mark.parametrize("key", keys_for_command("GO_DOWN")) + @pytest.mark.parametrize("key", key_config.keys_for_command("GO_DOWN")) def test_keypress_GO_DOWN_exception( self, mocker, msg_view, key, widget_size, view_is_focused ): @@ -312,7 +312,7 @@ def test_keypress_GO_DOWN_exception( msg_view.load_new_messages.assert_not_called() assert return_value == key - @pytest.mark.parametrize("key", keys_for_command("GO_UP")) + @pytest.mark.parametrize("key", key_config.keys_for_command("GO_UP")) def test_keypress_GO_UP(self, mocker, msg_view, key, widget_size): size = widget_size(msg_view) mocker.patch(MESSAGEVIEW + ".focus_position", return_value=0) @@ -325,7 +325,7 @@ def test_keypress_GO_UP(self, mocker, msg_view, key, widget_size): msg_view.set_focus_valign.assert_called_once_with("middle") @pytest.mark.parametrize("view_is_focused", [True, False]) - @pytest.mark.parametrize("key", keys_for_command("GO_UP")) + @pytest.mark.parametrize("key", key_config.keys_for_command("GO_UP")) def test_keypress_GO_UP_exception( self, mocker, msg_view, key, widget_size, view_is_focused ): @@ -542,7 +542,7 @@ def test_mouse_event(self, mocker, stream_view, mouse_scroll_event, widget_size) [mocker.call(size, key)] * SIDE_PANELS_MOUSE_SCROLL_LINES ) - @pytest.mark.parametrize("key", keys_for_command("SEARCH_STREAMS")) + @pytest.mark.parametrize("key", key_config.keys_for_command("SEARCH_STREAMS")) def test_keypress_SEARCH_STREAMS(self, mocker, stream_view, key, widget_size): size = widget_size(stream_view) mocker.patch.object(stream_view, "set_focus") @@ -559,7 +559,7 @@ def test_keypress_SEARCH_STREAMS(self, mocker, stream_view, key, widget_size): stream_view.stream_search_box ) - @pytest.mark.parametrize("key", keys_for_command("CLEAR_SEARCH")) + @pytest.mark.parametrize("key", key_config.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") @@ -572,7 +572,7 @@ def test_keypress_CLEAR_SEARCH(self, mocker, stream_view, key, widget_size): stream_view.log.clear() stream_view.log.extend(stream_view.streams_btn_list[3]) stream_view.log.set_focus(0) - stream_view.keypress(size, primary_key_for_command("GO_DOWN")) + stream_view.keypress(size, key_config.primary_key_for_command("GO_DOWN")) assert stream_view.log.get_focus()[1] != stream_view.focus_index_before_search # Exit search @@ -707,7 +707,7 @@ def test_update_topics_list( assert [topic.topic_name for topic in topic_view.log] == topic_final_log set_focus_valign.assert_called_once_with("bottom") - @pytest.mark.parametrize("key", keys_for_command("SEARCH_TOPICS")) + @pytest.mark.parametrize("key", key_config.keys_for_command("SEARCH_TOPICS")) def test_keypress_SEARCH_TOPICS(self, mocker, topic_view, key, widget_size): size = widget_size(topic_view) mocker.patch(VIEWS + ".TopicsView.set_focus") @@ -725,7 +725,7 @@ def test_keypress_SEARCH_TOPICS(self, mocker, topic_view, key, widget_size): topic_view.topic_search_box ) - @pytest.mark.parametrize("key", keys_for_command("CLEAR_SEARCH")) + @pytest.mark.parametrize("key", key_config.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") @@ -738,7 +738,7 @@ def test_keypress_CLEAR_SEARCH(self, mocker, topic_view, key, widget_size): topic_view.log.clear() topic_view.log.extend(topic_view.topics_btn_list[3]) topic_view.log.set_focus(0) - topic_view.keypress(size, primary_key_for_command("GO_DOWN")) + topic_view.keypress(size, key_config.primary_key_for_command("GO_DOWN")) assert topic_view.log.get_focus()[1] != topic_view.focus_index_before_search # Exit search @@ -846,14 +846,14 @@ def test_init(self, mid_col_view): "MSG_LIST", header=self.search_box, footer=self.write_box ) - @pytest.mark.parametrize("key", keys_for_command("SEARCH_MESSAGES")) + @pytest.mark.parametrize("key", key_config.keys_for_command("SEARCH_MESSAGES")) def test_keypress_focus_header(self, mid_col_view, mocker, key, widget_size): size = widget_size(mid_col_view) mid_col_view.focus_part = "header" mid_col_view.keypress(size, key) self.super_keypress.assert_called_once_with(size, key) - @pytest.mark.parametrize("key", keys_for_command("SEARCH_MESSAGES")) + @pytest.mark.parametrize("key", key_config.keys_for_command("SEARCH_MESSAGES")) def test_keypress_SEARCH_MESSAGES(self, mid_col_view, mocker, key, widget_size): size = widget_size(mid_col_view) mocker.patch(MIDCOLVIEW + ".focus_position") @@ -866,7 +866,9 @@ def test_keypress_SEARCH_MESSAGES(self, mid_col_view, mocker, key, widget_size): ) mid_col_view.set_focus.assert_called_once_with("header") - @pytest.mark.parametrize("reply_message_key", keys_for_command("REPLY_MESSAGE")) + @pytest.mark.parametrize( + "reply_message_key", key_config.keys_for_command("REPLY_MESSAGE") + ) def test_keypress_REPLY_MESSAGE( self, mid_col_view, mocker, widget_size, reply_message_key ): @@ -882,7 +884,7 @@ def test_keypress_REPLY_MESSAGE( mid_col_view.set_focus.assert_called_once_with("footer") assert mid_col_view.footer.focus_position == 1 - @pytest.mark.parametrize("key", keys_for_command("STREAM_MESSAGE")) + @pytest.mark.parametrize("key", key_config.keys_for_command("STREAM_MESSAGE")) def test_keypress_STREAM_MESSAGE(self, mid_col_view, mocker, key, widget_size): size = widget_size(mid_col_view) mocker.patch(MIDCOLVIEW + ".body") @@ -896,7 +898,7 @@ def test_keypress_STREAM_MESSAGE(self, mid_col_view, mocker, key, widget_size): mid_col_view.set_focus.assert_called_once_with("footer") assert mid_col_view.footer.focus_position == 0 - @pytest.mark.parametrize("key", keys_for_command("REPLY_AUTHOR")) + @pytest.mark.parametrize("key", key_config.keys_for_command("REPLY_AUTHOR")) def test_keypress_REPLY_AUTHOR(self, mid_col_view, mocker, key, widget_size): size = widget_size(mid_col_view) mocker.patch(MIDCOLVIEW + ".body") @@ -910,7 +912,7 @@ def test_keypress_REPLY_AUTHOR(self, mid_col_view, mocker, key, widget_size): mid_col_view.set_focus.assert_called_once_with("footer") assert mid_col_view.footer.focus_position == 1 - @pytest.mark.parametrize("key", keys_for_command("NEXT_UNREAD_TOPIC")) + @pytest.mark.parametrize("key", key_config.keys_for_command("NEXT_UNREAD_TOPIC")) def test_keypress_NEXT_UNREAD_TOPIC_stream( self, mid_col_view, mocker, widget_size, key ): @@ -928,7 +930,7 @@ def test_keypress_NEXT_UNREAD_TOPIC_stream( ) assert return_value == key - @pytest.mark.parametrize("key", keys_for_command("NEXT_UNREAD_TOPIC")) + @pytest.mark.parametrize("key", key_config.keys_for_command("NEXT_UNREAD_TOPIC")) def test_keypress_NEXT_UNREAD_TOPIC_no_stream( self, mid_col_view, mocker, widget_size, key ): @@ -942,7 +944,7 @@ def test_keypress_NEXT_UNREAD_TOPIC_no_stream( assert mid_col_view.controller.narrow_to_topic.called is False assert return_value == key - @pytest.mark.parametrize("key", keys_for_command("NEXT_UNREAD_PM")) + @pytest.mark.parametrize("key", key_config.keys_for_command("NEXT_UNREAD_PM")) def test_keypress_NEXT_UNREAD_PM_stream( self, mid_col_view, mocker, key, widget_size ): @@ -959,7 +961,7 @@ def test_keypress_NEXT_UNREAD_PM_stream( contextual_message_id=1, ) - @pytest.mark.parametrize("key", keys_for_command("NEXT_UNREAD_PM")) + @pytest.mark.parametrize("key", key_config.keys_for_command("NEXT_UNREAD_PM")) def test_keypress_NEXT_UNREAD_PM_no_pm( self, mid_col_view, mocker, key, widget_size ): @@ -971,7 +973,7 @@ def test_keypress_NEXT_UNREAD_PM_no_pm( assert return_value == key - @pytest.mark.parametrize("key", keys_for_command("PRIVATE_MESSAGE")) + @pytest.mark.parametrize("key", key_config.keys_for_command("PRIVATE_MESSAGE")) def test_keypress_PRIVATE_MESSAGE(self, mid_col_view, mocker, key, widget_size): size = widget_size(mid_col_view) mocker.patch(MIDCOLVIEW + ".focus_position") @@ -1093,7 +1095,7 @@ def test_users_view(self, users, users_btn_len, editor_mode, status, mocker): ) assert len(right_col_view.users_btn_list) == users_btn_len - @pytest.mark.parametrize("key", keys_for_command("SEARCH_PEOPLE")) + @pytest.mark.parametrize("key", key_config.keys_for_command("SEARCH_PEOPLE")) def test_keypress_SEARCH_PEOPLE(self, right_col_view, mocker, key, widget_size): size = widget_size(right_col_view) mocker.patch(VIEWS + ".RightColumnView.set_focus") @@ -1105,7 +1107,7 @@ 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("CLEAR_SEARCH")) + @pytest.mark.parametrize("key", key_config.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") diff --git a/tests/ui_tools/test_boxes.py b/tests/ui_tools/test_boxes.py index c4e89a2b58..bcc22a78ac 100644 --- a/tests/ui_tools/test_boxes.py +++ b/tests/ui_tools/test_boxes.py @@ -12,11 +12,7 @@ TYPING_STARTED_WAIT_PERIOD, TYPING_STOPPED_WAIT_PERIOD, ) -from zulipterminal.config.keys import ( - keys_for_command, - primary_display_key_for_command, - primary_key_for_command, -) +from zulipterminal.config.keys import key_config from zulipterminal.config.symbols import ( INVALID_MARKER, STREAM_MARKER_PRIVATE, @@ -223,7 +219,7 @@ def test_not_calling_typing_method_to_oneself( typing_recipient_user_ids, status="stop" ) - @pytest.mark.parametrize("key", keys_for_command("SEND_MESSAGE")) + @pytest.mark.parametrize("key", key_config.keys_for_command("SEND_MESSAGE")) def test_not_calling_send_private_message_without_recipients( self, key: str, @@ -240,7 +236,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("EXIT_COMPOSE")) + @pytest.mark.parametrize("key", key_config.keys_for_command("EXIT_COMPOSE")) def test__compose_attributes_reset_for_private_compose__no_popup( self, key: str, @@ -265,7 +261,7 @@ def test__compose_attributes_reset_for_private_compose__no_popup( assert write_box.msg_write_box.edit_text == "" assert write_box.compose_box_status == "closed" - @pytest.mark.parametrize("key", keys_for_command("EXIT_COMPOSE")) + @pytest.mark.parametrize("key", key_config.keys_for_command("EXIT_COMPOSE")) def test__compose_attributes_reset_for_private_compose__popup( self, key: str, @@ -285,7 +281,7 @@ def test__compose_attributes_reset_for_private_compose__popup( write_box.view.controller.exit_compose_confirmation_popup.assert_called_once() - @pytest.mark.parametrize("key", keys_for_command("EXIT_COMPOSE")) + @pytest.mark.parametrize("key", key_config.keys_for_command("EXIT_COMPOSE")) def test__compose_attributes_reset_for_stream_compose__no_popup( self, key: str, @@ -308,7 +304,7 @@ def test__compose_attributes_reset_for_stream_compose__no_popup( assert write_box.msg_write_box.edit_text == "" assert write_box.compose_box_status == "closed" - @pytest.mark.parametrize("key", keys_for_command("EXIT_COMPOSE")) + @pytest.mark.parametrize("key", key_config.keys_for_command("EXIT_COMPOSE")) def test__compose_attributes_reset_for_stream_compose__popup( self, key: str, @@ -372,9 +368,9 @@ def test__compose_attributes_reset_for_stream_compose__popup( ) @pytest.mark.parametrize( "key", - keys_for_command("SEND_MESSAGE") - + keys_for_command("SAVE_AS_DRAFT") - + keys_for_command("CYCLE_COMPOSE_FOCUS"), + key_config.keys_for_command("SEND_MESSAGE") + + key_config.keys_for_command("SAVE_AS_DRAFT") + + key_config.keys_for_command("CYCLE_COMPOSE_FOCUS"), ) def test_tidying_recipients_on_keypresses( self, @@ -410,9 +406,9 @@ def test_tidying_recipients_on_keypresses( ) @pytest.mark.parametrize( "key", - keys_for_command("SEND_MESSAGE") - + keys_for_command("SAVE_AS_DRAFT") - + keys_for_command("CYCLE_COMPOSE_FOCUS"), + key_config.keys_for_command("SEND_MESSAGE") + + key_config.keys_for_command("SAVE_AS_DRAFT") + + key_config.keys_for_command("CYCLE_COMPOSE_FOCUS"), ) def test_footer_notification_on_invalid_recipients( self, @@ -434,11 +430,14 @@ def test_footer_notification_on_invalid_recipients( expected_lines = [ "Invalid recipient(s) - " + invalid_recipients, " - Use ", - ("footer_contrast", primary_display_key_for_command("AUTOCOMPLETE")), + ( + "footer_contrast", + key_config.primary_display_key_for_command("AUTOCOMPLETE"), + ), " or ", ( "footer_contrast", - primary_display_key_for_command("AUTOCOMPLETE_REVERSE"), + key_config.primary_display_key_for_command("AUTOCOMPLETE_REVERSE"), ), " to autocomplete.", ] @@ -1135,7 +1134,7 @@ def test__to_box_autocomplete_with_spaces( write_box.focus_position = write_box.FOCUS_CONTAINER_HEADER size = widget_size(write_box) - write_box.keypress(size, primary_key_for_command("AUTOCOMPLETE")) + write_box.keypress(size, key_config.primary_key_for_command("AUTOCOMPLETE")) assert write_box.to_write_box.edit_text == expected_text @@ -1382,7 +1381,7 @@ def test__stream_box_autocomplete_with_spaces( write_box.header_write_box.focus_col = stream_focus size = widget_size(write_box) - write_box.keypress(size, primary_key_for_command("AUTOCOMPLETE")) + write_box.keypress(size, key_config.primary_key_for_command("AUTOCOMPLETE")) assert write_box.header_write_box[stream_focus].edit_text == expected_text @@ -1459,7 +1458,7 @@ def test__topic_box_autocomplete_with_spaces( write_box.header_write_box.focus_col = topic_focus size = widget_size(write_box) - write_box.keypress(size, primary_key_for_command("AUTOCOMPLETE")) + write_box.keypress(size, key_config.primary_key_for_command("AUTOCOMPLETE")) assert write_box.header_write_box[topic_focus].edit_text == expected_text @@ -1522,7 +1521,7 @@ def test__process_typeaheads( [_MessageEditState(message_id=10, old_topic="old topic"), None], ids=["update_message", "send_message"], ) - @pytest.mark.parametrize("key", keys_for_command("SEND_MESSAGE")) + @pytest.mark.parametrize("key", key_config.keys_for_command("SEND_MESSAGE")) def test_keypress_SEND_MESSAGE_no_topic( self, mocker: MockerFixture, @@ -1561,10 +1560,10 @@ def test_keypress_SEND_MESSAGE_no_topic( @pytest.mark.parametrize( "key, current_typeahead_mode, expected_typeahead_mode", [ - (primary_key_for_command("AUTOCOMPLETE"), False, False), - (primary_key_for_command("AUTOCOMPLETE_REVERSE"), False, False), - (primary_key_for_command("AUTOCOMPLETE"), True, True), - (primary_key_for_command("AUTOCOMPLETE_REVERSE"), True, True), + (key_config.primary_key_for_command("AUTOCOMPLETE"), False, False), + (key_config.primary_key_for_command("AUTOCOMPLETE_REVERSE"), False, False), + (key_config.primary_key_for_command("AUTOCOMPLETE"), True, True), + (key_config.primary_key_for_command("AUTOCOMPLETE_REVERSE"), True, True), ], ) def test__keypress_typeahead_mode_autocomplete_key_footer_no_reset( @@ -1588,7 +1587,7 @@ def test__keypress_typeahead_mode_autocomplete_key_footer_no_reset( @pytest.mark.parametrize( "key, current_typeahead_mode, expected_typeahead_mode", [ - (primary_key_for_command("EXIT_COMPOSE"), True, False), + (key_config.primary_key_for_command("EXIT_COMPOSE"), True, False), ("space", True, False), ("k", True, False), ], @@ -1736,7 +1735,9 @@ def test__keypress_typeahead_mode_autocomplete_key_footer_reset( ), ], ) - @pytest.mark.parametrize("tab_key", keys_for_command("CYCLE_COMPOSE_FOCUS")) + @pytest.mark.parametrize( + "tab_key", key_config.keys_for_command("CYCLE_COMPOSE_FOCUS") + ) def test_keypress_CYCLE_COMPOSE_FOCUS( self, write_box: WriteBox, @@ -1790,7 +1791,7 @@ def focus_val(x: str) -> int: expected_focus_col_name ) - @pytest.mark.parametrize("key", keys_for_command("MARKDOWN_HELP")) + @pytest.mark.parametrize("key", key_config.keys_for_command("MARKDOWN_HELP")) def test_keypress_MARKDOWN_HELP( self, write_box: WriteBox, key: str, widget_size: Callable[[Widget], urwid_Size] ) -> None: @@ -1839,8 +1840,8 @@ class TestPanelSearchBox: @pytest.fixture def panel_search_box(self, mocker: MockerFixture) -> PanelSearchBox: - # X is the return from display_keys_for_command("UNTESTED_TOKEN") - mocker.patch(MODULE + ".display_keys_for_command", return_value="X") + # X is the return from key_config.display_key_config.keys_for_command("UNTESTED_TOKEN") + mocker.patch(MODULE + ".key_config.display_keys_for_command", return_value="X") panel_view = mocker.Mock() update_func = mocker.Mock() return PanelSearchBox(panel_view, "UNTESTED_TOKEN", update_func) @@ -1891,7 +1892,7 @@ def test_valid_char( @pytest.mark.parametrize( "log, expect_body_focus_set", [([], False), (["SOMETHING"], True)] ) - @pytest.mark.parametrize("enter_key", keys_for_command("EXECUTE_SEARCH")) + @pytest.mark.parametrize("enter_key", key_config.keys_for_command("EXECUTE_SEARCH")) def test_keypress_ENTER( self, panel_search_box: PanelSearchBox, @@ -1930,7 +1931,7 @@ 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("CLEAR_SEARCH")) + @pytest.mark.parametrize("back_key", key_config.keys_for_command("CLEAR_SEARCH")) def test_keypress_CLEAR_SEARCH( self, panel_search_box: PanelSearchBox, diff --git a/tests/ui_tools/test_buttons.py b/tests/ui_tools/test_buttons.py index adc2faa127..ac5b27a6ab 100644 --- a/tests/ui_tools/test_buttons.py +++ b/tests/ui_tools/test_buttons.py @@ -6,7 +6,7 @@ from urwid import AttrMap, Overlay, Widget from zulipterminal.api_types import Message -from zulipterminal.config.keys import keys_for_command +from zulipterminal.config.keys import key_config from zulipterminal.config.symbols import CHECK_MARK, MUTE_MARKER from zulipterminal.ui_tools.buttons import ( DecodedStream, @@ -226,7 +226,7 @@ def test_mark_unmuted( assert stream_button.suffix_style == "unread_count" update_count.assert_called_once_with(unread_count) - @pytest.mark.parametrize("key", keys_for_command("TOGGLE_TOPIC")) + @pytest.mark.parametrize("key", key_config.keys_for_command("TOGGLE_TOPIC")) def test_keypress_ENTER_TOGGLE_TOPIC( self, mocker: MockerFixture, @@ -242,7 +242,7 @@ def test_keypress_ENTER_TOGGLE_TOPIC( stream_button ) - @pytest.mark.parametrize("key", keys_for_command("TOGGLE_MUTE_STREAM")) + @pytest.mark.parametrize("key", key_config.keys_for_command("TOGGLE_MUTE_STREAM")) def test_keypress_TOGGLE_MUTE_STREAM( self, mocker: MockerFixture, @@ -262,7 +262,9 @@ def test_keypress_TOGGLE_MUTE_STREAM( class TestUserButton: # FIXME Place this in a general test of a derived class? - @pytest.mark.parametrize("enter_key", keys_for_command("ACTIVATE_BUTTON")) + @pytest.mark.parametrize( + "enter_key", key_config.keys_for_command("ACTIVATE_BUTTON") + ) def test_activate_called_once_on_keypress( self, mocker: MockerFixture, @@ -292,7 +294,7 @@ def test_activate_called_once_on_keypress( assert activate.call_count == 1 - @pytest.mark.parametrize("key", keys_for_command("USER_INFO")) + @pytest.mark.parametrize("key", key_config.keys_for_command("USER_INFO")) def test_keypress_USER_INFO( self, mocker: MockerFixture, @@ -358,7 +360,7 @@ def test_init_calls_top_button( assert emoji_button.emoji_name == emoji_unit[0] assert emoji_button.reaction_count == count - @pytest.mark.parametrize("key", keys_for_command("ACTIVATE_BUTTON")) + @pytest.mark.parametrize("key", key_config.keys_for_command("ACTIVATE_BUTTON")) @pytest.mark.parametrize( "emoji, has_user_reacted, is_selected_final, expected_reaction_count", [ @@ -510,7 +512,7 @@ def test_mark_muted(self, mocker: MockerFixture, topic_button: TopicButton) -> N assert topic_button.suffix_text == MUTE_MARKER update_widget.assert_called_once_with() - @pytest.mark.parametrize("key", keys_for_command("TOGGLE_TOPIC")) + @pytest.mark.parametrize("key", key_config.keys_for_command("TOGGLE_TOPIC")) def test_keypress_EXIT_TOGGLE_TOPIC( self, mocker: MockerFixture, diff --git a/tests/ui_tools/test_messages.py b/tests/ui_tools/test_messages.py index 09a68bc89f..bd4603a668 100644 --- a/tests/ui_tools/test_messages.py +++ b/tests/ui_tools/test_messages.py @@ -8,7 +8,7 @@ from pytest import param as case from urwid import Columns, Divider, Padding, Text -from zulipterminal.config.keys import keys_for_command, primary_key_for_command +from zulipterminal.config.keys import key_config from zulipterminal.config.symbols import ( ALL_MESSAGES_MARKER, DIRECT_MESSAGE_MARKER, @@ -1194,7 +1194,7 @@ def test_update_message_author_status( assert msg_box.update_message_author_status() == update_required - @pytest.mark.parametrize("key", keys_for_command("STREAM_MESSAGE")) + @pytest.mark.parametrize("key", key_config.keys_for_command("STREAM_MESSAGE")) @pytest.mark.parametrize( "narrow, expect_to_prefill", [ @@ -1233,7 +1233,7 @@ def test_keypress_STREAM_MESSAGE( else: write_box.stream_box_view.assert_called_once_with(0) - @pytest.mark.parametrize("key", keys_for_command("EDIT_MESSAGE")) + @pytest.mark.parametrize("key", key_config.keys_for_command("EDIT_MESSAGE")) @pytest.mark.parametrize( [ "to_vary_in_each_message", @@ -1901,13 +1901,13 @@ def test_footlinks_limit(self, maximum_footlinks, expected_instance): @pytest.mark.parametrize( "key", - keys_for_command("ACTIVATE_BUTTON"), + key_config.keys_for_command("ACTIVATE_BUTTON"), ids=lambda param: f"left_click-key:{param}", ) def test_mouse_event_left_click( self, mocker, msg_box, key, widget_size, compose_box_is_open ): - expected_keypress = primary_key_for_command("ACTIVATE_BUTTON") + expected_keypress = key_config.primary_key_for_command("ACTIVATE_BUTTON") size = widget_size(msg_box) col = 1 row = 1 diff --git a/tests/ui_tools/test_popups.py b/tests/ui_tools/test_popups.py index 47b3ff199c..9d4530c42f 100644 --- a/tests/ui_tools/test_popups.py +++ b/tests/ui_tools/test_popups.py @@ -8,7 +8,7 @@ from urwid import Columns, Pile, Text, Widget from zulipterminal.api_types import Message -from zulipterminal.config.keys import is_command_key, keys_for_command +from zulipterminal.config.keys import key_config from zulipterminal.config.ui_mappings import EDIT_MODE_CAPTIONS from zulipterminal.helper import CustomProfileData, TidiedUserInfo from zulipterminal.ui_tools.messages import MessageBox @@ -79,7 +79,7 @@ def test_exit_popup_no( self.callback.assert_not_called() assert self.controller.exit_popup.called - @pytest.mark.parametrize("key", keys_for_command("EXIT_POPUP")) + @pytest.mark.parametrize("key", key_config.keys_for_command("EXIT_POPUP")) def test_exit_popup_EXIT_POPUP( self, popup_view: PopUpConfirmationView, @@ -134,7 +134,7 @@ 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("EXIT_POPUP")) + @pytest.mark.parametrize("key", key_config.keys_for_command("EXIT_POPUP")) def test_keypress_EXIT_POPUP( self, key: str, @@ -151,7 +151,7 @@ def test_keypress_command_key( ) -> None: size = widget_size(self.pop_up_view) mocker.patch( - MODULE + ".is_command_key", + MODULE + ".key_config.is_command_key", side_effect=(lambda command, key: command == self.command), ) self.pop_up_view.keypress(size, "cmd_key") @@ -164,15 +164,15 @@ def test_keypress_navigation( widget_size: Callable[[Widget], urwid_Size], ) -> None: size = widget_size(self.pop_up_view) - # Patch `is_command_key` to not raise an 'Invalid Command' exception + # Patch `key_config.is_command_key` to not raise an 'Invalid Command' exception # when its parameters are (self.command, key) as there is no # self.command='COMMAND' command in keys.py. mocker.patch( - MODULE + ".is_command_key", + MODULE + ".key_config.is_command_key", side_effect=( lambda command, key: False if command == self.command - else is_command_key(command, key) + else key_config.is_command_key(command, key) ), ) @@ -212,7 +212,11 @@ def mock_external_classes(self, mocker: MockerFixture) -> None: ) @pytest.mark.parametrize( - "key", {*keys_for_command("EXIT_POPUP"), *keys_for_command("ABOUT")} + "key", + { + *key_config.keys_for_command("EXIT_POPUP"), + *key_config.keys_for_command("ABOUT"), + }, ) def test_keypress_exit_popup( self, key: str, widget_size: Callable[[Widget], urwid_Size] @@ -221,7 +225,7 @@ def test_keypress_exit_popup( self.about_view.keypress(size, key) assert self.controller.exit_popup.called - @pytest.mark.parametrize("key", {*keys_for_command("COPY_ABOUT_INFO")}) + @pytest.mark.parametrize("key", {*key_config.keys_for_command("COPY_ABOUT_INFO")}) def test_keypress_copy_info( self, key: str, widget_size: Callable[[Widget], urwid_Size] ) -> None: @@ -471,7 +475,11 @@ def test__fetch_user_data_USER_NOT_FOUND(self, mocker: MockerFixture) -> None: assert custom_profile_data == {} @pytest.mark.parametrize( - "key", {*keys_for_command("EXIT_POPUP"), *keys_for_command("USER_INFO")} + "key", + { + *key_config.keys_for_command("EXIT_POPUP"), + *key_config.keys_for_command("USER_INFO"), + }, ) def test_keypress_exit_popup( self, key: str, widget_size: Callable[[Widget], urwid_Size] @@ -520,7 +528,7 @@ def test_init(self, msg_box: MessageBox) -> None: assert self.full_rendered_message.header.widget_list == msg_box.header assert self.full_rendered_message.footer.widget_list == msg_box.footer - @pytest.mark.parametrize("key", keys_for_command("MSG_INFO")) + @pytest.mark.parametrize("key", key_config.keys_for_command("MSG_INFO")) def test_keypress_exit_popup( self, key: str, widget_size: Callable[[Widget], urwid_Size] ) -> None: @@ -543,8 +551,8 @@ def test_keypress_exit_popup_invalid_key( @pytest.mark.parametrize( "key", { - *keys_for_command("FULL_RENDERED_MESSAGE"), - *keys_for_command("EXIT_POPUP"), + *key_config.keys_for_command("FULL_RENDERED_MESSAGE"), + *key_config.keys_for_command("EXIT_POPUP"), }, ) def test_keypress_show_msg_info( @@ -596,7 +604,7 @@ def test_init(self, msg_box: MessageBox) -> None: assert self.full_raw_message.header.widget_list == msg_box.header assert self.full_raw_message.footer.widget_list == msg_box.footer - @pytest.mark.parametrize("key", keys_for_command("MSG_INFO")) + @pytest.mark.parametrize("key", key_config.keys_for_command("MSG_INFO")) def test_keypress_exit_popup( self, key: str, widget_size: Callable[[Widget], urwid_Size] ) -> None: @@ -619,8 +627,8 @@ def test_keypress_exit_popup_invalid_key( @pytest.mark.parametrize( "key", { - *keys_for_command("FULL_RAW_MESSAGE"), - *keys_for_command("EXIT_POPUP"), + *key_config.keys_for_command("FULL_RAW_MESSAGE"), + *key_config.keys_for_command("EXIT_POPUP"), }, ) def test_keypress_show_msg_info( @@ -671,7 +679,7 @@ def test_init(self) -> None: message_id=self.message["id"], ) - @pytest.mark.parametrize("key", keys_for_command("MSG_INFO")) + @pytest.mark.parametrize("key", key_config.keys_for_command("MSG_INFO")) def test_keypress_exit_popup( self, key: str, widget_size: Callable[[Widget], urwid_Size] ) -> None: @@ -692,7 +700,11 @@ 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("EXIT_POPUP")} + "key", + { + *key_config.keys_for_command("EDIT_HISTORY"), + *key_config.keys_for_command("EXIT_POPUP"), + }, ) def test_keypress_show_msg_info( self, key: str, widget_size: Callable[[Widget], urwid_Size] @@ -869,7 +881,7 @@ def test_init(self, edit_mode_view: EditModeView) -> None: (2, "change_all"), ], ) - @pytest.mark.parametrize("key", keys_for_command("ACTIVATE_BUTTON")) + @pytest.mark.parametrize("key", key_config.keys_for_command("ACTIVATE_BUTTON")) def test_select_edit_mode( self, edit_mode_view: EditModeView, @@ -915,7 +927,11 @@ def test_keypress_any_key( assert not self.controller.exit_popup.called @pytest.mark.parametrize( - "key", {*keys_for_command("EXIT_POPUP"), *keys_for_command("MARKDOWN_HELP")} + "key", + { + *key_config.keys_for_command("EXIT_POPUP"), + *key_config.keys_for_command("MARKDOWN_HELP"), + }, ) def test_keypress_exit_popup( self, key: str, widget_size: Callable[[Widget], urwid_Size] @@ -946,7 +962,11 @@ def test_keypress_any_key( assert not self.controller.exit_popup.called @pytest.mark.parametrize( - "key", {*keys_for_command("EXIT_POPUP"), *keys_for_command("HELP")} + "key", + { + *key_config.keys_for_command("EXIT_POPUP"), + *key_config.keys_for_command("HELP"), + }, ) def test_keypress_exit_popup( self, key: str, widget_size: Callable[[Widget], urwid_Size] @@ -1013,7 +1033,7 @@ def test_keypress_any_key( self.msg_info_view.keypress(size, key) assert not self.controller.exit_popup.called - @pytest.mark.parametrize("key", keys_for_command("EDIT_HISTORY")) + @pytest.mark.parametrize("key", key_config.keys_for_command("EDIT_HISTORY")) @pytest.mark.parametrize("realm_allow_edit_history", [True, False]) @pytest.mark.parametrize( "edited_message_id", @@ -1064,7 +1084,9 @@ def test_keypress_edit_history( else: self.controller.show_edit_history.assert_not_called() - @pytest.mark.parametrize("key", keys_for_command("FULL_RENDERED_MESSAGE")) + @pytest.mark.parametrize( + "key", key_config.keys_for_command("FULL_RENDERED_MESSAGE") + ) def test_keypress_full_rendered_message( self, message_fixture: Message, @@ -1090,7 +1112,7 @@ def test_keypress_full_rendered_message( time_mentions=list(), ) - @pytest.mark.parametrize("key", keys_for_command("FULL_RAW_MESSAGE")) + @pytest.mark.parametrize("key", key_config.keys_for_command("FULL_RAW_MESSAGE")) def test_keypress_full_raw_message( self, message_fixture: Message, @@ -1117,7 +1139,11 @@ def test_keypress_full_raw_message( ) @pytest.mark.parametrize( - "key", {*keys_for_command("EXIT_POPUP"), *keys_for_command("MSG_INFO")} + "key", + { + *key_config.keys_for_command("EXIT_POPUP"), + *key_config.keys_for_command("MSG_INFO"), + }, ) def test_keypress_exit_popup( self, key: str, widget_size: Callable[[Widget], urwid_Size] @@ -1126,7 +1152,7 @@ def test_keypress_exit_popup( self.msg_info_view.keypress(size, key) assert self.controller.exit_popup.called - @pytest.mark.parametrize("key", keys_for_command("VIEW_IN_BROWSER")) + @pytest.mark.parametrize("key", key_config.keys_for_command("VIEW_IN_BROWSER")) def test_keypress_view_in_browser( self, mocker: MockerFixture, @@ -1311,7 +1337,7 @@ def test_keypress_any_key( self.stream_info_view.keypress(size, key) assert not self.controller.exit_popup.called - @pytest.mark.parametrize("key", keys_for_command("STREAM_MEMBERS")) + @pytest.mark.parametrize("key", key_config.keys_for_command("STREAM_MEMBERS")) def test_keypress_stream_members( self, key: str, widget_size: Callable[[Widget], urwid_Size] ) -> None: @@ -1466,7 +1492,7 @@ def test_stream_info_content__email_copy_text( assert ("Stream email", expected_copy_text) in stream_details_data @pytest.mark.parametrize("normalized_email_address", ("user@example.com", None)) - @pytest.mark.parametrize("key", keys_for_command("COPY_STREAM_EMAIL")) + @pytest.mark.parametrize("key", key_config.keys_for_command("COPY_STREAM_EMAIL")) def test_keypress_copy_stream_email( self, key: str, @@ -1565,7 +1591,11 @@ def test_footlinks( assert footlinks_width == expected_footlinks_width @pytest.mark.parametrize( - "key", {*keys_for_command("EXIT_POPUP"), *keys_for_command("STREAM_INFO")} + "key", + { + *key_config.keys_for_command("EXIT_POPUP"), + *key_config.keys_for_command("STREAM_INFO"), + }, ) def test_keypress_exit_popup( self, key: str, widget_size: Callable[[Widget], urwid_Size] @@ -1574,7 +1604,9 @@ def test_keypress_exit_popup( self.stream_info_view.keypress(size, key) assert self.controller.exit_popup.called - @pytest.mark.parametrize("key", (*keys_for_command("ACTIVATE_BUTTON"), " ")) + @pytest.mark.parametrize( + "key", (*key_config.keys_for_command("ACTIVATE_BUTTON"), " ") + ) def test_checkbox_toggle_mute_stream( self, key: str, widget_size: Callable[[Widget], urwid_Size] ) -> None: @@ -1587,7 +1619,9 @@ def test_checkbox_toggle_mute_stream( toggle_mute_status.assert_called_once_with(stream_id) - @pytest.mark.parametrize("key", (*keys_for_command("ACTIVATE_BUTTON"), " ")) + @pytest.mark.parametrize( + "key", (*key_config.keys_for_command("ACTIVATE_BUTTON"), " ") + ) def test_checkbox_toggle_pin_stream( self, key: str, widget_size: Callable[[Widget], urwid_Size] ) -> None: @@ -1600,7 +1634,9 @@ def test_checkbox_toggle_pin_stream( toggle_pin_status.assert_called_once_with(stream_id) - @pytest.mark.parametrize("key", (*keys_for_command("ACTIVATE_BUTTON"), " ")) + @pytest.mark.parametrize( + "key", (*key_config.keys_for_command("ACTIVATE_BUTTON"), " ") + ) def test_checkbox_toggle_visual_notification( self, key: str, widget_size: Callable[[Widget], urwid_Size] ) -> None: @@ -1630,7 +1666,11 @@ def mock_external_classes(self, mocker: MockerFixture) -> None: self.stream_members_view = StreamMembersView(self.controller, stream_id) @pytest.mark.parametrize( - "key", {*keys_for_command("EXIT_POPUP"), *keys_for_command("STREAM_MEMBERS")} + "key", + { + *key_config.keys_for_command("EXIT_POPUP"), + *key_config.keys_for_command("STREAM_MEMBERS"), + }, ) def test_keypress_exit_popup( self, key: str, widget_size: Callable[[Widget], urwid_Size] @@ -1733,7 +1773,7 @@ def test_mouse_event( emoji_picker.mouse_event(size, event, button, 0, 0, mocker.Mock()) mocked_emoji_picker_keypress.assert_called_once_with(size, keypress) - @pytest.mark.parametrize("key", keys_for_command("SEARCH_EMOJIS")) + @pytest.mark.parametrize("key", key_config.keys_for_command("SEARCH_EMOJIS")) def test_keypress_search_emoji( self, key: str, widget_size: Callable[[Widget], urwid_Size] ) -> None: @@ -1745,7 +1785,11 @@ def test_keypress_search_emoji( assert self.emoji_picker_view.get_focus() == "header" @pytest.mark.parametrize( - "key", {*keys_for_command("EXIT_POPUP"), *keys_for_command("ADD_REACTION")} + "key", + { + *key_config.keys_for_command("EXIT_POPUP"), + *key_config.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 c687b87f80..a7bba4d537 100755 --- a/tools/lint-hotkeys +++ b/tools/lint-hotkeys @@ -6,11 +6,7 @@ from collections import defaultdict from pathlib import Path, PurePath from typing import Dict, List, Tuple -from zulipterminal.config.keys import ( - HELP_CATEGORIES, - KEY_BINDINGS, - display_keys_for_command, -) +from zulipterminal.config.keys import key_config KEYS_FILE = ( @@ -42,7 +38,7 @@ def lint_hotkeys_file() -> None: # To lint keys description error_flag = 0 categories = read_help_categories() - for action in HELP_CATEGORIES: + for action in key_config.HELP_CATEGORIES: check_duplicate_keys_list: List[str] = [] for help_text, key_combinations_list in categories[action]: check_duplicate_keys_list.extend(key_combinations_list) @@ -65,7 +61,7 @@ def lint_hotkeys_file() -> None: ] if len(duplicate_keys) != 0: print( - f"Duplicate key combination for keys {duplicate_keys} for category ({HELP_CATEGORIES[action]}) detected" + f"Duplicate key combination for keys {duplicate_keys} for category ({key_config.HELP_CATEGORIES[action]}) detected" ) error_flag = 1 if error_flag == 1: @@ -101,9 +97,9 @@ def get_hotkeys_file_string() -> str: f"\n" "\n\n# Hot Keys\n" ) - for action in HELP_CATEGORIES: + for action in key_config.HELP_CATEGORIES: hotkeys_file_string += ( - f"## {HELP_CATEGORIES[action]}\n" + f"## {key_config.HELP_CATEGORIES[action]}\n" "|Command|Key Combination|\n" "| :--- | :---: |\n" ) @@ -135,9 +131,9 @@ def read_help_categories() -> Dict[str, List[Tuple[str, List[str]]]]: Get all help categories from KEYS_FILE """ categories = defaultdict(list) - for cmd, item in KEY_BINDINGS.items(): + for cmd, item in key_config.KEY_BINDINGS.items(): categories[item["key_category"]].append( - (item["help_text"], display_keys_for_command(cmd)) + (item["help_text"], key_config.display_keys_for_command(cmd)) ) return categories diff --git a/zulipterminal/config/keys.py b/zulipterminal/config/keys.py index a86420a364..baa899e12f 100644 --- a/zulipterminal/config/keys.py +++ b/zulipterminal/config/keys.py @@ -25,566 +25,582 @@ class KeyBinding(TypedDict): key_category: str -# fmt: off -KEY_BINDINGS: Dict[str, KeyBinding] = { - # Key that is displayed in the UI is determined by the method - # primary_key_for_command. (Currently the first key in the list) - - 'HELP': { - 'keys': ['?'], - 'help_text': 'Show/hide Help Menu', - 'excluded_from_random_tips': True, - 'key_category': 'general', - }, - 'MARKDOWN_HELP': { - 'keys': ['meta m'], - 'help_text': 'Show/hide Markdown Help Menu', - 'key_category': 'general', - }, - 'ABOUT': { - 'keys': ['meta ?'], - 'help_text': 'Show/hide About Menu', - 'key_category': 'general', - }, - 'OPEN_DRAFT': { - 'keys': ['d'], - 'help_text': 'Open draft message saved in this session', - 'key_category': 'open_compose', - }, - 'COPY_ABOUT_INFO': { - 'keys': ['c'], - 'help_text': 'Copy information from About Menu to clipboard', - 'key_category': 'general', - }, - 'COPY_TRACEBACK': { - 'keys': ['c'], - 'help_text': 'Copy traceback from Exception Popup to clipboard', - 'excluded_from_random_tips': True, - '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', - 'key_category': 'navigation', - }, - 'GO_DOWN': { - 'keys': ['down', 'j'], - 'help_text': 'Go down / Next message', - 'key_category': 'navigation', - }, - 'GO_LEFT': { - 'keys': ['left', 'h'], - 'help_text': 'Go left', - 'key_category': 'navigation', - }, - 'GO_RIGHT': { - 'keys': ['right', 'l'], - 'help_text': 'Go right', - 'key_category': 'navigation', - }, - 'SCROLL_UP': { - 'keys': ['page up', 'K'], - 'help_text': 'Scroll up', - 'key_category': 'navigation', - }, - 'SCROLL_DOWN': { - 'keys': ['page down', 'J'], - 'help_text': 'Scroll down', - 'key_category': 'navigation', - }, - 'GO_TO_BOTTOM': { - 'keys': ['end', 'G'], - 'help_text': 'Go to bottom / Last message', - 'key_category': 'navigation', - }, - 'ACTIVATE_BUTTON': { - 'keys': ['enter', ' '], - 'help_text': 'Trigger the selected entry', - 'key_category': 'navigation', - }, - 'REPLY_MESSAGE': { - 'keys': ['r', 'enter'], - 'help_text': 'Reply to the current message', - 'key_category': 'open_compose', - }, - 'MENTION_REPLY': { - 'keys': ['@'], - 'help_text': 'Reply mentioning the sender of the current message', - 'key_category': 'open_compose', - }, - 'QUOTE_REPLY': { - 'keys': ['>'], - 'help_text': 'Reply quoting the current message text', - 'key_category': 'open_compose', - }, - 'REPLY_AUTHOR': { - 'keys': ['R'], - 'help_text': 'Reply directly to the sender of the current message', - 'key_category': 'open_compose', - }, - 'EDIT_MESSAGE': { - 'keys': ['e'], - 'help_text': "Edit message's content or topic", - 'key_category': 'msg_actions' - }, - 'STREAM_MESSAGE': { - 'keys': ['c'], - 'help_text': 'New message to a stream', - 'key_category': 'open_compose', - }, - 'PRIVATE_MESSAGE': { - 'keys': ['x'], - 'help_text': 'New message to a person or group of people', - 'key_category': 'open_compose', - }, - 'CYCLE_COMPOSE_FOCUS': { - 'keys': ['tab'], - 'help_text': 'Cycle through recipient and content boxes', - 'key_category': 'compose_box', - }, - 'SEND_MESSAGE': { - 'keys': ['ctrl d', 'meta enter'], - 'help_text': 'Send a message', - 'key_category': 'compose_box', - }, - 'SAVE_AS_DRAFT': { - 'keys': ['meta s'], - 'help_text': 'Save current message as a draft', - 'key_category': 'compose_box', - }, - 'AUTOCOMPLETE': { - 'keys': ['ctrl f'], - 'help_text': ('Autocomplete @mentions, #stream_names, :emoji:' - ' and topics'), - 'key_category': 'compose_box', - }, - 'AUTOCOMPLETE_REVERSE': { - 'keys': ['ctrl r'], - 'help_text': 'Cycle through autocomplete suggestions in reverse', - 'key_category': 'compose_box', - }, - 'ADD_REACTION': { - 'keys': [':'], - 'help_text': 'Show/hide emoji picker for current message', - 'key_category': 'msg_actions', - }, - 'STREAM_NARROW': { - 'keys': ['s'], - 'help_text': 'View the stream of the current message', - 'key_category': 'narrowing', - }, - 'TOPIC_NARROW': { - 'keys': ['S'], - 'help_text': 'View the topic of the current message', - 'key_category': 'narrowing', - }, - 'TOGGLE_NARROW': { - 'keys': ['z'], - 'help_text': - "Zoom in/out the message's conversation context", - 'key_category': 'narrowing', - }, - 'NARROW_MESSAGE_RECIPIENT': { - 'keys': ['meta .'], - 'help_text': 'Switch message view to the compose box target', - 'key_category': 'narrowing', - }, - 'EXIT_COMPOSE': { - 'keys': ['esc'], - 'help_text': 'Exit message compose box', - 'key_category': 'compose_box', - }, - 'REACTION_AGREEMENT': { - 'keys': ['='], - 'help_text': 'Toggle first emoji reaction on selected message', - 'key_category': 'msg_actions', - }, - 'TOGGLE_TOPIC': { - 'keys': ['t'], - 'help_text': 'Toggle topics in a stream', - 'key_category': 'stream_list', - }, - 'ALL_MESSAGES': { - 'keys': ['a', 'esc'], - 'help_text': 'View all messages', - 'key_category': 'narrowing', - }, - 'ALL_PM': { - 'keys': ['P'], - 'help_text': 'View all direct messages', - 'key_category': 'narrowing', - }, - 'ALL_STARRED': { - 'keys': ['f'], - 'help_text': 'View all starred messages', - 'key_category': 'narrowing', - }, - 'ALL_MENTIONS': { - 'keys': ['#'], - 'help_text': "View all messages in which you're mentioned", - 'key_category': 'narrowing', - }, - 'NEXT_UNREAD_TOPIC': { - 'keys': ['n'], - 'help_text': 'Next unread topic', - 'key_category': 'narrowing', - }, - 'NEXT_UNREAD_PM': { - 'keys': ['p'], - 'help_text': 'Next unread direct message', - 'key_category': 'narrowing', - }, - 'SEARCH_PEOPLE': { - 'keys': ['w'], - 'help_text': 'Search users', - 'key_category': 'searching', - }, - 'SEARCH_MESSAGES': { - 'keys': ['/'], - 'help_text': 'Search messages', - 'key_category': 'searching', - }, - 'SEARCH_STREAMS': { - 'keys': ['q'], - 'help_text': 'Search streams', - 'key_category': 'searching', - }, - 'SEARCH_TOPICS': { - 'keys': ['q'], - 'help_text': 'Search topics in a stream', - 'key_category': 'searching', - }, - 'SEARCH_EMOJIS': { - 'keys': ['p'], - 'help_text': 'Search emojis from emoji picker', - 'excluded_from_random_tips': True, - 'key_category': 'searching', - }, - 'EXECUTE_SEARCH': { - 'keys': ['enter'], - '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', - 'key_category': 'stream_list', - }, - 'THUMBS_UP': { - 'keys': ['+'], - 'help_text': 'Toggle thumbs-up reaction to the current message', - 'key_category': 'msg_actions', - }, - 'TOGGLE_STAR_STATUS': { - 'keys': ['ctrl s', '*'], - 'help_text': 'Toggle star status of the current message', - 'key_category': 'msg_actions', - }, - 'MSG_INFO': { - 'keys': ['i'], - 'help_text': 'Show/hide message information', - 'key_category': 'msg_actions', - }, - 'MSG_SENDER_INFO': { - 'keys': ['u'], - 'help_text': 'Show/hide message sender information', - 'key_category': 'msg_actions', - }, - 'EDIT_HISTORY': { - 'keys': ['e'], - 'help_text': 'Show/hide edit history', - 'excluded_from_random_tips': True, - 'key_category': 'msg_info', - }, - 'VIEW_IN_BROWSER': { - 'keys': ['v'], - 'help_text': - 'View current message in browser', - 'excluded_from_random_tips': True, - 'key_category': 'msg_info', - }, - 'STREAM_INFO': { - 'keys': ['i'], - 'help_text': 'Show/hide stream information & modify settings', - 'key_category': 'stream_list', - }, - 'STREAM_MEMBERS': { - 'keys': ['m'], - 'help_text': 'Show/hide stream members', - 'excluded_from_random_tips': True, - 'key_category': 'stream_info', - }, - 'COPY_STREAM_EMAIL': { - 'keys': ['c'], - 'help_text': - 'Copy stream email to clipboard', - 'excluded_from_random_tips': True, - 'key_category': 'stream_info', - }, - 'REDRAW': { - 'keys': ['ctrl l'], - 'help_text': 'Redraw screen', - 'key_category': 'general', - }, - 'QUIT': { - 'keys': ['ctrl c'], - 'help_text': 'Quit', - 'key_category': 'general', - }, - 'USER_INFO': { - 'keys': ['i'], - 'help_text': 'Show/hide user information', - 'key_category': 'user_list', - }, - 'NARROW_TO_USER_PM': { - # Added to clarify functionality of button activation, - # as opposed to opening user profile or other effects. - # Implementation uses ACTIVATE_BUTTON command. - 'keys': ['enter'], - 'help_text': 'Narrow to direct messages with user', - 'key_category': 'user_list', - }, - 'BEGINNING_OF_LINE': { - 'keys': ['ctrl a', 'home'], - 'help_text': 'Start of line', - 'key_category': 'editor_navigation', - }, - 'END_OF_LINE': { - 'keys': ['ctrl e', 'end'], - 'help_text': 'End of line', - 'key_category': 'editor_navigation', - }, - 'ONE_WORD_BACKWARD': { - 'keys': ['meta b', 'shift left'], - 'help_text': 'Start of current or previous word', - 'key_category': 'editor_navigation', - }, - 'ONE_WORD_FORWARD': { - 'keys': ['meta f', 'shift right'], - 'help_text': 'Start of next word', - 'key_category': 'editor_navigation', - }, - 'PREV_LINE': { - 'keys': ['up', 'ctrl p'], - 'help_text': 'Previous line', - 'key_category': 'editor_navigation', - }, - 'NEXT_LINE': { - 'keys': ['down', 'ctrl n'], - 'help_text': 'Next line', - 'key_category': 'editor_navigation', - }, - 'UNDO_LAST_ACTION': { - 'keys': ['ctrl _'], - 'help_text': 'Undo last action', - 'key_category': 'editor_text_manipulation', - }, - 'CLEAR_MESSAGE': { - 'keys': ['ctrl l'], - 'help_text': 'Clear text box', - 'key_category': 'editor_text_manipulation', - }, - 'CUT_TO_END_OF_LINE': { - 'keys': ['ctrl k'], - 'help_text': 'Cut forwards to the end of the line', - 'key_category': 'editor_text_manipulation', - }, - 'CUT_TO_START_OF_LINE': { - 'keys': ['ctrl u'], - 'help_text': 'Cut backwards to the start of the line', - 'key_category': 'editor_text_manipulation', - }, - 'CUT_TO_END_OF_WORD': { - 'keys': ['meta d'], - 'help_text': 'Cut forwards to the end of the current word', - 'key_category': 'editor_text_manipulation', - }, - 'CUT_TO_START_OF_WORD': { - 'keys': ['ctrl w', 'meta backspace'], - 'help_text': 'Cut backwards to the start of the current word', - 'key_category': 'editor_text_manipulation', - }, - 'CUT_WHOLE_LINE': { - 'keys': ['meta x'], - 'help_text': 'Cut the current line', - 'key_category': 'editor_text_manipulation', - }, - 'PASTE_LAST_CUT': { - 'keys': ['ctrl y'], - 'help_text': 'Paste last cut section', - 'key_category': 'editor_text_manipulation', - }, - 'DELETE_LAST_CHARACTER': { - 'keys': ['ctrl h'], - 'help_text': 'Delete previous character', - 'key_category': 'editor_text_manipulation', - }, - 'TRANSPOSE_CHARACTERS': { - 'keys': ['ctrl t'], - 'help_text': 'Swap with previous character', - 'key_category': 'editor_text_manipulation', - }, - 'NEW_LINE': { - # urwid_readline's command - # This obvious hotkey is added to clarify against 'enter' to send - # and to differentiate from other hotkeys using 'enter'. - 'keys': ['enter'], - 'help_text': 'Insert new line', - 'key_category': 'compose_box', - }, - 'OPEN_EXTERNAL_EDITOR': { - 'keys': ['ctrl o'], - 'help_text': 'Open an external editor to edit the message content', - 'key_category': 'compose_box', - }, - 'FULL_RENDERED_MESSAGE': { - 'keys': ['f'], - 'help_text': 'Show/hide full rendered message', - 'key_category': 'msg_info', - }, - 'FULL_RAW_MESSAGE': { - 'keys': ['r'], - 'help_text': 'Show/hide full raw message', - 'key_category': 'msg_info', - }, - 'NEW_HINT': { - 'keys': ['tab'], - 'help_text': 'New footer hotkey hint', - 'key_category': 'general', - }, -} -# fmt: on - -HELP_CATEGORIES = { - "general": "General", - "navigation": "Navigation", - "narrowing": "Switching Messages View", - "searching": "Searching", - "msg_actions": "Message actions", - "stream_list": "Stream list actions", - "user_list": "User list actions", - "open_compose": "Begin composing a message", - "compose_box": "Writing a message", - "editor_navigation": "Editor: Navigation", - "editor_text_manipulation": "Editor: Text Manipulation", - "stream_info": ( - f"Stream information (press {KEY_BINDINGS['STREAM_INFO']['keys'][0]}" - f" to view info of a stream)" - ), - "msg_info": ( - f"Message information (press {KEY_BINDINGS['MSG_INFO']['keys'][0]}" - f" to view info of a message)" - ), -} - -ZT_TO_URWID_CMD_MAPPING = { - "GO_UP": CURSOR_UP, - "GO_DOWN": CURSOR_DOWN, - "GO_LEFT": CURSOR_LEFT, - "GO_RIGHT": CURSOR_RIGHT, - "SCROLL_UP": CURSOR_PAGE_UP, - "SCROLL_DOWN": CURSOR_PAGE_DOWN, - "GO_TO_BOTTOM": CURSOR_MAX_RIGHT, - "ACTIVATE_BUTTON": ACTIVATE, -} - - class InvalidCommand(Exception): pass -def is_command_key(command: str, key: str) -> bool: - """ - Returns the mapped binding for a key if mapped - or the key otherwise. - """ - try: - return key in KEY_BINDINGS[command]["keys"] - except KeyError as exception: - raise InvalidCommand(command) from exception +class KeyConfig: + def __init__(self) -> None: + self.terminology: str = "" # Default terminology + self.key_bindings: Dict[str, KeyBinding] = {} + self.help_categories: Dict[str, str] = {} + self.ZT_TO_URWID_CMD_MAPPING = { + "GO_UP": CURSOR_UP, + "GO_DOWN": CURSOR_DOWN, + "GO_LEFT": CURSOR_LEFT, + "GO_RIGHT": CURSOR_RIGHT, + "SCROLL_UP": CURSOR_PAGE_UP, + "SCROLL_DOWN": CURSOR_PAGE_DOWN, + "GO_TO_BOTTOM": CURSOR_MAX_RIGHT, + "ACTIVATE_BUTTON": ACTIVATE, + } -def keys_for_command(command: str) -> List[str]: - """ - Returns the actual keys for a given mapped command - """ - try: - return list(KEY_BINDINGS[command]["keys"]) - except KeyError as exception: - raise InvalidCommand(command) from exception + def set_terminology(self, terminology: str) -> None: + """ + Sets the terminology dynamically and updates dependent values. + """ + self.terminology = terminology + # Update key bindings and help categories + self.KEY_BINDINGS: Dict[str, KeyBinding] = { + # Key that is displayed in the UI is determined by the method + # primary_key_for_command. (Currently the first key in the list) + "HELP": { + "keys": ["?"], + "help_text": "Show/hide Help Menu", + "excluded_from_random_tips": True, + "key_category": "general", + }, + "MARKDOWN_HELP": { + "keys": ["meta m"], + "help_text": "Show/hide Markdown Help Menu", + "key_category": "general", + }, + "ABOUT": { + "keys": ["meta ?"], + "help_text": "Show/hide About Menu", + "key_category": "general", + }, + "OPEN_DRAFT": { + "keys": ["d"], + "help_text": "Open draft message saved in this session", + "key_category": "open_compose", + }, + "COPY_ABOUT_INFO": { + "keys": ["c"], + "help_text": "Copy information from About Menu to clipboard", + "key_category": "general", + }, + "COPY_TRACEBACK": { + "keys": ["c"], + "help_text": "Copy traceback from Exception Popup to clipboard", + "excluded_from_random_tips": True, + "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", + "key_category": "navigation", + }, + "GO_DOWN": { + "keys": ["down", "j"], + "help_text": "Go down / Next message", + "key_category": "navigation", + }, + "GO_LEFT": { + "keys": ["left", "h"], + "help_text": "Go left", + "key_category": "navigation", + }, + "GO_RIGHT": { + "keys": ["right", "l"], + "help_text": "Go right", + "key_category": "navigation", + }, + "SCROLL_UP": { + "keys": ["page up", "K"], + "help_text": "Scroll up", + "key_category": "navigation", + }, + "SCROLL_DOWN": { + "keys": ["page down", "J"], + "help_text": "Scroll down", + "key_category": "navigation", + }, + "GO_TO_BOTTOM": { + "keys": ["end", "G"], + "help_text": "Go to bottom / Last message", + "key_category": "navigation", + }, + "ACTIVATE_BUTTON": { + "keys": ["enter", " "], + "help_text": "Trigger the selected entry", + "key_category": "navigation", + }, + "REPLY_MESSAGE": { + "keys": ["r", "enter"], + "help_text": "Reply to the current message", + "key_category": "open_compose", + }, + "MENTION_REPLY": { + "keys": ["@"], + "help_text": "Reply mentioning the sender of the current message", + "key_category": "open_compose", + }, + "QUOTE_REPLY": { + "keys": [">"], + "help_text": "Reply quoting the current message text", + "key_category": "open_compose", + }, + "REPLY_AUTHOR": { + "keys": ["R"], + "help_text": "Reply directly to the sender of the current message", + "key_category": "open_compose", + }, + "EDIT_MESSAGE": { + "keys": ["e"], + "help_text": "Edit message's content or topic", + "key_category": "msg_actions", + }, + "STREAM_MESSAGE": { + "keys": ["c"], + "help_text": f"New message to a {self.terminology}", + "key_category": "open_compose", + }, + "PRIVATE_MESSAGE": { + "keys": ["x"], + "help_text": "New message to a person or group of people", + "key_category": "open_compose", + }, + "CYCLE_COMPOSE_FOCUS": { + "keys": ["tab"], + "help_text": "Cycle through recipient and content boxes", + "key_category": "compose_box", + }, + "SEND_MESSAGE": { + "keys": ["ctrl d", "meta enter"], + "help_text": "Send a message", + "key_category": "compose_box", + }, + "SAVE_AS_DRAFT": { + "keys": ["meta s"], + "help_text": "Save current message as a draft", + "key_category": "compose_box", + }, + "AUTOCOMPLETE": { + "keys": ["ctrl f"], + "help_text": ( + f"Autocomplete @mentions, #{self.terminology}_names, :emoji:" + " and topics" + ), + "key_category": "compose_box", + }, + "AUTOCOMPLETE_REVERSE": { + "keys": ["ctrl r"], + "help_text": "Cycle through autocomplete suggestions in reverse", + "key_category": "compose_box", + }, + "ADD_REACTION": { + "keys": [":"], + "help_text": "Show/hide emoji picker for current message", + "key_category": "msg_actions", + }, + "STREAM_NARROW": { + "keys": ["s"], + "help_text": f"View the {self.terminology} of the current message", + "key_category": "narrowing", + }, + "TOPIC_NARROW": { + "keys": ["S"], + "help_text": "View the topic of the current message", + "key_category": "narrowing", + }, + "TOGGLE_NARROW": { + "keys": ["z"], + "help_text": "Zoom in/out the message's conversation context", + "key_category": "narrowing", + }, + "NARROW_MESSAGE_RECIPIENT": { + "keys": ["meta ."], + "help_text": "Switch message view to the compose box target", + "key_category": "narrowing", + }, + "EXIT_COMPOSE": { + "keys": ["esc"], + "help_text": "Exit message compose box", + "key_category": "compose_box", + }, + "REACTION_AGREEMENT": { + "keys": ["="], + "help_text": "Toggle first emoji reaction on selected message", + "key_category": "msg_actions", + }, + "TOGGLE_TOPIC": { + "keys": ["t"], + "help_text": f"Toggle topics in a {self.terminology}", + "key_category": "stream_list", + }, + "ALL_MESSAGES": { + "keys": ["a", "esc"], + "help_text": "View all messages", + "key_category": "narrowing", + }, + "ALL_PM": { + "keys": ["P"], + "help_text": "View all direct messages", + "key_category": "narrowing", + }, + "ALL_STARRED": { + "keys": ["f"], + "help_text": "View all starred messages", + "key_category": "narrowing", + }, + "ALL_MENTIONS": { + "keys": ["#"], + "help_text": "View all messages in which you're mentioned", + "key_category": "narrowing", + }, + "NEXT_UNREAD_TOPIC": { + "keys": ["n"], + "help_text": "Next unread topic", + "key_category": "narrowing", + }, + "NEXT_UNREAD_PM": { + "keys": ["p"], + "help_text": "Next unread direct message", + "key_category": "narrowing", + }, + "SEARCH_PEOPLE": { + "keys": ["w"], + "help_text": "Search users", + "key_category": "searching", + }, + "SEARCH_MESSAGES": { + "keys": ["/"], + "help_text": "Search messages", + "key_category": "searching", + }, + "SEARCH_STREAMS": { + "keys": ["q"], + "help_text": f"Search {self.terminology}s", + "key_category": "searching", + }, + "SEARCH_TOPICS": { + "keys": ["q"], + "help_text": f"Search topics in a {self.terminology}", + "key_category": "searching", + }, + "SEARCH_EMOJIS": { + "keys": ["p"], + "help_text": "Search emojis from emoji picker", + "excluded_from_random_tips": True, + "key_category": "searching", + }, + "EXECUTE_SEARCH": { + "keys": ["enter"], + "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": f"Mute/unmute {self.terminology}s", + "key_category": "stream_list", + }, + "THUMBS_UP": { + "keys": ["+"], + "help_text": "Toggle thumbs-up reaction to the current message", + "key_category": "msg_actions", + }, + "TOGGLE_STAR_STATUS": { + "keys": ["ctrl s", "*"], + "help_text": "Toggle star status of the current message", + "key_category": "msg_actions", + }, + "MSG_INFO": { + "keys": ["i"], + "help_text": "Show/hide message information", + "key_category": "msg_actions", + }, + "MSG_SENDER_INFO": { + "keys": ["u"], + "help_text": "Show/hide message sender information", + "key_category": "msg_actions", + }, + "EDIT_HISTORY": { + "keys": ["e"], + "help_text": "Show/hide edit history", + "excluded_from_random_tips": True, + "key_category": "msg_info", + }, + "VIEW_IN_BROWSER": { + "keys": ["v"], + "help_text": "View current message in browser", + "excluded_from_random_tips": True, + "key_category": "msg_info", + }, + "STREAM_INFO": { + "keys": ["i"], + "help_text": f"Show/hide {self.terminology} information & modify settings", + "key_category": "stream_list", + }, + "STREAM_MEMBERS": { + "keys": ["m"], + "help_text": f"Show/hide {self.terminology} members", + "excluded_from_random_tips": True, + "key_category": "stream_info", + }, + "COPY_STREAM_EMAIL": { + "keys": ["c"], + "help_text": f"Copy {self.terminology} email to clipboard", + "excluded_from_random_tips": True, + "key_category": "stream_info", + }, + "REDRAW": { + "keys": ["ctrl l"], + "help_text": "Redraw screen", + "key_category": "general", + }, + "QUIT": { + "keys": ["ctrl c"], + "help_text": "Quit", + "key_category": "general", + }, + "USER_INFO": { + "keys": ["i"], + "help_text": "Show/hide user information", + "key_category": "user_list", + }, + "NARROW_TO_USER_PM": { + # Added to clarify functionality of button activation, + # as opposed to opening user profile or other effects. + # Implementation uses ACTIVATE_BUTTON command. + "keys": ["enter"], + "help_text": "Narrow to direct messages with user", + "key_category": "user_list", + }, + "BEGINNING_OF_LINE": { + "keys": ["ctrl a", "home"], + "help_text": "Start of line", + "key_category": "editor_navigation", + }, + "END_OF_LINE": { + "keys": ["ctrl e", "end"], + "help_text": "End of line", + "key_category": "editor_navigation", + }, + "ONE_WORD_BACKWARD": { + "keys": ["meta b", "shift left"], + "help_text": "Start of current or previous word", + "key_category": "editor_navigation", + }, + "ONE_WORD_FORWARD": { + "keys": ["meta f", "shift right"], + "help_text": "Start of next word", + "key_category": "editor_navigation", + }, + "PREV_LINE": { + "keys": ["up", "ctrl p"], + "help_text": "Previous line", + "key_category": "editor_navigation", + }, + "NEXT_LINE": { + "keys": ["down", "ctrl n"], + "help_text": "Next line", + "key_category": "editor_navigation", + }, + "UNDO_LAST_ACTION": { + "keys": ["ctrl _"], + "help_text": "Undo last action", + "key_category": "editor_text_manipulation", + }, + "CLEAR_MESSAGE": { + "keys": ["ctrl l"], + "help_text": "Clear text box", + "key_category": "editor_text_manipulation", + }, + "CUT_TO_END_OF_LINE": { + "keys": ["ctrl k"], + "help_text": "Cut forwards to the end of the line", + "key_category": "editor_text_manipulation", + }, + "CUT_TO_START_OF_LINE": { + "keys": ["ctrl u"], + "help_text": "Cut backwards to the start of the line", + "key_category": "editor_text_manipulation", + }, + "CUT_TO_END_OF_WORD": { + "keys": ["meta d"], + "help_text": "Cut forwards to the end of the current word", + "key_category": "editor_text_manipulation", + }, + "CUT_TO_START_OF_WORD": { + "keys": ["ctrl w", "meta backspace"], + "help_text": "Cut backwards to the start of the current word", + "key_category": "editor_text_manipulation", + }, + "CUT_WHOLE_LINE": { + "keys": ["meta x"], + "help_text": "Cut the current line", + "key_category": "editor_text_manipulation", + }, + "PASTE_LAST_CUT": { + "keys": ["ctrl y"], + "help_text": "Paste last cut section", + "key_category": "editor_text_manipulation", + }, + "DELETE_LAST_CHARACTER": { + "keys": ["ctrl h"], + "help_text": "Delete previous character", + "key_category": "editor_text_manipulation", + }, + "TRANSPOSE_CHARACTERS": { + "keys": ["ctrl t"], + "help_text": "Swap with previous character", + "key_category": "editor_text_manipulation", + }, + "NEW_LINE": { + # urwid_readline's command + # This obvious hotkey is added to clarify against 'enter' to send + # and to differentiate from other hotkeys using 'enter'. + "keys": ["enter"], + "help_text": "Insert new line", + "key_category": "compose_box", + }, + "OPEN_EXTERNAL_EDITOR": { + "keys": ["ctrl o"], + "help_text": "Open an external editor to edit the message content", + "key_category": "compose_box", + }, + "FULL_RENDERED_MESSAGE": { + "keys": ["f"], + "help_text": "Show/hide full rendered message", + "key_category": "msg_info", + }, + "FULL_RAW_MESSAGE": { + "keys": ["r"], + "help_text": "Show/hide full raw message", + "key_category": "msg_info", + }, + "NEW_HINT": { + "keys": ["tab"], + "help_text": "New footer hotkey hint", + "key_category": "general", + }, + } -def primary_key_for_command(command: str) -> str: - """ - Primary Key is the key that will be displayed eg. in the UI - """ - return keys_for_command(command).pop(0) + self.HELP_CATEGORIES = { + "general": "General", + "navigation": "Navigation", + "narrowing": "Switching Messages View", + "searching": "Searching", + "msg_actions": "Message actions", + "stream_list": "stream list actions", + "user_list": "User list actions", + "open_compose": "Begin composing a message", + "compose_box": "Writing a message", + "editor_navigation": "Editor: Navigation", + "editor_text_manipulation": "Editor: Text Manipulation", + "strream_info": ( + f"stream information (press {self.KEY_BINDINGS['STREAM_INFO']['keys'][0]}" + " to view info of a stream)" + ), + "msg_info": ( + f"Message information (press {self.KEY_BINDINGS['MSG_INFO']['keys'][0]}" + " to view info of a message)" + ), + } + def is_command_key(self, command: str, key: str) -> bool: + """ + Returns the mapped binding for a key if mapped + or the key otherwise. + """ + try: + return key in self.KEY_BINDINGS[command]["keys"] + except KeyError as exception: + raise InvalidCommand(command) from exception -URWID_KEY_TO_DISPLAY_KEY_MAPPING = { - "page up": "PgUp", - "page down": "PgDn", -} + def primary_key_for_command(self, command: str) -> str: + """ + Primary Key is the key that will be displayed eg. in the UI + """ + return self.keys_for_command(command).pop(0) + URWID_KEY_TO_DISPLAY_KEY_MAPPING = { + "page up": "PgUp", + "page down": "PgDn", + } -def display_key_for_urwid_key(urwid_key: str) -> str: - """ - Returns a displayable user-centric format of the urwid key. - """ - if urwid_key == " ": - return "Space" + def display_key_for_urwid_key(self, urwid_key: str) -> str: + """ + Returns a displayable user-centric format of the urwid key. + """ + if urwid_key == " ": + return "Space" - for urwid_map_key, display_map_key in URWID_KEY_TO_DISPLAY_KEY_MAPPING.items(): - if urwid_map_key in urwid_key: - urwid_key = urwid_key.replace(urwid_map_key, display_map_key) - display_key = [ - keyboard_key.capitalize() - if len(keyboard_key) > 1 and keyboard_key[0].islower() - else keyboard_key - for keyboard_key in urwid_key.split() - ] - return " ".join(display_key) + for ( + urwid_map_key, + display_map_key, + ) in self.URWID_KEY_TO_DISPLAY_KEY_MAPPING.items(): + if urwid_map_key in urwid_key: + urwid_key = urwid_key.replace(urwid_map_key, display_map_key) + display_key = [ + keyboard_key.capitalize() + if len(keyboard_key) > 1 and keyboard_key[0].islower() + else keyboard_key + for keyboard_key in urwid_key.split() + ] + return " ".join(display_key) + def display_keys_for_command(self, command: str) -> List[str]: + """ + Returns the user-friendly display keys for a given mapped command + """ + return [ + self.display_key_for_urwid_key(urwid_key) + for urwid_key in self.keys_for_command(command) + ] -def display_keys_for_command(command: str) -> List[str]: - """ - Returns the user-friendly display keys for a given mapped command - """ - return [ - display_key_for_urwid_key(urwid_key) for urwid_key in keys_for_command(command) - ] + def primary_display_key_for_command(self, command: str) -> str: + """ + Primary Display Key is the formatted display version of the primary key + """ + return self.display_key_for_urwid_key(self.primary_key_for_command(command)) + def commands_for_random_tips(self) -> List[KeyBinding]: + """ + Return list of commands which may be displayed as a random tip + """ + return [ + key_binding + for key_binding in self.KEY_BINDINGS.values() + if not key_binding.get("excluded_from_random_tips", False) + ] -def primary_display_key_for_command(command: str) -> str: - """ - Primary Display Key is the formatted display version of the primary key - """ - return display_key_for_urwid_key(primary_key_for_command(command)) + def keys_for_command(self, command: str) -> List[str]: + """ + Returns the actual keys for a given mapped command + """ + try: + return list(self.KEY_BINDINGS[command]["keys"]) + except KeyError as exception: + raise InvalidCommand(command) from exception + + +# Create a shared instance of KeyConfig +key_config = KeyConfig() +key_config.set_terminology("") -def commands_for_random_tips() -> List[KeyBinding]: +def initialize_command_map(key_config: KeyConfig) -> None: """ - Return list of commands which may be displayed as a random tip + Adds alternate keys for standard urwid navigational commands. """ - return [ - key_binding - for key_binding in KEY_BINDINGS.values() - if not key_binding.get("excluded_from_random_tips", False) - ] + for zt_cmd, urwid_cmd in key_config.ZT_TO_URWID_CMD_MAPPING.items(): + for key in key_config.keys_for_command(zt_cmd): + command_map[key] = urwid_cmd -# Refer urwid/command_map.py -# Adds alternate keys for standard urwid navigational commands. -for zt_cmd, urwid_cmd in ZT_TO_URWID_CMD_MAPPING.items(): - for key in keys_for_command(zt_cmd): - command_map[key] = urwid_cmd +# Initialize the command map after the KeyConfig instance is fully initialized +initialize_command_map(key_config) diff --git a/zulipterminal/core.py b/zulipterminal/core.py index 61c5f79922..7ae35589d4 100644 --- a/zulipterminal/core.py +++ b/zulipterminal/core.py @@ -2,6 +2,7 @@ Defines the `Controller`, which sets up the `Model`, `View`, and how they interact """ +import configparser import itertools import os import signal @@ -14,12 +15,13 @@ from typing import Any, Dict, List, Optional, Tuple, Type, Union import pyperclip +import requests import urwid import zulip from typing_extensions import Literal from zulipterminal.api_types import Composition, Message -from zulipterminal.config.keys import primary_display_key_for_command +from zulipterminal.config.keys import key_config from zulipterminal.config.symbols import POPUP_CONTENT_BORDER, POPUP_TOP_LINE from zulipterminal.config.themes import ThemeSpec from zulipterminal.config.ui_sizes import ( @@ -47,6 +49,7 @@ StreamInfoView, StreamMembersView, UserInfoView, + set_terminology, ) from zulipterminal.version import ZT_VERSION @@ -56,6 +59,10 @@ SCROLL_PROMPT = "(up/down scrolls)" +class NotAZulipOrganizationError(Exception): + pass + + class Controller: """ A class responsible for setting up the model and view and running @@ -97,7 +104,21 @@ def __init__( self.is_typing_notification_in_progress = False self.show_loading() + self.feature_level = None client_identifier = f"ZulipTerminal/{ZT_VERSION} {platform()}" + config_file = os.path.expanduser(config_file) + if config_file is not None and os.path.exists(config_file): + config = configparser.ConfigParser() + with open(config_file) as f: + config.read_file(f, config_file) + if self.feature_level is None: + self.feature_level = self.get_feature_level(config.get("api", "site")) + self.terminology = "channel" if self.feature_level > 254 else "stream" + self.key_config = key_config + self.key_config.set_terminology( + self.terminology + ) # to set the terminology for key_bindings + set_terminology(self.terminology) # to set terminology for the UI self.client = zulip.Client(config_file=config_file, client=client_identifier) self.model = Model(self) self.view = View(self) @@ -138,6 +159,12 @@ def raise_exception_in_main_thread( self._critical_exception = critical os.write(self._exception_pipe, b"1") + def get_feature_level(self, realm_url: str) -> str: + response = requests.get(url=f"{realm_url}/api/v1/server_settings") + if response.status_code != requests.codes.OK: + raise NotAZulipOrganizationError(realm_url) + return response.json()["zulip_feature_level"] + def is_in_editor_mode(self) -> bool: return self._editor is not None @@ -720,7 +747,7 @@ def _raise_exception(self, *args: Any, **kwargs: Any) -> Literal[True]: f"* from the end of '{exception_logfile}'" "\n" "* by copying the details to the clipboard now " - f"(press [{primary_display_key_for_command('COPY_TRACEBACK')}])" + f"(press [{self.key_config.primary_display_key_for_command('COPY_TRACEBACK')}])" ) full_traceback = "".join(traceback.format_exception(*exc)) self.show_exception_popup(message, traceback=full_traceback, width=80) diff --git a/zulipterminal/helper.py b/zulipterminal/helper.py index a71d055b74..872d41444c 100644 --- a/zulipterminal/helper.py +++ b/zulipterminal/helper.py @@ -33,7 +33,7 @@ from typing_extensions import Literal, ParamSpec, TypedDict from zulipterminal.api_types import Composition, EmojiType, Message -from zulipterminal.config.keys import primary_display_key_for_command +from zulipterminal.config.keys import key_config from zulipterminal.config.regexes import ( REGEX_COLOR_3_DIGIT, REGEX_COLOR_6_DIGIT, @@ -700,7 +700,7 @@ def check_narrow_and_notify( and current_narrow != outer_narrow and current_narrow != inner_narrow ): - key = primary_display_key_for_command("NARROW_MESSAGE_RECIPIENT") + key = key_config.primary_display_key_for_command("NARROW_MESSAGE_RECIPIENT") controller.report_success( [ diff --git a/zulipterminal/model.py b/zulipterminal/model.py index 1b8064fa87..4f02cc519f 100644 --- a/zulipterminal/model.py +++ b/zulipterminal/model.py @@ -59,7 +59,7 @@ UpdateMessageContentEvent, UpdateMessagesLocationEvent, ) -from zulipterminal.config.keys import primary_display_key_for_command +from zulipterminal.config.keys import key_config from zulipterminal.config.symbols import STREAM_TOPIC_SEPARATOR from zulipterminal.config.ui_mappings import EDIT_TOPIC_POLICY, ROLE_BY_ID, STATE_ICON from zulipterminal.helper import ( @@ -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("EXIT_POPUP") + failed_command, key_config.primary_display_key_for_command("EXIT_POPUP") ) self.controller.show_popup_with_message(notice, width=50) self.controller.update_screen() diff --git a/zulipterminal/ui.py b/zulipterminal/ui.py index d0785bd928..ed2e8263b4 100644 --- a/zulipterminal/ui.py +++ b/zulipterminal/ui.py @@ -9,11 +9,7 @@ import urwid -from zulipterminal.config.keys import ( - commands_for_random_tips, - display_key_for_urwid_key, - is_command_key, -) +from zulipterminal.config.keys import key_config from zulipterminal.config.symbols import ( APPLICATION_TITLE_BAR_LINE, AUTOHIDE_TAB_LEFT_ARROW, @@ -41,6 +37,8 @@ class View(urwid.WidgetWrap): def __init__(self, controller: Any) -> None: self.controller = controller + self.key_config = key_config + self.pm_button = controller self.palette = controller.theme self.model = controller.model self.users = self.model.users @@ -49,7 +47,6 @@ def __init__(self, controller: Any) -> None: self.write_box = WriteBox(self) self.search_box = MessageSearchBox(self.controller) self.stream_topic_map: Dict[int, str] = {} - self.message_view: Any = None self.displaying_selection_hint = False @@ -102,12 +99,15 @@ def right_column_view(self) -> Any: def get_random_help(self) -> List[Any]: # Get random allowed hotkey (ie. eligible for being displayed as a tip) - allowed_commands = commands_for_random_tips() + allowed_commands = self.key_config.commands_for_random_tips() if not allowed_commands: return ["Help(?): "] random_command = random.choice(allowed_commands) random_command_display_keys = ", ".join( - [display_key_for_urwid_key(key) for key in random_command["keys"]] + [ + self.key_config.display_key_for_urwid_key(key) + for key in random_command["keys"] + ] ) return [ "Help(?): ", @@ -258,40 +258,40 @@ def keypress(self, size: urwid_Box, key: str) -> Optional[str]: return self.controller.current_editor().keypress((size[1],), key) # Redirect commands to message_view. elif ( - is_command_key("SEARCH_MESSAGES", key) - or is_command_key("NEXT_UNREAD_TOPIC", key) - or is_command_key("NEXT_UNREAD_PM", key) - or is_command_key("STREAM_MESSAGE", key) - or is_command_key("PRIVATE_MESSAGE", key) + self.key_config.is_command_key("SEARCH_MESSAGES", key) + or self.key_config.is_command_key("NEXT_UNREAD_TOPIC", key) + or self.key_config.is_command_key("NEXT_UNREAD_PM", key) + or self.key_config.is_command_key("STREAM_MESSAGE", key) + or self.key_config.is_command_key("PRIVATE_MESSAGE", key) ): self.show_left_panel(visible=False) self.show_right_panel(visible=False) self.body.focus_col = 1 self.middle_column.keypress(size, key) return key - elif is_command_key("ALL_PM", key): + elif self.key_config.is_command_key("ALL_PM", key): self.pm_button.activate(key) - elif is_command_key("ALL_STARRED", key): + elif self.key_config.is_command_key("ALL_STARRED", key): self.starred_button.activate(key) - elif is_command_key("ALL_MENTIONS", key): + elif self.key_config.is_command_key("ALL_MENTIONS", key): self.mentioned_button.activate(key) - elif is_command_key("SEARCH_PEOPLE", key): + elif self.key_config.is_command_key("SEARCH_PEOPLE", key): # Start User Search if not in editor_mode self.show_left_panel(visible=False) self.show_right_panel(visible=True) self.body.focus_position = 2 self.users_view.keypress(size, key) return key - elif is_command_key("SEARCH_STREAMS", key) or is_command_key( - "SEARCH_TOPICS", key - ): + elif self.key_config.is_command_key( + "SEARCH_STREAMS", key + ) or self.key_config.is_command_key("SEARCH_TOPICS", key): # jump stream search self.show_right_panel(visible=False) self.show_left_panel(visible=True) self.body.focus_position = 0 self.left_panel.keypress(size, key) return key - elif is_command_key("OPEN_DRAFT", key): + elif self.key_config.is_command_key("OPEN_DRAFT", key): saved_draft = self.model.session_draft_message() if saved_draft: self.show_left_panel(visible=False) @@ -318,17 +318,17 @@ def keypress(self, size: urwid_Box, key: str) -> Optional[str]: ["No draft message was saved in this session."] ) return key - elif is_command_key("ABOUT", key): + elif self.key_config.is_command_key("ABOUT", key): self.controller.show_about() return key - elif is_command_key("HELP", key): + elif self.key_config.is_command_key("HELP", key): # Show help menu self.controller.show_help() return key - elif is_command_key("MARKDOWN_HELP", key): + elif self.key_config.is_command_key("MARKDOWN_HELP", key): self.controller.show_markdown_help() return key - elif is_command_key("NEW_HINT", key): + elif self.key_config.is_command_key("NEW_HINT", key): self.set_footer_text() return key return super().keypress(size, key) diff --git a/zulipterminal/ui_tools/boxes.py b/zulipterminal/ui_tools/boxes.py index 1a479cadad..207ab67694 100644 --- a/zulipterminal/ui_tools/boxes.py +++ b/zulipterminal/ui_tools/boxes.py @@ -18,12 +18,7 @@ from urwid_readline import ReadlineEdit from zulipterminal.api_types import Composition, PrivateComposition, StreamComposition -from zulipterminal.config.keys import ( - display_keys_for_command, - is_command_key, - primary_display_key_for_command, - primary_key_for_command, -) +from zulipterminal.config.keys import key_config from zulipterminal.config.regexes import ( REGEX_CLEANED_RECIPIENT, REGEX_RECIPIENT_EMAIL, @@ -212,8 +207,8 @@ def private_box_view( self.to_write_box = ReadlineEdit("To: ", edit_text=recipient_info) self.to_write_box.enable_autocomplete( func=self._to_box_autocomplete, - key=primary_key_for_command("AUTOCOMPLETE"), - key_reverse=primary_key_for_command("AUTOCOMPLETE_REVERSE"), + key=key_config.primary_key_for_command("AUTOCOMPLETE"), + key_reverse=key_config.primary_key_for_command("AUTOCOMPLETE_REVERSE"), ) self.to_write_box.set_completer_delims("") @@ -222,8 +217,8 @@ def private_box_view( ) self.msg_write_box.enable_autocomplete( func=self.generic_autocomplete, - key=primary_key_for_command("AUTOCOMPLETE"), - key_reverse=primary_key_for_command("AUTOCOMPLETE_REVERSE"), + key=key_config.primary_key_for_command("AUTOCOMPLETE"), + key_reverse=key_config.primary_key_for_command("AUTOCOMPLETE_REVERSE"), ) self.msg_write_box.set_completer_delims(DELIMS_MESSAGE_COMPOSE) @@ -312,11 +307,14 @@ def _tidy_valid_recipients_and_notify_invalid_ones( invalid_recipients_error = [ "Invalid recipient(s) - " + ", ".join(invalid_recipients), " - Use ", - ("footer_contrast", primary_display_key_for_command("AUTOCOMPLETE")), + ( + "footer_contrast", + key_config.primary_display_key_for_command("AUTOCOMPLETE"), + ), " or ", ( "footer_contrast", - primary_display_key_for_command("AUTOCOMPLETE_REVERSE"), + key_config.primary_display_key_for_command("AUTOCOMPLETE_REVERSE"), ), " to autocomplete.", ] @@ -339,8 +337,8 @@ def _setup_common_stream_compose( ) self.msg_write_box.enable_autocomplete( func=self.generic_autocomplete, - key=primary_key_for_command("AUTOCOMPLETE"), - key_reverse=primary_key_for_command("AUTOCOMPLETE_REVERSE"), + key=key_config.primary_key_for_command("AUTOCOMPLETE"), + key_reverse=key_config.primary_key_for_command("AUTOCOMPLETE_REVERSE"), ) self.msg_write_box.set_completer_delims(DELIMS_MESSAGE_COMPOSE) @@ -349,8 +347,8 @@ def _setup_common_stream_compose( ) self.title_write_box.enable_autocomplete( func=self._topic_box_autocomplete, - key=primary_key_for_command("AUTOCOMPLETE"), - key_reverse=primary_key_for_command("AUTOCOMPLETE_REVERSE"), + key=key_config.primary_key_for_command("AUTOCOMPLETE"), + key_reverse=key_config.primary_key_for_command("AUTOCOMPLETE_REVERSE"), ) self.title_write_box.set_completer_delims("") @@ -385,8 +383,8 @@ def stream_box_view( ) self.stream_write_box.enable_autocomplete( func=self._stream_box_autocomplete, - key=primary_key_for_command("AUTOCOMPLETE"), - key_reverse=primary_key_for_command("AUTOCOMPLETE_REVERSE"), + key=key_config.primary_key_for_command("AUTOCOMPLETE"), + key_reverse=key_config.primary_key_for_command("AUTOCOMPLETE_REVERSE"), ) self.stream_write_box.set_completer_delims("") self._setup_common_stream_compose(stream_id, caption, title) @@ -731,8 +729,8 @@ def _set_default_footer_after_autocomplete(self) -> None: def keypress(self, size: urwid_Size, key: str) -> Optional[str]: if self.is_in_typeahead_mode and not ( - is_command_key("AUTOCOMPLETE", key) - or is_command_key("AUTOCOMPLETE_REVERSE", key) + key_config.is_command_key("AUTOCOMPLETE", key) + or key_config.is_command_key("AUTOCOMPLETE_REVERSE", key) ): # As is, this exits autocomplete even if the user chooses to resume compose. # Including a check for "EXIT_COMPOSE" in the above logic would avoid @@ -741,7 +739,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: # TODO: Fully implement resuming of autocomplete upon resuming compose. self._set_default_footer_after_autocomplete() - if is_command_key("SEND_MESSAGE", key): + if key_config.is_command_key("SEND_MESSAGE", key): self.send_stop_typing_status() if self.compose_box_status == "open_with_stream": if re.fullmatch(r"\s*", self.title_write_box.edit_text): @@ -798,9 +796,11 @@ 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("EXIT_COMPOSE")) + self.keypress( + size, key_config.primary_key_for_command("EXIT_COMPOSE") + ) assert self.msg_edit_state is None - elif is_command_key("NARROW_MESSAGE_RECIPIENT", key): + elif key_config.is_command_key("NARROW_MESSAGE_RECIPIENT", key): if self.compose_box_status == "open_with_stream": self.model.controller.narrow_to_topic( stream_name=self.stream_write_box.edit_text, @@ -821,7 +821,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("EXIT_COMPOSE", key): + elif key_config.is_command_key("EXIT_COMPOSE", key): saved_draft = self.model.session_draft_message() self.send_stop_typing_status() @@ -840,10 +840,10 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: self.view.controller.exit_compose_confirmation_popup() else: self.exit_compose_box() - elif is_command_key("MARKDOWN_HELP", key): + elif key_config.is_command_key("MARKDOWN_HELP", key): self.view.controller.show_markdown_help() return key - elif is_command_key("OPEN_EXTERNAL_EDITOR", key): + elif key_config.is_command_key("OPEN_EXTERNAL_EDITOR", key): editor_command = self.view.controller.editor_command # None would indicate for shlex.split to read sys.stdin for Python < 3.12 @@ -884,7 +884,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: self.view.controller.loop.screen.start() return key - elif is_command_key("SAVE_AS_DRAFT", key): + elif key_config.is_command_key("SAVE_AS_DRAFT", key): if self.msg_edit_state is None: if self.compose_box_status == "open_with_private": all_valid = self._tidy_valid_recipients_and_notify_invalid_ones( @@ -914,7 +914,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: self.view.controller.save_draft_confirmation_popup( this_draft, ) - elif is_command_key("CYCLE_COMPOSE_FOCUS", key): + elif key_config.is_command_key("CYCLE_COMPOSE_FOCUS", key): if len(self.contents) == 0: return key header = self.header_write_box @@ -930,8 +930,10 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: invalid_stream_error = ( "Invalid stream name." " Use {} or {} to autocomplete.".format( - primary_display_key_for_command("AUTOCOMPLETE"), - primary_display_key_for_command( + key_config.primary_display_key_for_command( + "AUTOCOMPLETE" + ), + key_config.primary_display_key_for_command( "AUTOCOMPLETE_REVERSE" ), ) @@ -1000,9 +1002,7 @@ def __init__(self, controller: Any) -> None: super().__init__(self.main_view()) def main_view(self) -> Any: - search_text = ( - f"Search [{', '.join(display_keys_for_command('SEARCH_MESSAGES'))}]: " - ) + search_text = f"Search [{', '.join(key_config.display_keys_for_command('SEARCH_MESSAGES'))}]: " self.text_box = ReadlineEdit(f"{search_text} ") # Add some text so that when packing, # urwid doesn't hide the widget. @@ -1024,14 +1024,15 @@ 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("CLEAR_SEARCH", key): + key_config.is_command_key("EXECUTE_SEARCH", key) + and self.text_box.edit_text == "" + ) or key_config.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") return key - elif is_command_key("EXECUTE_SEARCH", key): + elif key_config.is_command_key("EXECUTE_SEARCH", key): self.controller.exit_editor_mode() self.controller.search_messages(self.text_box.edit_text) self.controller.view.middle_column.set_focus("body") @@ -1051,9 +1052,7 @@ def __init__( ) -> None: self.panel_view = panel_view self.search_command = search_command - self.search_text = ( - f" Search [{', '.join(display_keys_for_command(search_command))}]: " - ) + self.search_text = f" Search [{', '.join(key_config.display_keys_for_command(search_command))}]: " self.search_error = urwid.AttrMap( urwid.Text([" ", INVALID_MARKER, " No Results"]), "search_error" ) @@ -1080,15 +1079,21 @@ 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("CLEAR_SEARCH", key): + key_config.is_command_key("EXECUTE_SEARCH", key) + and self.get_edit_text() == "" + ) or key_config.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("CLEAR_SEARCH")) - elif is_command_key("EXECUTE_SEARCH", key) and not self.panel_view.empty_search: + self.panel_view.keypress( + size, key_config.primary_key_for_command("CLEAR_SEARCH") + ) + elif ( + key_config.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 "), " "]) self.panel_view.set_focus("body") diff --git a/zulipterminal/ui_tools/buttons.py b/zulipterminal/ui_tools/buttons.py index 8e67d79adf..4fb30eaae3 100644 --- a/zulipterminal/ui_tools/buttons.py +++ b/zulipterminal/ui_tools/buttons.py @@ -11,11 +11,7 @@ from typing_extensions import TypedDict from zulipterminal.api_types import RESOLVED_TOPIC_PREFIX, EditPropagateMode, Message -from zulipterminal.config.keys import ( - is_command_key, - primary_display_key_for_command, - primary_key_for_command, -) +from zulipterminal.config.keys import key_config from zulipterminal.config.regexes import REGEX_INTERNAL_LINK_STREAM_ID from zulipterminal.config.symbols import ( ALL_MESSAGES_MARKER, @@ -120,7 +116,7 @@ def activate(self, key: Any) -> None: self.show_function() def keypress(self, size: urwid_Size, key: str) -> Optional[str]: - if is_command_key("ACTIVATE_BUTTON", key): + if key_config.is_command_key("ACTIVATE_BUTTON", key): self.activate(key) return None else: # This is in the else clause, to avoid multiple activation @@ -129,9 +125,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: class HomeButton(TopButton): def __init__(self, *, controller: Any, count: int) -> None: - button_text = ( - f"All messages [{primary_display_key_for_command('ALL_MESSAGES')}]" - ) + button_text = f"All messages [{key_config.primary_display_key_for_command('ALL_MESSAGES')}]" super().__init__( controller=controller, @@ -145,7 +139,9 @@ def __init__(self, *, controller: Any, count: int) -> None: class PMButton(TopButton): def __init__(self, *, controller: Any, count: int) -> None: - button_text = f"Direct messages [{primary_display_key_for_command('ALL_PM')}]" + button_text = ( + f"Direct messages [{key_config.primary_display_key_for_command('ALL_PM')}]" + ) super().__init__( controller=controller, @@ -159,9 +155,7 @@ def __init__(self, *, controller: Any, count: int) -> None: class MentionedButton(TopButton): def __init__(self, *, controller: Any, count: int) -> None: - button_text = ( - f"Mentions [{primary_display_key_for_command('ALL_MENTIONS')}]" - ) + button_text = f"Mentions [{key_config.primary_display_key_for_command('ALL_MENTIONS')}]" super().__init__( controller=controller, @@ -175,9 +169,7 @@ def __init__(self, *, controller: Any, count: int) -> None: class StarredButton(TopButton): def __init__(self, *, controller: Any, count: int) -> None: - button_text = ( - f"Starred messages [{primary_display_key_for_command('ALL_STARRED')}]" - ) + button_text = f"Starred messages [{key_config.primary_display_key_for_command('ALL_STARRED')}]" super().__init__( controller=controller, @@ -267,13 +259,13 @@ def mark_unmuted(self, unread_count: int) -> None: self.view.home_button.update_count(self.model.unread_counts["all_msg"]) def keypress(self, size: urwid_Size, key: str) -> Optional[str]: - if is_command_key("TOGGLE_TOPIC", key): + if key_config.is_command_key("TOGGLE_TOPIC", key): self.view.left_panel.show_topic_view(self) - elif is_command_key("TOGGLE_MUTE_STREAM", key): + elif key_config.is_command_key("TOGGLE_MUTE_STREAM", key): self.controller.stream_muting_confirmation_popup( self.stream_id, self.stream_name ) - elif is_command_key("STREAM_INFO", key): + elif key_config.is_command_key("STREAM_INFO", key): self.model.controller.show_stream_info(self.stream_id) return super().keypress(size, key) @@ -322,7 +314,7 @@ def _narrow_with_compose(self) -> None: self._view.write_box.private_box_view(recipient_user_ids=[self.user_id]) def keypress(self, size: urwid_Size, key: str) -> Optional[str]: - if is_command_key("USER_INFO", key): + if key_config.is_command_key("USER_INFO", key): self.controller.show_user_info(self.user_id) return super().keypress(size, key) @@ -377,7 +369,7 @@ def mark_muted(self) -> None: # TODO: Handle event-based approach for topic-muting. def keypress(self, size: urwid_Size, key: str) -> Optional[str]: - if is_command_key("TOGGLE_TOPIC", key): + if key_config.is_command_key("TOGGLE_TOPIC", key): # Exit topic view self.view.associate_stream_with_topic(self.stream_id, self.topic_name) self.view.left_panel.show_stream_view() @@ -427,7 +419,7 @@ def mouse_event( self, size: urwid_Size, event: str, button: int, col: int, row: int, focus: int ) -> bool: if event == "mouse press" and button == 1: - self.keypress(size, primary_key_for_command("ACTIVATE_BUTTON")) + self.keypress(size, key_config.primary_key_for_command("ACTIVATE_BUTTON")) return True return super().mouse_event(size, event, button, col, row, focus) diff --git a/zulipterminal/ui_tools/messages.py b/zulipterminal/ui_tools/messages.py index 8fe2bb8f5c..e60a4ff223 100644 --- a/zulipterminal/ui_tools/messages.py +++ b/zulipterminal/ui_tools/messages.py @@ -16,7 +16,7 @@ from tzlocal import get_localzone from zulipterminal.api_types import Message -from zulipterminal.config.keys import is_command_key, primary_key_for_command +from zulipterminal.config.keys import key_config from zulipterminal.config.symbols import ( ALL_MESSAGES_MARKER, DIRECT_MESSAGE_MARKER, @@ -959,13 +959,13 @@ def mouse_event( if event == "mouse press" and button == 1: if self.model.controller.is_in_editor_mode(): return True - self.keypress(size, primary_key_for_command("ACTIVATE_BUTTON")) + self.keypress(size, key_config.primary_key_for_command("ACTIVATE_BUTTON")) return True return super().mouse_event(size, event, button, col, row, focus) def keypress(self, size: urwid_Size, key: str) -> Optional[str]: - if is_command_key("REPLY_MESSAGE", key): + if key_config.is_command_key("REPLY_MESSAGE", key): if self.message["type"] == "private": self.model.controller.view.write_box.private_box_view( recipient_user_ids=self.recipient_ids, @@ -976,7 +976,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: title=self.message["subject"], stream_id=self.stream_id, ) - elif is_command_key("STREAM_MESSAGE", key): + elif key_config.is_command_key("STREAM_MESSAGE", key): if len(self.model.narrow) != 0 and self.model.narrow[0][0] == "stream": self.model.controller.view.write_box.stream_box_view( caption=self.message["display_recipient"], @@ -984,7 +984,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: ) else: self.model.controller.view.write_box.stream_box_view(0) - elif is_command_key("STREAM_NARROW", key): + elif key_config.is_command_key("STREAM_NARROW", key): if self.message["type"] == "private": self.model.controller.narrow_to_user( recipient_emails=self.recipient_emails, @@ -995,7 +995,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: stream_name=self.stream_name, contextual_message_id=self.message["id"], ) - elif is_command_key("TOGGLE_NARROW", key): + elif key_config.is_command_key("TOGGLE_NARROW", key): self.model.unset_search_narrow() if self.message["type"] == "private": if len(self.model.narrow) == 1 and self.model.narrow[0][0] == "pm-with": @@ -1019,7 +1019,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: topic_name=self.topic_name, contextual_message_id=self.message["id"], ) - elif is_command_key("TOPIC_NARROW", key): + elif key_config.is_command_key("TOPIC_NARROW", key): if self.message["type"] == "private": self.model.controller.narrow_to_user( recipient_emails=self.recipient_emails, @@ -1031,25 +1031,25 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: topic_name=self.topic_name, contextual_message_id=self.message["id"], ) - elif is_command_key("ALL_MESSAGES", key): + elif key_config.is_command_key("ALL_MESSAGES", key): self.model.controller.narrow_to_all_messages( contextual_message_id=self.message["id"] ) - elif is_command_key("REPLY_AUTHOR", key): + elif key_config.is_command_key("REPLY_AUTHOR", key): # All subscribers from recipient_ids are not needed here. self.model.controller.view.write_box.private_box_view( recipient_user_ids=[self.message["sender_id"]], ) - elif is_command_key("MENTION_REPLY", key): - self.keypress(size, primary_key_for_command("REPLY_MESSAGE")) + elif key_config.is_command_key("MENTION_REPLY", key): + self.keypress(size, key_config.primary_key_for_command("REPLY_MESSAGE")) mention = f"@**{self.message['sender_full_name']}** " self.model.controller.view.write_box.msg_write_box.set_edit_text(mention) self.model.controller.view.write_box.msg_write_box.set_edit_pos( len(mention) ) self.model.controller.view.middle_column.set_focus("footer") - elif is_command_key("QUOTE_REPLY", key): - self.keypress(size, primary_key_for_command("REPLY_MESSAGE")) + elif key_config.is_command_key("QUOTE_REPLY", key): + self.keypress(size, key_config.primary_key_for_command("REPLY_MESSAGE")) # To correctly quote a message that contains quote/code-blocks, # we need to fence quoted message containing ``` with ````, @@ -1076,7 +1076,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: self.model.controller.view.write_box.msg_write_box.set_edit_text(quote) self.model.controller.view.write_box.msg_write_box.set_edit_pos(len(quote)) self.model.controller.view.middle_column.set_focus("footer") - elif is_command_key("EDIT_MESSAGE", key): + elif key_config.is_command_key("EDIT_MESSAGE", key): # User can't edit messages of others that already have a subject # For private messages, subject = "" (empty string) # This also handles the realm_message_content_edit_limit_seconds == 0 case @@ -1157,7 +1157,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: ) if self.message["type"] == "private": - self.keypress(size, primary_key_for_command("REPLY_MESSAGE")) + self.keypress(size, key_config.primary_key_for_command("REPLY_MESSAGE")) elif self.message["type"] == "stream": self.model.controller.view.write_box.stream_box_edit_view( stream_id=self.stream_id, @@ -1180,12 +1180,12 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: write_box.header_write_box.focus_col = write_box.FOCUS_HEADER_BOX_TOPIC self.model.controller.view.middle_column.set_focus("footer") - elif is_command_key("MSG_INFO", key): + elif key_config.is_command_key("MSG_INFO", key): self.model.controller.show_msg_info( self.message, self.topic_links, self.message_links, self.time_mentions ) - elif is_command_key("ADD_REACTION", key): + elif key_config.is_command_key("ADD_REACTION", key): self.model.controller.show_emoji_picker(self.message) - elif is_command_key("MSG_SENDER_INFO", key): + elif key_config.is_command_key("MSG_SENDER_INFO", key): self.model.controller.show_msg_sender_info(self.message["sender_id"]) return key diff --git a/zulipterminal/ui_tools/views.py b/zulipterminal/ui_tools/views.py index 02b3afbd0b..6f37c3ef00 100644 --- a/zulipterminal/ui_tools/views.py +++ b/zulipterminal/ui_tools/views.py @@ -11,14 +11,7 @@ from typing_extensions import Literal from zulipterminal.api_types import EditPropagateMode, Message -from zulipterminal.config.keys import ( - HELP_CATEGORIES, - KEY_BINDINGS, - display_key_for_urwid_key, - display_keys_for_command, - is_command_key, - primary_key_for_command, -) +from zulipterminal.config.keys import key_config from zulipterminal.config.markdown_examples import MARKDOWN_ELEMENTS from zulipterminal.config.symbols import ( CHECK_MARK, @@ -61,10 +54,16 @@ from zulipterminal.urwid_types import urwid_Size +TERMINOLOGY = None MIDDLE_COLUMN_MOUSE_SCROLL_LINES = 1 SIDE_PANELS_MOUSE_SCROLL_LINES = 5 +def set_terminology(terminology: str) -> None: + global TERMINOLOGY + TERMINOLOGY = terminology + + class ModListWalker(urwid.SimpleFocusListWalker): def __init__(self, *, contents: List[Any], action: Callable[[], None]) -> None: self._action = action @@ -184,16 +183,16 @@ def mouse_event( if event == "mouse press": if button == 4: for _ in range(MIDDLE_COLUMN_MOUSE_SCROLL_LINES): - self.keypress(size, primary_key_for_command("GO_UP")) + self.keypress(size, key_config.primary_key_for_command("GO_UP")) return True if button == 5: for _ in range(MIDDLE_COLUMN_MOUSE_SCROLL_LINES): - self.keypress(size, primary_key_for_command("GO_DOWN")) + self.keypress(size, key_config.primary_key_for_command("GO_DOWN")) return True return super().mouse_event(size, event, button, col, row, focus) def keypress(self, size: urwid_Size, key: str) -> Optional[str]: - if is_command_key("GO_DOWN", key) and not self.new_loading: + if key_config.is_command_key("GO_DOWN", key) and not self.new_loading: try: position = self.log.next_position(self.focus_position) self.set_focus(position, "above") @@ -206,7 +205,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: self.load_new_messages(id) return key - elif is_command_key("GO_UP", key) and not self.old_loading: + elif key_config.is_command_key("GO_UP", key) and not self.old_loading: try: position = self.log.prev_position(self.focus_position) self.set_focus(position, "below") @@ -218,27 +217,39 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: self.load_old_messages(id) return key - elif is_command_key("SCROLL_UP", key) and not self.old_loading: + elif key_config.is_command_key("SCROLL_UP", key) and not self.old_loading: if self.focus is not None and self.focus_position == 0: - return self.keypress(size, primary_key_for_command("GO_UP")) + return self.keypress(size, key_config.primary_key_for_command("GO_UP")) else: - return super().keypress(size, primary_key_for_command("SCROLL_UP")) + return super().keypress( + size, key_config.primary_key_for_command("SCROLL_UP") + ) - elif is_command_key("SCROLL_DOWN", key) and not self.old_loading: + elif key_config.is_command_key("SCROLL_DOWN", key) and not self.old_loading: if self.focus is not None and self.focus_position == len(self.log) - 1: - return self.keypress(size, primary_key_for_command("GO_DOWN")) + return self.keypress( + size, key_config.primary_key_for_command("GO_DOWN") + ) else: - return super().keypress(size, primary_key_for_command("SCROLL_DOWN")) + return super().keypress( + size, key_config.primary_key_for_command("SCROLL_DOWN") + ) - elif is_command_key("THUMBS_UP", key) and self.focus is not None: + elif key_config.is_command_key("THUMBS_UP", key) and self.focus is not None: message = self.focus.original_widget.message self.model.toggle_message_reaction(message, reaction_to_toggle="thumbs_up") - elif is_command_key("TOGGLE_STAR_STATUS", key) and self.focus is not None: + elif ( + key_config.is_command_key("TOGGLE_STAR_STATUS", key) + and self.focus is not None + ): message = self.focus.original_widget.message self.model.toggle_message_star_status(message) - elif is_command_key("REACTION_AGREEMENT", key) and self.focus is not None: + elif ( + key_config.is_command_key("REACTION_AGREEMENT", key) + and self.focus is not None + ): message = self.focus.original_widget.message message_reactions = message["reactions"] if message_reactions: @@ -382,22 +393,22 @@ def mouse_event( if event == "mouse press": if button == 4: for _ in range(SIDE_PANELS_MOUSE_SCROLL_LINES): - self.keypress(size, primary_key_for_command("GO_UP")) + self.keypress(size, key_config.primary_key_for_command("GO_UP")) return True elif button == 5: for _ in range(SIDE_PANELS_MOUSE_SCROLL_LINES): - self.keypress(size, primary_key_for_command("GO_DOWN")) + self.keypress(size, key_config.primary_key_for_command("GO_DOWN")) return True return super().mouse_event(size, event, button, col, row, focus) def keypress(self, size: urwid_Size, key: str) -> Optional[str]: - if is_command_key("SEARCH_STREAMS", key): + if key_config.is_command_key("SEARCH_STREAMS", key): _, self.focus_index_before_search = self.log.get_focus() self.set_focus("header") self.stream_search_box.set_caption(" ") self.view.controller.enter_editor_mode_with(self.stream_search_box) return key - elif is_command_key("CLEAR_SEARCH", key): + elif key_config.is_command_key("CLEAR_SEARCH", key): self.stream_search_box.reset_search_text() self.log.clear() self.log.extend(self.streams_btn_list) @@ -501,23 +512,23 @@ def mouse_event( if event == "mouse press": if button == 4: for _ in range(SIDE_PANELS_MOUSE_SCROLL_LINES): - self.keypress(size, primary_key_for_command("GO_UP")) + self.keypress(size, key_config.primary_key_for_command("GO_UP")) return True elif button == 5: for _ in range(SIDE_PANELS_MOUSE_SCROLL_LINES): - self.keypress(size, primary_key_for_command("GO_DOWN")) + self.keypress(size, key_config.primary_key_for_command("GO_DOWN")) return True return super().mouse_event(size, event, button, col, row, focus) def keypress(self, size: urwid_Size, key: str) -> Optional[str]: - if is_command_key("SEARCH_TOPICS", key): + if key_config.is_command_key("SEARCH_TOPICS", key): _, self.focus_index_before_search = self.log.get_focus() self.set_focus("header") self.header_list.set_focus(2) self.topic_search_box.set_caption(" ") self.view.controller.enter_editor_mode_with(self.topic_search_box) return key - elif is_command_key("CLEAR_SEARCH", key): + elif key_config.is_command_key("CLEAR_SEARCH", key): self.topic_search_box.reset_search_text() self.log.clear() self.log.extend(self.topics_btn_list) @@ -543,11 +554,11 @@ def mouse_event( return True if button == 4: for _ in range(SIDE_PANELS_MOUSE_SCROLL_LINES): - self.keypress(size, primary_key_for_command("GO_UP")) + self.keypress(size, key_config.primary_key_for_command("GO_UP")) return True elif button == 5: for _ in range(SIDE_PANELS_MOUSE_SCROLL_LINES): - self.keypress(size, primary_key_for_command("GO_DOWN")) + self.keypress(size, key_config.primary_key_for_command("GO_DOWN")) return super().mouse_event(size, event, button, col, row, focus) @@ -573,19 +584,19 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: if self.focus_position in ["footer", "header"]: return super().keypress(size, key) - elif is_command_key("SEARCH_MESSAGES", key): + elif key_config.is_command_key("SEARCH_MESSAGES", key): self.controller.enter_editor_mode_with(self.search_box) self.set_focus("header") return key - elif is_command_key("REPLY_MESSAGE", key): + elif key_config.is_command_key("REPLY_MESSAGE", key): self.body.keypress(size, key) if self.footer.focus is not None: self.set_focus("footer") self.footer.focus_position = 1 return key - elif is_command_key("STREAM_MESSAGE", key): + elif key_config.is_command_key("STREAM_MESSAGE", key): self.body.keypress(size, key) # For new streams with no previous conversation. if self.footer.focus is None: @@ -599,14 +610,14 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: self.footer.focus_position = 0 return key - elif is_command_key("REPLY_AUTHOR", key): + elif key_config.is_command_key("REPLY_AUTHOR", key): self.body.keypress(size, key) if self.footer.focus is not None: self.set_focus("footer") self.footer.focus_position = 1 return key - elif is_command_key("NEXT_UNREAD_TOPIC", key): + elif key_config.is_command_key("NEXT_UNREAD_TOPIC", key): # narrow to next unread topic focus = self.view.message_view.focus narrow = self.model.narrow @@ -628,7 +639,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: topic_name=topic, ) return key - elif is_command_key("NEXT_UNREAD_PM", key): + elif key_config.is_command_key("NEXT_UNREAD_PM", key): # narrow to next unread pm pm = self.model.get_next_unread_pm() if pm is None: @@ -638,15 +649,15 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: recipient_emails=[email], contextual_message_id=pm, ) - elif is_command_key("PRIVATE_MESSAGE", key): + elif key_config.is_command_key("PRIVATE_MESSAGE", key): # Create new PM message self.footer.private_box_view() self.set_focus("footer") self.footer.focus_position = 0 return key - elif is_command_key("GO_LEFT", key): + elif key_config.is_command_key("GO_LEFT", key): self.view.show_left_panel(visible=True) - elif is_command_key("GO_RIGHT", key): + elif key_config.is_command_key("GO_RIGHT", key): self.view.show_right_panel(visible=True) return super().keypress(size, key) @@ -752,13 +763,13 @@ def users_view(self, users: Any = None) -> Any: return user_w def keypress(self, size: urwid_Size, key: str) -> Optional[str]: - if is_command_key("SEARCH_PEOPLE", key): + if key_config.is_command_key("SEARCH_PEOPLE", key): self.allow_update_user_list = False self.set_focus("header") self.user_search.set_caption(" ") self.view.controller.enter_editor_mode_with(self.user_search) return key - elif is_command_key("CLEAR_SEARCH", key): + elif key_config.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) @@ -766,7 +777,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: self.set_focus("body") self.view.controller.update_screen() return key - elif is_command_key("GO_LEFT", key): + elif key_config.is_command_key("GO_LEFT", key): self.view.show_right_panel(visible=False) return super().keypress(size, key) @@ -846,7 +857,7 @@ def streams_view(self) -> Any: self.view.stream_w = StreamsView(streams_btn_list, self.view) w = urwid.LineBox( self.view.stream_w, - title="Streams", + title=f"{TERMINOLOGY}s", title_attr="column_title", tlcorner=COLUMN_TITLE_BAR_LINE, tline=COLUMN_TITLE_BAR_LINE, @@ -914,16 +925,16 @@ def show_topic_view(self, stream_button: Any) -> None: ) def keypress(self, size: urwid_Size, key: str) -> Optional[str]: - if is_command_key("SEARCH_STREAMS", key) or is_command_key( - "SEARCH_TOPICS", key - ): + if key_config.is_command_key( + "SEARCH_STREAMS", key + ) or key_config.is_command_key("SEARCH_TOPICS", key): self.focus_position = 1 if self.is_in_topic_view: self.view.topic_w.keypress(size, key) else: self.view.stream_w.keypress(size, key) return key - elif is_command_key("GO_RIGHT", key): + elif key_config.is_command_key("GO_RIGHT", key): self.view.show_left_panel(visible=False) return super().keypress(size, key) @@ -1057,7 +1068,9 @@ def make_table_with_categories( return widgets def keypress(self, size: urwid_Size, key: str) -> str: - if is_command_key("EXIT_POPUP", key) or is_command_key(self.command, key): + if key_config.is_command_key("EXIT_POPUP", key) or key_config.is_command_key( + self.command, key + ): self.controller.exit_popup() return super().keypress(size, key) @@ -1088,7 +1101,7 @@ def __init__( super().__init__(controller, notice_text, width, title) def keypress(self, size: urwid_Size, key: str) -> str: - if is_command_key("COPY_TRACEBACK", key): + if key_config.is_command_key("COPY_TRACEBACK", key): self.controller.copy_to_clipboard(self.traceback, "Traceback") return super().keypress(size, key) @@ -1149,7 +1162,11 @@ def __init__( sections.append(f"#### {section_title}\n{formatted_properties}") self.copy_info = "\n\n".join(sections) - about_keys = "[" + ", ".join(display_keys_for_command("COPY_ABOUT_INFO")) + "]" + about_keys = ( + "[" + + ", ".join(key_config.display_keys_for_command("COPY_ABOUT_INFO")) + + "]" + ) contents.append((f"Copy information to clipboard {about_keys}", [])) popup_width, column_widths = self.calculate_table_widths(contents, len(title)) @@ -1158,7 +1175,7 @@ def __init__( super().__init__(controller, widgets, "ABOUT", popup_width, title) def keypress(self, size: urwid_Size, key: str) -> str: - if is_command_key("COPY_ABOUT_INFO", key): + if key_config.is_command_key("COPY_ABOUT_INFO", key): self.controller.copy_to_clipboard(self.copy_info, "About info") return super().keypress(size, key) @@ -1257,21 +1274,24 @@ def _fetch_user_data( class HelpView(PopUpView): def __init__(self, controller: Any, title: str) -> None: help_menu_content = [] - for category in HELP_CATEGORIES: + for category in key_config.HELP_CATEGORIES: keys_in_category = ( binding - for binding in KEY_BINDINGS.values() + for binding in key_config.KEY_BINDINGS.values() if binding["key_category"] == category ) - key_bindings = [ + processed_bindings = [ ( binding["help_text"], - ", ".join(map(display_key_for_urwid_key, binding["keys"])), + ", ".join( + map(key_config.display_key_for_urwid_key, binding["keys"]) + ), ) for binding in keys_in_category ] - - help_menu_content.append((HELP_CATEGORIES[category], key_bindings)) + help_menu_content.append( + (key_config.HELP_CATEGORIES[category], processed_bindings) + ) popup_width, column_widths = self.calculate_table_widths( help_menu_content, len(title) @@ -1370,7 +1390,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("EXIT_POPUP", key): + if key_config.is_command_key("EXIT_POPUP", key): self.controller.exit_popup() return super().keypress(size, key) @@ -1422,20 +1442,24 @@ def __init__(self, controller: Any, stream_id: int) -> None: if stream["history_public_to_subscribers"] else "Not Public to Users" ) - member_keys = ", ".join(map(repr, display_keys_for_command("STREAM_MEMBERS"))) + member_keys = ", ".join( + map(repr, key_config.display_keys_for_command("STREAM_MEMBERS")) + ) self._stream_email = controller.model.get_stream_email_address(stream_id) if self._stream_email is None: - stream_copy_text = "< Stream email is unavailable >" + stream_copy_text = f"< {TERMINOLOGY} email is unavailable >" else: email_keys = ", ".join( - map(repr, display_keys_for_command("COPY_STREAM_EMAIL")) + map(repr, key_config.display_keys_for_command("COPY_STREAM_EMAIL")) ) - stream_copy_text = f"Press {email_keys} to copy Stream email address" + stream_copy_text = f"Press {email_keys} to copy {TERMINOLOGY} email address" weekly_traffic = stream["stream_weekly_traffic"] weekly_msg_count = ( - "Stream created recently" if weekly_traffic is None else str(weekly_traffic) + f"{TERMINOLOGY} created recently" + if weekly_traffic is None + else str(weekly_traffic) ) title = f"{stream_marker} {stream['name']}" @@ -1449,25 +1473,25 @@ def __init__(self, controller: Any, stream_id: int) -> None: # NOTE: This is treated as a member to make it easier to test self._stream_info_content = [ ( - "Stream Details", + f"{TERMINOLOGY} Details", [ - ("Stream ID", f"{self.stream_id}"), - ("Type of Stream", f"{type_of_stream}"), + (f"{TERMINOLOGY} ID", f"{self.stream_id}"), + (f"Type of {TERMINOLOGY}", f"{type_of_stream}"), ] + date_created + message_retention_days + [ ("Weekly Message Count", str(weekly_msg_count)), ( - "Stream Members", + f"{TERMINOLOGY} Members", f"{total_members} (Press {member_keys} to view list)", ), - ("Stream email", stream_copy_text), + (f"{TERMINOLOGY} email", stream_copy_text), ("History of Stream", f"{availability_of_history}"), ("Posting Policy", f"{stream_policy}"), ], ), - ("Stream settings", []), + (f"{TERMINOLOGY} settings", []), ] # type: PopUpViewTableContent popup_width, column_widths = self.calculate_table_widths( @@ -1550,12 +1574,15 @@ def toggle_visual_notification(self, button: Any, new_state: bool) -> None: self.controller.model.toggle_stream_visual_notifications(self.stream_id) def keypress(self, size: urwid_Size, key: str) -> str: - if is_command_key("STREAM_MEMBERS", key): + if key_config.is_command_key("STREAM_MEMBERS", key): self.controller.show_stream_members(stream_id=self.stream_id) elif ( - is_command_key("COPY_STREAM_EMAIL", key) and self._stream_email is not None + key_config.is_command_key("COPY_STREAM_EMAIL", key) + and self._stream_email is not None ): - self.controller.copy_to_clipboard(self._stream_email, "Stream email") + self.controller.copy_to_clipboard( + self._stream_email, f"{TERMINOLOGY} email" + ) return super().keypress(size, key) @@ -1569,7 +1596,7 @@ def __init__(self, controller: Any, stream_id: int) -> None: user_names = [model.user_name_from_id(id) for id in user_ids] sorted_user_names = sorted(user_names) sorted_user_names.insert(0, model.user_full_name) - title = "Stream Members (up/down scrolls)" + title = f"{TERMINOLOGY} Members (up/down scrolls)" stream_users_content = [("", [(name, "") for name in sorted_user_names])] popup_width, column_width = self.calculate_table_widths( @@ -1580,7 +1607,9 @@ 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("EXIT_POPUP", key) or is_command_key("STREAM_MEMBERS", key): + if key_config.is_command_key("EXIT_POPUP", key) or key_config.is_command_key( + "STREAM_MEMBERS", key + ): self.controller.show_stream_info(stream_id=self.stream_id) return key return super().keypress(size, key) @@ -1605,14 +1634,16 @@ def __init__( msg["timestamp"], show_seconds=True, show_year=True ) view_in_browser_keys = "[{}]".format( - ", ".join(map(str, display_keys_for_command("VIEW_IN_BROWSER"))) + ", ".join(map(str, key_config.display_keys_for_command("VIEW_IN_BROWSER"))) ) full_rendered_message_keys = "[{}]".format( - ", ".join(map(str, display_keys_for_command("FULL_RENDERED_MESSAGE"))) + ", ".join( + map(str, key_config.display_keys_for_command("FULL_RENDERED_MESSAGE")) + ) ) full_raw_message_keys = "[{}]".format( - ", ".join(map(str, display_keys_for_command("FULL_RAW_MESSAGE"))) + ", ".join(map(str, key_config.display_keys_for_command("FULL_RAW_MESSAGE"))) ) msg_info = [ ( @@ -1645,7 +1676,7 @@ def __init__( msg_info[0][1][0] = ("Date & Time (Original)", date_and_time) keys = "[{}]".format( - ", ".join(map(str, display_keys_for_command("EDIT_HISTORY"))) + ", ".join(map(str, key_config.display_keys_for_command("EDIT_HISTORY"))) ) msg_info[1][1].append(("Edit History", keys)) # Render the category using the existing table methods if links exist. @@ -1743,17 +1774,20 @@ def create_link_buttons( return link_widgets, link_width def keypress(self, size: urwid_Size, key: str) -> str: - if is_command_key("EDIT_HISTORY", key) and self.show_edit_history_label: + if ( + key_config.is_command_key("EDIT_HISTORY", key) + and self.show_edit_history_label + ): self.controller.show_edit_history( message=self.msg, topic_links=self.topic_links, message_links=self.message_links, time_mentions=self.time_mentions, ) - elif is_command_key("VIEW_IN_BROWSER", key): + elif key_config.is_command_key("VIEW_IN_BROWSER", key): url = near_message_url(self.server_url[:-1], self.msg) self.controller.open_in_browser(url) - elif is_command_key("FULL_RENDERED_MESSAGE", key): + elif key_config.is_command_key("FULL_RENDERED_MESSAGE", key): self.controller.show_full_rendered_message( message=self.msg, topic_links=self.topic_links, @@ -1761,7 +1795,7 @@ def keypress(self, size: urwid_Size, key: str) -> str: time_mentions=self.time_mentions, ) return key - elif is_command_key("FULL_RAW_MESSAGE", key): + elif key_config.is_command_key("FULL_RAW_MESSAGE", key): self.controller.show_full_raw_message( message=self.msg, topic_links=self.topic_links, @@ -1919,7 +1953,9 @@ 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("EXIT_POPUP", key) or is_command_key("EDIT_HISTORY", key): + if key_config.is_command_key("EXIT_POPUP", key) or key_config.is_command_key( + "EDIT_HISTORY", key + ): self.controller.show_msg_info( msg=self.message, topic_links=self.topic_links, @@ -1961,7 +1997,7 @@ def __init__( ) def keypress(self, size: urwid_Size, key: str) -> str: - if is_command_key("EXIT_POPUP", key) or is_command_key( + if key_config.is_command_key("EXIT_POPUP", key) or key_config.is_command_key( "FULL_RENDERED_MESSAGE", key ): self.controller.show_msg_info( @@ -2013,7 +2049,9 @@ def __init__( ) def keypress(self, size: urwid_Size, key: str) -> str: - if is_command_key("EXIT_POPUP", key) or is_command_key("FULL_RAW_MESSAGE", key): + if key_config.is_command_key("EXIT_POPUP", key) or key_config.is_command_key( + "FULL_RAW_MESSAGE", key + ): self.controller.show_msg_info( msg=self.message, topic_links=self.topic_links, @@ -2161,14 +2199,16 @@ def mouse_event( def keypress(self, size: urwid_Size, key: str) -> str: if ( - is_command_key("SEARCH_EMOJIS", key) + key_config.is_command_key("SEARCH_EMOJIS", key) and not self.controller.is_in_editor_mode() ): self.set_focus("header") self.emoji_search.set_caption(" ") self.controller.enter_editor_mode_with(self.emoji_search) return key - elif is_command_key("EXIT_POPUP", key) or is_command_key("ADD_REACTION", key): + elif key_config.is_command_key("EXIT_POPUP", key) or key_config.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()