-
-
Notifications
You must be signed in to change notification settings - Fork 259
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Handling Empty Narrows #1543
base: main
Are you sure you want to change the base?
Handling Empty Narrows #1543
Changes from 9 commits
4992dbe
936cf2f
6cac57d
f8503f8
dcb213a
0fb5bf0
df2feb4
299a1c1
3444552
2a66297
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,6 +48,7 @@ def test_init(self, mocker, message_type, set_fields): | |
display_recipient=[ | ||
{"id": 7, "email": "[email protected]", "full_name": "Boo is awesome"} | ||
], | ||
id=457823, | ||
stream_id=5, | ||
subject="hi", | ||
sender_email="[email protected]", | ||
|
@@ -65,18 +66,13 @@ 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", | ||
display_recipient=[ | ||
{"full_name": "Foo Foo", "email": "[email protected]", "id": None} | ||
], | ||
id=457823, | ||
sender_id=9, | ||
content="<p> self message. </p>", | ||
sender_full_name="Foo Foo", | ||
|
@@ -88,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 == ["[email protected]"] | ||
|
@@ -759,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( | ||
|
@@ -838,6 +836,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 +889,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 +1032,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() | ||
|
||
|
@@ -1151,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) | ||
|
||
|
@@ -1162,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] | ||
|
@@ -1188,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) | ||
|
||
|
@@ -1391,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 | ||
|
@@ -1405,6 +1414,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 +1830,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 +1987,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) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
Comment on lines
+1745
to
+1747
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this the part that is not working properly, in your 'Next steps'? It looks like this commit should come later in the sequencce, at or after where we add an empty message, or else the As per another comment, this isn't updated at all in Note that we will also want this to be triggered for star changes as it stands, and may be a lot simpler to test in that way, ie. to make a narrow empty using 0/1 starred messages - though of course is a different function under test in that case. |
||
msg_log.append(msg_w) | ||
|
||
self.controller.update_screen() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1014,7 +1014,7 @@ def main_view(self) -> Any: | |
self.text_box, | ||
] | ||
) | ||
self.msg_narrow = urwid.Text("DONT HIDE") | ||
self.msg_narrow = urwid.Text("") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As I said in the channel, it's great to remove this line, though I think this was associated with the comment some lines above, which we could also remove? (look through the git log via git blame?) In terms of this commit, it would be helpful to clarify under which conditions the application would display an empty recipient bar, since I very rarely actually see this odd text. This doesn't need to be via an exact analysis, but notes would be helpful, since looking back to this change in the future would help clarify what happens here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was going to suggest taking advantage of this small commit to eg. refactor the attribute naming to be private. However, this is one of those coupled classes/objects, so many of these names are actually public, so this likely would be best settled with a separate refactoring effort. In the absence of that, you could add comments to clarify each attribute for now; would that help you next time? If so, it'd help others who next read the code :) |
||
self.recipient_bar = urwid.LineBox( | ||
self.msg_narrow, | ||
title="Current message recipients", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -81,13 +81,12 @@ 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": | ||
self.email = self.message["sender_email"] | ||
self.user_id = self.message["sender_id"] | ||
else: | ||
raise RuntimeError("Invalid message type") | ||
# 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.message["type"] == "private": | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was trying to recall the separation of the two sets of branches here. I think we can combine them, perhaps again, for now. |
||
if self._is_private_message_to_self(): | ||
recipient = self.message["display_recipient"][0] | ||
self.recipients_names = recipient["full_name"] | ||
|
@@ -115,6 +114,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 | ||
Comment on lines
+117
to
+118
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This commit is the first of some which make the message and top bar better for each case, and would make sense together. Currently, after this commit you have a number of others that would make more sense as being separate/later instead? |
||
# Prevent redundant information in recipient bar | ||
if len(self.model.narrow) == 1 and self.model.narrow[0][0] == "pm-with": | ||
return False | ||
|
@@ -198,8 +199,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")]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm wondering if it would be clearer to simply leave this blank? |
||
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": | ||
Comment on lines
-202
to
+218
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a partial fix towards the issue? That is, we start to show a different message in the message header and the current-message header? Assuming so, the commit text could indicate that. Does the new header structure need to follow the form of the others? I know it's rather hacky in places, so it's likely fine for now. |
||
return self.stream_header() | ||
else: | ||
return self.private_header() | ||
|
@@ -669,7 +685,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 | ||
Comment on lines
-672
to
+689
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume this is simply a prep to allow for None datetimes in both, for dummy messages? It's not clear to me why this is necessary, when it's not applied to other fields? I suspect this relates to the crash I saw later. |
||
and ((message["this"]["datetime"] - message["last"]["datetime"]).days) | ||
), | ||
"timestamp": ( | ||
|
@@ -904,7 +921,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 | ||
Comment on lines
-907
to
+924
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If using the message as a placeholder, then I understand the need to disable lots of these hotkey behaviors. However, rather than disabling each of them individually, it would be a lot clearer to branch/exit separately, likely first. Currently you seem to be allowing only the following:
Should we allow a new DM message (x) too? Some actions you're excluding clearly don't make a lot of sense, when they have to act on a specific valid message, but I'm not sure about other cases, specifically narrowing. Should all narrowing be possible from an empty narrow? Maybe a stream/channel narrow is empty, so we can try an all-messages narrow, but then what if that is itself empty? That suggests we can try different (absolute) narrows from any empty narrow? How does that work 'without' a message id, ie. an empty narrow and 'fake' message? What does the message id fall back to in that case? Nothing like this works before this PR, so the simplest solution might be to start from including no narrowing operations, and treat this as a followup after discussion. |
||
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 +941,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 +952,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 +976,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,20 +992,20 @@ 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) | ||
self.model.controller.view.write_box.msg_write_box.set_edit_pos( | ||
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 +1033,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 +1137,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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was this small commit factored out, so that it would cover Sumanth's original process here? Oh, I think you said that it was where the inspiration came from?
(I'm torn whether this belongs in the model or controller here, but that's probably a refactoring to consider how to structure the whole narrowing behavior)