Skip to content

Commit 7a51850

Browse files
authored
keyboard: Use parser for key sequences (#2051)
## Checklist - [x] I have added [tests](https://www.cursorless.org/docs/contributing/test-case-recorder/) - [-] I have updated the [docs](https://github.com/cursorless-dev/cursorless/tree/main/docs) and [cheatsheet](https://github.com/cursorless-dev/cursorless/tree/main/cursorless-talon/src/cheatsheet) (keeping this somewhat under wraps while we experiment - [x] I have not broken the cheatsheet - [-] Refactor delete mapping to just issue token for delete action - [x] Extract partial parameters - [-] Add railroad to docs?
1 parent 4844109 commit 7a51850

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1955
-256
lines changed

.editorconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ trim_trailing_whitespace = false
2828
[Makefile]
2929
indent_style = tab
3030

31-
[**/vendor/**]
31+
[**/{vendor,generated}/**]
3232
charset = unset
3333
end_of_line = unset
3434
indent_size = unset

.eslintrc.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,5 +78,10 @@
7878
}
7979
}
8080
},
81-
"ignorePatterns": ["**/vendor/**/*.ts", "**/vendor/**/*.js", "**/out/**"]
81+
"ignorePatterns": [
82+
"**/vendor/**/*.ts",
83+
"**/vendor/**/*.js",
84+
"**/out/**",
85+
"**/generated/**"
86+
]
8287
}

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ repos:
4040
# tests use strings with trailing white space to represent the final
4141
# document contents. For example
4242
# packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/ruby/changeCondition.yml
43-
exclude: ^packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/.*/[^/]*\.yml$
43+
exclude: ^packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/.*/[^/]*\.yml$|/generated/|^patches/
4444
- repo: local
4545
hooks:
4646
- id: eslint

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
**/vendor
2+
**/generated
23

34
# We use our own format for our recorded yaml tests to keep them compact
45
/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/**/*.yml

.vscode/tasks.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"type": "npm",
2727
"script": "esbuild",
2828
"path": "packages/cursorless-vscode",
29+
"dependsOn": ["Generate grammar"],
2930
"presentation": {
3031
"reveal": "silent"
3132
},
@@ -61,6 +62,16 @@
6162
},
6263
"group": "build"
6364
},
65+
{
66+
"label": "Generate grammar",
67+
"type": "npm",
68+
"script": "generate-grammar",
69+
"path": "packages/cursorless-vscode",
70+
"presentation": {
71+
"reveal": "silent"
72+
},
73+
"group": "build"
74+
},
6475
{
6576
"label": "Ensure test subset file exists",
6677
"type": "npm",

docs/user/experimental/keyboard/modal.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ The above allows you to press `ctrl-c` to switch to Cursorless mode, `escape` to
4242
To bind keys that do not have modifiers (eg just pressing `a`), add entries like the following to your [VSCode `settings.json`](https://code.visualstudio.com/docs/getstarted/settings#_settingsjson) (or edit these settings in the VSCode settings gui by saying `"cursorless settings"`):
4343

4444
```json
45-
"cursorless.experimental.keyboard.modal.keybindings.scopes": {
45+
"cursorless.experimental.keyboard.modal.keybindings.scope": {
4646
"i": "line",
4747
"p": "paragraph",
4848
";": "statement",
@@ -60,7 +60,7 @@ To bind keys that do not have modifiers (eg just pressing `a`), add entries like
6060
"sa": "argumentOrParameter",
6161
"sl": "url",
6262
},
63-
"cursorless.experimental.keyboard.modal.keybindings.actions": {
63+
"cursorless.experimental.keyboard.modal.keybindings.action": {
6464
"t": "setSelection",
6565
"h": "setSelectionBefore",
6666
"l": "setSelectionAfter",
@@ -81,13 +81,13 @@ To bind keys that do not have modifiers (eg just pressing `a`), add entries like
8181
"ap": "pasteFromClipboard",
8282
"ad": "followLink"
8383
},
84-
"cursorless.experimental.keyboard.modal.keybindings.colors": {
84+
"cursorless.experimental.keyboard.modal.keybindings.color": {
8585
"d": "default",
8686
"b": "blue",
8787
"g": "yellow",
8888
"r": "red"
8989
},
90-
"cursorless.experimental.keyboard.modal.keybindings.shapes": {
90+
"cursorless.experimental.keyboard.modal.keybindings.shape": {
9191
"x": "ex",
9292
"f": "fox",
9393
"q": "frame",
@@ -97,7 +97,7 @@ To bind keys that do not have modifiers (eg just pressing `a`), add entries like
9797
"z": "bolt",
9898
"w": "crosshairs"
9999
},
100-
"cursorless.experimental.keyboard.modal.keybindings.vscodeCommands": {
100+
"cursorless.experimental.keyboard.modal.keybindings.vscodeCommand": {
101101
// For simple commands, just use the command name
102102
// "aa": "workbench.action.editor.changeLanguageMode",
103103

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@
4444
},
4545
"pnpm": {
4646
"patchedDependencies": {
47-
"@docusaurus/[email protected]": "patches/@[email protected]"
47+
"@docusaurus/[email protected]": "patches/@[email protected]",
48+
"@types/[email protected]": "patches/@[email protected]",
49+
4850
},
4951
"peerDependencyRules": {
5052
"ignoreMissing": [

packages/common/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"@types/mocha": "^10.0.3",
2626
"@types/sinon": "^10.0.2",
2727
"cross-spawn": "7.0.3",
28+
"fast-check": "3.12.0",
2829
"js-yaml": "^4.1.0",
2930
"mocha": "^10.2.0",
3031
"sinon": "^11.1.1"

packages/common/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export * from "./types/GeneralizedRange";
5656
export * from "./types/RangeOffsets";
5757
export * from "./util/omitByDeep";
5858
export * from "./util/range";
59+
export * from "./util/uniqWithHash";
5960
export * from "./testUtil/isTesting";
6061
export * from "./testUtil/testConstants";
6162
export * from "./testUtil/getFixturePaths";

packages/common/src/util/CompositeKeyMap.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,9 @@ export class CompositeKeyMap<K, V> {
3535
delete this.map[this.hash(key)];
3636
return this;
3737
}
38+
39+
clear(): this {
40+
this.map = {};
41+
return this;
42+
}
3843
}

packages/cursorless-engine/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
"@types/mocha": "^10.0.3",
3232
"@types/sbd": "^1.0.3",
3333
"@types/sinon": "^10.0.2",
34-
"fast-check": "3.12.0",
3534
"js-yaml": "^4.1.0",
3635
"mocha": "^10.2.0",
3736
"sinon": "^11.1.1"

packages/cursorless-engine/src/processTargets/TargetPipelineRunner.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
Modifier,
55
Range,
66
ScopeType,
7+
uniqWithHash,
78
} from "@cursorless/common";
89
import { zip } from "lodash";
910
import {
@@ -15,11 +16,10 @@ import { Target } from "../typings/target.types";
1516
import { MarkStageFactory } from "./MarkStageFactory";
1617
import { ModifierStageFactory } from "./ModifierStageFactory";
1718
import { MarkStage, ModifierStage } from "./PipelineStages.types";
19+
import { createContinuousRangeTarget } from "./createContinuousRangeTarget";
1820
import { ImplicitStage } from "./marks/ImplicitStage";
1921
import { ContainingTokenIfUntypedEmptyStage } from "./modifiers/ConditionalModifierStages";
2022
import { PlainTarget } from "./targets";
21-
import { uniqWithHash } from "../util/uniqWithHash";
22-
import { createContinuousRangeTarget } from "./createContinuousRangeTarget";
2323

2424
export class TargetPipelineRunner {
2525
constructor(

packages/cursorless-engine/src/util/setSelectionsAndFocusEditor.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import { EditableTextEditor, Selection } from "@cursorless/common";
2-
3-
import { uniqWithHash } from "./uniqWithHash";
1+
import {
2+
EditableTextEditor,
3+
Selection,
4+
uniqWithHash,
5+
} from "@cursorless/common";
46

57
export async function setSelectionsAndFocusEditor(
68
editor: EditableTextEditor,

packages/cursorless-vscode-e2e/src/suite/keyboard/basic.vscode.test.ts

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,18 @@ import { getCursorlessApi, openNewEditor } from "@cursorless/vscode-common";
22
import { assert } from "chai";
33
import * as vscode from "vscode";
44
import { endToEndTestSetup, sleepWithBackoff } from "../../endToEndTestSetup";
5+
import sinon from "sinon";
6+
import path from "path";
7+
import { getCursorlessRepoRoot } from "@cursorless/common";
8+
import { readFile } from "node:fs/promises";
59

610
suite("Basic keyboard test", async function () {
711
endToEndTestSetup(this);
812

13+
this.beforeEach(async () => {
14+
await injectFakes();
15+
});
16+
917
this.afterEach(async () => {
1018
await vscode.commands.executeCommand("cursorless.keyboard.modal.modeOff");
1119
});
@@ -46,7 +54,7 @@ async function basic() {
4654
await typeText("sf");
4755

4856
// Select target
49-
await typeText("t");
57+
await typeText("at");
5058

5159
assert.isTrue(editor.selection.isEqual(new vscode.Selection(0, 0, 0, 17)));
5260

@@ -73,16 +81,16 @@ async function vscodeCommand() {
7381
await typeText("db");
7482

7583
// Comment line containing *selection*
76-
await typeText("c");
84+
await typeText("va");
7785
assert.equal(editor.document.getText(), "// aaa;\nbbb;\nccc;\n");
7886

7987
// Comment line containing *target*
80-
await typeText("mc");
88+
await typeText("vb");
8189
assert.equal(editor.document.getText(), "// aaa;\n// bbb;\nccc;\n");
8290

8391
// Comment line containing *target*, keeping changed selection and exiting
8492
// cursorless mode
85-
await typeText("dcmma");
93+
await typeText("dcvca");
8694
assert.equal(editor.document.getText(), "// aaa;\n// bbb;\n// a;\n");
8795

8896
await vscode.commands.executeCommand("cursorless.keyboard.modal.modeOff");
@@ -111,3 +119,38 @@ async function typeText(text: string) {
111119
await sleepWithBackoff(100);
112120
}
113121
}
122+
123+
async function injectFakes(): Promise<void> {
124+
const { vscodeApi } = (await getCursorlessApi()).testHelpers!;
125+
126+
const keyboardConfigPath = path.join(
127+
getCursorlessRepoRoot(),
128+
"packages/cursorless-vscode/src/keyboard/keyboard-config.fixture.json",
129+
);
130+
131+
const keyboardConfig = JSON.parse(await readFile(keyboardConfigPath, "utf8"));
132+
133+
const getConfigurationValue = sinon.fake((sectionName) => {
134+
return keyboardConfig[
135+
`cursorless.experimental.keyboard.modal.keybindings.${sectionName}`
136+
];
137+
});
138+
139+
sinon.replace(
140+
vscodeApi.workspace,
141+
"getConfiguration",
142+
sinon.fake((section) => {
143+
if (
144+
!section?.startsWith(
145+
"cursorless.experimental.keyboard.modal.keybindings",
146+
)
147+
) {
148+
return vscode.workspace.getConfiguration(section);
149+
}
150+
151+
return {
152+
get: getConfigurationValue,
153+
} as unknown as vscode.WorkspaceConfiguration;
154+
}),
155+
);
156+
}

packages/cursorless-vscode/package.json

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -848,7 +848,7 @@
848848
"description": "Directory containing snippets for use in Cursorless",
849849
"type": "string"
850850
},
851-
"cursorless.experimental.keyboard.modal.keybindings.actions": {
851+
"cursorless.experimental.keyboard.modal.keybindings.action": {
852852
"description": "Define modal keybindings for actions",
853853
"type": "object",
854854
"additionalProperties": {
@@ -909,7 +909,7 @@
909909
]
910910
}
911911
},
912-
"cursorless.experimental.keyboard.modal.keybindings.vscodeCommands": {
912+
"cursorless.experimental.keyboard.modal.keybindings.vscodeCommand": {
913913
"description": "Define modal keybindings for running vscode commands",
914914
"type": "object",
915915
"additionalProperties": {
@@ -944,7 +944,7 @@
944944
]
945945
}
946946
},
947-
"cursorless.experimental.keyboard.modal.keybindings.colors": {
947+
"cursorless.experimental.keyboard.modal.keybindings.color": {
948948
"description": "Define modal keybindings for colors",
949949
"type": "object",
950950
"additionalProperties": {
@@ -961,7 +961,7 @@
961961
]
962962
}
963963
},
964-
"cursorless.experimental.keyboard.modal.keybindings.shapes": {
964+
"cursorless.experimental.keyboard.modal.keybindings.shape": {
965965
"description": "Define modal keybindings for shapes",
966966
"type": "object",
967967
"additionalProperties": {
@@ -980,7 +980,7 @@
980980
]
981981
}
982982
},
983-
"cursorless.experimental.keyboard.modal.keybindings.scopes": {
983+
"cursorless.experimental.keyboard.modal.keybindings.scope": {
984984
"description": "Define modal keybindings for scopes",
985985
"type": "object",
986986
"additionalProperties": {
@@ -1091,7 +1091,7 @@
10911091
"funding": "https://github.com/sponsors/pokey",
10921092
"scripts": {
10931093
"build": "pnpm run esbuild:prod && pnpm -F cheatsheet-local build:prod && pnpm run populate-dist",
1094-
"build:dev": "pnpm run esbuild && pnpm -F cheatsheet-local build && pnpm run populate-dist",
1094+
"build:dev": "pnpm generate-grammar && pnpm run esbuild && pnpm -F cheatsheet-local build && pnpm run populate-dist",
10951095
"esbuild:base": "esbuild ./src/extension.ts --conditions=cursorless:bundler --bundle --outfile=dist/extension.cjs --external:vscode --format=cjs --platform=node",
10961096
"install-local": "bash ./scripts/install-local.sh",
10971097
"install-from-pr": "bash ./scripts/install-from-pr.sh",
@@ -1104,6 +1104,11 @@
11041104
"preprocess-svg-hats": "my-ts-node src/scripts/preprocessSvgHats.ts",
11051105
"hat-adjustment-add": "my-ts-node src/scripts/hatAdjustments/add.ts",
11061106
"hat-adjustment-average": "my-ts-node src/scripts/hatAdjustments/average.ts",
1107+
"generate-grammar:base": "nearleyc src/keyboard/grammar/grammar.ne",
1108+
"ensure-grammar-up-to-date": "pnpm -s generate-grammar:base | diff -u src/keyboard/grammar/generated/grammar.ts -",
1109+
"generate-grammar": "pnpm generate-grammar:base -o src/keyboard/grammar/generated/grammar.ts",
1110+
"generate-railroad": "nearley-railroad src/keyboard/grammar/grammar.ne -o out/railroad.html",
1111+
"test": "pnpm ensure-grammar-up-to-date",
11071112
"compile": "tsc --build",
11081113
"watch": "tsc --build --watch",
11091114
"clean": "rm -rf ./out tsconfig.tsbuildinfo ./dist ./build"
@@ -1115,6 +1120,7 @@
11151120
"@types/js-yaml": "^4.0.2",
11161121
"@types/lodash": "4.14.181",
11171122
"@types/mocha": "^10.0.3",
1123+
"@types/nearley": "2.11.5",
11181124
"@types/node": "^18.18.2",
11191125
"@types/semver": "^7.3.9",
11201126
"@types/sinon": "^10.0.2",
@@ -1135,8 +1141,10 @@
11351141
"@cursorless/vscode-common": "workspace:*",
11361142
"itertools": "^2.1.1",
11371143
"lodash": "^4.17.21",
1144+
"nearley": "2.20.1",
11381145
"semver": "^7.5.2",
11391146
"tinycolor2": "1.6.0",
1147+
"trie-search": "2.0.0",
11401148
"uuid": "^9.0.0",
11411149
"vscode-uri": "^3.0.6"
11421150
},

packages/cursorless-vscode/src/extension.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,11 @@ export async function activate(
103103
addCommandRunnerDecorator(testCaseRecorder);
104104

105105
const statusBarItem = StatusBarItem.create("cursorless.showQuickPick");
106-
const keyboardCommands = KeyboardCommands.create(context, statusBarItem);
106+
const keyboardCommands = KeyboardCommands.create(
107+
context,
108+
vscodeApi,
109+
statusBarItem,
110+
);
107111
const scopeVisualizer = createScopeVisualizer(normalizedIde, scopeProvider);
108112
context.subscriptions.push(
109113
revisualizeOnCustomRegexChange(scopeVisualizer, scopeProvider),

0 commit comments

Comments
 (0)