-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Command options (in help message) #4554
base: master
Are you sure you want to change the base?
Conversation
This truncation method is that requested by philc.
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 worked through a few different methods for implementing this, and have tried to explain why I believe this is the best given the existing code. If you have any more questions about why I chose this implementation, please ask!
@@ -64,8 +64,8 @@ const Commands = { | |||
|
|||
for (const line of configLines) { | |||
const tokens = line.split(/\s+/); | |||
const command = tokens[0].toLowerCase(); | |||
switch (command) { | |||
const action = tokens[0].toLowerCase(); |
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.
This change is for understandability. Before there were two variables named command
within this block:
- This "command" meaning
map
,unmap
, etc. - The "command" on line 71 which is the actual command to be run, like "newTab" or "setZoom".
This caused understanding errors for me, so to avoid this for others in the future, I renamed themap
vsunmap
to the more fitting nameaction
. Please suggest your name preference if different.
} | ||
(commandToKey[registryEntry.command][optionString] != null | ||
? commandToKey[registryEntry.command][optionString] | ||
: (commandToKey[registryEntry.command][optionString] = [])).push(key); |
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.
This section changes the commandToKey
from a 1 level dictionary like:
{
"setZoom": ["z1", "z2"],// old design
}
to a 2 level dictionary mapping from command to options set, like:
{
"setZoom: {// new design
"1.1": ["z1"],
"1.2": ["z2"],
}
}
Commands with no options will have the empty string options set, like:
{
"zoomIn: {
"": ["zi"],//No options
}
}
This allows us to split key mappings to their respective command/options combo, while still allowing us to directly check if we have any mappings to a command with commandToKey[command]
as we did before.
advanced: this.advancedCommands.includes(command), | ||
options: options, | ||
}); | ||
} |
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 we now have a 2-level dictionary, we now have a nested loop to loop through both commands and each variation of the command with different options. This also allows us to remove the keys check for null because we provide a default empty array in the loop.
So, for each command
we either get each option set and add a line for each, or if there are no mappings to the key we use { "": [] }
which is like saying that the empty (default) options set (no options) has no key mappings. This all works correctly with the other code, including with the options "show available commands".
Note that now we are also passing the options with the command entry.
@@ -409,7 +416,7 @@ const defaultKeyMappings = { | |||
"d": "scrollPageDown", | |||
"u": "scrollPageUp", | |||
"r": "reload", | |||
"R": "hardReload", | |||
"R": "reload hard", |
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.
We also change the hardReload
to reload hard
and remove its associated code to avoid DRY violations and keep the code standard/maintainable.
zoomIn: ["Increase zoom", { background: true }], | ||
zoomOut: ["Decrease zoom", { background: true }], | ||
setZoom: ["Set zoom", { background: true }], | ||
zoomIn: ["Zoom in", { background: 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.
Change the command descriptions for zoom in/out to make them match the command name and provide better intuition for the default mappings "zi" and "zo" since that name/description is where the mappings come from.
@@ -124,7 +124,17 @@ const HelpDialog = { | |||
}); | |||
} | |||
|
|||
$$(descriptionElement, ".vimiumHelpDescription").textContent = command.description; | |||
const MAX_LENGTH = 50; | |||
const desiredOptionsLength = Math.max(0, MAX_LENGTH - command.description.length - 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.
Find the length that the options list should be to make the full description the right length. Don't ever let it be less than 0, and leave room for the ellipsis "...".
Since this might not be clear, I will explain why I implement it this correct way. It bothers me how many people implement this wrong. Leaving room means that we fit as long lines as we can. For example, if we want a line fewer than 10 characters, not leaving room (not having the - 3
) would mean we need to truncate anything over 7 characters so that the ... does not make the line too long. So we end up with:
"truncated!" -> "truncat..."
There is no reason to do this since the word could have fit on the line (word is 10 chars and we are fine with 10 chars). The other common wrong implementation is to truncate anything >= 10 and add the "..." giving:
"truncated!" -> "truncated!..."
This gives us a line that is longer than 10 chars!
The correct implementation (like this where we leave space) means only lines that need to be truncated are, and we never make a line too long:
"truncated!" -> "truncated!" (10 chars)
"even longer" -> "even lo..." (10 chars)
I hope this explains what the -3
is for. I thought about adding a quick comment in the code there since it might not be clear, and I will do so if you like.
const desiredOptionsLength = Math.max(0, MAX_LENGTH - command.description.length - 3); | ||
const optionsTruncated = command.options.substring(0, desiredOptionsLength); | ||
let ellipis = ""; | ||
if ((command.description.length + command.options.length) > MAX_LENGTH) { |
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 we needed to truncate, add a set of ellipsis and set the title to the full options list so that we can see it on hover.
} | ||
const optionsString = command.options ? ` (${optionsTruncated}${ellipis})` : ""; | ||
const fullDescription = `${command.description}${optionsString}`; | ||
$$(descriptionElement, ".vimiumHelpDescription").textContent = fullDescription; |
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.
Note that this implementation works exactly as you described in your comment on #4518 where the options list is truncated, but not the ending ")" or the command description itself.
I tried the other methods of truncation, and I like this one the most. I agree with your design, it works very well.
I also just pushed a commit to allow for a command + options set to be considered advanced. This way the If you do not like this feature, I can easily remove this commit. It seems like hard reloading should be an advanced command, so I wanted to let us mark it as such, but it does add some complexity and the potential for the advanced command list to explode with advanced option choices. Another implementation is to consider any command with options to be advanced, but then user mappings of simple commands (like new tab) with options are considered advanced and can't be seen in help without showing advanced options. I can imagine many users want to see their custom mappings without having "see advanced" checked, so I went with the implementation above. But, all options commands being advanced is a simpler solution to the problem with less scope-creep and still lets |
description: this.availableCommands[command].description, | ||
keys: keys, | ||
advanced: this.advancedCommands.includes(command) || | ||
this.advancedCommands.includes(`${command} ${options}`), |
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.
This is the line for allowing command + options sets to be considered advanced (like reload hard
). We just also check if the advancedCommands list includes the command + options set.
@@ -386,11 +395,11 @@ const Commands = { | |||
"enterVisualLineMode", | |||
"toggleViewSource", | |||
"passNextKey", | |||
"hardReload", |
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.
Previously (last PR), hardReload
was considered an advanced command, but reload is not (so we add reload hard
as an advanced command and add support for this).
? commandToKey[registryEntry.command] | ||
: (commandToKey[registryEntry.command] = [])).push(key); | ||
const optionString = registryEntry.optionList?.length > 0 | ||
? registryEntry.optionList?.join(" ") |
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.
Joining the option list here is the simplest way to turn the options back into a string. It does mean that differently written commands with the same options will be marked different. IE: command opt=example opt2=example2
is not equal to command opt2=example2 opt=example
even though they are the same. I can do a mapping from the options object which would fix this problem if you would like, but it seemed like code bloat.
Description
Show command options in the help message dialogue as specified in this comment and the associated PR discussion.
Handles truncating based on the full length of the line, not just the length of the options, and shows the full options on hover.
Always indicates the presence of options even on lines with very long descriptions.
Correctly handles flags (without showing them as flag=true)
Handles commands with multiple options.
This PR also switches the new
hardReload
for the oldreload hard
to avoid repeated code/functionality.Implementation
There could be cleaner implementations, so let me know if you have a better idea, but I believe I have found a good method that requires minimal changes and fits very well into the existing mechanism. I will describe the implementation and logic more in comments on the code.
I have formatted all new code (see #4553 for the other changes from
deno fmt
) and all tests pass.