From 47384bc17a41357edf7bb3bf376741aff16373cd Mon Sep 17 00:00:00 2001 From: rsashank Date: Wed, 8 Nov 2023 23:17:06 +0530 Subject: [PATCH] core/boxes: Improve handling of pressing Esc during message compose. Fixes #1342. Introduces variable CONFIRMATION_MSG_LENGTH to set how many characters are required in compose box to prompt a popup instead of the current instant exit when Esc is pressed. --- tests/ui_tools/test_boxes.py | 59 ++++++++++++++++++++++++++++++--- zulipterminal/core.py | 15 +++++++++ zulipterminal/ui_tools/boxes.py | 17 +++++++--- 3 files changed, 82 insertions(+), 9 deletions(-) diff --git a/tests/ui_tools/test_boxes.py b/tests/ui_tools/test_boxes.py index 869cc981b9..6f83c4ac28 100644 --- a/tests/ui_tools/test_boxes.py +++ b/tests/ui_tools/test_boxes.py @@ -16,7 +16,12 @@ ) from zulipterminal.config.ui_mappings import StreamAccessType from zulipterminal.helper import Index, MinimalUserData -from zulipterminal.ui_tools.boxes import PanelSearchBox, WriteBox, _MessageEditState +from zulipterminal.ui_tools.boxes import ( + CONFIRMATION_MSG_LENGTH, + PanelSearchBox, + WriteBox, + _MessageEditState, +) from zulipterminal.urwid_types import urwid_Size @@ -224,7 +229,7 @@ def test_not_calling_send_private_message_without_recipients( assert not write_box.model.send_private_message.called @pytest.mark.parametrize("key", keys_for_command("GO_BACK")) - def test__compose_attributes_reset_for_private_compose( + def test__compose_attributes_reset_for_private_compose__no_popup( self, key: str, mocker: MockerFixture, @@ -235,17 +240,39 @@ def test__compose_attributes_reset_for_private_compose( mocker.patch("urwid.connect_signal") write_box.model.user_id_email_dict = user_id_email_dict write_box.private_box_view(recipient_user_ids=[11]) - write_box.msg_write_box.edit_text = "random text" + + write_box.msg_write_box.edit_text = "." * (CONFIRMATION_MSG_LENGTH - 1) size = widget_size(write_box) write_box.keypress(size, key) + write_box.view.controller.exit_compose_confirmation_popup.assert_not_called() assert write_box.to_write_box is None assert write_box.msg_write_box.edit_text == "" assert write_box.compose_box_status == "closed" @pytest.mark.parametrize("key", keys_for_command("GO_BACK")) - def test__compose_attributes_reset_for_stream_compose( + def test__compose_attributes_reset_for_private_compose__popup( + self, + key: str, + mocker: MockerFixture, + write_box: WriteBox, + widget_size: Callable[[Widget], urwid_Size], + user_id_email_dict: Dict[int, str], + ) -> None: + mocker.patch("urwid.connect_signal") + write_box.model.user_id_email_dict = user_id_email_dict + write_box.private_box_view(recipient_user_ids=[11]) + + write_box.msg_write_box.edit_text = "." * CONFIRMATION_MSG_LENGTH + + size = widget_size(write_box) + write_box.keypress(size, key) + + write_box.view.controller.exit_compose_confirmation_popup.assert_called_once() + + @pytest.mark.parametrize("key", keys_for_command("GO_BACK")) + def test__compose_attributes_reset_for_stream_compose__no_popup( self, key: str, mocker: MockerFixture, @@ -254,15 +281,35 @@ def test__compose_attributes_reset_for_stream_compose( ) -> None: mocker.patch(WRITEBOX + "._set_stream_write_box_style") write_box.stream_box_view(stream_id=1) - write_box.msg_write_box.edit_text = "random text" + + write_box.msg_write_box.edit_text = "." * (CONFIRMATION_MSG_LENGTH - 1) size = widget_size(write_box) write_box.keypress(size, key) + write_box.view.controller.exit_compose_confirmation_popup.assert_not_called() assert write_box.stream_id is None assert write_box.msg_write_box.edit_text == "" assert write_box.compose_box_status == "closed" + @pytest.mark.parametrize("key", keys_for_command("GO_BACK")) + def test__compose_attributes_reset_for_stream_compose__popup( + self, + key: str, + mocker: MockerFixture, + write_box: WriteBox, + widget_size: Callable[[Widget], urwid_Size], + ) -> None: + mocker.patch(WRITEBOX + "._set_stream_write_box_style") + write_box.stream_box_view(stream_id=1) + + write_box.msg_write_box.edit_text = "." * CONFIRMATION_MSG_LENGTH + + size = widget_size(write_box) + write_box.keypress(size, key) + + write_box.view.controller.exit_compose_confirmation_popup.assert_called_once_with() + @pytest.mark.parametrize( ["raw_recipients", "tidied_recipients"], [ @@ -1508,6 +1555,7 @@ def test_keypress_SEND_MESSAGE_no_topic( ) def test_keypress_typeahead_mode_autocomplete_key( self, + mocker: MockerFixture, write_box: WriteBox, widget_size: Callable[[Widget], urwid_Size], current_typeahead_mode: bool, @@ -1515,6 +1563,7 @@ def test_keypress_typeahead_mode_autocomplete_key( expect_footer_was_reset: bool, key: str, ) -> None: + write_box.msg_write_box = mocker.Mock(edit_text="") write_box.is_in_typeahead_mode = current_typeahead_mode size = widget_size(write_box) diff --git a/zulipterminal/core.py b/zulipterminal/core.py index 2799210ab9..5fefb4cc45 100644 --- a/zulipterminal/core.py +++ b/zulipterminal/core.py @@ -523,6 +523,21 @@ def stream_muting_confirmation_popup( mute_this_stream = partial(self.model.toggle_stream_muted_status, stream_id) self.loop.widget = PopUpConfirmationView(self, question, mute_this_stream) + def exit_compose_confirmation_popup(self) -> None: + question = urwid.Text( + ( + "bold", + "Please confirm that you wish to exit the compose box.\n" + "(You can save the message as a draft upon returning to compose)", + ), + "center", + ) + write_box = self.view.write_box + popup_view = PopUpConfirmationView( + self, question, write_box.exit_compose_box, location="center" + ) + self.loop.widget = popup_view + def copy_to_clipboard(self, text: str, text_category: str) -> None: try: pyperclip.copy(text) diff --git a/zulipterminal/ui_tools/boxes.py b/zulipterminal/ui_tools/boxes.py index 36128f444a..c681d8b7fa 100644 --- a/zulipterminal/ui_tools/boxes.py +++ b/zulipterminal/ui_tools/boxes.py @@ -54,6 +54,9 @@ from zulipterminal.urwid_types import urwid_Size +CONFIRMATION_MSG_LENGTH = 15 + + class _MessageEditState(NamedTuple): message_id: int old_topic: str @@ -710,6 +713,12 @@ def autocomplete_emojis( return emoji_typeahead, emojis + def exit_compose_box(self) -> None: + self._set_compose_attributes_to_defaults() + self.view.controller.exit_editor_mode() + self.main_view(False) + self.view.middle_column.set_focus("body") + def keypress(self, size: urwid_Size, key: str) -> Optional[str]: if self.is_in_typeahead_mode and not ( is_command_key("AUTOCOMPLETE", key) @@ -801,10 +810,10 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: ) elif is_command_key("GO_BACK", key): self.send_stop_typing_status() - self._set_compose_attributes_to_defaults() - self.view.controller.exit_editor_mode() - self.main_view(False) - self.view.middle_column.set_focus("body") + if len(self.msg_write_box.edit_text) >= CONFIRMATION_MSG_LENGTH: + self.view.controller.exit_compose_confirmation_popup() + else: + self.exit_compose_box() elif is_command_key("MARKDOWN_HELP", key): self.view.controller.show_markdown_help() return key