Skip to content

Commit 3a2e88d

Browse files
Actually display scopes
1 parent 941c0ab commit 3a2e88d

File tree

2 files changed

+118
-63
lines changed

2 files changed

+118
-63
lines changed

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,15 @@ export function expandToFullLine(editor: TextEditor, range: Range) {
2727
}
2828

2929
export function getRangeLength(editor: TextEditor, range: Range) {
30-
return range.isEmpty
31-
? 0
32-
: editor.document.offsetAt(range.end) -
33-
editor.document.offsetAt(range.start);
30+
if (range.isEmpty) {
31+
return 0;
32+
}
33+
if (range.isSingleLine) {
34+
return range.end.character - range.start.character;
35+
}
36+
return (
37+
editor.document.offsetAt(range.end) - editor.document.offsetAt(range.start)
38+
);
3439
}
3540

3641
/**

packages/cursorless-vscode/src/ScopeTreeProvider.ts

Lines changed: 109 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import type {
22
CursorlessCommandId,
3+
Range,
34
ScopeProvider,
5+
ScopeRanges,
46
ScopeSupportInfo,
57
ScopeType,
68
ScopeTypeInfo,
9+
Selection,
10+
TextEditor,
711
} from "@cursorless/common";
812
import {
913
CURSORLESS_SCOPE_TREE_VIEW_ID,
@@ -43,6 +47,7 @@ import type {
4347
ScopeVisualizer,
4448
VisualizationType,
4549
} from "./ScopeVisualizerCommandApi";
50+
import { getRangeLength } from "../../cursorless-engine/src/util/rangeUtils";
4651

4752
export const DONT_SHOW_TALON_UPDATE_MESSAGE_KEY = "dontShowUpdateTalonMessage";
4853

@@ -51,6 +56,7 @@ export class ScopeTreeProvider implements TreeDataProvider<MyTreeItem> {
5156
private treeView: TreeView<MyTreeItem>;
5257
private supportLevels: ScopeSupportInfo[] = [];
5358
private shownUpdateTalonMessage = false;
59+
private selection: Selection | null = null;
5460

5561
private _onDidChangeTreeData: EventEmitter<
5662
MyTreeItem | undefined | null | void
@@ -66,8 +72,6 @@ export class ScopeTreeProvider implements TreeDataProvider<MyTreeItem> {
6672
private customSpokenFormGenerator: CustomSpokenFormGenerator,
6773
private hasCommandServer: boolean,
6874
) {
69-
this.onChangeTextSelection = this.onChangeTextSelection.bind(this);
70-
7175
this.treeView = vscodeApi.window.createTreeView(
7276
CURSORLESS_SCOPE_TREE_VIEW_ID,
7377
{
@@ -103,46 +107,6 @@ export class ScopeTreeProvider implements TreeDataProvider<MyTreeItem> {
103107
}
104108
}
105109

106-
private onChangeTextSelection(e: TextEditorSelectionChangeEvent) {
107-
if (e.selections.length !== 1) {
108-
return;
109-
}
110-
111-
const editor = ide().activeTextEditor;
112-
113-
if (editor == null) {
114-
return;
115-
}
116-
117-
const selection = fromVscodeSelection(e.selections[0]);
118-
119-
console.log("selection", selection.concise());
120-
121-
for (const supportLevel of this.supportLevels) {
122-
if (supportLevel.support !== ScopeSupport.supportedAndPresentInEditor) {
123-
continue;
124-
}
125-
126-
const scopes = this.scopeProvider.provideScopeRangesForRange(
127-
editor,
128-
supportLevel.scopeType,
129-
selection,
130-
);
131-
132-
if (scopes.length === 0) {
133-
continue;
134-
}
135-
136-
console.log(supportLevel.scopeType.type);
137-
138-
for (const scope of scopes) {
139-
for (const target of scope.targets) {
140-
console.log(target.contentRange.concise());
141-
}
142-
}
143-
}
144-
}
145-
146110
private registerScopeSupportListener() {
147111
this.visibleDisposable = disposableFrom(
148112
this.scopeProvider.onDidChangeScopeSupport((supportLevels) => {
@@ -152,9 +116,13 @@ export class ScopeTreeProvider implements TreeDataProvider<MyTreeItem> {
152116
this.scopeVisualizer.onDidChangeScopeType(() => {
153117
this._onDidChangeTreeData.fire();
154118
}),
155-
this.vscodeApi.window.onDidChangeTextEditorSelection(
156-
this.onChangeTextSelection,
157-
),
119+
this.vscodeApi.window.onDidChangeTextEditorSelection((e) => {
120+
this.selection =
121+
e.selections.length === 1
122+
? fromVscodeSelection(e.selections[0])
123+
: null;
124+
this._onDidChangeTreeData.fire();
125+
}),
158126
);
159127
}
160128

@@ -172,6 +140,10 @@ export class ScopeTreeProvider implements TreeDataProvider<MyTreeItem> {
172140
return this.getScopeTypesWithSupport(element.scopeSupport);
173141
}
174142

143+
if (element instanceof SelectedCategoryTreeItem) {
144+
return this.getSelectedScopeTypes();
145+
}
146+
175147
throw new Error("Unexpected element");
176148
}
177149

@@ -210,18 +182,7 @@ export class ScopeTreeProvider implements TreeDataProvider<MyTreeItem> {
210182
private getScopeTypesWithSupport(
211183
scopeSupport: ScopeSupport,
212184
): ScopeSupportTreeItem[] {
213-
return this.supportLevels
214-
.filter(
215-
(supportLevel) =>
216-
supportLevel.support === scopeSupport &&
217-
// Skip scope if it doesn't have a spoken form and it's private. That
218-
// is the default state for scopes that are private; we don't want to
219-
// show these to the user.
220-
!(
221-
supportLevel.spokenForm.type === "error" &&
222-
supportLevel.spokenForm.isPrivate
223-
),
224-
)
185+
return this.getScopeSupportInfo(scopeSupport)
225186
.map(
226187
(supportLevel) =>
227188
new ScopeSupportTreeItem(
@@ -250,16 +211,69 @@ export class ScopeTreeProvider implements TreeDataProvider<MyTreeItem> {
250211
});
251212
}
252213

214+
private getSelectedScopeTypes(): ScopeSupportTreeItem[] {
215+
if (this.selection == null) {
216+
return [];
217+
}
218+
219+
const editor = ide().activeTextEditor;
220+
const selection = this.selection;
221+
222+
if (editor == null) {
223+
return [];
224+
}
225+
226+
return this.getScopeSupportInfo(ScopeSupport.supportedAndPresentInEditor)
227+
.map((supportLevel) => {
228+
const scopes = this.scopeProvider.provideScopeRangesForRange(
229+
editor,
230+
supportLevel.scopeType,
231+
selection,
232+
);
233+
return {
234+
supportLevel,
235+
length: getSmallestTargetLength(editor, selection, scopes),
236+
};
237+
})
238+
.filter(({ length }) => length > -1)
239+
.sort((a, b) => a.length - b.length)
240+
.map(({ supportLevel, length }) => {
241+
console.log(serializeScopeType(supportLevel.scopeType), length);
242+
return new ScopeSupportTreeItem(
243+
supportLevel,
244+
isEqual(supportLevel.scopeType, this.scopeVisualizer.scopeType),
245+
);
246+
});
247+
}
248+
249+
private getScopeSupportInfo(scopeSupport: ScopeSupport): ScopeSupportInfo[] {
250+
return this.supportLevels.filter(
251+
(supportLevel) =>
252+
supportLevel.support === scopeSupport &&
253+
// Skip scope if it doesn't have a spoken form and it's private. That
254+
// is the default state for scopes that are private; we don't want to
255+
// show these to the user.
256+
!(
257+
supportLevel.spokenForm.type === "error" &&
258+
supportLevel.spokenForm.isPrivate
259+
),
260+
);
261+
}
262+
253263
dispose() {
254264
this.visibleDisposable?.dispose();
255265
}
256266
}
257267

258-
function getSupportCategories(): SupportCategoryTreeItem[] {
268+
function getSupportCategories(): (
269+
| SupportCategoryTreeItem
270+
| SelectedCategoryTreeItem
271+
)[] {
259272
return [
260273
new SupportCategoryTreeItem(ScopeSupport.supportedAndPresentInEditor),
261274
new SupportCategoryTreeItem(ScopeSupport.supportedButNotPresentInEditor),
262275
new SupportCategoryTreeItem(ScopeSupport.unsupported),
276+
new SelectedCategoryTreeItem(),
263277
];
264278
}
265279

@@ -378,7 +392,17 @@ class SupportCategoryTreeItem extends TreeItem {
378392
}
379393
}
380394

381-
type MyTreeItem = ScopeSupportTreeItem | SupportCategoryTreeItem;
395+
class SelectedCategoryTreeItem extends TreeItem {
396+
constructor() {
397+
super("Selected", TreeItemCollapsibleState.Collapsed);
398+
this.description = "scopes";
399+
}
400+
}
401+
402+
type MyTreeItem =
403+
| ScopeSupportTreeItem
404+
| SupportCategoryTreeItem
405+
| SelectedCategoryTreeItem;
382406

383407
/**
384408
* Get file extension example from vscode [Language Id](https://code.visualstudio.com/docs/languages/identifiers)
@@ -405,3 +429,29 @@ export function getLanguageExtensionSampleFromLanguageId(
405429
}
406430
}
407431
}
432+
433+
function getSmallestTargetLength(
434+
editor: TextEditor,
435+
selection: Range,
436+
scopes: ScopeRanges[],
437+
): number {
438+
let length: number | null = null;
439+
for (const scope of scopes) {
440+
for (const target of scope.targets) {
441+
// Don't use targets smaller than the selection
442+
if (
443+
selection.contains(target.contentRange) &&
444+
!selection.isRangeEqual(target.contentRange)
445+
) {
446+
continue;
447+
}
448+
const targetIntersection = target.contentRange.intersection(selection);
449+
if (targetIntersection == null) {
450+
continue;
451+
}
452+
const targetLength = getRangeLength(editor, target.contentRange);
453+
length = length != null ? Math.min(length, targetLength) : targetLength;
454+
}
455+
}
456+
return length ?? -1;
457+
}

0 commit comments

Comments
 (0)