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

Adds configurable keymaps for navigation movements #77

Merged
merged 19 commits into from
Oct 9, 2023

Conversation

LukeHalasy
Copy link
Contributor

@LukeHalasy LukeHalasy commented Sep 29, 2023

Adds the ability to customize keymaps pertaining to navigation. First part of #35

@LukeHalasy
Copy link
Contributor Author

LukeHalasy commented Sep 29, 2023

Am I heading in the right direction with this? If so, I can continue on with setting up configurable keymaps for all actions/commands.

Copy link
Owner

@Piturnah Piturnah left a comment

Choose a reason for hiding this comment

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

All the configuration stuff looks great!

I did leave a couple of comments, the main thing at the moment is just that there is a bit of unnecessary repetition of logic (which can be fixed easily)

src/main.rs Outdated Show resolved Hide resolved
src/main.rs Outdated Show resolved Hide resolved
src/config.rs Outdated Show resolved Hide resolved
@Piturnah
Copy link
Owner

Piturnah commented Sep 30, 2023

After thinking a bit further, I've realised another problem:

There are certain actions in gex that are bound to multiple keys. For example, to toggle expand, you can press either space or tab. I think that this should be preserved in the configuration implementation

If you could provide a list of keys, e.g.

toggle_expand = [ "Tab", " " ]

You could look up the relevant action thusly:

if config.keymap.navigation.toggle_expand.contains(&c1) {
    state.status.expand()?;
}

At this point, it does feel that since there are so many actions it may be worth the overhead of spinning up a hashmap. Then the API would look something like:

match config.keymap.navigation.get(keycode) {
    Action::ToggleExpand => state.status.expand()?,
    _ => todo!()
}

I think this approach makes the most sense, but it would make the deserialisation logic more involved as you would need an intermediate type (or custom Deserialize impl on Keymap).

What do you think?

@LukeHalasy
Copy link
Contributor Author

After thinking a bit further, I've realised another problem:

There are certain actions in gex that are bound to multiple keys. For example, to toggle expand, you can press either space or tab. I think that this should be preserved in the configuration implementation

If you could provide a list of keys, e.g.

toggle_expand = [ "Tab", " " ]

You could look up the relevant action thusly:

if config.keymap.navigation.toggle_expand.contains(&c1) {
    state.status.expand()?;
}

At this point, it does feel that since there are so many actions it may be worth the overhead of spinning up a hashmap. Then the API would look something like:

match config.keymap.navigation.get(keycode) {
    Action::ToggleExpand => state.status.expand()?,
    _ => todo!()
}

I think this approach makes the most sense, but it would make the deserialisation logic more involved as you would need an intermediate type (or custom Deserialize impl on Keymap).

What do you think?

I think it makes sense to be able to provide a list of keys. That way we don't have hardcoded defaults like we do now with the current approach (where the arrow keys are hard-coded in).

With this approach will the user always provide a list ?

Like would you have to do
move_down = ['j']
if you only wanted j mapped to move_down ?

I'm all for spinning up a hashmap.

@LukeHalasy
Copy link
Contributor Author

I appreciate the thorough review + suggestions. Still very new to Rust so I appreciate the explanations.

@LukeHalasy
Copy link
Contributor Author

Another question: What's the difference between an action and a command ?

@Piturnah
Copy link
Owner

With this approach will the user always provide a list ?

This way would be the most straightforward to implement, but it's not necessary. You would just need a custom Deserialize impl. See Serde docs.

If you don't want to implement this, then the best way would be to have the user always provide a list, as then in future the "from single" implementation can be added in a way that's easily backwards compatible.

Another question: What's the difference between an action and a command ?

A command in gex is kind of vaguely defined as it's just an abstraction that sort've naturally occurred while I was working on it. It is a subset of an action, and one that performs some kind of operation on the repository state. This would be a sufficient distinction if it weren't for the fact that staging operations are not considered commands. (or Fetch, currently, but Fetch is in a weird place at the moment, pending refactors that I have locally)

The main reason for the distinction is practical -- the commands tend to have "subcommands" whereas an action does not. In fact, currently all commands have subcommands. For this reason their implementation is separated so that I could do some work to make implementing new ones more straightforward (hence the command macro)

I'm all for spinning up a hashmap.

Great!

@LukeHalasy
Copy link
Contributor Author

Took me a while, but I figured out how to write the custom deserialize method.

This PR is still incomplete (need to uncomment the text, update README.md, update other navigation movements (in the branch section)).

I will hold off on completing that stuff until I can figure out how to deserialize the KeyCode from forms other than just Char + get the flow I have here reviewed.

src/config.rs Outdated Show resolved Hide resolved
src/config.rs Outdated Show resolved Hide resolved
@LukeHalasy LukeHalasy requested a review from Piturnah October 2, 2023 00:22
Copy link
Owner

@Piturnah Piturnah left a comment

Choose a reason for hiding this comment

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

Looks like we are heading in the right direction!

src/config.rs Outdated Show resolved Hide resolved
src/config.rs Outdated Show resolved Hide resolved
src/config.rs Outdated Show resolved Hide resolved
@Piturnah Piturnah mentioned this pull request Oct 3, 2023
7 tasks
@LukeHalasy LukeHalasy force-pushed the configurable-keymaps branch from 08e30ee to 7af7fad Compare October 5, 2023 18:14
@LukeHalasy LukeHalasy marked this pull request as ready for review October 5, 2023 18:26
@LukeHalasy LukeHalasy requested a review from Piturnah October 5, 2023 18:30
@LukeHalasy
Copy link
Contributor Author

Think v1 of this is done.
Will be confusing to think through how to add customizable keymaps for commands + actions

Copy link
Owner

@Piturnah Piturnah left a comment

Choose a reason for hiding this comment

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

This is all looking great, and we're almost ready to merge.

The most important thing here is the comment I left about filling in default values, as soon as that's resolved, nothing else is really blocking and I think it's good to go!

Take a look at the strum crate, which provides some handy derivable constructs for enums. I haven't yet given a lot of thought as to how specifically you could fix the issue, but I do have a hunch that the EnumIter derive could be helpful.

src/main.rs Outdated Show resolved Hide resolved
src/config.rs Show resolved Hide resolved
src/config.rs Show resolved Hide resolved
@@ -171,6 +171,9 @@ impl<'de> Deserialize<'de> for Keymaps {
de::value::StringDeserializer::new(action),
)?;

// over-write default key-map to action
navigation.retain(|_, value| value != &ac);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is there a less jank way to do this?

Copy link
Owner

Choose a reason for hiding this comment

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

Looks fine to me, only happens once anyway!

@LukeHalasy LukeHalasy requested a review from Piturnah October 8, 2023 00:21
@LukeHalasy
Copy link
Contributor Author

LukeHalasy commented Oct 8, 2023

Implemented requested changes.

It will be interesting to think through how to refactor this when we add keymaps for actions/commands.
Would love recommendations before I work on that PR (to follow this one up)

Copy link
Owner

@Piturnah Piturnah left a comment

Choose a reason for hiding this comment

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

All looks good. Thanks for your hard work, and for implementing my feedback! Sorry if I've been a bit slow to respond to things.

There's one point, but it's so minor that I'm happy to just merge this and fix it in another commit, so we can get this closed.

It will be interesting to think through how to refactor this when we add keymaps for actions/commands.

I have a bit of a refactor in the works for the command system anyway to make it a bit more coherent, which should be coming soon. So, it might make sense to hold off on this for now as it's likely to change

Comment on lines +13 to +14
#[allow(unused_imports)]
use strum::{EnumIter, IntoEnumIterator};
Copy link
Owner

Choose a reason for hiding this comment

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

If we're only using strum in the unit test, you can move it to dev-dependencies, and move this use statement into the test module

@Piturnah Piturnah merged commit 8399a23 into Piturnah:main Oct 9, 2023
2 checks passed
@Piturnah
Copy link
Owner

Released in 0.6.4 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants