Skip to content
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

Introduce help context #1484

Open
wants to merge 34 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
de8d7a6
ui: Make the Help hotkey suggestion in the footer more conventional.
Niloth-p Jul 10, 2024
9fccce3
refactor: ui/core/boxes: Separate out function to reset footer text.
Niloth-p Jul 15, 2024
13410a2
refactor: ui/core: Separate footer events from set_footer_text().
Niloth-p Jul 15, 2024
b9b5ee8
ui: Add state variable to track live footer events.
Niloth-p Jul 15, 2024
af00b47
lint-hotkeys: Handle the doc file does not exist exception.
Niloth-p Jul 15, 2024
d2e0c17
refactor: lint-hotkeys: Remove the write_hotkeys_file() function.
Niloth-p Jul 15, 2024
4e8783c
refactor: lint-hotkeys: Make int error flag boolean & use SystemExit.
Niloth-p Jul 15, 2024
e954870
lint-hotkeys: Rewrite function docstrings and comments.
Niloth-p Jul 15, 2024
4ed23c7
refactor: lint-hotkeys: Rename get_hotkeys_file_string().
Niloth-p Jul 15, 2024
9717776
refactor: lint-hotkeys: Rename ambiguous keyword "action" to "batch".
Niloth-p Jul 15, 2024
11a7ead
refactor: lint-hotkeys: Improve variable naming of generated dict.
Niloth-p Jul 17, 2024
3237251
lint-hotkeys: Refactor the flow by creating new ENTRY_BY_CATEGORIES.
Niloth-p Jul 15, 2024
188bd7f
lint-hotkeys: Delay generation of file string when linting.
Niloth-p Jul 17, 2024
0dd8aa4
refactor: lint-hotkeys: Split out the help text linting function.
Niloth-p Jul 17, 2024
5286488
refactor: lint-hotkeys: Split out the function that lints categories.
Niloth-p Jul 17, 2024
4e7a4f2
lint-hotkeys: Restructure the error messages of lint_help_text().
Niloth-p Jul 17, 2024
efc8f82
lint-hotkeys: Lint for typos in key_category values.
Niloth-p Jul 17, 2024
ac8e715
refactor: lint-hotkeys: Use help_group instead of HELP_CATEGORIES.
Niloth-p Jul 17, 2024
58ec337
refactor: lint-hotkeys: Create generic variable entries_by_group.
Niloth-p Jul 17, 2024
3ce52db
refactor: lint-hotkeys: Create generic variables for key_category.
Niloth-p Jul 17, 2024
4911685
refactor: lint-hotkeys: Replace 'category(ies)' with 'group(s)'.
Niloth-p Jul 17, 2024
b35e0ce
refactor: lint-hotkeys: Delete constant OUTPUT_FILE_NAME, compute it.
Niloth-p Jul 17, 2024
001814b
refactor: lint-hotkeys: Replace usage of OUTPUT_FILE.
Niloth-p Jul 17, 2024
e2c7e57
refactor: lint-hotkeys: Generalize the traversal of key group values.
Niloth-p Jul 17, 2024
f908dc6
refactor: lint-hotkeys: Use bundled group values.
Niloth-p Jul 18, 2024
f5d0bc4
keys: Add a contexts field to Key Binding.
Niloth-p Jul 8, 2024
a8dd4de
lint-hotkeys: Add argument to generate a keys file grouped by contexts.
Niloth-p Jul 18, 2024
a76da7f
keys: Use the help contexts in generating random help tips.
Niloth-p Jul 8, 2024
9c48eb7
keys: Include previously excluded random help hints.
Niloth-p Apr 16, 2024
2e1b0f0
ui: Enable footer texts to display contextual help tips.
Niloth-p Jul 16, 2024
1b6ad6b
ui/keys: Display the context of the footer hint.
Niloth-p Jul 10, 2024
f75a309
contexts/core/ui: Track the currently focused widget in the UI.
Niloth-p Jul 11, 2024
58b1a96
core/ui/views/keys: Add a Contextual Help Menu.
Niloth-p Jul 10, 2024
3513d73
keys/views: Add mapping of contexts to broader contexts they belong to.
Niloth-p Jul 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/developer-file-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Zulip Terminal uses [Zulip's API](https://zulip.com/api/) to store and retrieve
| Folder | File | Description |
| ---------------------- | ------------------- | ----------------------------------------------------------------------------------------|
| zulipterminal | api_types.py | Types from the Zulip API, translated into python, to improve type checking |
| | contexts.py | Tracks the currently focused widget in the UI |
| | core.py | Defines the `Controller`, which sets up the `Model`, `View`, and how they interact |
| | helper.py | Helper functions used in multiple places |
| | model.py | Defines the `Model`, fetching and storing data retrieved from the Zulip server |
Expand Down
1 change: 1 addition & 0 deletions docs/hotkeys.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
|Command|Key Combination|
| :--- | :---: |
|Show/hide Help Menu|<kbd>?</kbd>|
|Show/hide Contextual Help Menu|<kbd>Meta</kbd> + <kbd>c</kbd>|
|Show/hide Markdown Help Menu|<kbd>Meta</kbd> + <kbd>m</kbd>|
|Show/hide About Menu|<kbd>Meta</kbd> + <kbd>?</kbd>|
|Copy information from About Menu to clipboard|<kbd>c</kbd>|
Expand Down
33 changes: 27 additions & 6 deletions tests/config/test_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,40 +67,61 @@ def test_is_command_key_invalid_command(invalid_command: str) -> None:

def test_HELP_is_not_allowed_as_tip() -> None:
assert keys.KEY_BINDINGS["HELP"]["excluded_from_random_tips"] is True
assert keys.KEY_BINDINGS["HELP"] not in keys.commands_for_random_tips()
commands, _ = keys.commands_for_random_tips()
assert keys.KEY_BINDINGS["HELP"] not in commands


def test_commands_for_random_tips(mocker: MockerFixture) -> None:
@pytest.mark.parametrize(
"context, expected_command, expected_context",
[
(None, "GAMMA", "Global"),
("context_1", "BETA", "Context 1"),
("context_2", "GAMMA", "Global"),
],
)
def test_commands_for_random_tips(
context: str, expected_command: str, expected_context: str, mocker: MockerFixture
) -> None:
new_key_bindings: Dict[str, keys.KeyBinding] = {
"ALPHA": {
"keys": ["a"],
"help_text": "alpha",
"key_category": "category 1",
"key_contexts": ["context_1", "context_2"],
"excluded_from_random_tips": True,
},
"BETA": {
"keys": ["b"],
"help_text": "beta",
"key_category": "category 1",
"key_contexts": ["context_1"],
"excluded_from_random_tips": False,
},
"GAMMA": {
"keys": ["g"],
"help_text": "gamma",
"key_category": "category 1",
"key_contexts": ["global"],
},
"DELTA": {
"keys": ["d"],
"help_text": "delta",
"key_category": "category 2",
"key_contexts": ["context_2"],
"excluded_from_random_tips": True,
},
}
new_help_contexts: Dict[str, str] = {
"global": "Global",
"context_1": "Context 1",
"context_2": "Context 2",
}
mocker.patch.dict(keys.KEY_BINDINGS, new_key_bindings, clear=True)
result = keys.commands_for_random_tips()
assert len(result) == 2
assert new_key_bindings["BETA"] in result
assert new_key_bindings["GAMMA"] in result
mocker.patch.object(keys, "HELP_CONTEXTS", new_help_contexts)
commands, context_display_name = keys.commands_for_random_tips(context)
assert len(commands) == 1
assert new_key_bindings[expected_command] in commands
assert context_display_name == expected_context


def test_updated_urwid_command_map() -> None:
Expand Down
11 changes: 6 additions & 5 deletions tests/core/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def controller(self, mocker: MockerFixture) -> Controller:
self.poll_for_events = mocker.patch(MODEL + ".poll_for_events")
mocker.patch(MODULE + ".Controller.show_loading")
self.main_loop = mocker.patch(
MODULE + ".urwid.MainLoop", return_value=mocker.Mock()
MODULE + ".FocusTrackingMainLoop", return_value=mocker.Mock()
)

self.config_file = "path/to/zuliprc"
Expand Down Expand Up @@ -589,7 +589,8 @@ def test_show_typing_notification(
controller: Controller,
active_conversation_info: Dict[str, str],
) -> None:
set_footer_text = mocker.patch(VIEW + ".set_footer_text")
set_footer_text_for_event = mocker.patch(VIEW + ".set_footer_text_for_event")
reset_footer_text = mocker.patch(VIEW + ".reset_footer_text")
mocker.patch(MODULE + ".time.sleep")
controller.active_conversation_info = active_conversation_info

Expand All @@ -600,16 +601,16 @@ def mock_typing() -> None:
Thread(controller.show_typing_notification()).start()

if active_conversation_info:
set_footer_text.assert_has_calls(
set_footer_text_for_event.assert_has_calls(
[
mocker.call([("footer_contrast", " hamlet "), " is typing"]),
mocker.call([("footer_contrast", " hamlet "), " is typing."]),
mocker.call([("footer_contrast", " hamlet "), " is typing.."]),
mocker.call([("footer_contrast", " hamlet "), " is typing..."]),
]
)
set_footer_text.assert_called_with()
reset_footer_text.assert_called_with()
else:
set_footer_text.assert_called_once_with()
reset_footer_text.assert_called_once_with()
assert controller.is_typing_notification_in_progress is False
assert controller.active_conversation_info == {}
35 changes: 28 additions & 7 deletions tests/ui/test_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,39 +85,60 @@ def test_set_footer_text_same_test(

view._w.footer.set_text.assert_not_called()

def test_set_footer_text_default(self, view: View, mocker: MockerFixture) -> None:
def test_reset_footer_text(self, view: View, mocker: MockerFixture) -> None:
mocker.patch(VIEW + ".get_random_help", return_value=["some help text"])

view.set_footer_text()
view.reset_footer_text()

view.frame.footer.set_text.assert_called_once_with(["some help text"])
view.controller.update_screen.assert_called_once_with()
assert view._is_footer_event_running is False

def test_set_footer_text_specific_text(
self, view: View, text: str = "blah"
) -> None:
view.set_footer_text([text])
view.set_footer_text_for_event([text])

view.frame.footer.set_text.assert_called_once_with([text])
view.controller.update_screen.assert_called_once_with()
assert view._is_footer_event_running is True

def test_set_footer_text_with_duration(
self,
view: View,
mocker: MockerFixture,
custom_text: str = "custom",
duration: Optional[float] = 5.3,
duration: float = 5.3,
) -> None:
mocker.patch(VIEW + ".get_random_help", return_value=["some help text"])
mock_sleep = mocker.patch("time.sleep")

view.set_footer_text([custom_text], duration=duration)
view.set_footer_text_for_event_duration([custom_text], duration=duration)

view.frame.footer.set_text.assert_has_calls(
[mocker.call([custom_text]), mocker.call(["some help text"])]
)
mock_sleep.assert_called_once_with(duration)
assert view.controller.update_screen.call_count == 2
assert view._is_footer_event_running is False

@pytest.mark.parametrize(
"event_running, expected_call_count", [(True, 0), (False, 1)]
)
def test_set_footer_text_on_context_change(
self,
view: View,
mocker: MockerFixture,
event_running: bool,
expected_call_count: int,
) -> None:
mocker.patch(VIEW + ".get_random_help", return_value=["some help text"])
view._is_footer_event_running = event_running

view.set_footer_text_on_context_change()

assert view.frame.footer.set_text.call_count == expected_call_count
assert view.controller.update_screen.call_count == expected_call_count

@pytest.mark.parametrize(
"suggestions, state, truncated, footer_text",
Expand Down Expand Up @@ -350,12 +371,12 @@ def test_keypress_NEW_HINT(
widget_size: Callable[[Widget], urwid_Box],
) -> None:
size = widget_size(view)
set_footer_text = mocker.patch(VIEW + ".set_footer_text")
reset_footer_text = mocker.patch(VIEW + ".reset_footer_text")
mocker.patch(CONTROLLER + ".is_in_editor_mode", return_value=False)

returned_key = view.keypress(size, key)

set_footer_text.assert_called_once_with()
reset_footer_text.assert_called_once_with()
assert returned_key == key

@pytest.mark.parametrize("key", keys_for_command("SEARCH_PEOPLE"))
Expand Down
4 changes: 2 additions & 2 deletions tests/ui_tools/test_boxes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1583,7 +1583,7 @@ def test__keypress_typeahead_mode_autocomplete_key_footer_no_reset(
write_box.keypress(size, key)

assert write_box.is_in_typeahead_mode == expected_typeahead_mode
assert not self.view.set_footer_text.called
assert not self.view.reset_footer_text.called

@pytest.mark.parametrize(
"key, current_typeahead_mode, expected_typeahead_mode",
Expand Down Expand Up @@ -1611,7 +1611,7 @@ def test__keypress_typeahead_mode_autocomplete_key_footer_reset(
assert write_box.is_in_typeahead_mode == expected_typeahead_mode

# We may prefer called-once in future, but the key part is that we do reset
assert self.view.set_footer_text.called
assert self.view.reset_footer_text.called

@pytest.mark.parametrize(
[
Expand Down
Loading
Loading