-
-
Notifications
You must be signed in to change notification settings - Fork 96
Expand file tree
/
Copy pathCommandHistoryAnalyzer.ts
More file actions
161 lines (139 loc) · 4.76 KB
/
Copy pathCommandHistoryAnalyzer.ts
File metadata and controls
161 lines (139 loc) · 4.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
import type {
CommandHistoryEntry,
CommandHistoryStorage,
IDE,
Modifier,
PartialPrimitiveTargetDescriptor,
ScopeType,
} from "@cursorless/common";
import { showWarning } from "@cursorless/common";
import { groupBy, map, sum } from "lodash-es";
import { canonicalizeAndValidateCommand } from "./core/commandVersionUpgrades/canonicalizeAndValidateCommand";
import { getPartialTargetDescriptors } from "./util/getPartialTargetDescriptors";
import { getPartialPrimitiveTargets } from "./util/getPrimitiveTargets";
import { getScopeType } from "./util/getScopeType";
/**
* Analyzes the command history for a given time period, and outputs a report
*/
class Period {
private readonly period: string;
private readonly actions: Record<string, number> = {};
private readonly modifiers: Record<string, number> = {};
private readonly scopeTypes: Record<string, number> = {};
private readonly dates = new Set<string>();
private readonly commandCount: number;
private decoratedMarkCommandCount: number = 0;
constructor(period: string, entries: CommandHistoryEntry[]) {
this.period = period;
this.commandCount = entries.length;
for (const entry of entries) {
this.append(entry);
}
}
toString(): string {
const avgCommandsPerDay = Math.round(this.commandCount / this.dates.size);
const percentageDecoratedMarkCommands = Math.round(
(100 * this.decoratedMarkCommandCount) / this.commandCount,
);
const meta = [
`Command count: ${this.commandCount}`,
`Days used: ${this.dates.size}`,
`Average commands / day: ${avgCommandsPerDay}`,
`Commands with hats: ${this.decoratedMarkCommandCount} (${percentageDecoratedMarkCommands}%)`,
].join("\n");
return [
`# ${this.period}`,
meta,
this.serializeMap("Actions", this.actions),
this.serializeMap("Modifiers", this.modifiers),
this.serializeMap("Scope types", this.scopeTypes),
].join("\n\n");
}
private serializeMap(name: string, map: Record<string, number>) {
const total = sum(Object.values(map));
const entries = Object.entries(map);
entries.sort((a, b) => b[1] - a[1]);
const entriesSerialized = entries
.map(([key, value]) => ` ${key}: ${value} (${toPercent(value / total)})`)
.join("\n");
return `${name}:\n${entriesSerialized}`;
}
private append(entry: CommandHistoryEntry) {
this.dates.add(entry.date);
const command = canonicalizeAndValidateCommand(entry.command);
this.incrementAction(command.action.name);
this.parsePrimitiveTargets(
getPartialPrimitiveTargets(getPartialTargetDescriptors(command.action)),
);
}
private parsePrimitiveTargets(
partialPrimitiveTargets: PartialPrimitiveTargetDescriptor[],
) {
let hasDecoratedMark = false;
for (const target of partialPrimitiveTargets) {
if (target.mark?.type === "decoratedSymbol") {
hasDecoratedMark = true;
}
for (const modifier of target.modifiers ?? []) {
this.incrementModifier(modifier);
const scopeType = getScopeType(modifier);
if (scopeType != null) {
this.incrementScope(scopeType);
}
}
}
if (hasDecoratedMark) {
this.decoratedMarkCommandCount++;
}
}
private incrementAction(actionName: string) {
this.actions[actionName] = (this.actions[actionName] ?? 0) + 1;
}
private incrementModifier(modifier: Modifier) {
this.modifiers[modifier.type] = (this.modifiers[modifier.type] ?? 0) + 1;
}
private incrementScope(scopeType: ScopeType) {
this.scopeTypes[scopeType.type] =
(this.scopeTypes[scopeType.type] ?? 0) + 1;
}
}
function getMonth(entry: CommandHistoryEntry): string {
return entry.date.slice(0, 7);
}
export async function analyzeCommandHistory(
ide: IDE,
commandHistoryStorage: CommandHistoryStorage,
) {
const entries = await commandHistoryStorage.getEntries();
if (entries.length === 0) {
const TAKE_ME_THERE = "Show me";
const result = await showWarning(
ide.messages,
"noHistory",
"No command history entries found. Please enable the command history in the settings.",
TAKE_ME_THERE,
);
if (result === TAKE_ME_THERE) {
// FIXME: This is VSCode-specific
await ide.executeCommand(
"workbench.action.openSettings",
"cursorless.commandHistory",
);
}
return;
}
const content = [
new Period("Totals", entries).toString(),
...map(Object.entries(groupBy(entries, getMonth)), ([key, entries]) =>
new Period(key, entries).toString(),
),
].join("\n\n\n");
await ide.openUntitledTextDocument({ content });
}
function toPercent(value: number) {
return Intl.NumberFormat(undefined, {
style: "percent",
minimumFractionDigits: 0,
maximumFractionDigits: 1,
}).format(value);
}