Skip to content

Commit

Permalink
Visual Docs: Modifier Key Toggling (#31)
Browse files Browse the repository at this point in the history
- Implelments tests for modifier key toggling
- Improves how the frequency toggling works:
   - fewer cycles
   - more consistent modifier placement
- Removes the commands that toggle each key/modifier individually; they
aren't very useful.
  • Loading branch information
haberdashPI authored Sep 29, 2024
1 parent aad3d07 commit 8c74a7d
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 68 deletions.
96 changes: 28 additions & 68 deletions src/web/commands/visualKeyDoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ import {CommandState} from '../state';
import {withState} from '../state';
import * as vscode from 'vscode';
import {MODE} from './mode';
import z from 'zod';
import {Bindings, IConfigKeyBinding} from '../keybindings/processing';
import {filterBindingFn} from '../keybindings';
import {bindings, onChangeBindings} from '../keybindings/config';
import {PREFIX_CODE} from './prefix';
import {reverse, uniqBy} from 'lodash';
import {modifierKey, prettifyPrefix, validateInput} from '../utils';
import {modifierKey, prettifyPrefix} from '../utils';
import {Map} from 'immutable';

// TODO: use KeyboardLayoutMap to improve behavior
Expand Down Expand Up @@ -216,16 +215,24 @@ export class DocViewProvider implements vscode.WebviewViewProvider {
this._modifierSetIndex += 2;
}
}
this._bottomModifier = this._modifierOrder[this._modifierSetIndex];
this._topModifier =
const a = this._modifierOrder[this._modifierSetIndex];
const b =
this._modifierOrder[(this._modifierSetIndex + 1) % this._modifierOrder.length];
const aLength = a.map(x => x.length).reduce((x, y) => x + y);
const bLength = b.map(x => x.length).reduce((x, y) => x + y);
if (aLength > bLength) {
this._topModifier = a;
this._bottomModifier = b;
} else {
this._topModifier = b;
this._bottomModifier = a;
}

this.updateKeyHelper();
this.refresh();
}

private updateKeys(values: CommandState | Map<string, unknown>) {
// TODO: prevent this from being updated on every keypress
// e.g. when pressing single-key commands
const allBindings = bindings?.bind || [];
if (this._oldBindings !== allBindings) {
this._modifierSetIndex = 0;
Expand All @@ -235,9 +242,21 @@ export class DocViewProvider implements vscode.WebviewViewProvider {
modifierCounts[key] = get(modifierCounts, key, 0) + 1;
}

modifierCounts[''] = modifierCounts[''] === undefined ? 0 : modifierCounts[''];
modifierCounts['⇧'] =
modifierCounts['⇧'] === undefined ? 0 : modifierCounts['⇧'];
// handle cases where there are fewer than 2 modifier keys in the
// binding set
if (Object.keys(modifierCounts).length < 1) {
modifierCounts[''] =
modifierCounts[''] === undefined ? 0 : modifierCounts[''];
}
if (Object.keys(modifierCounts).length < 2) {
const modifier = Object.keys(modifierCounts)[0];
if (modifierCounts[modifier + '⇧'] === undefined) {
modifierCounts[modifier] = 0;
} else {
modifierCounts[modifier] = modifierCounts[modifier + '⇧'];
}
}

const modifiers = Object.keys(modifierCounts);
modifiers.sort((x, y) => modifierCounts[y] - modifierCounts[x]);
this._modifierOrder = modifiers.map(x => x.split('.'));
Expand Down Expand Up @@ -403,55 +422,6 @@ export class DocViewProvider implements vscode.WebviewViewProvider {
}
}

const visualDocModifierArgs = z.object({
modifiers: z.array(z.string()).optional(),
});
async function setVisualDocModifier(
pos: 'top' | 'bottom',
provider: DocViewProvider,
args_: unknown
) {
let modifiers: string[];
if (args_) {
const args = validateInput(
`master-key.setVisualDoc${pos}Modifiers`,
args_,
visualDocModifierArgs
);
if (args?.modifiers) {
modifiers = args.modifiers;
} else {
return;
}
} else {
// TODO: make this list specific to the platform
const items: vscode.QuickPickItem[] = [
{label: '^', description: 'control'},
{label: '⌘', description: 'command'},
{label: '⌥', description: 'alt'},
{label: '⇧', description: 'shift'},
];
const picked = pos === 'top' ? provider.topModifier : provider.bottomModifier;
for (const item of items) {
item.picked = picked.some(x => x === item.label);
}
const selections = await vscode.window.showQuickPick(items, {
canPickMany: true,
matchOnDescription: true,
});
if (selections) {
modifiers = selections.map(sel => sel.label);
} else {
return;
}
}
if (pos === 'top') {
provider.topModifier = modifiers;
} else {
provider.bottomModifier = modifiers;
}
}

async function showVisualDoc() {
const editor = vscode.window.activeTextEditor;
await vscode.commands.executeCommand('workbench.view.extension.masterKeyVisualDoc');
Expand All @@ -471,16 +441,6 @@ export async function activate(context: vscode.ExtensionContext) {
// TODO: only show command in os x
// TODO: make a meta key for linux (and windows for windows)
// TODO: the modifiers need to be able to be combined...
context.subscriptions.push(
vscode.commands.registerCommand('master-key.setVisualDocTopModifiers', args =>
setVisualDocModifier('top', docProvider, args)
)
);
context.subscriptions.push(
vscode.commands.registerCommand('master-key.setVisualDocBottomModifiers', args =>
setVisualDocModifier('bottom', docProvider, args)
)
);
context.subscriptions.push(
vscode.commands.registerCommand('master-key.toggleVisualDocModifiers', _args =>
docProvider.toggleModifier()
Expand Down
75 changes: 75 additions & 0 deletions test/specs/visualDocs.ux.mts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,30 @@ describe('Visual Docs', () => {
mode = "normal"
kind = "right"
[[bind]]
path = "motion"
key = "ctrl+i"
name = "magic insert"
command = "foobar"
mode = "normal"
kind = "right"
[[bind]]
path = "motion"
key = "ctrl+o"
name = "magic outsert"
command = "foobiz"
mode = "normal"
kind = "right"
[[bind]]
path = "motion"
key = "alt+i"
name = "evil insert"
command = "die"
mode = "normal"
kind = "right"
# Final paragraph shows up.
`);
editor = await setupEditor('A simple test');
Expand Down Expand Up @@ -171,6 +195,57 @@ describe('Visual Docs', () => {
await docView.close();
});

it('Toggled by command', async () => {
await docView.open();

// eslint-disable-next-line prefer-const
let iLabel = await browser.$('div.keyboard').$('div*=I');
expect(await iLabel.getText()).toMatch(/I/);
let iLowerName = (await iLabel.parentElement()).$('div.name.bottom');
expect(iLowerName).toHaveText('insert');
let iUpperName = (await iLabel.parentElement()).$('div.name.top');
expect(iUpperName).toHaveText('magic insert');

let oLabel = await browser.$('div.keyboard').$('div*=0');
expect(await oLabel.getText()).toMatch(/0/);
let oName = (await iLabel.parentElement()).$('div.name.top');
expect(oName).toHaveText('magic outsert');

await browser.executeWorkbench(vscode => {
vscode.commands.executeCommand('master-key.toggleVisualDocModifiers');
});

iLabel = await browser.$('div.keyboard').$('div*=I');
expect(await iLabel.getText()).toMatch(/I/);
iLowerName = (await iLabel.parentElement()).$('div.name.bottom');
expect(iLowerName).toHaveText('insert');
iUpperName = (await iLabel.parentElement()).$('div.name.top');
expect(iUpperName).toHaveText('evil insert');

oLabel = await browser.$('div.keyboard').$('div*=O');
expect(await oLabel.getText()).toMatch(/O/);
oName = (await iLabel.parentElement()).$('div.name.top');
expect(oName).toHaveText('');

await browser.executeWorkbench(vscode => {
vscode.commands.executeCommand('master-key.toggleVisualDocModifiers');
});

iLabel = await browser.$('div.keyboard').$('div*=I');
expect(await iLabel.getText()).toMatch(/I/);
iLowerName = (await iLabel.parentElement()).$('div.name.bottom');
expect(iLowerName).toHaveText('insert');
iUpperName = (await iLabel.parentElement()).$('div.name.top');
expect(iUpperName).toHaveText('magic insert');

oLabel = await browser.$('div.keyboard').$('div*=0');
expect(await oLabel.getText()).toMatch(/0/);
oName = (await iLabel.parentElement()).$('div.name.top');
expect(oName).toHaveText('magic outsert');

await docView.close();
});

after(async () => {
await storeCoverageStats('visualDoc');
});
Expand Down

0 comments on commit 8c74a7d

Please sign in to comment.