-
-
Notifications
You must be signed in to change notification settings - Fork 5.2k
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
Desktop: KeymapService: Initial implementation #3252
Conversation
e839134
to
2be3878
Compare
I've removed label and section properties. I totally agree with your point here. This service should map an Accelerator to a command. However, we need still to display labels for each command and group them by section in the GUI editor. I'm thinking maybe hard-coding them in the editor's code itself? For example, I see no other option, at least until we get the actions centralized and sorted out. Would that approach make sense? |
a82a0a3
to
5deec3b
Compare
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 for the update Anjula, I've added a few comments to the code.
Also I think the "CommandAndControl" thing is going to cause us troubles down the line. On Windows and Linux "CommandAndControl" is meaningless. And on macOS it also makes things more complicated - I create a CommandAndControl+S shortcut, so does it work when pressing Cmd+S and Ctrl+S or just one and not the other? Then if I create a separate Cmd+S shortcut, I guess that's a conflict? I also can't wrap my head how it's going to work on platforms without Command.
So my thinking is that we should not use this shortcut and only use either Cmd or Ctrl. In practice, I think it means there should be separate default config for Windows/Linux and for macOS. That's a bit of duplicated config but I think that's fine as it won't change that often. That's how Sublime Text handle the config and I think it makes sense. What do you think?
ElectronClient/app.js
Outdated
await KeymapService.instance().setKeymap(customKeymap); | ||
} catch (error) { | ||
const msg = error.message ? error.message : ''; | ||
console.error(`Failed to parse keymap ${keymapPath}\n${msg}`); |
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.
Error should be displayed to the user with an explanation on how to fix the problem. For example, by giving the line number where the error happens.
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.
If there's an error in the JSON formatting, now it'll be shown in an error dialogue to the user (Standard error message thrown by the JSON.parse()
function)
I've also added more helpful error messages.
However it's a bit complicated to find the line number of some object. I think we'll have to write a mini JSON parser to do that. Is that really necessary?
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 it's a bit complicated to find the line number of some object. I think we'll have to write a mini JSON parser to do that. Is that really necessary?
No that's fine - we just display the errors the lib gives us. If they aren't very good then so be it. Later we can indeed think about using a more advanced parser with comment support, better error message, etc. but that's later.
I thought about it again and as I see it there's just two cases for the keymap:
CommandOrControl modifier should not be present in the keymap. Please remove the "platforms" property from the keymap. Each platform should have its own separate keymap.json named keymap.macos.json, keymap.windows.json and keymap.linux.json. Differences like this or this should be removed. All platforms should have the same basic shortcut and it's up to the user to change them to something else if they want. In general I think your implementation is pretty good. I just wonder about the part where you load the keymap, as you rely on the log to handle errors, but log is just for us developers. We also need to tell the user about any issue, and for that throwing exceptions is best: something is unexpected? Just throw an exception and break the app. That's much better than letting it "work" but with undetected issues. If you throw exceptions from the beginning it will force you to find solutions on how to handle them well - for example by displaying a dialog box or a banner in the app. In some cases logging the error is reasonable too, for example for issues that are very unlikely. |
What's your proposition to retrieve the label for a given action? As you will need this for the config screen. I can't really think of any good way without some refactoring but please let me know what you would suggest. |
Could you clarify this a bit more?
Are you referring to the default keymap which is included in the source code? If yes, rather than storing them as separate files, I think we should just include them in the source code as objects. Then we can dynamically use the appropriate keymap object. I'll make the changes as soon as possible. Thanks for the review. |
I expect there would be a way to get all labels, and all commands after the refactoring. For the moment, I suppose we can hard-code these two properties in the config screen itself. Would that be appropriate? |
I've separated the keymap into two: One for macOS and one for all the other platforms. I had to use synchronous functions for reading the
Menu items are still created even if they're not relevant to the current platform. I've added special checks to prevent calling More helpful errors will be shown to the user in an error dialogue box. For some reason, the service doesn't seem to be able to access values from Please have a look and give your feedback. |
Yes that's reasonable. Please just make sure the labels are exactly the same, so that they don't generate new translation strings (and that of course they are wrapped in |
@anjulalk, please don't force-push your commits because then it makes it hard to know what has changed since the last time I reviewed. Was the only new commit this one? 680428b8e9f602151748a7fac0fb9aab33ccb83e |
@anjulalk, I recently made some important changes to the way the commands work in Joplin, in this commit: c63c637 It will impact your work a bit because the way the accelerators are set is a bit different now, it's done like so: c63c637#diff-bff206348900b3c1ff4cbc14ca186272R509 I expect it shouldn't be too complicated to apply your changes on top though, for example using a regex search and replace, you could replace this:
to this:
Perhaps some of the command names will be slightly different too. Finally, you'll need to change how you get the accelerator label in your Config screen. For that, please add a In any case I would recommend merging master to your branch as soon as possible to avoid diverging too much. And if you have any question about the command service, please let me know. |
Sorry about that. Yes, it's the only new one. I rebased to the latest commits on master branch and it messes up the history a bit. I'll make the changes immediately. |
@laurent22 Accelerator for "Print" command seems to be removed in c63c637 |
I think it means that if the GotoAnything shortcut is changed it's never going to be updated, unless you restart the app. I'd say you should put the require statement where it was and make manifest.accelerator a function, so that it always get the latest value from your service. |
926b181
to
f375718
Compare
Thanks for reviewing. I've made the requested changes. Please have a look if there are any issues. |
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 for the latest changes Anjula, I think we're getting there. Please don't add GUI-related files in here to keep the pull request small (I think the one you've added was by mistake but just want to confirm).
@@ -75,6 +76,8 @@ export default class CommandService extends BaseService { | |||
private commandPreviousStates_:CommandStates = {}; | |||
private mapStateToPropsIID_:any = null; | |||
|
|||
private keymapService = KeymapService.instance(); |
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.
That should be passed as a dependency to initialize() (like the store now). This makes it easier to control the initialization order, or indeed to find bugs in the program when we realize that for example the keymap service is initialized after the command service. Dependency injection also makes it easier to test.
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 see. I added an extra parameter for the KeymapService instance to the initialize() function. The instance is now created in the app.js
file and passed to CommandService.
@@ -0,0 +1,106 @@ | |||
'use strict'; | |||
Object.defineProperty(exports, '__esModule', { value: true }); |
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.
Please remove this file
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.
Sorry about that. I removed the file.
ElectronClient/package-lock.json
Outdated
@@ -10047,11 +10047,6 @@ | |||
"through2": "^2.0.3" | |||
} |
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.
If package.json hasn't change, this file should not be included so please remove it.
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 believe this is from a merge commit which had a new dependency for aws-sdk
package. But I rebased on the latest commit on master so that I wouldn't need to commit this file, just to be sure..
describe('services_KeymapService', () => { | ||
describe('validateAccelerator', () => { | ||
it('should identify valid Accelerators', () => { | ||
const testCases = shim.isMac() |
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.
Tests should not be platform-specific, because if you make changes to one platform code, the tests might pass even though something is broken in another platform. It's best to pass the platform name to the KeymapService constructor for example. You can default the value to shim.platformName() if you want, but it should be possible to set it to something else.
This comment applies to all the uses of shim.isMac in the tests.
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 for pointing that out. I faced this exact situation situation after splitting the keymap into macOS and other platforms. What I did was either use the CI to see if tests had been passed, or boot up macOS in a VM and perform tests manually.
Providing a method to change the platform will enable it to test for both platforms at once. I'll come up with a solution to this.
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.
Yes when we build the apps on CI (Travis and Appveyor) we could technically run platform-specific tests, however it's nice too to be able to run them all locally, and for that the easiest way (at least in this case) is to provide the platform to the service.
By the way, rather than changing the keymap based on a boolean (isMac) please make it change based on a string (platformName). So for now, your code would be very similar, just checking for platformName = 'darwin', but later we also have the option to add support for other platform-specific shortcuts.
@@ -1,4 +1,5 @@ | |||
const BaseService = require('lib/services/BaseService'); | |||
const KeymapService = require('lib/services/KeymapService.js'); |
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.
Since KeymapService is a TypeScript file, you should import it with import KeymapService from "lib/services/KeymapService"
so as to benefit from type-checking.
private keymapService:typeof KeymapService = null; | ||
|
||
initialize(store:any, keymapService:typeof KeymapService) { |
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.
Once you've updated the import statement, the "typeof KeymapService" should no longer be needed, and you can just do keymapService:KeymapService
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 for pointing that out. I'll update it right away.
this.modifiersRegExp = modifiersRegExp.default; | ||
break; | ||
default: | ||
throw new Error(`Unsupported platform: ${platform}`); |
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.
Why throw an exception here? It should be either "darwin" or "default". There's no need to list all the supported platforms. That way if it's a platform we don't know about it will work instead of crashing for no particular reason.
…te; Remove side-effects from validation method
… validateAccelerator()
Refactor import statements Removed explicit platform support
d9a549e
to
d84cbcb
Compare
I've changed import statements and removed explicit platform checks. Please have a look and give your feedback. |
Thanks @anjulalk, I'm going to check, but I'm wondering about your current git workflow. Since you've used forced push I have no idea what you've changed since last time. It looks like all your commits were done 8 hours ago, even though they've been there for several months. What it means is that I cannot follow your progress - I have to start over and review everything every time. So I'm wondering why is there a need for force push? Is that because you rebase from dev? Instead of rebasing, could you merge dev in your branch, then do a regular push? Or are there some issues that are difficult to solve without force push? |
Other than the force push issue it's all looking good, so let's merge. Good job Anjula! |
This PR includes the initial implementation of the
KeymapService
service. This service is responsible for abstracting the in-memory keymap.It's now possible to correctly parse
keymap-desktop.json
file and update the keymap.ElectronClient/app.js
andElectronClient/plugins/goToAnything.jsx
files with thegetAccelerator()
method defined in the service class mentioned above.setAccelerator()
method for updating the in-memory keymap.getKeymap()
which compares current keymap with default keymap and returns an object with the changes. This object can be converted to a string and saved to the disk.setKeymap()
which accepts a custom keymap object as the parameter, validates, checks for duplicate Accelerators in the current platform and updates the in-memory keymap.isAccelerator()
method for validating Accelerator strings.resetCommand()
andresetKeymap()
which resets shortcut for a single command and restores the entire keymap to default respectively.The service is implemented using TypeScript classes and interfaces. All the methods currently implemented have been unit tested.