Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/hotkeys.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
|View current message in browser (from message information)|<kbd>v</kbd>|
|Show/hide full rendered message (from message information)|<kbd>f</kbd>|
|Show/hide full raw message (from message information)|<kbd>r</kbd>|
|Copy message content to clipboard|<kbd>C</kbd>|

## Stream list actions
|Command|Key Combination|
Expand Down
23 changes: 18 additions & 5 deletions tests/ui_tools/test_popups.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")}
)
Expand All @@ -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
Expand Down Expand Up @@ -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(
Expand Down
6 changes: 6 additions & 0 deletions zulipterminal/config/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Comment on lines +399 to +403
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've discussed this in the stream, but

  • c should not need capitalization, with some adjustment, if we stick with that hotkey, though key choice right now is not important
    • though note that we already have c for copying stream email address
    • plus we discussed something similar for the copy non-markdown content version
  • the help_text likely doesn't need to wrap? That's only been done for long lines
  • we may want the COPY_MESSAGE to be more meaningful if we have two versions :)

}),
])
# fmt: on

Expand Down
15 changes: 15 additions & 0 deletions zulipterminal/ui_tools/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
(
"",
Expand All @@ -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)
Expand Down Expand Up @@ -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")
Comment on lines +1685 to +1695
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The principle here is just fine, but we do perform a few extra adjustments to content which may not follow the principles that you assume here.

That's likely what gave rise to the crash I reported in the stream.

return super().keypress(size, key)


Expand Down