Skip to content

Commit 9f70536

Browse files
Refactor command history to remove node dependencies from engine (#2479)
## 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: Pokey Rule <[email protected]>
1 parent 2caaa66 commit 9f70536

File tree

12 files changed

+98
-53
lines changed

12 files changed

+98
-53
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { CommandHistoryEntry } from "../../types/commandHistory";
2+
3+
/**
4+
* Used by command history machinery to store entries.
5+
*/
6+
export interface CommandHistoryStorage {
7+
/**
8+
* Append an entry to a command history file.
9+
*
10+
* @param fileName The name of the file to append the entry to. We usually use
11+
* the a name derived from the current month to do a form of log rotation.
12+
* @param entry The entry to append to the file
13+
*/
14+
appendEntry(fileName: string, entry: CommandHistoryEntry): Promise<void>;
15+
16+
/**
17+
* Get all entries from all command history files.
18+
*/
19+
getEntries(): Promise<CommandHistoryEntry[]>;
20+
}

packages/common/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export * from "./ide/types/Events";
3030
export * from "./ide/types/QuickPickOptions";
3131
export * from "./ide/types/events.types";
3232
export * from "./ide/types/Paths";
33+
export * from "./ide/types/CommandHistoryStorage";
3334
export * from "./ide/types/FileSystem.types";
3435
export * from "./types/RangeExpansionBehavior";
3536
export * from "./types/InputBoxOptions";

packages/cursorless-engine/src/CommandHistory.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,15 @@ import {
33
CommandComplete,
44
CommandHistoryEntry,
55
CommandServerApi,
6-
FileSystem,
76
IDE,
87
ReadOnlyHatMap,
8+
type CommandHistoryStorage,
99
} from "@cursorless/common";
1010
import type {
1111
CommandRunner,
1212
CommandRunnerDecorator,
1313
} from "@cursorless/cursorless-engine";
1414
import { produce } from "immer";
15-
import * as fs from "node:fs/promises";
16-
import * as path from "node:path";
1715
import { v4 as uuid } from "uuid";
1816

1917
const filePrefix = "cursorlessCommandHistory";
@@ -23,17 +21,14 @@ const filePrefix = "cursorlessCommandHistory";
2321
* to a local log file in `.cursorless/commandHistory` dir.
2422
*/
2523
export class CommandHistory implements CommandRunnerDecorator {
26-
private readonly dirPath: string;
2724
private currentPhraseSignal = "";
2825
private currentPhraseId = "";
2926

3027
constructor(
3128
private ide: IDE,
29+
private storage: CommandHistoryStorage,
3230
private commandServerApi: CommandServerApi | null,
33-
fileSystem: FileSystem,
34-
) {
35-
this.dirPath = fileSystem.cursorlessCommandHistoryDirPath;
36-
}
31+
) {}
3732

3833
wrapCommandRunner(
3934
_readableHatMap: ReadOnlyHatMap,
@@ -65,7 +60,6 @@ export class CommandHistory implements CommandRunnerDecorator {
6560
): Promise<void> {
6661
const date = new Date();
6762
const fileName = `${filePrefix}_${getMonthDate(date)}.jsonl`;
68-
const file = path.join(this.dirPath, fileName);
6963

7064
const historyItem: CommandHistoryEntry = {
7165
id: uuid(),
@@ -75,10 +69,8 @@ export class CommandHistory implements CommandRunnerDecorator {
7569
phraseId: await this.getPhraseId(),
7670
command: produce(command, sanitizeCommandInPlace),
7771
};
78-
const data = JSON.stringify(historyItem) + "\n";
7972

80-
await fs.mkdir(this.dirPath, { recursive: true });
81-
await fs.appendFile(file, data, "utf8");
73+
await this.storage.appendEntry(fileName, historyItem);
8274
}
8375

8476
private async getPhraseId(): Promise<string | undefined> {

packages/cursorless-engine/src/CommandHistoryAnalyzer.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,10 @@ import {
44
PartialPrimitiveTargetDescriptor,
55
ScopeType,
66
showWarning,
7+
type CommandHistoryStorage,
78
} from "@cursorless/common";
89
import { groupBy, map, sum } from "lodash-es";
9-
import { asyncIteratorToList } from "./asyncIteratorToList";
1010
import { canonicalizeAndValidateCommand } from "./core/commandVersionUpgrades/canonicalizeAndValidateCommand";
11-
import { generateCommandHistoryEntries } from "./generateCommandHistoryEntries";
1211
import { ide } from "./singletons/ide.singleton";
1312
import { getPartialTargetDescriptors } from "./util/getPartialTargetDescriptors";
1413
import { getPartialPrimitiveTargets } from "./util/getPrimitiveTargets";
@@ -97,8 +96,10 @@ function getMonth(entry: CommandHistoryEntry): string {
9796
return entry.date.slice(0, 7);
9897
}
9998

100-
export async function analyzeCommandHistory(dir: string) {
101-
const entries = await asyncIteratorToList(generateCommandHistoryEntries(dir));
99+
export async function analyzeCommandHistory(
100+
commandHistoryStorage: CommandHistoryStorage,
101+
) {
102+
const entries = await commandHistoryStorage.getEntries();
102103

103104
if (entries.length === 0) {
104105
const TAKE_ME_THERE = "Show me";

packages/cursorless-engine/src/asyncIteratorToList.ts

Lines changed: 0 additions & 9 deletions
This file was deleted.

packages/cursorless-engine/src/generateCommandHistoryEntries.ts

Lines changed: 0 additions & 22 deletions
This file was deleted.

packages/cursorless-vscode/src/extension.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import {
5151
import { StatusBarItem } from "./StatusBarItem";
5252
import { storedTargetHighlighter } from "./storedTargetHighlighter";
5353
import { vscodeApi } from "./vscodeApi";
54+
import { FileSystemCommandHistoryStorage } from "@cursorless/file-system-common";
5455

5556
/**
5657
* Extension entrypoint called by VSCode on Cursorless startup.
@@ -101,8 +102,12 @@ export async function activate(
101102
fileSystem,
102103
);
103104

105+
const commandHistoryStorage = new FileSystemCommandHistoryStorage(
106+
fileSystem.cursorlessCommandHistoryDirPath,
107+
);
108+
104109
addCommandRunnerDecorator(
105-
new CommandHistory(normalizedIde, commandServerApi, fileSystem),
110+
new CommandHistory(normalizedIde, commandHistoryStorage, commandServerApi),
106111
);
107112

108113
const testCaseRecorder = new TestCaseRecorder(
@@ -140,7 +145,7 @@ export async function activate(
140145
context,
141146
vscodeIDE,
142147
commandApi,
143-
fileSystem,
148+
commandHistoryStorage,
144149
testCaseRecorder,
145150
scopeTestRecorder,
146151
scopeVisualizer,

packages/cursorless-vscode/src/registerCommands.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import {
22
CURSORLESS_COMMAND_ID,
33
CursorlessCommandId,
4-
FileSystem,
54
isTesting,
5+
type CommandHistoryStorage,
66
} from "@cursorless/common";
77
import {
88
CommandApi,
@@ -25,7 +25,7 @@ export function registerCommands(
2525
extensionContext: vscode.ExtensionContext,
2626
vscodeIde: VscodeIDE,
2727
commandApi: CommandApi,
28-
fileSystem: FileSystem,
28+
commandHistoryStorage: CommandHistoryStorage,
2929
testCaseRecorder: TestCaseRecorder,
3030
scopeTestRecorder: ScopeTestRecorder,
3131
scopeVisualizer: ScopeVisualizer,
@@ -90,7 +90,7 @@ export function registerCommands(
9090

9191
// Command history
9292
["cursorless.analyzeCommandHistory"]: () =>
93-
analyzeCommandHistory(fileSystem.cursorlessCommandHistoryDirPath),
93+
analyzeCommandHistory(commandHistoryStorage),
9494

9595
// General keyboard commands
9696
["cursorless.keyboard.escape"]:

packages/file-system-common/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
}
2525
},
2626
"dependencies": {
27-
"@cursorless/common": "workspace:*"
27+
"@cursorless/common": "workspace:*",
28+
"glob": "^10.3.10"
29+
},
30+
"devDependencies": {
31+
"@types/glob": "^8.1.0"
2832
}
2933
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import type {
2+
CommandHistoryEntry,
3+
CommandHistoryStorage,
4+
} from "@cursorless/common";
5+
import { glob } from "glob";
6+
import fs from "node:fs/promises";
7+
import path from "node:path";
8+
9+
export class FileSystemCommandHistoryStorage implements CommandHistoryStorage {
10+
constructor(private dir: string) {}
11+
12+
async appendEntry(
13+
fileName: string,
14+
entry: CommandHistoryEntry,
15+
): Promise<void> {
16+
const data = JSON.stringify(entry) + "\n";
17+
const file = path.join(this.dir, fileName);
18+
await fs.mkdir(this.dir, { recursive: true });
19+
await fs.appendFile(file, data, "utf8");
20+
}
21+
22+
async getEntries(): Promise<CommandHistoryEntry[]> {
23+
const files = await glob("*.jsonl", {
24+
cwd: this.dir,
25+
});
26+
27+
const entries: CommandHistoryEntry[] = [];
28+
29+
for (const file of files) {
30+
const filePath = path.join(this.dir, file);
31+
const content = await fs.readFile(filePath, "utf8");
32+
const lines = content.split("\n");
33+
34+
for (const line of lines) {
35+
if (line.length === 0) {
36+
continue;
37+
}
38+
39+
entries.push(JSON.parse(line));
40+
}
41+
}
42+
43+
return entries;
44+
}
45+
}

0 commit comments

Comments
 (0)