-
Notifications
You must be signed in to change notification settings - Fork 15
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
feat: add a way to get a user-facing string representation of keybindings #211
Conversation
…th optional key symbols and taking into account OS/platform differences)
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #211 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 31 31
Lines 1814 1851 +37
=========================================
+ Hits 1814 1851 +37 ☔ View full report in Codecov by Sentry. |
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.
thanks @dalthviz, this would be a nice addition. couple thoughts about where this should live, before we consider the final api
@@ -19,6 +19,58 @@ | |||
_re_cmd = re.compile(r"cmd[\+|\-]") | |||
|
|||
|
|||
def get_keys_user_facing_representation( |
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.
probably don't need a function to re-define these dicts every time a string is generated, since they won't change at all at runtime, the only thing is which dict to pull from... so maybe a module level:
WIN_KEY_SYMBOLS: dict[str, str] = {...}
MACOS_KEY_SYMBOLS: dict[str, str] = {**WIN_SYMBOLS, "Ctrl": "⌃", "Alt": "⌥", "Meta": "⌘"}
LINUX_KEY_SYMBOLS: dict[str, str] = {**WIN_SYMBOLS, "Meta": "Super"}
# same for names
and then wherever you would have previously used this function, just pick a dict
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.
(however, see later comment)
Optionally, the string representation can be constructed with symbols | ||
like ↵ for Enter or OS specific ones like ⌘ for Cmd on MacOS. | ||
""" | ||
joinchar, key_symbols, key_names = get_keys_user_facing_representation(os=os) |
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.
""" | ||
joinchar, key_symbols, key_names = get_keys_user_facing_representation(os=os) | ||
return joinchar.join( | ||
key_symbols.get(x, x) if use_symbols else key_names.get(x, x) |
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.
i wonder if much of this PR (including the look into the mapping here with .get(x, x)
) should actually be a method on KeyCode
. Currently, this method is first casting self.key
(which is a KeyCode
) to a string, and then using that string to again index into another map. What if did that more directly:
class KeyCode(IntEnum):
...
def to_os_symbol(self, os: Optional[OperatingSystem]) -> str: ...
def to_os_name(self, os: Optional[OperatingSystem]) -> str: ...
and then put these lookups at a lower level in _key_codes.py
?
that would also have the benefit of being able to directly get these symbols at a lower level, given something like KeyCode.Meta
... without first having to put it into a keybinding object
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.
Oh I see, I think that makes sense, will give it a try 👍 also thank you so much for the early feedback!
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.
the layout of _key_codes.py
is a little dense at first glance. so let me know if you run into any confusion
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.
Thank you! And actually, checking _key_codes.py
, I'm wondering if it could make sense to add the symbols and names mappings to the logic over _build_maps
along side the addition there of some new functions like:
def _keycode_to_os_symbol(keystr: str, os: OperatingSystem) -> str:...
def _keycode_to_os_name(keystr: str, os: OperatingSystem) -> str:...
Seems like that could be a good place 🤔 What do you think?
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.
yeah i agree, _build_maps
is the right place to construct these lookups.
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.
Give that a check and I think the work at #213 partialy captures the parse_platform
logic at https://github.com/napari/napari/blob/main/napari/utils/interactions.py#L312 The missing thing I would say is also being able to parse symbols from non modifier keys like enter, arrows, backspace, delete, tab, space, etc. Could it be worthy to further extend the parsing capabilities to any symbol or representation that os_symbol
and os_name
can return? From a quick check, maybe extending the KEYCODE_FROM_LOWERCASE_STRING
dictionary could be a way to achieve this? So something like:
KEYCODE_FROM_LOWERCASE_STRING: Dict[str, KeyCode] = {
# two special cases for assigning os-specific strings to the meta key
'win': KeyCode.Meta,
'cmd': KeyCode.Meta,
# special cases for symbols and other os-specific strings
'↵': KeyCode.Enter,
'→': KeyCode.RightArrow,
...
}
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.
ah I see... yeah, perhaps. Lets discuss that in another issue though. Seems like it might be a cool but YAGNI feature? do you actively parse strings with key symbols in napari? or was it more of a "why not allow this" sort of feature?
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.
Created #214 👍 and I think that was added to be able to store and comper shortcuts in a canonical/normalized form from a representation of a keybinding like the one you would get from the GUI (like for example the napari shortcuts widget) 🤔 So, to support logic like the following:
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.
Ok, plenty to discuss there. I would definitely keep things as KeyCodes and not strings as much as possible. So, rather than using current.text() in that widget, you should take advantage of itemData(), or somehow have the visual representation of the keybinding in the widget not be the actual data that napari uses in the model. (Think of this as a classic model view thing, where the model is the KeyCode, and the view is some string that the user sees)
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.
Yep, that makes sense to me 👍 I think I will mark this as ready for review then. Let me know if further changes are needed here!
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.
getting close! i made some refactoring suggestions, and some other minor requests
@@ -152,6 +156,14 @@ class KeyCode(IntEnum): | |||
def __str__(self) -> str: | |||
return keycode_to_string(self) | |||
|
|||
def to_os_symbol(self, os: Optional[OperatingSystem] = None) -> str: |
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.
i'm waffling on this name... what about just os_symbol()
? and os_name()
? preference? or who cares :) ?
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.
also please add docstrings to these two public methods, with a brief clarification on how to_name
differs from __str__
Co-authored-by: Talley Lambert <[email protected]>
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.
thank you @dalthviz, lovely
…tcut` logic (#7113) # References and relevant issues Part of #7059 # Description * Use the work done at pyapp-kit/app-model#211 * Bump app-model constraint to >=0.2.8 * Alternative to PR #7093 (without doing any major refactor to the shortcut widget logic)
Hi there, this is an initial attempt to add a way to get a user-facing string representation of keybindings (with optional key symbols and taking into account OS differences) by porting the logic already implemented over napari (napari/napari#5064 and napari/napari#5604).
This is still a work in progress but any early feedback is appreciated!
Closes #89