From 28afba83f3e8ff8e4d481498be63d44f2831bd74 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. Introduces variable MAX_MESSAGE_LENGTH_CONFIRMATION_POPUP to set a threshold for maximum length of message in compose box beyond which it prompts a confirmation popup instead of the current instant exit when Esc is pressed. Fixes #1342. --- tests/ui_tools/test_boxes.py | 59 +++++++++++++++++++++++++++--- zulipterminal/core.py | 15 ++++++++ zulipterminal/ui_tools/boxes.py | 32 ++++++++++++++-- zulipterminal/ui_tools/messages.py | 1 + 4 files changed, 98 insertions(+), 9 deletions(-) diff --git a/tests/ui_tools/test_boxes.py b/tests/ui_tools/test_boxes.py index dbdd0829e7..6d9e57a023 100644 --- a/tests/ui_tools/test_boxes.py +++ b/tests/ui_tools/test_boxes.py @@ -21,7 +21,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 ( + MAX_MESSAGE_LENGTH_CONFIRMATION_POPUP, + PanelSearchBox, + WriteBox, + _MessageEditState, +) from zulipterminal.urwid_types import urwid_Size @@ -232,7 +237,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, @@ -243,17 +248,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 = "." * (MAX_MESSAGE_LENGTH_CONFIRMATION_POPUP - 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 = "." * MAX_MESSAGE_LENGTH_CONFIRMATION_POPUP + + 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, @@ -262,15 +289,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 = "." * (MAX_MESSAGE_LENGTH_CONFIRMATION_POPUP - 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 = "." * MAX_MESSAGE_LENGTH_CONFIRMATION_POPUP + + 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"], [ @@ -1516,6 +1563,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, @@ -1523,6 +1571,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 1e113545e8..ee9976dc04 100644 --- a/zulipterminal/core.py +++ b/zulipterminal/core.py @@ -527,6 +527,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 6867d5a0e8..767093509e 100644 --- a/zulipterminal/ui_tools/boxes.py +++ b/zulipterminal/ui_tools/boxes.py @@ -48,6 +48,9 @@ from zulipterminal.urwid_types import urwid_Size +MAX_MESSAGE_LENGTH_CONFIRMATION_POPUP = 15 + + class _MessageEditState(NamedTuple): message_id: int old_topic: str @@ -708,10 +711,19 @@ def autocomplete_emojis( return emoji_typeahead, emojis + def exit_compose_box(self) -> None: + self.is_in_typeahead_mode = False + self.view.set_footer_text() + 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) or is_command_key("AUTOCOMPLETE_REVERSE", key) + or is_command_key("GO_BACK", key) ): # set default footer when done with autocomplete self.is_in_typeahead_mode = False @@ -798,11 +810,23 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: "Cannot narrow to message without specifying recipients." ) elif is_command_key("GO_BACK", key): + saved_draft = self.model.session_draft_message() 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 ( + self.msg_edit_state is None + and len(self.msg_write_box.edit_text) + >= MAX_MESSAGE_LENGTH_CONFIRMATION_POPUP + ): + if ( + saved_draft is None + or self.msg_write_box.edit_text != saved_draft.get("content") + ): + self.view.controller.exit_compose_confirmation_popup() + else: + self.exit_compose_box() + else: + self.exit_compose_box() elif is_command_key("MARKDOWN_HELP", key): self.view.controller.show_markdown_help() return key diff --git a/zulipterminal/ui_tools/messages.py b/zulipterminal/ui_tools/messages.py index 3ddfa10a70..81f5e56bac 100644 --- a/zulipterminal/ui_tools/messages.py +++ b/zulipterminal/ui_tools/messages.py @@ -1110,6 +1110,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]: write_box.msg_edit_state = _MessageEditState( message_id=msg_id, old_topic=self.message["subject"] ) + print(msg + " :it reached here \n") write_box.msg_write_box.set_edit_text(msg) write_box.msg_write_box.set_edit_pos(len(msg)) write_box.msg_body_edit_enabled = msg_body_edit_enabled