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

Command options (in help message) #4554

Merged
merged 7 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 25 additions & 19 deletions background_scripts/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
switch (action) {
case "map":
if (tokens.length >= 3) {
const [_, key, command, ...optionList] = tokens;
Expand Down Expand Up @@ -118,7 +118,7 @@ const Commands = {
}
break;
default:
logWarning(`"${command}" is not a valid config command in line:`, line);
logWarning(`"${action}" is not a valid config command in line:`, line);
}
}

Expand Down Expand Up @@ -260,21 +260,30 @@ const Commands = {
const commandToKey = {};
for (const key of Object.keys(this.keyToRegistryEntry || {})) {
const registryEntry = this.keyToRegistryEntry[key];
(commandToKey[registryEntry.command] != null
? commandToKey[registryEntry.command]
: (commandToKey[registryEntry.command] = [])).push(key);
const optionString = registryEntry.optionList?.length > 0
? registryEntry.optionList?.join(" ")
: "";
if (commandToKey[registryEntry.command] == null) {
commandToKey[registryEntry.command] = {};
}
(commandToKey[registryEntry.command][optionString] != null
? commandToKey[registryEntry.command][optionString]
: (commandToKey[registryEntry.command][optionString] = [])).push(key);
Copy link
Contributor Author

@UncleSnail UncleSnail Oct 8, 2024

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.

Copy link
Owner

Choose a reason for hiding this comment

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

Given the new structure, the current name is actively misleading. The map should be called commandToOptionStringToKeys, or commandToOptionsToKeys. A shorter name if possible would be nice, too.

And it would be nice to include your illustrative example data structure in a comment, which makes the name of this variable less important, because after reading your comment, it's easy to hold the shape of the data structure in one's head as one reads the code. The example should indicate that a command with no options will have an empty string as its key.

The use of the ternary operator here for control flow is an artifact of the coffeescript->js conversion process awhile ago. Let's change it to less dense and easier to read statements:

commandToKey[registryEntry.command][optionString] ||= [];
commandToKey[registryEntry.command][optionString].push(key);

}
const commandGroups = {};
for (const group of Object.keys(this.commandGroups || {})) {
const commands = this.commandGroups[group];
commandGroups[group] = [];
for (const command of commands) {
commandGroups[group].push({
command,
description: this.availableCommands[command].description,
keys: commandToKey[command] != null ? commandToKey[command] : [],
advanced: this.advancedCommands.includes(command),
});
for (const [options, keys] of Object.entries(commandToKey[command] ?? { "": [] })) {
commandGroups[group].push({
command,
description: this.availableCommands[command].description,
keys: keys,
advanced: this.advancedCommands.includes(command),
options: options,
});
}
Copy link
Contributor Author

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.

}
}
chrome.storage.session.set({ helpPageData: commandGroups });
Expand All @@ -297,7 +306,6 @@ const Commands = {
"scrollToLeft",
"scrollToRight",
"reload",
"hardReload",
"copyCurrentUrl",
"openCopiedUrlInCurrentTab",
"openCopiedUrlInNewTab",
Expand Down Expand Up @@ -386,7 +394,6 @@ const Commands = {
"enterVisualLineMode",
"toggleViewSource",
"passNextKey",
"hardReload",
Copy link
Contributor Author

@UncleSnail UncleSnail Oct 9, 2024

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).

"setZoom",
"zoomIn",
"zoomOut",
Expand All @@ -409,7 +416,7 @@ const defaultKeyMappings = {
"d": "scrollPageDown",
"u": "scrollPageUp",
"r": "reload",
"R": "hardReload",
"R": "reload hard",
Copy link
Contributor Author

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.

"yy": "copyCurrentUrl",
"p": "openCopiedUrlInCurrentTab",
"P": "openCopiedUrlInNewTab",
Expand Down Expand Up @@ -499,7 +506,6 @@ const commandDescriptions = {
scrollFullPageUp: ["Scroll a full page up"],

reload: ["Reload the page", { background: true }],
hardReload: ["Hard reload the page", { background: true }],
toggleViewSource: ["View page source", { noRepeat: true }],

copyCurrentUrl: ["Copy the current URL to the clipboard", { noRepeat: true }],
Expand Down Expand Up @@ -562,9 +568,9 @@ const commandDescriptions = {
moveTabLeft: ["Move tab to the left", { background: true }],
moveTabRight: ["Move tab to the right", { background: true }],

setZoom: ["Set zoom level to a given value. E.g. map zz setZoom 1.5", { background: true }],
zoomIn: ["Increase zoom", { background: true }],
zoomOut: ["Decrease zoom", { background: true }],
setZoom: ["Set zoom", { background: true }],
zoomIn: ["Zoom in", { background: true }],
Copy link
Contributor Author

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.

zoomOut: ["Zoom out", { background: true }],
zoomReset: ["Reset zoom", { background: true }],

"Vomnibar.activate": ["Open URL, bookmark or history entry", { topFrame: true }],
Expand Down
6 changes: 0 additions & 6 deletions background_scripts/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -405,12 +405,6 @@ const BackgroundCommands = {
chrome.tabs.reload(tab.id, { bypassCache });
});
},

async hardReload({ count, tab }) {
await forCountTabs(count, tab, (tab) => {
chrome.tabs.reload(tab.id, { bypassCache: true });
});
},
};

async function forCountTabs(count, currentTab, callback) {
Expand Down
12 changes: 11 additions & 1 deletion pages/help_dialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
const optionsTruncated = command.options.substring(0, desiredOptionsLength);
let ellipis = "";
if ((command.description.length + command.options.length) > MAX_LENGTH) {
Copy link
Contributor Author

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.

ellipis = "...";
$$(descriptionElement, ".vimiumHelpDescription").title = command.options;
}
const optionsString = command.options ? ` (${optionsTruncated}${ellipis})` : "";
const fullDescription = `${command.description}${optionsString}`;
$$(descriptionElement, ".vimiumHelpDescription").textContent = fullDescription;
Copy link
Contributor Author

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.


keysElement = $$(keysElement, ".vimiumKeyBindings");
let lastElement = null;
Expand Down