From 4992dbe1dc9f2a4f677bc06a26bc3589cb5f520b Mon Sep 17 00:00:00 2001 From: Niloth P <20315308+Niloth-p@users.noreply.github.com> Date: Fri, 2 Aug 2024 16:17:30 +0530 Subject: [PATCH 01/10] boxes: On opening application, display an empty recipient bar. It was previously showing a text "DONT HIDE". --- zulipterminal/ui_tools/boxes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zulipterminal/ui_tools/boxes.py b/zulipterminal/ui_tools/boxes.py index 1a479cadad..64a1cae9a1 100644 --- a/zulipterminal/ui_tools/boxes.py +++ b/zulipterminal/ui_tools/boxes.py @@ -1014,7 +1014,7 @@ def main_view(self) -> Any: self.text_box, ] ) - self.msg_narrow = urwid.Text("DONT HIDE") + self.msg_narrow = urwid.Text("") self.recipient_bar = urwid.LineBox( self.msg_narrow, title="Current message recipients", From 936cf2f25b491b91d33cdc57c31453550ab4718f Mon Sep 17 00:00:00 2001 From: Niloth P <20315308+Niloth-p@users.noreply.github.com> Date: Fri, 2 Aug 2024 16:19:37 +0530 Subject: [PATCH 02/10] core: Track if the current narrow is empty. Co-authored-by: Sumanth V Rao --- zulipterminal/core.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zulipterminal/core.py b/zulipterminal/core.py index a23b1596f1..712feddcde 100644 --- a/zulipterminal/core.py +++ b/zulipterminal/core.py @@ -93,6 +93,7 @@ def __init__( self.active_conversation_info: Dict[str, Any] = {} self.is_typing_notification_in_progress = False + self.is_in_empty_narrow = False self.show_loading() client_identifier = f"ZulipTerminal/{ZT_VERSION} {platform()}" @@ -598,6 +599,7 @@ def _narrow_to(self, anchor: Optional[int], **narrow: Any) -> None: if len(msg_id_list) == 0 or (anchor is not None and anchor not in msg_id_list): self.model.get_messages(num_before=30, num_after=10, anchor=anchor) msg_id_list = self.model.get_message_ids_in_current_narrow() + self.is_in_empty_narrow = bool(len(msg_id_list) == 0) w_list = create_msg_box_list(self.model, msg_id_list, focus_msg_id=anchor) From 6cac57d44f3d8ffee6a871cb6972026ea2464612 Mon Sep 17 00:00:00 2001 From: Niloth P <20315308+Niloth-p@users.noreply.github.com> Date: Fri, 2 Aug 2024 16:32:26 +0530 Subject: [PATCH 03/10] messages/views: Disable keypresses from dummy messages. Updated tests. --- tests/ui/test_ui_tools.py | 4 ++++ tests/ui_tools/test_messages.py | 7 +++++++ zulipterminal/ui_tools/messages.py | 23 ++++++++++++----------- zulipterminal/ui_tools/views.py | 2 ++ 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/tests/ui/test_ui_tools.py b/tests/ui/test_ui_tools.py index 0a4d9ec030..653e67ffb7 100644 --- a/tests/ui/test_ui_tools.py +++ b/tests/ui/test_ui_tools.py @@ -276,6 +276,7 @@ def test_mouse_event(self, mocker, msg_view, mouse_scroll_event, widget_size): @pytest.mark.parametrize("key", keys_for_command("GO_DOWN")) def test_keypress_GO_DOWN(self, mocker, msg_view, key, widget_size): size = widget_size(msg_view) + msg_view.model.controller.is_in_empty_narrow = False msg_view.new_loading = False mocker.patch(MESSAGEVIEW + ".focus_position", return_value=0) mocker.patch(MESSAGEVIEW + ".set_focus_valign") @@ -291,6 +292,7 @@ def test_keypress_GO_DOWN_exception( self, mocker, msg_view, key, widget_size, view_is_focused ): size = widget_size(msg_view) + msg_view.model.controller.is_in_empty_narrow = False msg_view.new_loading = False mocker.patch(MESSAGEVIEW + ".focus_position", return_value=0) mocker.patch(MESSAGEVIEW + ".set_focus_valign") @@ -315,6 +317,7 @@ def test_keypress_GO_DOWN_exception( @pytest.mark.parametrize("key", keys_for_command("GO_UP")) def test_keypress_GO_UP(self, mocker, msg_view, key, widget_size): size = widget_size(msg_view) + msg_view.model.controller.is_in_empty_narrow = False mocker.patch(MESSAGEVIEW + ".focus_position", return_value=0) mocker.patch(MESSAGEVIEW + ".set_focus_valign") msg_view.old_loading = False @@ -330,6 +333,7 @@ def test_keypress_GO_UP_exception( self, mocker, msg_view, key, widget_size, view_is_focused ): size = widget_size(msg_view) + msg_view.model.controller.is_in_empty_narrow = False msg_view.old_loading = False mocker.patch(MESSAGEVIEW + ".focus_position", return_value=0) mocker.patch(MESSAGEVIEW + ".set_focus_valign") diff --git a/tests/ui_tools/test_messages.py b/tests/ui_tools/test_messages.py index 08ba7d0661..d808a53bed 100644 --- a/tests/ui_tools/test_messages.py +++ b/tests/ui_tools/test_messages.py @@ -838,6 +838,7 @@ def test_main_view_generates_stream_header( "color": "#bd6", }, } + self.model.controller.is_in_empty_narrow = False last_message = dict(message, **to_vary_in_last_message) msg_box = MessageBox(message, self.model, last_message) view_components = msg_box.main_view() @@ -890,6 +891,7 @@ def test_main_view_generates_stream_header( def test_main_view_generates_PM_header( self, mocker, message, to_vary_in_last_message ): + self.model.controller.is_in_empty_narrow = False last_message = dict(message, **to_vary_in_last_message) msg_box = MessageBox(message, self.model, last_message) view_components = msg_box.main_view() @@ -1032,9 +1034,11 @@ def test_msg_generates_search_and_header_bar( }, } self.model.narrow = msg_narrow + self.model.controller.is_in_empty_narrow = False messages = messages_successful_response["messages"] current_message = messages[msg_type] msg_box = MessageBox(current_message, self.model, messages[0]) + search_bar = msg_box.top_search_bar() header_bar = msg_box.recipient_header() @@ -1405,6 +1409,7 @@ def test_keypress_EDIT_MESSAGE( report_error = msg_box.model.controller.report_error report_warning = msg_box.model.controller.report_warning mocker.patch(MODULE + ".time", return_value=100) + msg_box.model.controller.is_in_empty_narrow = False msg_box.keypress(size, key) @@ -1820,6 +1825,7 @@ def test_reactions_view( varied_message = dict(message_fixture, **to_vary_in_each_message) msg_box = MessageBox(varied_message, self.model, None) reactions = to_vary_in_each_message["reactions"] + msg_box.model.controller.is_in_empty_narrow = False reactions_view = msg_box.reactions_view(reactions) @@ -1976,6 +1982,7 @@ def test_mouse_event_left_click( mocker.patch.object(msg_box, "keypress") msg_box.model = mocker.Mock() msg_box.model.controller.is_in_editor_mode.return_value = compose_box_is_open + msg_box.model.controller.is_in_empty_narrow = False msg_box.mouse_event(size, "mouse press", 1, col, row, focus) diff --git a/zulipterminal/ui_tools/messages.py b/zulipterminal/ui_tools/messages.py index b8552fdc92..02ad641098 100644 --- a/zulipterminal/ui_tools/messages.py +++ b/zulipterminal/ui_tools/messages.py @@ -904,7 +904,8 @@ def mouse_event( return super().mouse_event(size, event, button, col, row, focus) def keypress(self, size: urwid_Size, key: str) -> Optional[str]: - if is_command_key("REPLY_MESSAGE", key): + is_in_empty_narrow = self.model.controller.is_in_empty_narrow + if is_command_key("REPLY_MESSAGE", key) and not is_in_empty_narrow: if self.message["type"] == "private": self.model.controller.view.write_box.private_box_view( recipient_user_ids=self.recipient_ids, @@ -923,7 +924,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: ) else: self.model.controller.view.write_box.stream_box_view(0) - elif is_command_key("STREAM_NARROW", key): + elif is_command_key("STREAM_NARROW", key) and not is_in_empty_narrow: if self.message["type"] == "private": self.model.controller.narrow_to_user( recipient_emails=self.recipient_emails, @@ -934,7 +935,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: stream_name=self.stream_name, contextual_message_id=self.message["id"], ) - elif is_command_key("TOGGLE_NARROW", key): + elif is_command_key("TOGGLE_NARROW", key) and not is_in_empty_narrow: self.model.unset_search_narrow() if self.message["type"] == "private": if len(self.model.narrow) == 1 and self.model.narrow[0][0] == "pm-with": @@ -958,7 +959,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: topic_name=self.topic_name, contextual_message_id=self.message["id"], ) - elif is_command_key("TOPIC_NARROW", key): + elif is_command_key("TOPIC_NARROW", key) and not is_in_empty_narrow: if self.message["type"] == "private": self.model.controller.narrow_to_user( recipient_emails=self.recipient_emails, @@ -974,12 +975,12 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: self.model.controller.narrow_to_all_messages( contextual_message_id=self.message["id"] ) - elif is_command_key("REPLY_AUTHOR", key): + elif is_command_key("REPLY_AUTHOR", key) and not is_in_empty_narrow: # All subscribers from recipient_ids are not needed here. self.model.controller.view.write_box.private_box_view( recipient_user_ids=[self.message["sender_id"]], ) - elif is_command_key("MENTION_REPLY", key): + elif is_command_key("MENTION_REPLY", key) and not is_in_empty_narrow: self.keypress(size, primary_key_for_command("REPLY_MESSAGE")) mention = f"@**{self.message['sender_full_name']}** " self.model.controller.view.write_box.msg_write_box.set_edit_text(mention) @@ -987,7 +988,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: len(mention) ) self.model.controller.view.middle_column.set_focus("footer") - elif is_command_key("QUOTE_REPLY", key): + elif is_command_key("QUOTE_REPLY", key) and not is_in_empty_narrow: self.keypress(size, primary_key_for_command("REPLY_MESSAGE")) # To correctly quote a message that contains quote/code-blocks, @@ -1015,7 +1016,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: self.model.controller.view.write_box.msg_write_box.set_edit_text(quote) self.model.controller.view.write_box.msg_write_box.set_edit_pos(len(quote)) self.model.controller.view.middle_column.set_focus("footer") - elif is_command_key("EDIT_MESSAGE", key): + elif is_command_key("EDIT_MESSAGE", key) and not is_in_empty_narrow: # User can't edit messages of others that already have a subject # For private messages, subject = "" (empty string) # This also handles the realm_message_content_edit_limit_seconds == 0 case @@ -1119,12 +1120,12 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: write_box.header_write_box.focus_col = write_box.FOCUS_HEADER_BOX_TOPIC self.model.controller.view.middle_column.set_focus("footer") - elif is_command_key("MSG_INFO", key): + elif is_command_key("MSG_INFO", key) and not is_in_empty_narrow: self.model.controller.show_msg_info( self.message, self.topic_links, self.message_links, self.time_mentions ) - elif is_command_key("ADD_REACTION", key): + elif is_command_key("ADD_REACTION", key) and not is_in_empty_narrow: self.model.controller.show_emoji_picker(self.message) - elif is_command_key("MSG_SENDER_INFO", key): + elif is_command_key("MSG_SENDER_INFO", key) and not is_in_empty_narrow: self.model.controller.show_msg_sender_info(self.message["sender_id"]) return key diff --git a/zulipterminal/ui_tools/views.py b/zulipterminal/ui_tools/views.py index 6d01a82566..f5d3e86c7b 100644 --- a/zulipterminal/ui_tools/views.py +++ b/zulipterminal/ui_tools/views.py @@ -193,6 +193,8 @@ def mouse_event( return super().mouse_event(size, event, button, col, row, focus) def keypress(self, size: urwid_Size, key: str) -> Optional[str]: + if self.model.controller.is_in_empty_narrow: + return super().keypress(size, key) if is_command_key("GO_DOWN", key) and not self.new_loading: try: position = self.log.next_position(self.focus_position) From f8503f8951dbc415e9f967fe7477949e4d900501 Mon Sep 17 00:00:00 2001 From: Niloth P <20315308+Niloth-p@users.noreply.github.com> Date: Fri, 2 Aug 2024 16:36:04 +0530 Subject: [PATCH 04/10] messages: Customize recipient bar text for dummy messages. --- zulipterminal/ui_tools/messages.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/zulipterminal/ui_tools/messages.py b/zulipterminal/ui_tools/messages.py index 02ad641098..e61a75b712 100644 --- a/zulipterminal/ui_tools/messages.py +++ b/zulipterminal/ui_tools/messages.py @@ -198,8 +198,23 @@ def private_header(self) -> Any: header.markup = title_markup return header + def empty_narrow_header(self) -> Any: + title_markup = ("header", [("general_narrow", "No selected message")]) + title = urwid.Text(title_markup) + header = urwid.Columns( + [ + ("pack", title), + (1, urwid.Text(("general_bar", " "))), + urwid.AttrWrap(urwid.Divider(MESSAGE_HEADER_DIVIDER), "general_bar"), + ] + ) + header.markup = title_markup + return header + def recipient_header(self) -> Any: - if self.message["type"] == "stream": + if self.model.controller.is_in_empty_narrow: + return self.empty_narrow_header() + elif self.message["type"] == "stream": return self.stream_header() else: return self.private_header() From dcb213acf5f9dd543ac54206d811b2d316d87290 Mon Sep 17 00:00:00 2001 From: Niloth P <20315308+Niloth-p@users.noreply.github.com> Date: Fri, 2 Aug 2024 16:37:29 +0530 Subject: [PATCH 05/10] messages: Turn off recipient header for dummy messages. --- zulipterminal/ui_tools/messages.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zulipterminal/ui_tools/messages.py b/zulipterminal/ui_tools/messages.py index e61a75b712..7ba1a78bd3 100644 --- a/zulipterminal/ui_tools/messages.py +++ b/zulipterminal/ui_tools/messages.py @@ -115,6 +115,8 @@ def __init__(self, message: Message, model: "Model", last_message: Any) -> None: super().__init__(self.main_view()) def need_recipient_header(self) -> bool: + if self.model.controller.is_in_empty_narrow: + return False # Prevent redundant information in recipient bar if len(self.model.narrow) == 1 and self.model.narrow[0][0] == "pm-with": return False From 0fb5bf0f508540d064984c1da3f5d3b1e906fe96 Mon Sep 17 00:00:00 2001 From: Niloth P <20315308+Niloth-p@users.noreply.github.com> Date: Fri, 2 Aug 2024 16:39:32 +0530 Subject: [PATCH 06/10] model: Replace the dummy message on receiving a new message. Co-authored-by: Sumanth V Rao --- tests/model/test_model.py | 4 ++++ zulipterminal/model.py | 3 +++ 2 files changed, 7 insertions(+) diff --git a/tests/model/test_model.py b/tests/model/test_model.py index 119b3aecab..c8bb51537c 100644 --- a/tests/model/test_model.py +++ b/tests/model/test_model.py @@ -1913,6 +1913,7 @@ def test__handle_message_event_with_Falsey_log( mocker.patch(MODEL + "._update_topic_index") mocker.patch(MODULE + ".index_messages", return_value={}) self.controller.view.message_view = mocker.Mock(log=[]) + self.controller.is_in_empty_narrow = False create_msg_box_list = mocker.patch( MODULE + ".create_msg_box_list", return_value=["msg_w"] ) @@ -1932,6 +1933,7 @@ def test__handle_message_event_with_valid_log(self, mocker, model, message_fixtu mocker.patch(MODEL + "._update_topic_index") mocker.patch(MODULE + ".index_messages", return_value={}) self.controller.view.message_view = mocker.Mock(log=[mocker.Mock()]) + self.controller.is_in_empty_narrow = False create_msg_box_list = mocker.patch( MODULE + ".create_msg_box_list", return_value=["msg_w"] ) @@ -1954,6 +1956,7 @@ def test__handle_message_event_with_flags(self, mocker, model, message_fixture): mocker.patch(MODEL + "._update_topic_index") mocker.patch(MODULE + ".index_messages", return_value={}) self.controller.view.message_view = mocker.Mock(log=[mocker.Mock()]) + self.controller.is_in_empty_narrow = False mocker.patch(MODULE + ".create_msg_box_list", return_value=["msg_w"]) model.notify_user = mocker.Mock() set_count = mocker.patch(MODULE + ".set_count") @@ -2097,6 +2100,7 @@ def test__handle_message_event( ( self.controller.view.left_panel.is_in_topic_view_with_stream_id.return_value ) = False + self.controller.is_in_empty_narrow = False model.notify_user = mocker.Mock() model.narrow = narrow model.recipients = recipients diff --git a/zulipterminal/model.py b/zulipterminal/model.py index 1d7688cdeb..c12acb62e1 100644 --- a/zulipterminal/model.py +++ b/zulipterminal/model.py @@ -1742,6 +1742,9 @@ def _handle_message_event(self, event: Event) -> None: msg_w = msg_w_list[0] if self.current_narrow_contains_message(message): + if self.controller.is_in_empty_narrow: + del msg_log[0] + self.controller.is_in_empty_narrow = False msg_log.append(msg_w) self.controller.update_screen() From df2feb4a98118e6333d3d0619b52512fdd7c5e50 Mon Sep 17 00:00:00 2001 From: Niloth P <20315308+Niloth-p@users.noreply.github.com> Date: Sat, 3 Aug 2024 10:33:05 +0530 Subject: [PATCH 07/10] messages: Remove error handling for unreachable case. Message type can only be "stream" or "private", as returned by the API. Simplified the flow to bunch if-else blocks together. Test removed. --- tests/ui_tools/test_messages.py | 6 ------ zulipterminal/ui_tools/messages.py | 5 +---- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/tests/ui_tools/test_messages.py b/tests/ui_tools/test_messages.py index d808a53bed..4c486535ba 100644 --- a/tests/ui_tools/test_messages.py +++ b/tests/ui_tools/test_messages.py @@ -65,12 +65,6 @@ def test_init(self, mocker, message_type, set_fields): assert msg_box.message_links == OrderedDict() assert msg_box.time_mentions == list() - def test_init_fails_with_bad_message_type(self): - message = dict(type="BLAH") - - with pytest.raises(RuntimeError): - MessageBox(message, self.model, None) - def test_private_message_to_self(self, mocker): message = dict( type="private", diff --git a/zulipterminal/ui_tools/messages.py b/zulipterminal/ui_tools/messages.py index 7ba1a78bd3..c5e602f1bd 100644 --- a/zulipterminal/ui_tools/messages.py +++ b/zulipterminal/ui_tools/messages.py @@ -81,13 +81,10 @@ def __init__(self, message: Message, model: "Model", last_message: Any) -> None: self.stream_name = self.message["display_recipient"] self.stream_id = self.message["stream_id"] self.topic_name = self.message["subject"] - elif self.message["type"] == "private": + else: self.email = self.message["sender_email"] self.user_id = self.message["sender_id"] - else: - raise RuntimeError("Invalid message type") - if self.message["type"] == "private": if self._is_private_message_to_self(): recipient = self.message["display_recipient"][0] self.recipients_names = recipient["full_name"] From 299a1c13e4a2c5a1b5696253df2c7e7dad3f7f2f Mon Sep 17 00:00:00 2001 From: Niloth P <20315308+Niloth-p@users.noreply.github.com> Date: Sat, 3 Aug 2024 10:38:43 +0530 Subject: [PATCH 08/10] refactor: messages: Allow datetime field of current message to be empty. --- zulipterminal/ui_tools/messages.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zulipterminal/ui_tools/messages.py b/zulipterminal/ui_tools/messages.py index c5e602f1bd..4999384774 100644 --- a/zulipterminal/ui_tools/messages.py +++ b/zulipterminal/ui_tools/messages.py @@ -683,7 +683,8 @@ def main_view(self) -> List[Any]: "recipients": recipient_header is not None, "author": message["this"]["author"] != message["last"]["author"], "24h": ( - message["last"]["datetime"] is not None + message["this"]["datetime"] is not None + and message["last"]["datetime"] is not None and ((message["this"]["datetime"] - message["last"]["datetime"]).days) ), "timestamp": ( From 344455287ae7c1cc4078bb9fe8141b7d741a494c Mon Sep 17 00:00:00 2001 From: Niloth P <20315308+Niloth-p@users.noreply.github.com> Date: Sat, 3 Aug 2024 10:43:42 +0530 Subject: [PATCH 09/10] utils/messages: Add a dummy message for empty narrows. Tests updated. Co-authored-by: Sumanth V Rao --- tests/ui_tools/test_messages.py | 11 ++++++++ zulipterminal/ui_tools/messages.py | 6 +++-- zulipterminal/ui_tools/utils.py | 40 ++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/tests/ui_tools/test_messages.py b/tests/ui_tools/test_messages.py index 4c486535ba..2be27c212c 100644 --- a/tests/ui_tools/test_messages.py +++ b/tests/ui_tools/test_messages.py @@ -48,6 +48,7 @@ def test_init(self, mocker, message_type, set_fields): display_recipient=[ {"id": 7, "email": "boo@zulip.com", "full_name": "Boo is awesome"} ], + id=457823, stream_id=5, subject="hi", sender_email="foo@zulip.com", @@ -71,6 +72,7 @@ def test_private_message_to_self(self, mocker): display_recipient=[ {"full_name": "Foo Foo", "email": "foo@zulip.com", "id": None} ], + id=457823, sender_id=9, content="

self message.

", sender_full_name="Foo Foo", @@ -82,6 +84,7 @@ def test_private_message_to_self(self, mocker): MODULE + ".MessageBox._is_private_message_to_self", return_value=True ) mocker.patch.object(MessageBox, "main_view") + msg_box = MessageBox(message, self.model, None) assert msg_box.recipient_emails == ["foo@zulip.com"] @@ -753,6 +756,7 @@ def test_main_view(self, mocker, message, last_message): "color": "#bd6", }, } + self.model.controller.is_in_empty_narrow = False MessageBox(message, self.model, last_message) @pytest.mark.parametrize( @@ -1149,8 +1153,11 @@ def test_main_view_compact_output( ): message_fixture.update({"id": 4}) varied_message = dict(message_fixture, **to_vary_in_each_message) + self.model.controller.is_in_empty_narrow = False msg_box = MessageBox(varied_message, self.model, varied_message) + view_components = msg_box.main_view() + assert len(view_components) == 1 assert isinstance(view_components[0], Padding) @@ -1160,7 +1167,9 @@ def test_main_view_generates_EDITED_label( messages = messages_successful_response["messages"] for message in messages: self.model.index["edited_messages"].add(message["id"]) + self.model.controller.is_in_empty_narrow = False msg_box = MessageBox(message, self.model, message) + view_components = msg_box.main_view() label = view_components[0].original_widget.contents[0] @@ -1186,6 +1195,7 @@ def test_update_message_author_status( ): message = message_fixture last_msg = dict(message, **to_vary_in_last_message) + self.model.controller.is_in_empty_narrow = False msg_box = MessageBox(message, self.model, last_msg) @@ -1389,6 +1399,7 @@ def test_keypress_EDIT_MESSAGE( to_vary_in_each_message["subject"] = "" varied_message = dict(message_fixture, **to_vary_in_each_message) message_type = varied_message["type"] + self.model.controller.is_in_empty_narrow = False msg_box = MessageBox(varied_message, self.model, message_fixture) size = widget_size(msg_box) msg_box.model.user_id = 1 diff --git a/zulipterminal/ui_tools/messages.py b/zulipterminal/ui_tools/messages.py index 4999384774..f7799ceca9 100644 --- a/zulipterminal/ui_tools/messages.py +++ b/zulipterminal/ui_tools/messages.py @@ -82,8 +82,10 @@ def __init__(self, message: Message, model: "Model", last_message: Any) -> None: self.stream_id = self.message["stream_id"] self.topic_name = self.message["subject"] else: - self.email = self.message["sender_email"] - self.user_id = self.message["sender_id"] + # Get sender details only for non-empty narrows + if self.message.get("id") is not None: + self.email = self.message["sender_email"] + self.user_id = self.message["sender_id"] if self._is_private_message_to_self(): recipient = self.message["display_recipient"][0] diff --git a/zulipterminal/ui_tools/utils.py b/zulipterminal/ui_tools/utils.py index d055af44b7..c3d0473ce4 100644 --- a/zulipterminal/ui_tools/utils.py +++ b/zulipterminal/ui_tools/utils.py @@ -29,6 +29,46 @@ def create_msg_box_list( focus_msg = None last_msg = last_message muted_msgs = 0 # No of messages that are muted. + + # Add a dummy message if no new or old messages are found + if not message_list and not last_msg: + message_type = "stream" if model.stream_id is not None else "private" + dummy_message = { + "id": None, + "content": ( + "No search hits" if model.is_search_narrow() else "No messages here" + ), + "is_me_message": False, + "flags": ["read"], + "reactions": [], + "type": message_type, + "stream_id": model.stream_id if message_type == "stream" else None, + "stream_name": model.stream_dict[model.stream_id]["name"] + if message_type == "stream" + else None, + "subject": next( + (subnarrow[1] for subnarrow in model.narrow if subnarrow[0] == "topic"), + "No topics in channel", + ) + if message_type == "stream" + else None, + "display_recipient": ( + model.stream_dict[model.stream_id]["name"] + if message_type == "stream" + else [ + { + "email": model.user_id_email_dict[recipient_id], + "full_name": model.user_dict[ + model.user_id_email_dict[recipient_id] + ]["full_name"], + "id": recipient_id, + } + for recipient_id in model.recipients + ] + ), + } + message_list.append(dummy_message) + for msg in message_list: if is_unsubscribed_message(msg, model): continue From 2a66297ea1a649f0b2fe562abf0f949b0a7cc044 Mon Sep 17 00:00:00 2001 From: Niloth P <20315308+Niloth-p@users.noreply.github.com> Date: Fri, 2 Aug 2024 16:41:44 +0530 Subject: [PATCH 10/10] core/boxes: Flash footer message on search misses, without narrowing. --- tests/core/test_core.py | 58 +++++++++++++++++++++++++++++++-- zulipterminal/core.py | 5 ++- zulipterminal/ui_tools/boxes.py | 8 +++-- 3 files changed, 65 insertions(+), 6 deletions(-) diff --git a/tests/core/test_core.py b/tests/core/test_core.py index ff6701a41d..5bc1373692 100644 --- a/tests/core/test_core.py +++ b/tests/core/test_core.py @@ -518,8 +518,8 @@ def test_stream_muting_confirmation_popup( "search_within_topic_narrow", ], ) - @pytest.mark.parametrize("msg_ids", [({200, 300, 400}), (set()), ({100})]) - def test_search_message( + @pytest.mark.parametrize("msg_ids", [({200, 300, 400}), ({100})]) + def test_search_message__hits( self, initial_narrow: List[Any], final_narrow: List[Any], @@ -550,6 +550,60 @@ def set_msg_ids(*args: Any, **kwargs: Any) -> None: create_msg.assert_called_once_with(controller.model, msg_ids) assert controller.model.index == dict(index_search_messages, search=msg_ids) + @pytest.mark.parametrize( + "initial_narrow, final_narrow", + [ + ([], [["search", "FOO"]]), + ([["search", "BOO"]], [["search", "FOO"]]), + ([["stream", "PTEST"]], [["stream", "PTEST"], ["search", "FOO"]]), + ( + [["pm-with", "foo@zulip.com"], ["search", "BOO"]], + [["pm-with", "foo@zulip.com"], ["search", "FOO"]], + ), + ( + [["stream", "PTEST"], ["topic", "RDS"]], + [["stream", "PTEST"], ["topic", "RDS"], ["search", "FOO"]], + ), + ], + ids=[ + "Default_all_msg_search", + "redo_default_search", + "search_within_stream", + "pm_search_again", + "search_within_topic_narrow", + ], + ) + def test_search_message__no_hits( + self, + initial_narrow: List[Any], + final_narrow: List[Any], + controller: Controller, + mocker: MockerFixture, + index_search_messages: Index, + msg_ids: Set[int] = set(), + ) -> None: + get_message = mocker.patch(MODEL + ".get_messages") + create_msg = mocker.patch(MODULE + ".create_msg_box_list") + mocker.patch(MODEL + ".get_message_ids_in_current_narrow", return_value=msg_ids) + controller.model.index = index_search_messages # Any initial search index + controller.view.message_view = mocker.patch("urwid.ListBox") + controller.model.narrow = initial_narrow + + def set_msg_ids(*args: Any, **kwargs: Any) -> None: + controller.model.index["search"].update(msg_ids) + + get_message.side_effect = set_msg_ids + assert controller.model.index["search"] == {500} + + controller.search_messages("FOO") + + assert controller.model.narrow == final_narrow + get_message.assert_called_once_with( + num_after=0, num_before=30, anchor=10000000000 + ) + create_msg.assert_not_called() + assert controller.model.index == dict(index_search_messages, search=msg_ids) + @pytest.mark.parametrize( "screen_size, expected_popup_size", [ diff --git a/zulipterminal/core.py b/zulipterminal/core.py index 712feddcde..6972a80be6 100644 --- a/zulipterminal/core.py +++ b/zulipterminal/core.py @@ -504,13 +504,15 @@ def show_media_confirmation_popup( self, question, callback, location="center" ) - def search_messages(self, text: str) -> None: + def search_messages(self, text: str) -> bool: # Search for a text in messages self.model.index["search"].clear() self.model.set_search_narrow(text) self.model.get_messages(num_after=0, num_before=30, anchor=10000000000) msg_id_list = self.model.get_message_ids_in_current_narrow() + if len(msg_id_list) == 0: + return False w_list = create_msg_box_list(self.model, msg_id_list) self.view.message_view.log.clear() @@ -518,6 +520,7 @@ def search_messages(self, text: str) -> None: focus_position = 0 if 0 <= focus_position < len(w_list): self.view.message_view.set_focus(focus_position) + return True def save_draft_confirmation_popup(self, draft: Composition) -> None: question = urwid.Text( diff --git a/zulipterminal/ui_tools/boxes.py b/zulipterminal/ui_tools/boxes.py index 64a1cae9a1..c1803ef67e 100644 --- a/zulipterminal/ui_tools/boxes.py +++ b/zulipterminal/ui_tools/boxes.py @@ -1032,9 +1032,11 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: return 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") + if self.controller.search_messages(self.text_box.edit_text): + self.controller.exit_editor_mode() + self.controller.view.middle_column.set_focus("body") + else: + self.controller.report_error(["No results found."]) return key key = super().keypress(size, key)