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

Added custom override of hotkeys based on zuliprc file #1446

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

jamesdchu
Copy link

What does this PR do, and why?

This PR edits the keys.py file to enable custom hotkey mapping. This means that users can change the zuliprc file to get their desired hotkey mappings.

External discussion & connections

  • Discussed in #zulip-terminal in topic
  • Fully fixes #
  • [x ] Partially fixes issue Enable remapping hotkeys #1439
  • Builds upon previous unmerged work in PR #
  • Is a follow-up to work in PR #
  • Requires merge of PR #
  • Merge will enable work on #

How did you test this?

  • Manually - Behavioral changes
  • Manually - Visual changes
  • Adapting existing automated tests
  • [x ] Adding automated tests for new behavior (or missing tests)
  • [ x] Existing automated tests should already cover this (only a refactor of tested code)

@zulipbot zulipbot added the size: L [Automatic label added by zulipbot] label Dec 5, 2023
@zulipbot zulipbot added size: XL [Automatic label added by zulipbot] and removed size: L [Automatic label added by zulipbot] labels Dec 5, 2023
@jamesdchu jamesdchu marked this pull request as draft December 5, 2023 22:37
@jamesdchu jamesdchu marked this pull request as ready for review December 5, 2023 22:37
@neiljp
Copy link
Collaborator

neiljp commented Dec 8, 2023

@jamesdchu This looks like you're making progress towards this - are you looking for feedback yet?

@neiljp neiljp added the PR awaiting update PR has been reviewed & is awaiting update or response to reviewer feedback label Jan 4, 2024
Copy link
Collaborator

@neiljp neiljp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jamesdchu Thanks for working on this 👍 I left the question re if you were asking for feedback in December, but only recently noticed you'd changed it from a draft to a regular PR before my comment, so here's some feedback in case you had intended that to be the case already :)

You currently have this as a lot of commits, so I've not left comments on individual commits, but rather the total set of changes. See the contributing notes in the README regarding how Zulip in general, and this repo in particular, structure commits and why. This is one reason why the PR is failing the 'isolated commits' phase of the automatic tests (CI).

I gave this a manual test and after adjusting a line in run.py I could get it to run OK, as per one comment inline. That comment discusses adding tests for that run.py part, which suggests one way to separate into two parts (commits) - implementing the functionality, and then integrating it into run.py, each commit with tests (and passing linting). The documentation part could also be separate, particularly since a FAQ entry could be useful too. Beyond that, you could also break the functionality part into stages, with tests added/adjusted as the functionality improves, but we can discuss that later if you're interested :)

Comment on lines +250 to +251
## Custom Keybindings: keybindings can be customized for user preferences (see elsewhere for default)
custom_hotkeys=GO_UP:G, GO_DOWN:B
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be clearer here to have a second line ## line indicating specifically where to find the defaults, and also the symbols themselves.

We likely also want the custom_hotkeys line to be commented out by default, since this represents a departure from the default configuration - based on the introduction to this section.

@@ -554,6 +555,15 @@ def print_setting(setting: str, data: SettingData, suffix: str = "") -> None:
print_setting("color depth setting", zterm["color-depth"])
print_setting("notify setting", zterm["notify"])

if "custom_keybindings" in zterm:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have this section earlier? The loading (and validation) of config is generally above this point.

@@ -112,3 +112,83 @@ def test_updated_urwid_command_map() -> None:
assert key in keys.keys_for_command(zt_cmd)
except KeyError:
pass


def test_override_keybindings_valid(mocker: MockerFixture) -> None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I'd suggest using a double underscore to separate the function name from the variation you're testing. We possibly don't do this everywhere yet, but it makes it easier to distinguish the two parts :)

Comment on lines +559 to +560
custom_keybindings_str = zterm["custom_keybindings"].value
_, key_value_pairs = custom_keybindings_str.split("=")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't work correctly - maybe you had an older version here?

I tested manually by working around this.

You have tests for the function that generates the overrides, but not for this stage. We have a test file for this, so it would be useful to see some for this new code too.

Comment on lines +561 to +564
# Split each pair and convert to a dictionary
custom_keybindings = dict(
pair.split(":") for pair in key_value_pairs.split(", ")
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good first version, but it'd be useful to see a little error handling (or more flexibility) here, since this crashes if the configuration is laid out incorrectly - such as being ,-separated, instead of with a space too.

I would understand more if multiple keys could be provided, since you might be separating those without a space, but that seems not to be available at this stage? Of course it's not a problem to skip multiple keys with an initial version :)

Comment on lines +131 to +136

# Mock the KEY_BINDINGS to use the test copy
mocker.patch.object(keys, "KEY_BINDINGS", test_key_bindings)

# Now this will modify the test copy, not the original
keys.override_keybindings(custom_keybindings, test_key_bindings)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this mocking necessary?

Comment on lines +119 to +130
test_key_bindings: Dict[str, keys.KeyBinding] = {
"GO_UP": {
"keys": ["up", "k"],
"help_text": "Go up / Previous message",
"key_category": "navigation",
},
"GO_DOWN": {
"keys": ["down", "j"],
"help_text": "Go down / Next message",
"key_category": "navigation",
},
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like the same starting dict is used in all these tests and copy/pasted. Putting that into a fixture local to this file would reduce the duplication.

Comment on lines +474 to +475
def override_keybindings(
custom_keybindings: Dict[str, str], existing_keybindings: Dict[str, KeyBinding]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might read clearer with the existing_keybindings first, and the custom ones named slightly differently - they are a different type.

Comment on lines +497 to +499
# Resolve direct swaps
for command, new_key in custom_keybindings.items():
if command in conflicts:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tracking possible conflicts is a good idea, and a direct swap should likely always avoid a problem.

However, the problem is more complex. Note that we already have apparent conflicts depending on different scopes. Our lint-hotkeys script works around some, but there are others (eg. i). These are not a problem in practice due to scoping, but this is not sufficiently encoded in the keybindings data right now.

# Collect requested changes and detect conflicts
for command, new_key in custom_keybindings.items():
if command not in existing_keybindings:
raise InvalidCommand(f"Invalid command {command} in custom keybindings")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Errors from this method should be handled and cleanly expressed to the user in run.py, not just left to escape unhandled.

@zulipbot
Copy link
Member

Heads up @jamesdchu, we just merged some commits that conflict with the changes you made in this pull request! You can review this repository's recent commits to see where the conflicts occur. Please rebase your feature branch against the upstream/main branch and resolve your pull request's merge conflicts accordingly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
has conflicts PR awaiting update PR has been reviewed & is awaiting update or response to reviewer feedback size: XL [Automatic label added by zulipbot]
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants