From 0bf31b78df1aaa28750893661e21272a606f7626 Mon Sep 17 00:00:00 2001 From: Niloth-p <20315308+Niloth-p@users.noreply.github.com> Date: Thu, 6 Jun 2024 06:04:27 +0530 Subject: [PATCH 1/3] refactor: ui_tools/helper: Add new enum Search Status. The existing empty_search boolean property of views with search boxes is replaced by an enum property search_status that supports the states - DEFAULT, FILTERED and EMPTY. This allows tracking whether the results are filtered or not as well, without introducing a separate boolean property for that. Updated tests. --- tests/ui/test_ui_tools.py | 5 +++- tests/ui_tools/test_boxes.py | 6 ++--- zulipterminal/helper.py | 7 ++++++ zulipterminal/ui_tools/boxes.py | 6 ++++- zulipterminal/ui_tools/views.py | 43 ++++++++++++++++++++++++--------- 5 files changed, 50 insertions(+), 17 deletions(-) diff --git a/tests/ui/test_ui_tools.py b/tests/ui/test_ui_tools.py index 0a4d9ec030..17968c1cdc 100644 --- a/tests/ui/test_ui_tools.py +++ b/tests/ui/test_ui_tools.py @@ -7,7 +7,7 @@ from zulipterminal.config.keys import keys_for_command, primary_key_for_command from zulipterminal.config.symbols import STATUS_ACTIVE -from zulipterminal.helper import powerset +from zulipterminal.helper import SearchStatus, powerset from zulipterminal.ui_tools.views import ( SIDE_PANELS_MOUSE_SCROLL_LINES, LeftColumnView, @@ -565,6 +565,7 @@ def test_keypress_CLEAR_SEARCH(self, mocker, stream_view, key, widget_size): mocker.patch.object(stream_view, "set_focus") mocker.patch(VIEWS + ".urwid.Frame.keypress") mocker.patch.object(stream_view.stream_search_box, "reset_search_text") + stream_view.search_status = SearchStatus.FILTERED stream_view.streams_btn_list = ["FOO", "foo", "fan", "boo", "BOO"] stream_view.focus_index_before_search = 3 @@ -731,6 +732,7 @@ def test_keypress_CLEAR_SEARCH(self, mocker, topic_view, key, widget_size): mocker.patch(VIEWS + ".TopicsView.set_focus") mocker.patch(VIEWS + ".urwid.Frame.keypress") mocker.patch.object(topic_view.topic_search_box, "reset_search_text") + topic_view.search_status = SearchStatus.FILTERED topic_view.topics_btn_list = ["FOO", "foo", "fan", "boo", "BOO"] topic_view.focus_index_before_search = 3 @@ -1112,6 +1114,7 @@ def test_keypress_CLEAR_SEARCH(self, right_col_view, mocker, key, widget_size): mocker.patch(VIEWS + ".RightColumnView.set_focus") mocker.patch(VIEWS + ".RightColumnView.set_body") mocker.patch.object(right_col_view.user_search, "reset_search_text") + right_col_view.search_status = SearchStatus.FILTERED right_col_view.users_btn_list = [] right_col_view.keypress(size, key) diff --git a/tests/ui_tools/test_boxes.py b/tests/ui_tools/test_boxes.py index c4e89a2b58..729efeee8c 100644 --- a/tests/ui_tools/test_boxes.py +++ b/tests/ui_tools/test_boxes.py @@ -24,7 +24,7 @@ STREAM_MARKER_WEB_PUBLIC, ) from zulipterminal.config.ui_mappings import StreamAccessType -from zulipterminal.helper import Index, MinimalUserData +from zulipterminal.helper import Index, MinimalUserData, SearchStatus from zulipterminal.ui_tools.boxes import ( MAX_MESSAGE_LENGTH_CONFIRMATION_POPUP, PanelSearchBox, @@ -1903,8 +1903,8 @@ def test_keypress_ENTER( size = widget_size(panel_search_box) panel_search_box.panel_view.view.controller.is_in_editor_mode = lambda: True panel_search_box.panel_view.log = log - empty_search = not log - panel_search_box.panel_view.empty_search = empty_search + search_status = SearchStatus.FILTERED if log else SearchStatus.EMPTY + panel_search_box.panel_view.search_status = search_status panel_search_box.set_caption("") panel_search_box.edit_text = "key words" diff --git a/zulipterminal/helper.py b/zulipterminal/helper.py index a71d055b74..733e1159fe 100644 --- a/zulipterminal/helper.py +++ b/zulipterminal/helper.py @@ -7,6 +7,7 @@ import time from collections import defaultdict from contextlib import contextmanager +from enum import Enum from functools import partial, wraps from itertools import chain, combinations from re import ASCII, MULTILINE, findall, match @@ -49,6 +50,12 @@ StreamAccessType = Literal["public", "private", "web-public"] +class SearchStatus(Enum): + DEFAULT = 0 + FILTERED = 1 + EMPTY = 2 + + class StreamData(TypedDict): name: str id: int diff --git a/zulipterminal/ui_tools/boxes.py b/zulipterminal/ui_tools/boxes.py index 9856685336..b2ca3f3f02 100644 --- a/zulipterminal/ui_tools/boxes.py +++ b/zulipterminal/ui_tools/boxes.py @@ -36,6 +36,7 @@ ) from zulipterminal.config.ui_mappings import STREAM_ACCESS_TYPE from zulipterminal.helper import ( + SearchStatus, asynch, format_string, match_emoji, @@ -1043,7 +1044,10 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: # Don't call 'Esc' when inside a popup search-box. if not self.panel_view.view.controller.is_any_popup_open(): self.panel_view.keypress(size, primary_key_for_command("CLEAR_SEARCH")) - elif is_command_key("EXECUTE_SEARCH", key) and not self.panel_view.empty_search: + elif ( + is_command_key("EXECUTE_SEARCH", key) + and self.panel_view.search_status != SearchStatus.EMPTY + ): self.panel_view.view.controller.exit_editor_mode() self.set_caption([("filter_results", " Search Results "), " "]) self.panel_view.set_focus("body") diff --git a/zulipterminal/ui_tools/views.py b/zulipterminal/ui_tools/views.py index c2034b3ef7..d77ecb7dde 100644 --- a/zulipterminal/ui_tools/views.py +++ b/zulipterminal/ui_tools/views.py @@ -36,6 +36,7 @@ ) from zulipterminal.config.ui_sizes import LEFT_WIDTH from zulipterminal.helper import ( + SearchStatus, TidiedUserInfo, asynch, match_emoji, @@ -335,7 +336,7 @@ def __init__(self, streams_btn_list: List[Any], view: Any) -> None: ), ) self.search_lock = threading.Lock() - self.empty_search = False + self.search_status = SearchStatus.DEFAULT @asynch def update_streams(self, search_box: Any, new_text: str) -> None: @@ -352,7 +353,11 @@ def update_streams(self, search_box: Any, new_text: str) -> None: )[0] streams_display_num = len(streams_display) - self.empty_search = streams_display_num == 0 + self.search_status = ( + SearchStatus.EMPTY + if streams_display_num == 0 + else SearchStatus.FILTERED + ) # Add a divider to separate pinned streams from the rest. pinned_stream_names = [ @@ -371,7 +376,7 @@ def update_streams(self, search_box: Any, new_text: str) -> None: streams_display.insert(first_unpinned_index, StreamsViewDivider()) self.log.clear() - if not self.empty_search: + if self.search_status == SearchStatus.FILTERED: self.log.extend(streams_display) else: self.log.extend([self.stream_search_box.search_error]) @@ -404,6 +409,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: self.log.extend(self.streams_btn_list) self.set_focus("body") self.log.set_focus(self.focus_index_before_search) + self.search_status = SearchStatus.DEFAULT self.view.controller.update_screen() return key return super().keypress(size, key) @@ -436,7 +442,7 @@ def __init__( header=self.header_list, ) self.search_lock = threading.Lock() - self.empty_search = False + self.search_status = SearchStatus.DEFAULT def _focus_position_for_topic_name(self) -> int: saved_topic_state = self.view.saved_topic_in_stream_id( @@ -461,10 +467,14 @@ def update_topics(self, search_box: Any, new_text: str) -> None: for topic in self.topics_btn_list.copy() if new_text in topic.topic_name.lower() ] - self.empty_search = len(topics_to_display) == 0 + self.search_status = ( + SearchStatus.EMPTY + if len(topics_to_display) == 0 + else SearchStatus.FILTERED + ) self.log.clear() - if not self.empty_search: + if self.search_status == SearchStatus.FILTERED: self.log.extend(topics_to_display) else: self.log.extend([self.topic_search_box.search_error]) @@ -524,6 +534,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: self.log.extend(self.topics_btn_list) self.set_focus("body") self.log.set_focus(self.focus_index_before_search) + self.search_status = SearchStatus.DEFAULT self.view.controller.update_screen() return key return super().keypress(size, key) @@ -665,7 +676,7 @@ def __init__(self, view: Any) -> None: self.allow_update_user_list = True self.search_lock = threading.Lock() - self.empty_search = False + self.search_status = SearchStatus.DEFAULT super().__init__(self.users_view(), header=search_box) @asynch @@ -706,10 +717,12 @@ def update_user_list( else: users_display = users - self.empty_search = len(users_display) == 0 + self.search_status = ( + SearchStatus.EMPTY if len(users_display) == 0 else SearchStatus.FILTERED + ) # FIXME Update log directly? - if not self.empty_search: + if self.search_status != SearchStatus.EMPTY: self.body = self.users_view(users_display) else: self.body = UsersView( @@ -765,6 +778,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: self.body = UsersView(self.view.controller, self.users_btn_list) self.set_body(self.body) self.set_focus("body") + self.search_status = SearchStatus.DEFAULT self.view.controller.update_screen() return key elif is_command_key("GO_LEFT", key): @@ -2027,7 +2041,7 @@ def __init__( search_box = urwid.Pile( [self.emoji_search, urwid.Divider(SECTION_DIVIDER_LINE)] ) - self.empty_search = False + self.search_status = SearchStatus.DEFAULT self.search_lock = threading.Lock() super().__init__( controller, @@ -2073,10 +2087,14 @@ def update_emoji_list( else: self.emojis_display = self.emoji_buttons - self.empty_search = len(self.emojis_display) == 0 + self.search_status = ( + SearchStatus.EMPTY + if len(self.emojis_display) == 0 + else SearchStatus.FILTERED + ) body_content = self.emojis_display - if self.empty_search: + if self.search_status == SearchStatus.EMPTY: body_content = [self.emoji_search.search_error] self.contents["body"] = ( @@ -2150,5 +2168,6 @@ def keypress(self, size: urwid_Size, key: str) -> str: self.emoji_search.reset_search_text() self.controller.exit_editor_mode() self.controller.exit_popup() + self.search_status = SearchStatus.DEFAULT return key return super().keypress(size, key) From 4f15cacce157beb3765b4d78e701ed06b9867b82 Mon Sep 17 00:00:00 2001 From: Niloth-p <20315308+Niloth-p@users.noreply.github.com> Date: Thu, 6 Jun 2024 06:22:07 +0530 Subject: [PATCH 2/3] views/boxes: Reset search only after verifying search status. Added conditional checks to ensure that a search was previously performed before resetting search, using the newly added search_status. --- zulipterminal/ui_tools/boxes.py | 5 ++++- zulipterminal/ui_tools/views.py | 15 ++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/zulipterminal/ui_tools/boxes.py b/zulipterminal/ui_tools/boxes.py index b2ca3f3f02..da0a5acfc7 100644 --- a/zulipterminal/ui_tools/boxes.py +++ b/zulipterminal/ui_tools/boxes.py @@ -1042,7 +1042,10 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: self.reset_search_text() self.panel_view.set_focus("body") # Don't call 'Esc' when inside a popup search-box. - if not self.panel_view.view.controller.is_any_popup_open(): + if ( + not self.panel_view.view.controller.is_any_popup_open() + and self.panel_view.search_status != SearchStatus.DEFAULT + ): self.panel_view.keypress(size, primary_key_for_command("CLEAR_SEARCH")) elif ( is_command_key("EXECUTE_SEARCH", key) diff --git a/zulipterminal/ui_tools/views.py b/zulipterminal/ui_tools/views.py index d77ecb7dde..6db731e016 100644 --- a/zulipterminal/ui_tools/views.py +++ b/zulipterminal/ui_tools/views.py @@ -403,7 +403,10 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: self.stream_search_box.set_caption(" ") self.view.controller.enter_editor_mode_with(self.stream_search_box) return key - elif is_command_key("CLEAR_SEARCH", key): + elif ( + is_command_key("CLEAR_SEARCH", key) + and self.search_status != SearchStatus.DEFAULT + ): self.stream_search_box.reset_search_text() self.log.clear() self.log.extend(self.streams_btn_list) @@ -528,7 +531,10 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: self.topic_search_box.set_caption(" ") self.view.controller.enter_editor_mode_with(self.topic_search_box) return key - elif is_command_key("CLEAR_SEARCH", key): + elif ( + is_command_key("CLEAR_SEARCH", key) + and self.search_status != SearchStatus.DEFAULT + ): self.topic_search_box.reset_search_text() self.log.clear() self.log.extend(self.topics_btn_list) @@ -772,7 +778,10 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: self.user_search.set_caption(" ") self.view.controller.enter_editor_mode_with(self.user_search) return key - elif is_command_key("CLEAR_SEARCH", key): + elif ( + is_command_key("CLEAR_SEARCH", key) + and self.search_status != SearchStatus.DEFAULT + ): self.user_search.reset_search_text() self.allow_update_user_list = True self.body = UsersView(self.view.controller, self.users_btn_list) From 2525a798f4b485d17e0512493ba362cf1cbc1e98 Mon Sep 17 00:00:00 2001 From: Niloth-p <20315308+Niloth-p@users.noreply.github.com> Date: Thu, 6 Jun 2024 06:53:08 +0530 Subject: [PATCH 3/3] views: Make the ALL_MESSAGES command work globally. This command worked only when a message was selected, using it as an anchor to fetch messages. Now, it has been made consistent with the other narrow commands, to work from any panel. This could not be implemented in ui.py along with other narrow commands, because of the conflict caused by the Esc key also being assigned to reset search in the side panels (GO_BACK command). When both operations, 1. reset search in the current panel view 2. narrow to all messages are possible, pressing `Esc` is set to trigger only the reset search operation and not the ALL_MESSAGES command. The next keypress of `Esc` will go to the home view once the current panel view is restored to its default state. Fixes #1505. --- zulipterminal/ui_tools/views.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/zulipterminal/ui_tools/views.py b/zulipterminal/ui_tools/views.py index 6db731e016..776cdaa387 100644 --- a/zulipterminal/ui_tools/views.py +++ b/zulipterminal/ui_tools/views.py @@ -415,6 +415,8 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: self.search_status = SearchStatus.DEFAULT self.view.controller.update_screen() return key + elif is_command_key("ALL_MESSAGES", key): + self.view.home_button.activate(key) return super().keypress(size, key) @@ -543,6 +545,8 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: self.search_status = SearchStatus.DEFAULT self.view.controller.update_screen() return key + elif is_command_key("ALL_MESSAGES", key): + self.view.home_button.activate(key) return super().keypress(size, key) @@ -790,6 +794,8 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: self.search_status = SearchStatus.DEFAULT self.view.controller.update_screen() return key + elif is_command_key("ALL_MESSAGES", key): + self.view.home_button.activate(key) elif is_command_key("GO_LEFT", key): self.view.show_right_panel(visible=False) return super().keypress(size, key) @@ -949,6 +955,8 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: return key elif is_command_key("GO_RIGHT", key): self.view.show_left_panel(visible=False) + elif is_command_key("ALL_MESSAGES", key) and self.get_focus() is self.menu_v: + self.view.home_button.activate(key) return super().keypress(size, key)