diff --git a/docs/hotkeys.md b/docs/hotkeys.md
index 1c9e4bf528..f2819fe8c1 100644
--- a/docs/hotkeys.md
+++ b/docs/hotkeys.md
@@ -24,13 +24,13 @@
|Scroll up|PgUp / K|
|Scroll down|PgDn / J|
|Go to bottom / Last message|End / G|
+|Trigger the selected entry|Enter / Space|
|Narrow to all messages|a / Esc|
|Narrow to all direct messages|P|
|Narrow to all starred messages|f|
|Narrow to messages in which you're mentioned|#|
|Next unread topic|n|
|Next unread direct message|p|
-|Perform current action|Enter|
## Searching
|Command|Key Combination|
@@ -40,6 +40,7 @@
|Search streams|q|
|Search topics in a stream|q|
|Search emojis from emoji picker|p|
+|Submit search and browse results|Enter|
## Message actions
|Command|Key Combination|
@@ -83,6 +84,7 @@
|Autocomplete @mentions, #stream_names, :emoji: and topics|Ctrl + f|
|Cycle through autocomplete suggestions in reverse|Ctrl + r|
|Narrow to compose box message recipient|Meta + .|
+|Insert new line|Enter|
## Editor: Navigation
|Command|Key Combination|
diff --git a/tests/ui_tools/test_boxes.py b/tests/ui_tools/test_boxes.py
index 8fc050391f..b6c763c0cb 100644
--- a/tests/ui_tools/test_boxes.py
+++ b/tests/ui_tools/test_boxes.py
@@ -1819,7 +1819,7 @@ def test_valid_char(
@pytest.mark.parametrize(
"log, expect_body_focus_set", [([], False), (["SOMETHING"], True)]
)
- @pytest.mark.parametrize("enter_key", keys_for_command("ENTER"))
+ @pytest.mark.parametrize("enter_key", keys_for_command("EXECUTE_SEARCH"))
def test_keypress_ENTER(
self,
panel_search_box: PanelSearchBox,
diff --git a/tests/ui_tools/test_buttons.py b/tests/ui_tools/test_buttons.py
index 3705bbaefe..adc2faa127 100644
--- a/tests/ui_tools/test_buttons.py
+++ b/tests/ui_tools/test_buttons.py
@@ -262,7 +262,7 @@ 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("ENTER"))
+ @pytest.mark.parametrize("enter_key", keys_for_command("ACTIVATE_BUTTON"))
def test_activate_called_once_on_keypress(
self,
mocker: MockerFixture,
@@ -358,7 +358,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("ENTER"))
+ @pytest.mark.parametrize("key", keys_for_command("ACTIVATE_BUTTON"))
@pytest.mark.parametrize(
"emoji, has_user_reacted, is_selected_final, expected_reaction_count",
[
diff --git a/tests/ui_tools/test_messages.py b/tests/ui_tools/test_messages.py
index 8deb4ebf3c..08ba7d0661 100644
--- a/tests/ui_tools/test_messages.py
+++ b/tests/ui_tools/test_messages.py
@@ -7,7 +7,7 @@
from pytest import param as case
from urwid import Columns, Divider, Padding, Text
-from zulipterminal.config.keys import keys_for_command
+from zulipterminal.config.keys import keys_for_command, primary_key_for_command
from zulipterminal.config.symbols import (
ALL_MESSAGES_MARKER,
DIRECT_MESSAGE_MARKER,
@@ -1961,11 +1961,14 @@ def test_footlinks_limit(self, maximum_footlinks, expected_instance):
assert isinstance(footlinks, expected_instance)
@pytest.mark.parametrize(
- "key", keys_for_command("ENTER"), ids=lambda param: f"left_click-key:{param}"
+ "key",
+ 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")
size = widget_size(msg_box)
col = 1
row = 1
@@ -1979,4 +1982,4 @@ def test_mouse_event_left_click(
if compose_box_is_open:
msg_box.keypress.assert_not_called()
else:
- msg_box.keypress.assert_called_once_with(size, key)
+ msg_box.keypress.assert_called_once_with(size, expected_keypress)
diff --git a/tests/ui_tools/test_popups.py b/tests/ui_tools/test_popups.py
index cb26395896..c4f55a1bb3 100644
--- a/tests/ui_tools/test_popups.py
+++ b/tests/ui_tools/test_popups.py
@@ -830,7 +830,7 @@ def test_init(self, edit_mode_view: EditModeView) -> None:
(2, "change_all"),
],
)
- @pytest.mark.parametrize("key", keys_for_command("ENTER"))
+ @pytest.mark.parametrize("key", keys_for_command("ACTIVATE_BUTTON"))
def test_select_edit_mode(
self,
edit_mode_view: EditModeView,
@@ -1523,7 +1523,7 @@ 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("ENTER"), " "))
+ @pytest.mark.parametrize("key", (*keys_for_command("ACTIVATE_BUTTON"), " "))
def test_checkbox_toggle_mute_stream(
self, key: str, widget_size: Callable[[Widget], urwid_Size]
) -> None:
@@ -1536,7 +1536,7 @@ def test_checkbox_toggle_mute_stream(
toggle_mute_status.assert_called_once_with(stream_id)
- @pytest.mark.parametrize("key", (*keys_for_command("ENTER"), " "))
+ @pytest.mark.parametrize("key", (*keys_for_command("ACTIVATE_BUTTON"), " "))
def test_checkbox_toggle_pin_stream(
self, key: str, widget_size: Callable[[Widget], urwid_Size]
) -> None:
@@ -1549,7 +1549,7 @@ def test_checkbox_toggle_pin_stream(
toggle_pin_status.assert_called_once_with(stream_id)
- @pytest.mark.parametrize("key", (*keys_for_command("ENTER"), " "))
+ @pytest.mark.parametrize("key", (*keys_for_command("ACTIVATE_BUTTON"), " "))
def test_checkbox_toggle_visual_notification(
self, key: str, widget_size: Callable[[Widget], urwid_Size]
) -> None:
diff --git a/zulipterminal/config/keys.py b/zulipterminal/config/keys.py
index 473d19c0f9..89e25355fa 100644
--- a/zulipterminal/config/keys.py
+++ b/zulipterminal/config/keys.py
@@ -6,6 +6,7 @@
from typing_extensions import NotRequired, TypedDict
from urwid.command_map import (
+ ACTIVATE,
CURSOR_DOWN,
CURSOR_LEFT,
CURSOR_MAX_RIGHT,
@@ -91,6 +92,11 @@ class KeyBinding(TypedDict):
'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',
@@ -244,16 +250,16 @@ class KeyBinding(TypedDict):
'excluded_from_random_tips': True,
'key_category': 'searching',
},
+ 'EXECUTE_SEARCH': {
+ 'keys': ['enter'],
+ 'help_text': 'Submit search and browse results',
+ 'key_category': 'searching',
+ },
'TOGGLE_MUTE_STREAM': {
'keys': ['m'],
'help_text': 'Mute/unmute streams',
'key_category': 'stream_list',
},
- 'ENTER': {
- 'keys': ['enter'],
- 'help_text': 'Perform current action',
- 'key_category': 'navigation',
- },
'THUMBS_UP': {
'keys': ['+'],
'help_text': 'Toggle thumbs-up reaction to the current message',
@@ -400,6 +406,14 @@ class KeyBinding(TypedDict):
'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': 'msg_compose',
+ },
'FULL_RENDERED_MESSAGE': {
'keys': ['f'],
'help_text': 'Show/hide full rendered message (from message information)',
@@ -432,6 +446,7 @@ class KeyBinding(TypedDict):
"SCROLL_UP": CURSOR_PAGE_UP,
"SCROLL_DOWN": CURSOR_PAGE_DOWN,
"GO_TO_BOTTOM": CURSOR_MAX_RIGHT,
+ "ACTIVATE_BUTTON": ACTIVATE,
}
@@ -477,6 +492,9 @@ 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"
+
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)
diff --git a/zulipterminal/ui_tools/boxes.py b/zulipterminal/ui_tools/boxes.py
index f6d3294241..91e2f80966 100644
--- a/zulipterminal/ui_tools/boxes.py
+++ b/zulipterminal/ui_tools/boxes.py
@@ -947,14 +947,14 @@ def main_view(self) -> Any:
def keypress(self, size: urwid_Size, key: str) -> Optional[str]:
if (
- is_command_key("ENTER", key) and self.text_box.edit_text == ""
+ is_command_key("EXECUTE_SEARCH", key) and self.text_box.edit_text == ""
) or is_command_key("GO_BACK", 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("ENTER", key):
+ elif 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")
@@ -1003,7 +1003,7 @@ def valid_char(self, ch: str) -> bool:
def keypress(self, size: urwid_Size, key: str) -> Optional[str]:
if (
- is_command_key("ENTER", key) and self.get_edit_text() == ""
+ is_command_key("EXECUTE_SEARCH", key) and self.get_edit_text() == ""
) or is_command_key("GO_BACK", key):
self.panel_view.view.controller.exit_editor_mode()
self.reset_search_text()
@@ -1011,7 +1011,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]:
# Don't call 'Esc' when inside a popup search-box.
if not self.panel_view.view.controller.is_any_popup_open():
self.panel_view.keypress(size, primary_key_for_command("GO_BACK"))
- elif is_command_key("ENTER", key) and not self.panel_view.empty_search:
+ elif is_command_key("EXECUTE_SEARCH", key) and not self.panel_view.empty_search:
self.panel_view.view.controller.exit_editor_mode()
self.set_caption([("filter_results", " Search Results "), " "])
self.panel_view.set_focus("body")
diff --git a/zulipterminal/ui_tools/buttons.py b/zulipterminal/ui_tools/buttons.py
index 9ecb2d32e0..91c428a2b7 100644
--- a/zulipterminal/ui_tools/buttons.py
+++ b/zulipterminal/ui_tools/buttons.py
@@ -120,7 +120,7 @@ def activate(self, key: Any) -> None:
self.show_function()
def keypress(self, size: urwid_Size, key: str) -> Optional[str]:
- if is_command_key("ENTER", key):
+ if is_command_key("ACTIVATE_BUTTON", key):
self.activate(key)
return None
else: # This is in the else clause, to avoid multiple activation
@@ -417,7 +417,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("ENTER"))
+ self.keypress(size, 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 3ddfa10a70..b8552fdc92 100644
--- a/zulipterminal/ui_tools/messages.py
+++ b/zulipterminal/ui_tools/messages.py
@@ -898,7 +898,7 @@ 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("ENTER"))
+ self.keypress(size, primary_key_for_command("ACTIVATE_BUTTON"))
return True
return super().mouse_event(size, event, button, col, row, focus)
diff --git a/zulipterminal/ui_tools/views.py b/zulipterminal/ui_tools/views.py
index 30fcc42a49..85dad25b79 100644
--- a/zulipterminal/ui_tools/views.py
+++ b/zulipterminal/ui_tools/views.py
@@ -1740,7 +1740,11 @@ def __init__(self, controller: Any, button: Any) -> None:
for mode in EDIT_MODE_CAPTIONS:
self.add_radio_button(mode)
super().__init__(
- controller, self.widgets, "ENTER", 62, "Topic edit propagation mode"
+ controller,
+ self.widgets,
+ "ACTIVATE_BUTTON",
+ 62,
+ "Topic edit propagation mode",
)
# Set cursor to marked checkbox.
for i in range(len(self.widgets)):