Skip to content

Commit

Permalink
views: Add support for copying code snippets.
Browse files Browse the repository at this point in the history
Fixes #1123.
Introduces support for copying code snippets in the message information popup.
Makes use of the CodeSnippetButton class and its methods.
Tests added.
  • Loading branch information
rsashank committed Dec 25, 2023
1 parent 83e5c4a commit e806d56
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 1 deletion.
67 changes: 67 additions & 0 deletions tests/ui_tools/test_popups.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,7 @@ def mock_external_classes(self, mocker: MockerFixture, msg_box: MessageBox) -> N
message=self.message,
topic_links=OrderedDict(),
message_links=OrderedDict(),
code_snippets=list(),
time_mentions=list(),
title="Full Rendered Message",
)
Expand Down Expand Up @@ -519,6 +520,7 @@ def test_keypress_show_msg_info(
msg=self.message,
topic_links=OrderedDict(),
message_links=OrderedDict(),
code_snippets=list(),
time_mentions=list(),
)

Expand All @@ -543,6 +545,7 @@ def mock_external_classes(self, mocker: MockerFixture, msg_box: MessageBox) -> N
message=self.message,
topic_links=OrderedDict(),
message_links=OrderedDict(),
code_snippets=list(),
time_mentions=list(),
title="Full Raw Message",
)
Expand Down Expand Up @@ -595,6 +598,7 @@ def test_keypress_show_msg_info(
msg=self.message,
topic_links=OrderedDict(),
message_links=OrderedDict(),
code_snippets=list(),
time_mentions=list(),
)

Expand All @@ -618,6 +622,7 @@ def mock_external_classes(self, mocker: MockerFixture) -> None:
message=self.message,
topic_links=OrderedDict(),
message_links=OrderedDict(),
code_snippets=list(),
time_mentions=list(),
title="Edit History",
)
Expand Down Expand Up @@ -666,6 +671,7 @@ def test_keypress_show_msg_info(
msg=self.message,
topic_links=OrderedDict(),
message_links=OrderedDict(),
code_snippets=list(),
time_mentions=list(),
)

Expand Down Expand Up @@ -944,6 +950,7 @@ def mock_external_classes(
OrderedDict(),
OrderedDict(),
list(),
list(),
)

def test_init(self, message_fixture: Message) -> None:
Expand All @@ -961,6 +968,7 @@ def test_pop_up_info_order(self, message_fixture: Message) -> None:
title="Message Information",
topic_links=topic_links,
message_links=message_links,
code_snippets=list(),
time_mentions=list(),
)
msg_links = msg_info_view.button_widgets
Expand Down Expand Up @@ -1009,6 +1017,7 @@ def test_keypress_edit_history(
title="Message Information",
topic_links=OrderedDict(),
message_links=OrderedDict(),
code_snippets=list(),
time_mentions=list(),
)
size = widget_size(msg_info_view)
Expand All @@ -1020,6 +1029,7 @@ def test_keypress_edit_history(
message=message_fixture,
topic_links=OrderedDict(),
message_links=OrderedDict(),
code_snippets=list(),
time_mentions=list(),
)
else:
Expand All @@ -1038,6 +1048,7 @@ def test_keypress_full_rendered_message(
title="Message Information",
topic_links=OrderedDict(),
message_links=OrderedDict(),
code_snippets=list(),
time_mentions=list(),
)
size = widget_size(msg_info_view)
Expand All @@ -1048,6 +1059,7 @@ def test_keypress_full_rendered_message(
message=message_fixture,
topic_links=OrderedDict(),
message_links=OrderedDict(),
code_snippets=list(),
time_mentions=list(),
)

Expand All @@ -1064,6 +1076,7 @@ def test_keypress_full_raw_message(
title="Message Information",
topic_links=OrderedDict(),
message_links=OrderedDict(),
code_snippets=list(),
time_mentions=list(),
)
size = widget_size(msg_info_view)
Expand All @@ -1074,6 +1087,7 @@ def test_keypress_full_raw_message(
message=message_fixture,
topic_links=OrderedDict(),
message_links=OrderedDict(),
code_snippets=list(),
time_mentions=list(),
)

Expand Down Expand Up @@ -1176,6 +1190,7 @@ def test_height_reactions(
OrderedDict(),
OrderedDict(),
list(),
list(),
)
# 12 = 7 labels + 2 blank lines + 1 'Reactions' (category)
# + 4 reactions (excluding 'Message Links').
Expand Down Expand Up @@ -1229,6 +1244,58 @@ def test_create_link_buttons(
assert link_w._wrapped_widget.attr_map == expected_attr_map
assert link_width == expected_link_width

@pytest.mark.parametrize(
[
"initial_code_snippet",
"expected_code",
"expected_attr_map",
"expected_focus_map",
"expected_code_width",
],
[
(
[
(
"Python",
[
("pygments:k", "def"),
("pygments:w", " "),
("pygments:nf", "main"),
("pygments:p", "()"),
("pygments:w", "\n "),
("pygments:nb", "print"),
("pygments:p", "("),
("pygments:s2", '"Hello"'),
("pygments:p", ")"),
("pygments:w", "\n"),
],
)
],
'1: Python\ndef main()\n print("Hello")...',
{None: "popup_contrast"},
{None: "selected"},
95,
)
],
ids=["with_code_snippet"],
)
def test_create_code_snippet_buttons(
self,
initial_code_snippet: List[Tuple[str, List[Tuple[str, str]]]],
expected_code: str,
expected_attr_map: Dict[None, str],
expected_focus_map: Dict[None, str],
expected_code_width: int,
) -> None:
[code_w], copy_code_width = self.msg_info_view.create_code_snippet_buttons(
self.controller, initial_code_snippet
)

assert code_w._wrapped_widget.original_widget.text == expected_code
assert code_w._wrapped_widget.focus_map == expected_focus_map
assert code_w._wrapped_widget.attr_map == expected_attr_map
assert copy_code_width == expected_code_width


class TestStreamInfoView:
@pytest.fixture(autouse=True)
Expand Down
76 changes: 75 additions & 1 deletion zulipterminal/ui_tools/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
from zulipterminal.server_url import near_message_url
from zulipterminal.ui_tools.boxes import PanelSearchBox
from zulipterminal.ui_tools.buttons import (
CodeSnippetButton,
EmojiButton,
HomeButton,
MentionedButton,
Expand Down Expand Up @@ -1548,11 +1549,13 @@ def __init__(
title: str,
topic_links: Dict[str, Tuple[str, int, bool]],
message_links: Dict[str, Tuple[str, int, bool]],
code_snippets: List[Tuple[str, List[Tuple[str, str]]]],
time_mentions: List[Tuple[str, str]],
) -> None:
self.msg = msg
self.topic_links = topic_links
self.message_links = message_links
self.code_snippets = code_snippets
self.time_mentions = time_mentions
self.server_url = controller.model.server_url
date_and_time = controller.model.formatted_local_time(
Expand All @@ -1568,6 +1571,9 @@ def __init__(
full_raw_message_keys = "[{}]".format(
", ".join(map(str, keys_for_command("FULL_RAW_MESSAGE")))
)
copy_code_keys = "[{}]".format(
", ".join(map(str, keys_for_command("COPY_CODE_SNIPPET")))
)
msg_info = [
(
"",
Expand All @@ -1586,6 +1592,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 code to clipboard", copy_code_keys),
],
)
msg_info.append(viewing_actions)
Expand All @@ -1607,6 +1614,8 @@ def __init__(
msg_info.append(("Topic Links", []))
if time_mentions:
msg_info.append(("Time mentions", time_mentions))
if code_snippets:
msg_info.append(("Code Snippets", []))
if msg["reactions"]:
reactions = sorted(
(reaction["emoji_name"], reaction["user"]["full_name"])
Expand Down Expand Up @@ -1644,7 +1653,6 @@ def __init__(
widgets[:slice_index] + message_link_widgets + widgets[slice_index:]
)
popup_width = max(popup_width, message_link_width)

if topic_links:
topic_link_widgets, topic_link_width = self.create_link_buttons(
controller, topic_links
Expand All @@ -1660,6 +1668,21 @@ def __init__(
widgets = widgets[:slice_index] + topic_link_widgets + widgets[slice_index:]
popup_width = max(popup_width, topic_link_width)

if code_snippets:
(
code_snippets_widgets,
code_snippets_width,
) = self.create_code_snippet_buttons(controller, code_snippets)
# slice_index = Number of labels before code snippets + 1 newline
# + 1 'Code Snippets' category label.
slice_index = len(msg_info[0][1]) + len(msg_info[1][1]) + 2 + 2
slice_index += sum([len(w) + 2 for w in self.button_widgets])
self.button_widgets.append(code_snippets)
widgets = (
widgets[:slice_index] + code_snippets_widgets + widgets[slice_index:]
)
popup_width = max(popup_width, code_snippets_width)

super().__init__(controller, widgets, "MSG_INFO", popup_width, title)

@staticmethod
Expand Down Expand Up @@ -1689,12 +1712,52 @@ def create_link_buttons(

return link_widgets, link_width

def create_code_snippet_buttons(
self, controller: Any, code_snippets: List[Tuple[str, List[Tuple[str, str]]]]
) -> Tuple[List[Any], int]:
code_snippet_widgets = []
code_snippet_width = 0

for index, snippet in enumerate(code_snippets):
language, snippet_list = snippet
language = "" if language is None else language
display_code, copy_code = CodeSnippetButton.get_code_from_snippet(
self, snippet_list
)
if display_code:
code_snippet_width = max(
code_snippet_width, len(max(display_code, key=len))
)
display_code[-1] = (
display_code[-1][0],
display_code[-1][1].rstrip("\n"),
)
caption = f"{str(index+1)}: {language}\n"

display_attr = None if index % 2 else "popup_contrast"
display_code = [("pygments:w", caption)] + display_code
code_snippet_widgets.append(
CodeSnippetButton(
controller=controller,
caption=caption,
display_code=display_code,
copy_code=copy_code,
display_attr=display_attr,
)
)
code = caption + str(snip[1] for snip in snippet_list)
code_snippet_width = max(
code_snippet_width, len(max(code.split("\n"), key=len))
)
return code_snippet_widgets, code_snippet_width

def keypress(self, size: urwid_Size, key: str) -> str:
if 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,
code_snippets=self.code_snippets,
time_mentions=self.time_mentions,
)
elif is_command_key("VIEW_IN_BROWSER", key):
Expand All @@ -1705,6 +1768,7 @@ def keypress(self, size: urwid_Size, key: str) -> str:
message=self.msg,
topic_links=self.topic_links,
message_links=self.message_links,
code_snippets=self.code_snippets,
time_mentions=self.time_mentions,
)
return key
Expand All @@ -1713,6 +1777,7 @@ def keypress(self, size: urwid_Size, key: str) -> str:
message=self.msg,
topic_links=self.topic_links,
message_links=self.message_links,
code_snippets=self.code_snippets,
time_mentions=self.time_mentions,
)
return key
Expand Down Expand Up @@ -1762,13 +1827,15 @@ def __init__(
message: Message,
topic_links: Dict[str, Tuple[str, int, bool]],
message_links: Dict[str, Tuple[str, int, bool]],
code_snippets: List[Tuple[str, List[Tuple[str, str]]]],
time_mentions: List[Tuple[str, str]],
title: str,
) -> None:
self.controller = controller
self.message = message
self.topic_links = topic_links
self.message_links = message_links
self.code_snippets = code_snippets
self.time_mentions = time_mentions
width = 64
widgets: List[Any] = []
Expand Down Expand Up @@ -1867,6 +1934,7 @@ def keypress(self, size: urwid_Size, key: str) -> str:
msg=self.message,
topic_links=self.topic_links,
message_links=self.message_links,
code_snippets=self.code_snippets,
time_mentions=self.time_mentions,
)
return key
Expand All @@ -1880,13 +1948,15 @@ def __init__(
message: Message,
topic_links: Dict[str, Tuple[str, int, bool]],
message_links: Dict[str, Tuple[str, int, bool]],
code_snippets: List[Tuple[str, List[Tuple[str, str]]]],
time_mentions: List[Tuple[str, str]],
title: str,
) -> None:
self.controller = controller
self.message = message
self.topic_links = topic_links
self.message_links = message_links
self.code_snippets = code_snippets
self.time_mentions = time_mentions
max_cols, max_rows = controller.maximum_popup_dimensions()

Expand All @@ -1911,6 +1981,7 @@ def keypress(self, size: urwid_Size, key: str) -> str:
msg=self.message,
topic_links=self.topic_links,
message_links=self.message_links,
code_snippets=self.code_snippets,
time_mentions=self.time_mentions,
)
return key
Expand All @@ -1924,13 +1995,15 @@ def __init__(
message: Message,
topic_links: Dict[str, Tuple[str, int, bool]],
message_links: Dict[str, Tuple[str, int, bool]],
code_snippets: List[Tuple[str, List[Tuple[str, str]]]],
time_mentions: List[Tuple[str, str]],
title: str,
) -> None:
self.controller = controller
self.message = message
self.topic_links = topic_links
self.message_links = message_links
self.code_snippets = code_snippets
self.time_mentions = time_mentions
max_cols, max_rows = controller.maximum_popup_dimensions()

Expand Down Expand Up @@ -1961,6 +2034,7 @@ def keypress(self, size: urwid_Size, key: str) -> str:
msg=self.message,
topic_links=self.topic_links,
message_links=self.message_links,
code_snippets=self.code_snippets,
time_mentions=self.time_mentions,
)
return key
Expand Down

0 comments on commit e806d56

Please sign in to comment.