Skip to content

Commit 597fd4a

Browse files
Remove vscode references from test case recorder (#1272)
Also simplifies workflow for adding a new directory to recorded test case dir by adding an inline option: <img width="870" alt="image" src="https://user-images.githubusercontent.com/755842/219060655-8a8d3a1d-5989-403e-b56e-80ede9f5526f.png"> ## Checklist - [ ] 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) - [ ] I have not broken the cheatsheet --------- Co-authored-by: Andreas Arvidsson <[email protected]>
1 parent 48bb93b commit 597fd4a

File tree

12 files changed

+310
-159
lines changed

12 files changed

+310
-159
lines changed

package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,15 @@
146146
"light": "#60daff7a",
147147
"highContrast": "#60daff7a"
148148
}
149+
},
150+
{
151+
"id": "cursorless.timingCalibrationBackground",
152+
"description": "Background color to use for calibrating timing when recording a video",
153+
"defaults": {
154+
"dark": "#230026",
155+
"light": "#230026",
156+
"highContrast": "#230026"
157+
}
149158
}
150159
],
151160
"configurationDefaults": {

src/core/commandRunner/CommandRunner.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ export default class CommandRunner {
153153
} catch (e) {
154154
const err = e as Error;
155155
console.error(err.stack);
156+
await this.graph.testCaseRecorder.commandErrorHook(err);
156157
throw e;
157158
} finally {
158159
if (this.graph.testCaseRecorder.isActive()) {

src/extension.ts

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ export async function activate(
6464
graph.debug.init();
6565
graph.snippets.init();
6666
graph.hatTokenMap.init();
67-
graph.testCaseRecorder.init();
6867
graph.statusBarItem.init();
6968
graph.keyboardCommands.init();
7069

@@ -116,8 +115,9 @@ function registerCommands(
116115
commandRunner: CommandRunner,
117116
testCaseRecorder: TestCaseRecorder,
118117
): void {
119-
extensionContext.subscriptions.push(
120-
vscode.commands.registerCommand(
118+
const commands = [
119+
// The core Cursorless command
120+
[
121121
CURSORLESS_COMMAND_ID,
122122
async (spokenFormOrCommand: string | Command, ...rest: unknown[]) => {
123123
try {
@@ -126,23 +126,28 @@ function registerCommands(
126126
...rest,
127127
);
128128
} catch (e) {
129-
const error = e as Error;
130129
if (!isTesting()) {
131-
vscodeIde.handleCommandError(error);
130+
vscodeIde.handleCommandError(e as Error);
132131
}
133-
await testCaseRecorder.commandErrorHook(error);
134-
throw error;
132+
throw e;
135133
}
136134
},
137-
),
135+
],
138136

139-
vscode.commands.registerCommand(
140-
"cursorless.showCheatsheet",
141-
showCheatsheet,
142-
),
143-
vscode.commands.registerCommand(
144-
"cursorless.internal.updateCheatsheetDefaults",
145-
updateDefaults,
137+
// Cheatsheet commands
138+
["cursorless.showCheatsheet", showCheatsheet],
139+
["cursorless.internal.updateCheatsheetDefaults", updateDefaults],
140+
141+
// Testcase recorder commands
142+
["cursorless.recordTestCase", testCaseRecorder.toggle],
143+
["cursorless.pauseRecording", testCaseRecorder.pause],
144+
["cursorless.resumeRecording", testCaseRecorder.resume],
145+
["cursorless.takeSnapshot", testCaseRecorder.takeSnapshot],
146+
] as const;
147+
148+
extensionContext.subscriptions.push(
149+
...commands.map(([commandId, callback]) =>
150+
vscode.commands.registerCommand(commandId, callback),
146151
),
147152
);
148153
}

src/ide/vscode/VscodeHighlights.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ import { VscodeTextEditorImpl } from "./VscodeTextEditorImpl";
1414
export enum HighlightStyle {
1515
highlight0 = "highlight0",
1616
highlight1 = "highlight1",
17+
/**
18+
* Used for calibrating timing when recording a video
19+
*/
20+
timingCalibration = "timingCalibration",
1721
}
1822

1923
export type VscodeStyle = FlashStyle | HighlightStyle;

src/ide/vscode/VscodeIDE.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import type {
1717
IDE,
1818
RunMode,
1919
} from "../../libs/common/ide/types/ide.types";
20+
import { QuickPickOptions } from "../../libs/common/ide/types/QuickPickOptions";
2021
import {
2122
fromVscodeRange,
2223
fromVscodeSelection,
@@ -31,6 +32,7 @@ import VscodeGlobalState from "./VscodeGlobalState";
3132
import VscodeHighlights, { HighlightStyle } from "./VscodeHighlights";
3233
import VscodeMessages from "./VscodeMessages";
3334
import { vscodeRunMode } from "./VscodeRunMode";
35+
import { vscodeShowQuickPick } from "./vscodeShowQuickPick";
3436
import { VscodeTextDocumentImpl } from "./VscodeTextDocumentImpl";
3537
import { VscodeTextEditorImpl } from "./VscodeTextEditorImpl";
3638

@@ -57,6 +59,13 @@ export default class VscodeIDE implements IDE {
5759
this.editorMap = new WeakMap<vscode.TextEditor, VscodeTextEditorImpl>();
5860
}
5961

62+
async showQuickPick(
63+
items: readonly string[],
64+
options?: QuickPickOptions,
65+
): Promise<string | undefined> {
66+
return await vscodeShowQuickPick(items, options);
67+
}
68+
6069
async init() {
6170
await this.hats.init();
6271
}

src/ide/vscode/vscodeShowQuickPick.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import * as vscode from "vscode";
2+
import {
3+
QuickPickOptions,
4+
UnknownValuesOptions,
5+
} from "../../libs/common/ide/types/QuickPickOptions";
6+
7+
export async function vscodeShowQuickPick(
8+
items: readonly string[],
9+
options: QuickPickOptions | undefined,
10+
) {
11+
if (options?.unknownValues == null) {
12+
return await vscode.window.showQuickPick(items, options);
13+
}
14+
15+
const { unknownValues, ...rest } = options;
16+
return await showQuickPickAllowingUnknown(items, unknownValues, rest);
17+
}
18+
19+
interface CustomQuickPickItem extends vscode.QuickPickItem {
20+
value: string;
21+
}
22+
23+
const DEFAULT_NEW_VALUE_TEMPLATE = "Add new value '{}' →";
24+
25+
/**
26+
* Show a quick pick that allows the user to enter a new value. We do this by
27+
* adding a new dummy item to the list as the user is typing. If they select
28+
* the dummy item, we return the value they typed. It is up to the client to
29+
* detect that it is an unknown value and handle that case.
30+
*
31+
* Based on https://stackoverflow.com/a/69842249
32+
* @param items
33+
* @param options
34+
*/
35+
function showQuickPickAllowingUnknown(
36+
choices: readonly string[],
37+
unknownValues: UnknownValuesOptions,
38+
options: vscode.QuickPickOptions,
39+
) {
40+
return new Promise<string | undefined>((resolve, _reject) => {
41+
const quickPick = vscode.window.createQuickPick<CustomQuickPickItem>();
42+
const quickPickItems = choices.map((choice) => ({
43+
label: choice,
44+
value: choice,
45+
}));
46+
quickPick.items = quickPickItems;
47+
48+
if (options.title != null) {
49+
quickPick.title = options.title;
50+
}
51+
52+
const { newValueTemplate = DEFAULT_NEW_VALUE_TEMPLATE } = unknownValues;
53+
54+
quickPick.onDidChangeValue(() => {
55+
quickPick.items = [
56+
...quickPickItems,
57+
58+
// INJECT user values into proposed values
59+
...(choices.includes(quickPick.value)
60+
? []
61+
: [
62+
{
63+
label: newValueTemplate.replace("{}", quickPick.value),
64+
value: quickPick.value,
65+
},
66+
]),
67+
];
68+
});
69+
70+
quickPick.onDidAccept(() => {
71+
const selection = quickPick.activeItems[0];
72+
resolve(selection.value);
73+
quickPick.hide();
74+
});
75+
76+
quickPick.onDidHide(() => {
77+
resolve(undefined);
78+
});
79+
80+
quickPick.show();
81+
});
82+
}

src/libs/common/ide/PassthroughIDEBase.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { FlashDescriptor } from "./types/FlashDescriptor";
1313
import { Hats } from "./types/Hats";
1414
import { Disposable, IDE, RunMode, WorkspaceFolder } from "./types/ide.types";
1515
import { Messages } from "./types/Messages";
16+
import { QuickPickOptions } from "./types/QuickPickOptions";
1617
import { State } from "./types/State";
1718

1819
export default class PassthroughIDEBase implements IDE {
@@ -139,6 +140,13 @@ export default class PassthroughIDEBase implements IDE {
139140
return this.original.openTextDocument(path);
140141
}
141142

143+
public showQuickPick(
144+
items: readonly string[],
145+
options?: QuickPickOptions,
146+
): Promise<string | undefined> {
147+
return this.original.showQuickPick(items, options);
148+
}
149+
142150
public showInputBox(options?: any): Promise<string | undefined> {
143151
return this.original.showInputBox(options);
144152
}

src/libs/common/ide/fake/FakeIDE.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
RunMode,
1616
WorkspaceFolder,
1717
} from "../types/ide.types";
18+
import { QuickPickOptions } from "../types/QuickPickOptions";
1819
import { FakeCapabilities } from "./FakeCapabilities";
1920
import FakeClipboard from "./FakeClipboard";
2021
import FakeConfiguration from "./FakeConfiguration";
@@ -92,6 +93,13 @@ export default class FakeIDE implements IDE {
9293
throw Error("Not implemented");
9394
}
9495

96+
public showQuickPick(
97+
_items: readonly string[],
98+
_options?: QuickPickOptions,
99+
): Promise<string | undefined> {
100+
throw new Error("Method not implemented.");
101+
}
102+
95103
public showInputBox(_options?: any): Promise<string | undefined> {
96104
throw Error("Not implemented");
97105
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
export interface UnknownValuesOptions {
2+
allowed: true;
3+
4+
/**
5+
* An optional template to use when displaying the new option to the user. The
6+
* template must include `{}` as a substring, which will be replaced with the
7+
* user's input when displaying an option to add the unknown item.
8+
*
9+
* For example:
10+
*
11+
* "Add new value '{}' →"
12+
*
13+
* In this case, when the user types `foo`, the following option will be
14+
* displayed in the quickpick that will allow the user to select the unknown
15+
* value `foo`:
16+
*
17+
* "Add new value 'foo' →"
18+
*/
19+
newValueTemplate?: string;
20+
}
21+
22+
export interface QuickPickOptions {
23+
/**
24+
* An optional string that represents the title of the quick pick.
25+
*/
26+
title?: string;
27+
28+
/**
29+
* Indicates whether the quick pick should allow unknown values, and if so,
30+
* how to handle them.
31+
*/
32+
unknownValues?: UnknownValuesOptions;
33+
}

src/libs/common/ide/types/ide.types.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
import { FlashDescriptor } from "./FlashDescriptor";
1919
import { Hats } from "./Hats";
2020
import { Messages } from "./Messages";
21+
import { QuickPickOptions } from "./QuickPickOptions";
2122
import { State } from "./State";
2223

2324
export type RunMode = "production" | "development" | "test";
@@ -167,6 +168,18 @@ export interface IDE {
167168
*/
168169
showInputBox(options?: InputBoxOptions): Promise<string | undefined>;
169170

171+
/**
172+
* Shows a selection list.
173+
*
174+
* @param items An array of string choices to present to the user.
175+
* @param options Configures the behavior of the selection list.
176+
* @return A promise that resolves to the selection or `undefined`.
177+
*/
178+
showQuickPick(
179+
items: readonly string[],
180+
options?: QuickPickOptions,
181+
): Promise<string | undefined>;
182+
170183
/**
171184
* Executes the built-in ide command denoted by the given command identifier.
172185
*

0 commit comments

Comments
 (0)