Skip to content

Commit

Permalink
messages/views: Add markup to code blocks.
Browse files Browse the repository at this point in the history
Updated existing tests. New test added.

Co-authored-by: kingjuno <[email protected]>
Co-authored-by: yuktasarode <[email protected]>
  • Loading branch information
3 people committed Sep 19, 2024
1 parent 2c6efeb commit 87ac1c0
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 15 deletions.
73 changes: 72 additions & 1 deletion tests/ui_tools/test_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,7 @@ def test_soup2markup(self, content, expected_markup, mocker):
server_url=SERVER_URL,
message_links=OrderedDict(),
time_mentions=list(),
code_blocks=list(),
bq_len=0,
)

Expand Down Expand Up @@ -1585,14 +1586,84 @@ def test_keypress_EDIT_MESSAGE(
# fmt: on
],
)
def test_transform_content(self, mocker, raw_html, expected_content):
def test_transform_content(self, raw_html, expected_content):
expected_content = expected_content.replace("{}", QUOTED_TEXT_MARKER)

content, *_ = MessageBox.transform_content(raw_html, SERVER_URL)

rendered_text = Text(content)
assert rendered_text.text == expected_content

@pytest.mark.parametrize(
"raw_html, expected_code_blocks",
[
(
"""<div class="codehilite" data-code-language="Python"><pre><span></span><code><span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
<span class="k">return</span><span class="p">(</span><span class="n">x</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span>
</code></pre></div>""", # noqa: E501
[
(
"Python",
[
("pygments:k", "def"),
("pygments:w", " "),
("pygments:nf", "foo"),
("pygments:p", "("),
("pygments:n", "x"),
("pygments:p", "):"),
("pygments:w", "\n "),
("pygments:k", "return"),
("pygments:p", "("),
("pygments:n", "x"),
("pygments:o", "+"),
("pygments:mi", "1"),
("pygments:p", ")"),
("pygments:w", "\n"),
],
)
],
),
(
"""<div class="codehilite" data-code-language="JavaScript"><pre><span></span><code><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">"Hello, world!"</span><span class="p">);</span>
</code></pre></div>""", # noqa: E501
[
(
"JavaScript",
[
("pygments:nx", "console"),
("pygments:p", "."),
("pygments:nx", "log"),
("pygments:p", "("),
("pygments:s2", '"Hello, world!"'),
("pygments:p", ");"),
("pygments:w", "\n"),
],
)
],
),
(
"""<div class="codehilite" data-code-language="Python"><pre><span></span><code><span class="nb">print</span><span class="p">(</span><span class="s2">"Hello, world!"</span><span class="p">)</span>
</code></pre></div>""", # noqa: E501
[
(
"Python",
[
("pygments:nb", "print"),
("pygments:p", "("),
("pygments:s2", '"Hello, world!"'),
("pygments:p", ")"),
("pygments:w", "\n"),
],
)
],
),
],
)
def test_transform_content_code_blocks(self, raw_html, expected_code_blocks):
_, _, _, code_blocks = MessageBox.transform_content(raw_html, SERVER_URL)

assert code_blocks == expected_code_blocks

# FIXME This is the same parametrize as MsgInfoView:test_height_reactions
@pytest.mark.parametrize(
"to_vary_in_each_message, expected_text, expected_attributes",
Expand Down
62 changes: 49 additions & 13 deletions zulipterminal/ui_tools/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ def __init__(self, message: Message, model: "Model", last_message: Any) -> None:
self.message_links: Dict[str, Tuple[str, int, bool]] = dict()
self.topic_links: Dict[str, Tuple[str, int, bool]] = dict()
self.time_mentions: List[Tuple[str, str]] = list()
self.code_blocks: List[Tuple[str, List[Tuple[str, str]]]] = list()
self.last_message = last_message
# if this is the first message
if self.last_message is None:
Expand Down Expand Up @@ -371,12 +372,22 @@ def footlinks_view(
@classmethod
def soup2markup(
cls, soup: Any, metadata: Dict[str, Any], **state: Any
) -> Tuple[List[Any], Dict[str, Tuple[str, int, bool]], List[Tuple[str, str]]]:
) -> Tuple[
List[Any],
Dict[str, Tuple[str, int, bool]],
List[Tuple[str, str]],
List[Tuple[str, List[Tuple[str, str]]]],
]:
# Ensure a string is provided, in case the soup finds none
# This could occur if eg. an image is removed or not shown
markup: List[Union[str, Tuple[Optional[str], Any]]] = [""]
if soup is None: # This is not iterable, so return promptly
return markup, metadata["message_links"], metadata["time_mentions"]
return (
markup,
metadata["message_links"],
metadata["time_mentions"],
metadata["code_blocks"],
)
unrendered_tags = { # In pairs of 'tag_name': 'text'
# TODO: Some of these could be implemented
"br": "", # No indicator of absence
Expand Down Expand Up @@ -552,6 +563,8 @@ def soup2markup(
if code_soup is None:
code_soup = element.pre

code_block = []

for code_element in code_soup.contents:
code_text = (
code_element.text
Expand All @@ -562,10 +575,18 @@ def soup2markup(
if code_element.name == "span":
if len(code_text) == 0:
continue
css_style = code_element.attrs.get("class", ["w"])
markup.append((f"pygments:{css_style[0]}", code_text))
css_style = (
f"pygments:{code_element.attrs.get('class', ['w'])[0]}"
)
else:
markup.append(("pygments:w", code_text))
css_style = "pygments:w"

markup.append((css_style, code_text))
code_block.append((css_style, code_text))

code_language = element.attrs.get("data-code-language")

metadata["code_blocks"].append((code_language, code_block))
elif tag in ("strong", "em"):
# BOLD & ITALIC
markup.append(("msg_bold", tag_text))
Expand Down Expand Up @@ -634,7 +655,12 @@ def soup2markup(
metadata["time_mentions"].append((time_string, source_text))
else:
markup.extend(cls.soup2markup(element, metadata)[0])
return markup, metadata["message_links"], metadata["time_mentions"]
return (
markup,
metadata["message_links"],
metadata["time_mentions"],
metadata["code_blocks"],
)

def main_view(self) -> List[Any]:
# Recipient Header
Expand Down Expand Up @@ -730,11 +756,13 @@ def main_view(self) -> List[Any]:
)

# Transform raw message content into markup (As needed by urwid.Text)
content, self.message_links, self.time_mentions = self.transform_content(
self.message["content"], self.model.server_url
)
(
content,
self.message_links,
self.time_mentions,
self.code_blocks,
) = self.transform_content(self.message["content"], self.model.server_url)
self.content.set_text(content)

if self.message["id"] in self.model.index["edited_messages"]:
edited_label_size = 7
left_padding = 1
Expand Down Expand Up @@ -819,6 +847,7 @@ def transform_content(
Tuple[None, Any],
Dict[str, Tuple[str, int, bool]],
List[Tuple[str, str]],
List[Tuple[str, List[Tuple[str, str]]]],
]:
soup = BeautifulSoup(content, "lxml")
body = soup.find(name="body")
Expand All @@ -827,13 +856,16 @@ def transform_content(
server_url=server_url,
message_links=dict(),
time_mentions=list(),
code_blocks=list(),
) # type: Dict[str, Any]

if isinstance(body, Tag) and body.find(name="blockquote"):
metadata["bq_len"] = cls.indent_quoted_content(soup, QUOTED_TEXT_MARKER)

markup, message_links, time_mentions = cls.soup2markup(body, metadata)
return (None, markup), message_links, time_mentions
markup, message_links, time_mentions, code_blocks = cls.soup2markup(
body, metadata
)
return (None, markup), message_links, time_mentions, code_blocks

@staticmethod
def indent_quoted_content(soup: Any, padding_char: str) -> int:
Expand Down Expand Up @@ -1121,7 +1153,11 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]:
self.model.controller.view.middle_column.set_focus("footer")
elif is_command_key("MSG_INFO", key):
self.model.controller.show_msg_info(
self.message, self.topic_links, self.message_links, self.time_mentions
self.message,
self.topic_links,
self.message_links,
self.time_mentions,
self.code_blocks,
)
elif is_command_key("ADD_REACTION", key):
self.model.controller.show_emoji_picker(self.message)
Expand Down
2 changes: 1 addition & 1 deletion zulipterminal/ui_tools/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1423,7 +1423,7 @@ def __init__(self, controller: Any, stream_id: int) -> None:

title = f"{stream_marker} {stream['name']}"
rendered_desc = stream["rendered_description"]
self.markup_desc, message_links, _ = MessageBox.transform_content(
self.markup_desc, message_links, _, _ = MessageBox.transform_content(
rendered_desc,
self.controller.model.server_url,
)
Expand Down

0 comments on commit 87ac1c0

Please sign in to comment.