diff --git a/docs/hotkeys.md b/docs/hotkeys.md
index db4972036f..e7e164b31b 100644
--- a/docs/hotkeys.md
+++ b/docs/hotkeys.md
@@ -62,6 +62,7 @@
|View current message in browser (from message information)|v|
|Show/hide full rendered message (from message information)|f|
|Show/hide full raw message (from message information)|r|
+|Copy message content to clipboard|C|
## Stream list actions
|Command|Key Combination|
diff --git a/tests/ui_tools/test_popups.py b/tests/ui_tools/test_popups.py
index 3205a13d52..68762dbf5c 100644
--- a/tests/ui_tools/test_popups.py
+++ b/tests/ui_tools/test_popups.py
@@ -1001,6 +1001,18 @@ def test_keypress_full_raw_message(
time_mentions=list(),
)
+ @pytest.mark.parametrize("key", keys_for_command("COPY_MESSAGE"))
+ def test_keypress_copy_message(
+ self, key: str, widget_size: Callable[[Widget], urwid_Size]
+ ) -> None:
+ size = widget_size(self.msg_info_view)
+
+ self.msg_info_view.keypress(size, key)
+
+ self.controller.copy_to_clipboard.assert_called_once_with(
+ self.msg_info_view.msg["content"], "Message Content"
+ )
+
@pytest.mark.parametrize(
"key", {*keys_for_command("GO_BACK"), *keys_for_command("MSG_INFO")}
)
@@ -1027,13 +1039,14 @@ def test_keypress_view_in_browser(
assert self.controller.open_in_browser.called
def test_height_noreactions(self) -> None:
- expected_height = 8
- # 6 = 1 (date & time) +1 (sender's name) +1 (sender's email)
- # +1 (display group header)
+ expected_height = 9
+ # 9 = 1 (date & time) +1 (sender's name) +1 (sender's email)
# +1 (whitespace column)
+ # +1 (display group header)
# +1 (view message in browser)
# +1 (full rendered message)
# +1 (full raw message)
+ # +1 (copy message content)
assert self.msg_info_view.height == expected_height
# FIXME This is the same parametrize as MessageBox:test_reactions_view
@@ -1101,9 +1114,9 @@ def test_height_reactions(
OrderedDict(),
list(),
)
- # 12 = 7 labels + 2 blank lines + 1 'Reactions' (category)
+ # 15 = 8 labels + 2 blank lines + 1 'Reactions' (category)
# + 4 reactions (excluding 'Message Links').
- expected_height = 14
+ expected_height = 15
assert self.msg_info_view.height == expected_height
@pytest.mark.parametrize(
diff --git a/zulipterminal/config/keys.py b/zulipterminal/config/keys.py
index 62482b6c58..3fa73e12d8 100644
--- a/zulipterminal/config/keys.py
+++ b/zulipterminal/config/keys.py
@@ -396,6 +396,12 @@ class KeyBinding(TypedDict):
'help_text': 'Show/hide full raw message (from message information)',
'key_category': 'msg_actions',
}),
+ ('COPY_MESSAGE', {
+ 'keys': ['C'],
+ 'help_text':
+ 'Copy message content to clipboard',
+ 'key_category': 'msg_actions',
+ }),
])
# fmt: on
diff --git a/zulipterminal/ui_tools/views.py b/zulipterminal/ui_tools/views.py
index 8f6ad15de4..9204950c2e 100644
--- a/zulipterminal/ui_tools/views.py
+++ b/zulipterminal/ui_tools/views.py
@@ -1530,6 +1530,9 @@ def __init__(
full_raw_message_keys = "[{}]".format(
", ".join(map(str, keys_for_command("FULL_RAW_MESSAGE")))
)
+ copy_message_keys = "[{}]".format(
+ ", ".join(map(str, keys_for_command("COPY_MESSAGE")))
+ )
msg_info = [
(
"",
@@ -1548,6 +1551,7 @@ def __init__(
("Open in web browser", view_in_browser_keys),
("Full rendered message", full_rendered_message_keys),
("Full raw message", full_raw_message_keys),
+ ("Copy message", copy_message_keys),
],
)
msg_info.append(viewing_actions)
@@ -1678,6 +1682,17 @@ def keypress(self, size: urwid_Size, key: str) -> str:
time_mentions=self.time_mentions,
)
return key
+ elif is_command_key("COPY_MESSAGE", key):
+ rendered_content, *_ = MessageBox.transform_content(
+ self.msg["content"], self.controller.model.server_url
+ )
+ content = []
+ for word in rendered_content[1]:
+ if isinstance(word, tuple): # if msg content has markup
+ content.append(word[1])
+ else:
+ content.append(word)
+ self.controller.copy_to_clipboard("".join(content), "Message Content")
return super().keypress(size, key)