Skip to content

Commit d7b3ba6

Browse files
committed
Detect which scope is being visualized
1 parent 7c25bf8 commit d7b3ba6

File tree

4 files changed

+95
-33
lines changed

4 files changed

+95
-33
lines changed

packages/cursorless-vscode/src/ScopeSupportTreeProvider.ts

Lines changed: 58 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
1-
import { CursorlessCommandId } from "@cursorless/common";
1+
import { CursorlessCommandId, Disposer } from "@cursorless/common";
22
import {
33
ScopeProvider,
44
ScopeSupport,
55
ScopeSupportLevels,
66
ScopeTypeInfo,
77
} from "@cursorless/cursorless-engine";
88
import * as vscode from "vscode";
9-
import { VisualizationType } from "./ScopeVisualizerCommandApi";
9+
import {
10+
ScopeVisualizer,
11+
VisualizationType,
12+
} from "./ScopeVisualizerCommandApi";
13+
import { isEqual } from "lodash";
1014

1115
export class ScopeSupportTreeProvider
1216
implements vscode.TreeDataProvider<MyTreeItem>
1317
{
14-
private onDidChangeScopeSupportDisposable: vscode.Disposable | undefined;
18+
private visibleDisposable: Disposer | undefined;
1519
private treeView: vscode.TreeView<MyTreeItem>;
1620
private supportLevels: ScopeSupportLevels = [];
1721

@@ -25,6 +29,7 @@ export class ScopeSupportTreeProvider
2529
constructor(
2630
private context: vscode.ExtensionContext,
2731
private scopeProvider: ScopeProvider,
32+
private scopeVisualizer: ScopeVisualizer,
2833
) {
2934
this.treeView = vscode.window.createTreeView("cursorless.scopeSupport", {
3035
treeDataProvider: this,
@@ -40,8 +45,13 @@ export class ScopeSupportTreeProvider
4045
static create(
4146
context: vscode.ExtensionContext,
4247
scopeProvider: ScopeProvider,
48+
scopeVisualizer: ScopeVisualizer,
4349
): ScopeSupportTreeProvider {
44-
const treeProvider = new ScopeSupportTreeProvider(context, scopeProvider);
50+
const treeProvider = new ScopeSupportTreeProvider(
51+
context,
52+
scopeProvider,
53+
scopeVisualizer,
54+
);
4555
treeProvider.init();
4656
return treeProvider;
4757
}
@@ -54,27 +64,32 @@ export class ScopeSupportTreeProvider
5464

5565
onDidChangeVisible(e: vscode.TreeViewVisibilityChangeEvent) {
5666
if (e.visible) {
57-
if (this.onDidChangeScopeSupportDisposable != null) {
67+
if (this.visibleDisposable != null) {
5868
return;
5969
}
6070

6171
this.registerScopeSupportListener();
6272
} else {
63-
if (this.onDidChangeScopeSupportDisposable == null) {
73+
if (this.visibleDisposable == null) {
6474
return;
6575
}
6676

67-
this.onDidChangeScopeSupportDisposable.dispose();
68-
this.onDidChangeScopeSupportDisposable = undefined;
77+
this.visibleDisposable.dispose();
78+
this.visibleDisposable = undefined;
6979
}
7080
}
7181

7282
private registerScopeSupportListener() {
73-
this.onDidChangeScopeSupportDisposable =
83+
this.visibleDisposable = new Disposer();
84+
this.visibleDisposable.push(
7485
this.scopeProvider.onDidChangeScopeSupport((supportLevels) => {
7586
this.supportLevels = supportLevels;
7687
this._onDidChangeTreeData.fire();
77-
});
88+
}),
89+
this.scopeVisualizer.onDidChangeScopeType(() => {
90+
this._onDidChangeTreeData.fire();
91+
}),
92+
);
7893
}
7994

8095
getTreeItem(element: MyTreeItem): MyTreeItem {
@@ -96,7 +111,13 @@ export class ScopeSupportTreeProvider
96111
getScopeTypesWithSupport(scopeSupport: ScopeSupport): ScopeSupportTreeItem[] {
97112
return this.supportLevels
98113
.filter((supportLevel) => supportLevel.support === scopeSupport)
99-
.map((supportLevel) => new ScopeSupportTreeItem(supportLevel))
114+
.map(
115+
(supportLevel) =>
116+
new ScopeSupportTreeItem(
117+
supportLevel,
118+
isEqual(supportLevel.scopeType, this.scopeVisualizer.scopeType),
119+
),
120+
)
100121
.sort((a, b) => {
101122
if (
102123
a.scopeTypeInfo.spokenForm.type !== b.scopeTypeInfo.spokenForm.type
@@ -111,12 +132,12 @@ export class ScopeSupportTreeProvider
111132
return a.scopeTypeInfo.isLanguageSpecific ? -1 : 1;
112133
}
113134

114-
return a.label.localeCompare(b.label);
135+
return a.label.label.localeCompare(b.label.label);
115136
});
116137
}
117138

118139
dispose() {
119-
this.onDidChangeScopeSupportDisposable?.dispose();
140+
this.visibleDisposable?.dispose();
120141
}
121142
}
122143

@@ -146,9 +167,12 @@ function getSupportCategories(): SupportCategoryTreeItem[] {
146167
}
147168

148169
class ScopeSupportTreeItem extends vscode.TreeItem {
149-
public label: string;
170+
public label: vscode.TreeItemLabel;
150171

151-
constructor(public scopeTypeInfo: ScopeTypeInfo) {
172+
constructor(
173+
public scopeTypeInfo: ScopeTypeInfo,
174+
isVisualized: boolean,
175+
) {
152176
const label =
153177
scopeTypeInfo.spokenForm.type === "error"
154178
? "-"
@@ -157,7 +181,10 @@ class ScopeSupportTreeItem extends vscode.TreeItem {
157181

158182
super(label, vscode.TreeItemCollapsibleState.None);
159183

160-
this.label = label;
184+
this.label = {
185+
label,
186+
highlights: isVisualized ? [[0, label.length]] : [],
187+
};
161188

162189
this.description = description;
163190

@@ -170,14 +197,21 @@ class ScopeSupportTreeItem extends vscode.TreeItem {
170197
.join("\n");
171198
}
172199

173-
this.command = {
174-
command: "cursorless.showScopeVisualizer" satisfies CursorlessCommandId,
175-
arguments: [
176-
scopeTypeInfo.scopeType,
177-
"content" satisfies VisualizationType,
178-
],
179-
title: `Visualize ${scopeTypeInfo.humanReadableName}`,
180-
};
200+
this.command = isVisualized
201+
? {
202+
command:
203+
"cursorless.hideScopeVisualizer" satisfies CursorlessCommandId,
204+
title: "Hide the scope visualizer",
205+
}
206+
: {
207+
command:
208+
"cursorless.showScopeVisualizer" satisfies CursorlessCommandId,
209+
arguments: [
210+
scopeTypeInfo.scopeType,
211+
"content" satisfies VisualizationType,
212+
],
213+
title: `Visualize ${scopeTypeInfo.humanReadableName}`,
214+
};
181215

182216
if (scopeTypeInfo.isLanguageSpecific) {
183217
this.iconPath = new vscode.ThemeIcon("code");
Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1-
import { ScopeType } from "@cursorless/common";
1+
import { Disposable, ScopeType } from "@cursorless/common";
22

3-
export interface ScopeVisualizerCommandApi {
3+
export type VisualizerScopeTypeListener = (
4+
scopeType: ScopeType | undefined,
5+
) => void;
6+
7+
export interface ScopeVisualizer {
48
start(scopeType: ScopeType, visualizationType: VisualizationType): void;
59
stop(): void;
10+
readonly scopeType: ScopeType | undefined;
11+
onDidChangeScopeType(listener: VisualizerScopeTypeListener): Disposable;
612
}
713

814
export type VisualizationType = "content" | "removal" | "iteration";

packages/cursorless-vscode/src/extension.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import { KeyboardCommands } from "./keyboard/KeyboardCommands";
3535
import { registerCommands } from "./registerCommands";
3636
import { ReleaseNotes } from "./ReleaseNotes";
3737
import {
38-
ScopeVisualizerCommandApi,
38+
ScopeVisualizer,
3939
VisualizationType,
4040
} from "./ScopeVisualizerCommandApi";
4141
import { StatusBarItem } from "./StatusBarItem";
@@ -92,14 +92,15 @@ export async function activate(
9292

9393
const statusBarItem = StatusBarItem.create("cursorless.showQuickPick");
9494
const keyboardCommands = KeyboardCommands.create(context, statusBarItem);
95-
ScopeSupportTreeProvider.create(context, scopeProvider);
95+
const scopeVisualizer = createScopeVisualizer(normalizedIde, scopeProvider);
96+
ScopeSupportTreeProvider.create(context, scopeProvider, scopeVisualizer);
9697

9798
registerCommands(
9899
context,
99100
vscodeIDE,
100101
commandApi,
101102
testCaseRecorder,
102-
createScopeVisualizerCommandApi(normalizedIde, scopeProvider),
103+
scopeVisualizer,
103104
keyboardCommands,
104105
hats,
105106
);
@@ -157,11 +158,14 @@ function createTreeSitter(parseTreeApi: ParseTreeApi): TreeSitter {
157158
};
158159
}
159160

160-
function createScopeVisualizerCommandApi(
161+
function createScopeVisualizer(
161162
ide: IDE,
162163
scopeProvider: ScopeProvider,
163-
): ScopeVisualizerCommandApi {
164+
): ScopeVisualizer {
164165
let scopeVisualizer: VscodeScopeVisualizer | undefined;
166+
let currentScopeType: ScopeType | undefined;
167+
168+
const listeners: VisualizerScopeTypeListener[] = [];
165169

166170
return {
167171
start(scopeType: ScopeType, visualizationType: VisualizationType) {
@@ -173,11 +177,29 @@ function createScopeVisualizerCommandApi(
173177
visualizationType,
174178
);
175179
scopeVisualizer.start();
180+
currentScopeType = scopeType;
181+
listeners.forEach((listener) => listener(scopeType));
176182
},
177183

178184
stop() {
179185
scopeVisualizer?.dispose();
180186
scopeVisualizer = undefined;
187+
currentScopeType = undefined;
188+
listeners.forEach((listener) => listener(undefined));
189+
},
190+
191+
get scopeType() {
192+
return currentScopeType;
193+
},
194+
195+
onDidChangeScopeType(listener: VisualizerScopeTypeListener): Disposable {
196+
listeners.push(listener);
197+
198+
return {
199+
dispose() {
200+
listeners.splice(listeners.indexOf(listener), 1);
201+
},
202+
};
181203
},
182204
};
183205
}

packages/cursorless-vscode/src/registerCommands.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ import { showDocumentation, showQuickPick } from "./commands";
1414
import { VscodeIDE } from "./ide/vscode/VscodeIDE";
1515
import { VscodeHats } from "./ide/vscode/hats/VscodeHats";
1616
import { KeyboardCommands } from "./keyboard/KeyboardCommands";
17-
import { ScopeVisualizerCommandApi } from "./ScopeVisualizerCommandApi";
17+
import { ScopeVisualizer } from "./ScopeVisualizerCommandApi";
1818

1919
export function registerCommands(
2020
extensionContext: vscode.ExtensionContext,
2121
vscodeIde: VscodeIDE,
2222
commandApi: CommandApi,
2323
testCaseRecorder: TestCaseRecorder,
24-
scopeVisualizer: ScopeVisualizerCommandApi,
24+
scopeVisualizer: ScopeVisualizer,
2525
keyboardCommands: KeyboardCommands,
2626
hats: VscodeHats,
2727
): void {

0 commit comments

Comments
 (0)