From 941c0ab07b40f2a5548fdf9dcfbeaba1b7ed8549 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sat, 6 Sep 2025 10:42:23 +0200 Subject: [PATCH 01/13] Started working on selected scope visualizer --- packages/common/src/types/ScopeProvider.ts | 6 ++ .../cursorless-engine/src/cursorlessEngine.ts | 1 + .../src/scopeProviders/ScopeRangeProvider.ts | 32 ++++++++++- .../src/ScopeTreeProvider.ts | 56 ++++++++++++++++++- 4 files changed, 91 insertions(+), 4 deletions(-) diff --git a/packages/common/src/types/ScopeProvider.ts b/packages/common/src/types/ScopeProvider.ts index 2e987b84d4..e100371ad2 100644 --- a/packages/common/src/types/ScopeProvider.ts +++ b/packages/common/src/types/ScopeProvider.ts @@ -19,6 +19,12 @@ export interface ScopeProvider { config: ScopeRangeConfig, ) => ScopeRanges[]; + provideScopeRangesForRange( + editor: TextEditor, + scopeType: ScopeType, + range: Range, + ): ScopeRanges[]; + /** * Get the iteration scope ranges for the given editor. * @param editor The editor diff --git a/packages/cursorless-engine/src/cursorlessEngine.ts b/packages/cursorless-engine/src/cursorlessEngine.ts index 57375bc3ec..d60dbb3057 100644 --- a/packages/cursorless-engine/src/cursorlessEngine.ts +++ b/packages/cursorless-engine/src/cursorlessEngine.ts @@ -175,6 +175,7 @@ function createScopeProvider( return { provideScopeRanges: rangeProvider.provideScopeRanges, + provideScopeRangesForRange: rangeProvider.provideScopeRangesForRange, provideIterationScopeRanges: rangeProvider.provideIterationScopeRanges, onDidChangeScopeRanges: rangeWatcher.onDidChangeScopeRanges, onDidChangeIterationScopeRanges: diff --git a/packages/cursorless-engine/src/scopeProviders/ScopeRangeProvider.ts b/packages/cursorless-engine/src/scopeProviders/ScopeRangeProvider.ts index 5ae8efc6b0..cb38f0b133 100644 --- a/packages/cursorless-engine/src/scopeProviders/ScopeRangeProvider.ts +++ b/packages/cursorless-engine/src/scopeProviders/ScopeRangeProvider.ts @@ -1,8 +1,10 @@ -import type { +import { IterationScopeRangeConfig, IterationScopeRanges, + Range, ScopeRangeConfig, ScopeRanges, + ScopeType, TextEditor, } from "@cursorless/common"; @@ -21,6 +23,8 @@ export class ScopeRangeProvider { private modifierStageFactory: ModifierStageFactory, ) { this.provideScopeRanges = this.provideScopeRanges.bind(this); + this.provideScopeRangesForRange = + this.provideScopeRangesForRange.bind(this); this.provideIterationScopeRanges = this.provideIterationScopeRanges.bind(this); } @@ -45,6 +49,32 @@ export class ScopeRangeProvider { ); } + provideScopeRangesForRange( + editor: TextEditor, + scopeType: ScopeType, + range: Range, + ): ScopeRanges[] { + const scopeHandler = this.scopeHandlerFactory.maybeCreate( + scopeType, + editor.document.languageId, + ); + + if (scopeHandler == null) { + return []; + } + + // Need to have a non empty intersection with the scopes + if (range.isEmpty) { + const offset = editor.document.offsetAt(range.start); + range = new Range( + editor.document.positionAt(offset - 1), + editor.document.positionAt(offset + 1), + ); + } + + return getScopeRanges(editor, scopeHandler, range); + } + provideIterationScopeRanges( editor: TextEditor, { scopeType, visibleOnly, includeNestedTargets }: IterationScopeRangeConfig, diff --git a/packages/cursorless-vscode/src/ScopeTreeProvider.ts b/packages/cursorless-vscode/src/ScopeTreeProvider.ts index 0c88f39a15..5d2b0c0699 100644 --- a/packages/cursorless-vscode/src/ScopeTreeProvider.ts +++ b/packages/cursorless-vscode/src/ScopeTreeProvider.ts @@ -2,6 +2,7 @@ import type { CursorlessCommandId, ScopeProvider, ScopeSupportInfo, + ScopeType, ScopeTypeInfo, } from "@cursorless/common"; import { @@ -12,13 +13,17 @@ import { serializeScopeType, uriEncodeHashId, } from "@cursorless/common"; -import type { CustomSpokenFormGenerator } from "@cursorless/cursorless-engine"; -import type { VscodeApi } from "@cursorless/vscode-common"; +import { + ide, + type CustomSpokenFormGenerator, +} from "@cursorless/cursorless-engine"; +import { fromVscodeSelection, type VscodeApi } from "@cursorless/vscode-common"; import { isEqual } from "lodash-es"; import type { Disposable, Event, ExtensionContext, + TextEditorSelectionChangeEvent, TreeDataProvider, TreeItemLabel, TreeView, @@ -61,6 +66,8 @@ export class ScopeTreeProvider implements TreeDataProvider { private customSpokenFormGenerator: CustomSpokenFormGenerator, private hasCommandServer: boolean, ) { + this.onChangeTextSelection = this.onChangeTextSelection.bind(this); + this.treeView = vscodeApi.window.createTreeView( CURSORLESS_SCOPE_TREE_VIEW_ID, { @@ -79,7 +86,7 @@ export class ScopeTreeProvider implements TreeDataProvider { } } - onDidChangeVisible(e: TreeViewVisibilityChangeEvent) { + private onDidChangeVisible(e: TreeViewVisibilityChangeEvent) { if (e.visible) { if (this.visibleDisposable != null) { return; @@ -96,6 +103,46 @@ export class ScopeTreeProvider implements TreeDataProvider { } } + private onChangeTextSelection(e: TextEditorSelectionChangeEvent) { + if (e.selections.length !== 1) { + return; + } + + const editor = ide().activeTextEditor; + + if (editor == null) { + return; + } + + const selection = fromVscodeSelection(e.selections[0]); + + console.log("selection", selection.concise()); + + for (const supportLevel of this.supportLevels) { + if (supportLevel.support !== ScopeSupport.supportedAndPresentInEditor) { + continue; + } + + const scopes = this.scopeProvider.provideScopeRangesForRange( + editor, + supportLevel.scopeType, + selection, + ); + + if (scopes.length === 0) { + continue; + } + + console.log(supportLevel.scopeType.type); + + for (const scope of scopes) { + for (const target of scope.targets) { + console.log(target.contentRange.concise()); + } + } + } + } + private registerScopeSupportListener() { this.visibleDisposable = disposableFrom( this.scopeProvider.onDidChangeScopeSupport((supportLevels) => { @@ -105,6 +152,9 @@ export class ScopeTreeProvider implements TreeDataProvider { this.scopeVisualizer.onDidChangeScopeType(() => { this._onDidChangeTreeData.fire(); }), + this.vscodeApi.window.onDidChangeTextEditorSelection( + this.onChangeTextSelection, + ), ); } From 3a2e88d870ac170b738bf25a426e179da51b8378 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sat, 6 Sep 2025 12:01:07 +0200 Subject: [PATCH 02/13] Actually display scopes --- .../cursorless-engine/src/util/rangeUtils.ts | 13 +- .../src/ScopeTreeProvider.ts | 168 ++++++++++++------ 2 files changed, 118 insertions(+), 63 deletions(-) diff --git a/packages/cursorless-engine/src/util/rangeUtils.ts b/packages/cursorless-engine/src/util/rangeUtils.ts index bf2b5d17c8..5edcb8a170 100644 --- a/packages/cursorless-engine/src/util/rangeUtils.ts +++ b/packages/cursorless-engine/src/util/rangeUtils.ts @@ -27,10 +27,15 @@ export function expandToFullLine(editor: TextEditor, range: Range) { } export function getRangeLength(editor: TextEditor, range: Range) { - return range.isEmpty - ? 0 - : editor.document.offsetAt(range.end) - - editor.document.offsetAt(range.start); + if (range.isEmpty) { + return 0; + } + if (range.isSingleLine) { + return range.end.character - range.start.character; + } + return ( + editor.document.offsetAt(range.end) - editor.document.offsetAt(range.start) + ); } /** diff --git a/packages/cursorless-vscode/src/ScopeTreeProvider.ts b/packages/cursorless-vscode/src/ScopeTreeProvider.ts index 5d2b0c0699..19ddf7fbed 100644 --- a/packages/cursorless-vscode/src/ScopeTreeProvider.ts +++ b/packages/cursorless-vscode/src/ScopeTreeProvider.ts @@ -1,9 +1,13 @@ import type { CursorlessCommandId, + Range, ScopeProvider, + ScopeRanges, ScopeSupportInfo, ScopeType, ScopeTypeInfo, + Selection, + TextEditor, } from "@cursorless/common"; import { CURSORLESS_SCOPE_TREE_VIEW_ID, @@ -43,6 +47,7 @@ import type { ScopeVisualizer, VisualizationType, } from "./ScopeVisualizerCommandApi"; +import { getRangeLength } from "../../cursorless-engine/src/util/rangeUtils"; export const DONT_SHOW_TALON_UPDATE_MESSAGE_KEY = "dontShowUpdateTalonMessage"; @@ -51,6 +56,7 @@ export class ScopeTreeProvider implements TreeDataProvider { private treeView: TreeView; private supportLevels: ScopeSupportInfo[] = []; private shownUpdateTalonMessage = false; + private selection: Selection | null = null; private _onDidChangeTreeData: EventEmitter< MyTreeItem | undefined | null | void @@ -66,8 +72,6 @@ export class ScopeTreeProvider implements TreeDataProvider { private customSpokenFormGenerator: CustomSpokenFormGenerator, private hasCommandServer: boolean, ) { - this.onChangeTextSelection = this.onChangeTextSelection.bind(this); - this.treeView = vscodeApi.window.createTreeView( CURSORLESS_SCOPE_TREE_VIEW_ID, { @@ -103,46 +107,6 @@ export class ScopeTreeProvider implements TreeDataProvider { } } - private onChangeTextSelection(e: TextEditorSelectionChangeEvent) { - if (e.selections.length !== 1) { - return; - } - - const editor = ide().activeTextEditor; - - if (editor == null) { - return; - } - - const selection = fromVscodeSelection(e.selections[0]); - - console.log("selection", selection.concise()); - - for (const supportLevel of this.supportLevels) { - if (supportLevel.support !== ScopeSupport.supportedAndPresentInEditor) { - continue; - } - - const scopes = this.scopeProvider.provideScopeRangesForRange( - editor, - supportLevel.scopeType, - selection, - ); - - if (scopes.length === 0) { - continue; - } - - console.log(supportLevel.scopeType.type); - - for (const scope of scopes) { - for (const target of scope.targets) { - console.log(target.contentRange.concise()); - } - } - } - } - private registerScopeSupportListener() { this.visibleDisposable = disposableFrom( this.scopeProvider.onDidChangeScopeSupport((supportLevels) => { @@ -152,9 +116,13 @@ export class ScopeTreeProvider implements TreeDataProvider { this.scopeVisualizer.onDidChangeScopeType(() => { this._onDidChangeTreeData.fire(); }), - this.vscodeApi.window.onDidChangeTextEditorSelection( - this.onChangeTextSelection, - ), + this.vscodeApi.window.onDidChangeTextEditorSelection((e) => { + this.selection = + e.selections.length === 1 + ? fromVscodeSelection(e.selections[0]) + : null; + this._onDidChangeTreeData.fire(); + }), ); } @@ -172,6 +140,10 @@ export class ScopeTreeProvider implements TreeDataProvider { return this.getScopeTypesWithSupport(element.scopeSupport); } + if (element instanceof SelectedCategoryTreeItem) { + return this.getSelectedScopeTypes(); + } + throw new Error("Unexpected element"); } @@ -210,18 +182,7 @@ export class ScopeTreeProvider implements TreeDataProvider { private getScopeTypesWithSupport( scopeSupport: ScopeSupport, ): ScopeSupportTreeItem[] { - return this.supportLevels - .filter( - (supportLevel) => - supportLevel.support === scopeSupport && - // Skip scope if it doesn't have a spoken form and it's private. That - // is the default state for scopes that are private; we don't want to - // show these to the user. - !( - supportLevel.spokenForm.type === "error" && - supportLevel.spokenForm.isPrivate - ), - ) + return this.getScopeSupportInfo(scopeSupport) .map( (supportLevel) => new ScopeSupportTreeItem( @@ -250,16 +211,69 @@ export class ScopeTreeProvider implements TreeDataProvider { }); } + private getSelectedScopeTypes(): ScopeSupportTreeItem[] { + if (this.selection == null) { + return []; + } + + const editor = ide().activeTextEditor; + const selection = this.selection; + + if (editor == null) { + return []; + } + + return this.getScopeSupportInfo(ScopeSupport.supportedAndPresentInEditor) + .map((supportLevel) => { + const scopes = this.scopeProvider.provideScopeRangesForRange( + editor, + supportLevel.scopeType, + selection, + ); + return { + supportLevel, + length: getSmallestTargetLength(editor, selection, scopes), + }; + }) + .filter(({ length }) => length > -1) + .sort((a, b) => a.length - b.length) + .map(({ supportLevel, length }) => { + console.log(serializeScopeType(supportLevel.scopeType), length); + return new ScopeSupportTreeItem( + supportLevel, + isEqual(supportLevel.scopeType, this.scopeVisualizer.scopeType), + ); + }); + } + + private getScopeSupportInfo(scopeSupport: ScopeSupport): ScopeSupportInfo[] { + return this.supportLevels.filter( + (supportLevel) => + supportLevel.support === scopeSupport && + // Skip scope if it doesn't have a spoken form and it's private. That + // is the default state for scopes that are private; we don't want to + // show these to the user. + !( + supportLevel.spokenForm.type === "error" && + supportLevel.spokenForm.isPrivate + ), + ); + } + dispose() { this.visibleDisposable?.dispose(); } } -function getSupportCategories(): SupportCategoryTreeItem[] { +function getSupportCategories(): ( + | SupportCategoryTreeItem + | SelectedCategoryTreeItem +)[] { return [ new SupportCategoryTreeItem(ScopeSupport.supportedAndPresentInEditor), new SupportCategoryTreeItem(ScopeSupport.supportedButNotPresentInEditor), new SupportCategoryTreeItem(ScopeSupport.unsupported), + new SelectedCategoryTreeItem(), ]; } @@ -378,7 +392,17 @@ class SupportCategoryTreeItem extends TreeItem { } } -type MyTreeItem = ScopeSupportTreeItem | SupportCategoryTreeItem; +class SelectedCategoryTreeItem extends TreeItem { + constructor() { + super("Selected", TreeItemCollapsibleState.Collapsed); + this.description = "scopes"; + } +} + +type MyTreeItem = + | ScopeSupportTreeItem + | SupportCategoryTreeItem + | SelectedCategoryTreeItem; /** * Get file extension example from vscode [Language Id](https://code.visualstudio.com/docs/languages/identifiers) @@ -405,3 +429,29 @@ export function getLanguageExtensionSampleFromLanguageId( } } } + +function getSmallestTargetLength( + editor: TextEditor, + selection: Range, + scopes: ScopeRanges[], +): number { + let length: number | null = null; + for (const scope of scopes) { + for (const target of scope.targets) { + // Don't use targets smaller than the selection + if ( + selection.contains(target.contentRange) && + !selection.isRangeEqual(target.contentRange) + ) { + continue; + } + const targetIntersection = target.contentRange.intersection(selection); + if (targetIntersection == null) { + continue; + } + const targetLength = getRangeLength(editor, target.contentRange); + length = length != null ? Math.min(length, targetLength) : targetLength; + } + } + return length ?? -1; +} From 073b7aa1ebc0ad9da2ccf4131e50dd316d066bb4 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sat, 6 Sep 2025 13:22:12 +0200 Subject: [PATCH 03/13] Sort order of scopes in scope visualizer --- .../src/ScopeTreeProvider.ts | 60 +++++++++++-------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/packages/cursorless-vscode/src/ScopeTreeProvider.ts b/packages/cursorless-vscode/src/ScopeTreeProvider.ts index 19ddf7fbed..56412c2e46 100644 --- a/packages/cursorless-vscode/src/ScopeTreeProvider.ts +++ b/packages/cursorless-vscode/src/ScopeTreeProvider.ts @@ -111,6 +111,11 @@ export class ScopeTreeProvider implements TreeDataProvider { this.visibleDisposable = disposableFrom( this.scopeProvider.onDidChangeScopeSupport((supportLevels) => { this.supportLevels = supportLevels; + const editor = ide().activeTextEditor; + this.selection = + editor != null && editor.selections.length === 1 + ? editor.selections[0] + : null; this._onDidChangeTreeData.fire(); }), this.scopeVisualizer.onDidChangeScopeType(() => { @@ -190,25 +195,7 @@ export class ScopeTreeProvider implements TreeDataProvider { isEqual(supportLevel.scopeType, this.scopeVisualizer.scopeType), ), ) - .sort((a, b) => { - if ( - a.scopeTypeInfo.spokenForm.type !== b.scopeTypeInfo.spokenForm.type - ) { - // Scopes with no spoken form are sorted to the bottom - return a.scopeTypeInfo.spokenForm.type === "error" ? 1 : -1; - } - - if ( - a.scopeTypeInfo.isLanguageSpecific !== - b.scopeTypeInfo.isLanguageSpecific - ) { - // Then language-specific scopes are sorted to the top - return a.scopeTypeInfo.isLanguageSpecific ? -1 : 1; - } - - // Then alphabetical by label - return a.label.label.localeCompare(b.label.label); - }); + .sort(treeItemComparator); } private getSelectedScopeTypes(): ScopeSupportTreeItem[] { @@ -236,14 +223,14 @@ export class ScopeTreeProvider implements TreeDataProvider { }; }) .filter(({ length }) => length > -1) - .sort((a, b) => a.length - b.length) .map(({ supportLevel, length }) => { - console.log(serializeScopeType(supportLevel.scopeType), length); return new ScopeSupportTreeItem( supportLevel, isEqual(supportLevel.scopeType, this.scopeVisualizer.scopeType), + length, ); - }); + }) + .sort(treeItemComparator); } private getScopeSupportInfo(scopeSupport: ScopeSupport): ScopeSupportInfo[] { @@ -270,10 +257,10 @@ function getSupportCategories(): ( | SelectedCategoryTreeItem )[] { return [ + new SelectedCategoryTreeItem(), new SupportCategoryTreeItem(ScopeSupport.supportedAndPresentInEditor), new SupportCategoryTreeItem(ScopeSupport.supportedButNotPresentInEditor), new SupportCategoryTreeItem(ScopeSupport.unsupported), - new SelectedCategoryTreeItem(), ]; } @@ -289,6 +276,7 @@ class ScopeSupportTreeItem extends TreeItem { constructor( public readonly scopeTypeInfo: ScopeTypeInfo, isVisualized: boolean, + public priority: number = 0, ) { let label: string; let tooltip: string; @@ -378,7 +366,7 @@ class SupportCategoryTreeItem extends TreeItem { case ScopeSupport.supportedButNotPresentInEditor: label = "Supported"; description = "but not present in active editor"; - collapsibleState = TreeItemCollapsibleState.Expanded; + collapsibleState = TreeItemCollapsibleState.Collapsed; break; case ScopeSupport.unsupported: label = "Unsupported"; @@ -394,7 +382,7 @@ class SupportCategoryTreeItem extends TreeItem { class SelectedCategoryTreeItem extends TreeItem { constructor() { - super("Selected", TreeItemCollapsibleState.Collapsed); + super("Selected", TreeItemCollapsibleState.Expanded); this.description = "scopes"; } } @@ -455,3 +443,25 @@ function getSmallestTargetLength( } return length ?? -1; } + +function treeItemComparator(a: ScopeSupportTreeItem, b: ScopeSupportTreeItem) { + // First by priority (lower number is higher priority) + if (a.priority !== b.priority) { + return a.priority - b.priority; + } + + // Scopes with no spoken form are sorted to the bottom + if (a.scopeTypeInfo.spokenForm.type !== b.scopeTypeInfo.spokenForm.type) { + return a.scopeTypeInfo.spokenForm.type === "error" ? 1 : -1; + } + + // Then language-specific scopes are sorted to the top + if ( + a.scopeTypeInfo.isLanguageSpecific !== b.scopeTypeInfo.isLanguageSpecific + ) { + return a.scopeTypeInfo.isLanguageSpecific ? -1 : 1; + } + + // Then alphabetical by label + return a.label.label.localeCompare(b.label.label); +} From e01e57f421ba1e7aec063fe87f012ce632309f2f Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sat, 6 Sep 2025 13:36:30 +0200 Subject: [PATCH 04/13] Move range utils to common --- packages/common/src/index.ts | 1 + .../{cursorless-engine => common}/src/util/rangeUtils.ts | 0 .../modifiers/scopeHandlers/shouldYieldScope.ts | 3 +-- .../scopeHandlers/util/getCollectionItemRemovalRange.ts | 3 +-- .../src/processTargets/targets/BoundedParagraphTarget.ts | 3 +-- .../src/processTargets/targets/DestinationImpl.ts | 3 +-- .../src/processTargets/targets/LineTarget.ts | 8 ++++++-- .../src/processTargets/targets/ParagraphTarget.ts | 8 ++++++-- .../DelimitedSequenceInsertionRemovalBehavior.ts | 2 +- .../TokenInsertionRemovalBehavior.ts | 3 +-- .../insertionRemovalBehaviors/getSmartRemovalTarget.ts | 2 +- packages/cursorless-vscode/src/ScopeTreeProvider.ts | 4 +--- 12 files changed, 21 insertions(+), 19 deletions(-) rename packages/{cursorless-engine => common}/src/util/rangeUtils.ts (100%) diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 7744e99d3c..6854cd3ae2 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -110,6 +110,7 @@ export * from "./util/Notifier"; export * from "./util/object"; export * from "./util/omitByDeep"; export * from "./util/prettifyLanguageName"; +export * from "./util/rangeUtils"; export * from "./util/regex"; export * from "./util/selectionsEqual"; export * from "./util/serializedMarksToTokenHats"; diff --git a/packages/cursorless-engine/src/util/rangeUtils.ts b/packages/common/src/util/rangeUtils.ts similarity index 100% rename from packages/cursorless-engine/src/util/rangeUtils.ts rename to packages/common/src/util/rangeUtils.ts diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/shouldYieldScope.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/shouldYieldScope.ts index 283259baed..e5f2792092 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/shouldYieldScope.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/shouldYieldScope.ts @@ -1,6 +1,5 @@ import type { Direction, Position } from "@cursorless/common"; -import { Range } from "@cursorless/common"; -import { strictlyContains } from "../../../util/rangeUtils"; +import { Range, strictlyContains } from "@cursorless/common"; import { compareTargetScopes } from "./compareTargetScopes"; import type { TargetScope } from "./scope.types"; import type { ScopeIteratorRequirements } from "./scopeHandler.types"; diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/util/getCollectionItemRemovalRange.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/util/getCollectionItemRemovalRange.ts index e08b72b82a..e2afa562c6 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/util/getCollectionItemRemovalRange.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/util/getCollectionItemRemovalRange.ts @@ -1,6 +1,5 @@ import type { Range, TextEditor } from "@cursorless/common"; - -import { getRangeLength } from "../../../../util/rangeUtils"; +import { getRangeLength } from "@cursorless/common"; /** * Picks which of the leading and trailing delimiter ranges to use for removal when both are present. diff --git a/packages/cursorless-engine/src/processTargets/targets/BoundedParagraphTarget.ts b/packages/cursorless-engine/src/processTargets/targets/BoundedParagraphTarget.ts index 291249c197..51f13fd8bd 100644 --- a/packages/cursorless-engine/src/processTargets/targets/BoundedParagraphTarget.ts +++ b/packages/cursorless-engine/src/processTargets/targets/BoundedParagraphTarget.ts @@ -1,7 +1,6 @@ -import { toLineRange, type Range } from "@cursorless/common"; +import { expandToFullLine, toLineRange, type Range } from "@cursorless/common"; import type { InteriorTarget, ParagraphTarget } from "."; import type { TextualType } from "../../typings/target.types"; -import { expandToFullLine } from "../../util/rangeUtils"; import type { MinimumTargetParameters } from "./BaseTarget"; import { BaseTarget } from "./BaseTarget"; import { LineTarget } from "./LineTarget"; diff --git a/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts b/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts index 2070544772..2d8026435e 100644 --- a/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts +++ b/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts @@ -1,12 +1,11 @@ import type { InsertionMode, Selection, TextEditor } from "@cursorless/common"; -import { Range } from "@cursorless/common"; +import { Range, union } from "@cursorless/common"; import type { EditWithRangeUpdater } from "../../typings/Types"; import type { Destination, EditNewActionType, Target, } from "../../typings/target.types"; -import { union } from "../../util/rangeUtils"; export class DestinationImpl implements Destination { public readonly contentRange: Range; diff --git a/packages/cursorless-engine/src/processTargets/targets/LineTarget.ts b/packages/cursorless-engine/src/processTargets/targets/LineTarget.ts index a34c8be7af..0207acc6e4 100644 --- a/packages/cursorless-engine/src/processTargets/targets/LineTarget.ts +++ b/packages/cursorless-engine/src/processTargets/targets/LineTarget.ts @@ -1,7 +1,11 @@ import type { TextEditor } from "@cursorless/common"; -import { Position, Range, toLineRange } from "@cursorless/common"; +import { + expandToFullLine, + Position, + Range, + toLineRange, +} from "@cursorless/common"; import type { TextualType } from "../../typings/target.types"; -import { expandToFullLine } from "../../util/rangeUtils"; import { tryConstructTarget } from "../../util/tryConstructTarget"; import type { CommonTargetParameters } from "./BaseTarget"; import { BaseTarget } from "./BaseTarget"; diff --git a/packages/cursorless-engine/src/processTargets/targets/ParagraphTarget.ts b/packages/cursorless-engine/src/processTargets/targets/ParagraphTarget.ts index affb0a87a6..c6ae74bbff 100644 --- a/packages/cursorless-engine/src/processTargets/targets/ParagraphTarget.ts +++ b/packages/cursorless-engine/src/processTargets/targets/ParagraphTarget.ts @@ -4,9 +4,13 @@ import type { TextEditor, TextLine, } from "@cursorless/common"; -import { Position, Range, toLineRange } from "@cursorless/common"; +import { + expandToFullLine, + Position, + Range, + toLineRange, +} from "@cursorless/common"; import type { TextualType } from "../../typings/target.types"; -import { expandToFullLine } from "../../util/rangeUtils"; import type { CommonTargetParameters } from "./BaseTarget"; import { BaseTarget } from "./BaseTarget"; import { constructLineTarget, LineTarget } from "./LineTarget"; diff --git a/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/DelimitedSequenceInsertionRemovalBehavior.ts b/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/DelimitedSequenceInsertionRemovalBehavior.ts index a518aa2192..40aa29a928 100644 --- a/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/DelimitedSequenceInsertionRemovalBehavior.ts +++ b/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/DelimitedSequenceInsertionRemovalBehavior.ts @@ -1,6 +1,6 @@ import type { Range } from "@cursorless/common"; +import { union } from "@cursorless/common"; import type { Target } from "../../../../typings/target.types"; -import { union } from "../../../../util/rangeUtils"; /** * Constructs a removal range for the given target that includes either the diff --git a/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/TokenInsertionRemovalBehavior.ts b/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/TokenInsertionRemovalBehavior.ts index 29a6f15047..f188781a24 100644 --- a/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/TokenInsertionRemovalBehavior.ts +++ b/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/TokenInsertionRemovalBehavior.ts @@ -1,7 +1,6 @@ import type { TextEditor } from "@cursorless/common"; -import { Range } from "@cursorless/common"; +import { expandToFullLine, Range, union } from "@cursorless/common"; import type { Target } from "../../../../typings/target.types"; -import { expandToFullLine, union } from "../../../../util/rangeUtils"; import { PlainTarget } from "../../PlainTarget"; const leadingDelimiters = ['"', "'", "(", "[", "{", "<"]; diff --git a/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/getSmartRemovalTarget.ts b/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/getSmartRemovalTarget.ts index da7b91bdd3..bc506e7009 100644 --- a/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/getSmartRemovalTarget.ts +++ b/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/getSmartRemovalTarget.ts @@ -1,6 +1,6 @@ import type { Range, TextDocument } from "@cursorless/common"; +import { union } from "@cursorless/common"; import type { Target } from "../../../../typings/target.types"; -import { union } from "../../../../util/rangeUtils"; import { LineTarget } from "../../LineTarget"; import { ParagraphTarget } from "../../ParagraphTarget"; import { TokenTarget } from "../../TokenTarget"; diff --git a/packages/cursorless-vscode/src/ScopeTreeProvider.ts b/packages/cursorless-vscode/src/ScopeTreeProvider.ts index 56412c2e46..40beff384e 100644 --- a/packages/cursorless-vscode/src/ScopeTreeProvider.ts +++ b/packages/cursorless-vscode/src/ScopeTreeProvider.ts @@ -4,7 +4,6 @@ import type { ScopeProvider, ScopeRanges, ScopeSupportInfo, - ScopeType, ScopeTypeInfo, Selection, TextEditor, @@ -14,6 +13,7 @@ import { DOCS_URL, ScopeSupport, disposableFrom, + getRangeLength, serializeScopeType, uriEncodeHashId, } from "@cursorless/common"; @@ -27,7 +27,6 @@ import type { Disposable, Event, ExtensionContext, - TextEditorSelectionChangeEvent, TreeDataProvider, TreeItemLabel, TreeView, @@ -47,7 +46,6 @@ import type { ScopeVisualizer, VisualizationType, } from "./ScopeVisualizerCommandApi"; -import { getRangeLength } from "../../cursorless-engine/src/util/rangeUtils"; export const DONT_SHOW_TALON_UPDATE_MESSAGE_KEY = "dontShowUpdateTalonMessage"; From 121fda96fbceabb587505fa97b32f5e4c8f158db Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sat, 6 Sep 2025 13:39:11 +0200 Subject: [PATCH 05/13] Added type to imports --- .../src/scopeProviders/ScopeRangeProvider.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/cursorless-engine/src/scopeProviders/ScopeRangeProvider.ts b/packages/cursorless-engine/src/scopeProviders/ScopeRangeProvider.ts index cb38f0b133..971e234af8 100644 --- a/packages/cursorless-engine/src/scopeProviders/ScopeRangeProvider.ts +++ b/packages/cursorless-engine/src/scopeProviders/ScopeRangeProvider.ts @@ -1,13 +1,12 @@ -import { +import type { IterationScopeRangeConfig, IterationScopeRanges, - Range, ScopeRangeConfig, ScopeRanges, ScopeType, TextEditor, } from "@cursorless/common"; - +import { Range } from "@cursorless/common"; import type { ModifierStageFactory } from "../processTargets/ModifierStageFactory"; import type { ScopeHandlerFactory } from "../processTargets/modifiers/scopeHandlers/ScopeHandlerFactory"; import { getIterationRange } from "./getIterationRange"; From 3346be5871294bff99a46a33c3da95fe88f93953 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sat, 6 Sep 2025 13:45:01 +0200 Subject: [PATCH 06/13] Added doc comment --- packages/common/src/types/ScopeProvider.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/common/src/types/ScopeProvider.ts b/packages/common/src/types/ScopeProvider.ts index e100371ad2..6d25b7613d 100644 --- a/packages/common/src/types/ScopeProvider.ts +++ b/packages/common/src/types/ScopeProvider.ts @@ -19,6 +19,13 @@ export interface ScopeProvider { config: ScopeRangeConfig, ) => ScopeRanges[]; + /** + * Get the scope ranges for the given editor and range. + * @param editor The editor + * @param scopeType The scope type to get ranges for + * @param range The range to get scope ranges for + * @returns A list of scope ranges + */ provideScopeRangesForRange( editor: TextEditor, scopeType: ScopeType, From 9317b9bb79675ea02a09d4af288c595b88d1d9d5 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sun, 7 Sep 2025 10:24:10 +0200 Subject: [PATCH 07/13] Use icons for intersection instead of category --- packages/common/src/index.ts | 1 - .../scopeHandlers/shouldYieldScope.ts | 3 +- .../util/getCollectionItemRemovalRange.ts | 2 +- .../targets/BoundedParagraphTarget.ts | 3 +- .../processTargets/targets/DestinationImpl.ts | 3 +- .../src/processTargets/targets/LineTarget.ts | 8 +- .../processTargets/targets/ParagraphTarget.ts | 8 +- ...limitedSequenceInsertionRemovalBehavior.ts | 2 +- .../TokenInsertionRemovalBehavior.ts | 3 +- .../getSmartRemovalTarget.ts | 2 +- .../src/util/rangeUtils.ts | 0 .../src/ScopeTreeProvider.ts | 216 +++++++----------- 12 files changed, 97 insertions(+), 154 deletions(-) rename packages/{common => cursorless-engine}/src/util/rangeUtils.ts (100%) diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 6854cd3ae2..7744e99d3c 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -110,7 +110,6 @@ export * from "./util/Notifier"; export * from "./util/object"; export * from "./util/omitByDeep"; export * from "./util/prettifyLanguageName"; -export * from "./util/rangeUtils"; export * from "./util/regex"; export * from "./util/selectionsEqual"; export * from "./util/serializedMarksToTokenHats"; diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/shouldYieldScope.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/shouldYieldScope.ts index e5f2792092..283259baed 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/shouldYieldScope.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/shouldYieldScope.ts @@ -1,5 +1,6 @@ import type { Direction, Position } from "@cursorless/common"; -import { Range, strictlyContains } from "@cursorless/common"; +import { Range } from "@cursorless/common"; +import { strictlyContains } from "../../../util/rangeUtils"; import { compareTargetScopes } from "./compareTargetScopes"; import type { TargetScope } from "./scope.types"; import type { ScopeIteratorRequirements } from "./scopeHandler.types"; diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/util/getCollectionItemRemovalRange.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/util/getCollectionItemRemovalRange.ts index e2afa562c6..d06cee56ca 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/util/getCollectionItemRemovalRange.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/util/getCollectionItemRemovalRange.ts @@ -1,5 +1,5 @@ import type { Range, TextEditor } from "@cursorless/common"; -import { getRangeLength } from "@cursorless/common"; +import { getRangeLength } from "../../../../util/rangeUtils"; /** * Picks which of the leading and trailing delimiter ranges to use for removal when both are present. diff --git a/packages/cursorless-engine/src/processTargets/targets/BoundedParagraphTarget.ts b/packages/cursorless-engine/src/processTargets/targets/BoundedParagraphTarget.ts index 51f13fd8bd..291249c197 100644 --- a/packages/cursorless-engine/src/processTargets/targets/BoundedParagraphTarget.ts +++ b/packages/cursorless-engine/src/processTargets/targets/BoundedParagraphTarget.ts @@ -1,6 +1,7 @@ -import { expandToFullLine, toLineRange, type Range } from "@cursorless/common"; +import { toLineRange, type Range } from "@cursorless/common"; import type { InteriorTarget, ParagraphTarget } from "."; import type { TextualType } from "../../typings/target.types"; +import { expandToFullLine } from "../../util/rangeUtils"; import type { MinimumTargetParameters } from "./BaseTarget"; import { BaseTarget } from "./BaseTarget"; import { LineTarget } from "./LineTarget"; diff --git a/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts b/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts index 2d8026435e..2070544772 100644 --- a/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts +++ b/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts @@ -1,11 +1,12 @@ import type { InsertionMode, Selection, TextEditor } from "@cursorless/common"; -import { Range, union } from "@cursorless/common"; +import { Range } from "@cursorless/common"; import type { EditWithRangeUpdater } from "../../typings/Types"; import type { Destination, EditNewActionType, Target, } from "../../typings/target.types"; +import { union } from "../../util/rangeUtils"; export class DestinationImpl implements Destination { public readonly contentRange: Range; diff --git a/packages/cursorless-engine/src/processTargets/targets/LineTarget.ts b/packages/cursorless-engine/src/processTargets/targets/LineTarget.ts index 0207acc6e4..a34c8be7af 100644 --- a/packages/cursorless-engine/src/processTargets/targets/LineTarget.ts +++ b/packages/cursorless-engine/src/processTargets/targets/LineTarget.ts @@ -1,11 +1,7 @@ import type { TextEditor } from "@cursorless/common"; -import { - expandToFullLine, - Position, - Range, - toLineRange, -} from "@cursorless/common"; +import { Position, Range, toLineRange } from "@cursorless/common"; import type { TextualType } from "../../typings/target.types"; +import { expandToFullLine } from "../../util/rangeUtils"; import { tryConstructTarget } from "../../util/tryConstructTarget"; import type { CommonTargetParameters } from "./BaseTarget"; import { BaseTarget } from "./BaseTarget"; diff --git a/packages/cursorless-engine/src/processTargets/targets/ParagraphTarget.ts b/packages/cursorless-engine/src/processTargets/targets/ParagraphTarget.ts index c6ae74bbff..affb0a87a6 100644 --- a/packages/cursorless-engine/src/processTargets/targets/ParagraphTarget.ts +++ b/packages/cursorless-engine/src/processTargets/targets/ParagraphTarget.ts @@ -4,13 +4,9 @@ import type { TextEditor, TextLine, } from "@cursorless/common"; -import { - expandToFullLine, - Position, - Range, - toLineRange, -} from "@cursorless/common"; +import { Position, Range, toLineRange } from "@cursorless/common"; import type { TextualType } from "../../typings/target.types"; +import { expandToFullLine } from "../../util/rangeUtils"; import type { CommonTargetParameters } from "./BaseTarget"; import { BaseTarget } from "./BaseTarget"; import { constructLineTarget, LineTarget } from "./LineTarget"; diff --git a/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/DelimitedSequenceInsertionRemovalBehavior.ts b/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/DelimitedSequenceInsertionRemovalBehavior.ts index 40aa29a928..a518aa2192 100644 --- a/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/DelimitedSequenceInsertionRemovalBehavior.ts +++ b/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/DelimitedSequenceInsertionRemovalBehavior.ts @@ -1,6 +1,6 @@ import type { Range } from "@cursorless/common"; -import { union } from "@cursorless/common"; import type { Target } from "../../../../typings/target.types"; +import { union } from "../../../../util/rangeUtils"; /** * Constructs a removal range for the given target that includes either the diff --git a/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/TokenInsertionRemovalBehavior.ts b/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/TokenInsertionRemovalBehavior.ts index f188781a24..29a6f15047 100644 --- a/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/TokenInsertionRemovalBehavior.ts +++ b/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/TokenInsertionRemovalBehavior.ts @@ -1,6 +1,7 @@ import type { TextEditor } from "@cursorless/common"; -import { expandToFullLine, Range, union } from "@cursorless/common"; +import { Range } from "@cursorless/common"; import type { Target } from "../../../../typings/target.types"; +import { expandToFullLine, union } from "../../../../util/rangeUtils"; import { PlainTarget } from "../../PlainTarget"; const leadingDelimiters = ['"', "'", "(", "[", "{", "<"]; diff --git a/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/getSmartRemovalTarget.ts b/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/getSmartRemovalTarget.ts index bc506e7009..da7b91bdd3 100644 --- a/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/getSmartRemovalTarget.ts +++ b/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/getSmartRemovalTarget.ts @@ -1,6 +1,6 @@ import type { Range, TextDocument } from "@cursorless/common"; -import { union } from "@cursorless/common"; import type { Target } from "../../../../typings/target.types"; +import { union } from "../../../../util/rangeUtils"; import { LineTarget } from "../../LineTarget"; import { ParagraphTarget } from "../../ParagraphTarget"; import { TokenTarget } from "../../TokenTarget"; diff --git a/packages/common/src/util/rangeUtils.ts b/packages/cursorless-engine/src/util/rangeUtils.ts similarity index 100% rename from packages/common/src/util/rangeUtils.ts rename to packages/cursorless-engine/src/util/rangeUtils.ts diff --git a/packages/cursorless-vscode/src/ScopeTreeProvider.ts b/packages/cursorless-vscode/src/ScopeTreeProvider.ts index 40beff384e..4b1672ce53 100644 --- a/packages/cursorless-vscode/src/ScopeTreeProvider.ts +++ b/packages/cursorless-vscode/src/ScopeTreeProvider.ts @@ -4,6 +4,7 @@ import type { ScopeProvider, ScopeRanges, ScopeSupportInfo, + ScopeType, ScopeTypeInfo, Selection, TextEditor, @@ -13,7 +14,6 @@ import { DOCS_URL, ScopeSupport, disposableFrom, - getRangeLength, serializeScopeType, uriEncodeHashId, } from "@cursorless/common"; @@ -54,7 +54,6 @@ export class ScopeTreeProvider implements TreeDataProvider { private treeView: TreeView; private supportLevels: ScopeSupportInfo[] = []; private shownUpdateTalonMessage = false; - private selection: Selection | null = null; private _onDidChangeTreeData: EventEmitter< MyTreeItem | undefined | null | void @@ -109,21 +108,12 @@ export class ScopeTreeProvider implements TreeDataProvider { this.visibleDisposable = disposableFrom( this.scopeProvider.onDidChangeScopeSupport((supportLevels) => { this.supportLevels = supportLevels; - const editor = ide().activeTextEditor; - this.selection = - editor != null && editor.selections.length === 1 - ? editor.selections[0] - : null; this._onDidChangeTreeData.fire(); }), this.scopeVisualizer.onDidChangeScopeType(() => { this._onDidChangeTreeData.fire(); }), - this.vscodeApi.window.onDidChangeTextEditorSelection((e) => { - this.selection = - e.selections.length === 1 - ? fromVscodeSelection(e.selections[0]) - : null; + this.vscodeApi.window.onDidChangeTextEditorSelection(() => { this._onDidChangeTreeData.fire(); }), ); @@ -143,10 +133,6 @@ export class ScopeTreeProvider implements TreeDataProvider { return this.getScopeTypesWithSupport(element.scopeSupport); } - if (element instanceof SelectedCategoryTreeItem) { - return this.getSelectedScopeTypes(); - } - throw new Error("Unexpected element"); } @@ -182,67 +168,89 @@ export class ScopeTreeProvider implements TreeDataProvider { } } + private getIntersectionIcon( + editor: TextEditor, + selection: Selection, + scopeType: ScopeType, + ): string | undefined { + const scopes = this.scopeProvider.provideScopeRangesForRange( + editor, + scopeType, + selection, + ); + + for (const scope of scopes) { + for (const target of scope.targets) { + // Scope target exactly matches selection + if (target.contentRange.isRangeEqual(selection)) { + return "🎯"; + } + // Scope target contains selection + if (target.contentRange.contains(selection)) { + return "📦"; + } + } + } + + return undefined; + } + private getScopeTypesWithSupport( scopeSupport: ScopeSupport, ): ScopeSupportTreeItem[] { - return this.getScopeSupportInfo(scopeSupport) - .map( + const getIntersectionIcon = (() => { + if (scopeSupport !== ScopeSupport.supportedAndPresentInEditor) { + return null; + } + const editor = ide().activeTextEditor; + if (editor == null || editor.selections.length !== 1) { + return null; + } + const selection = editor.selections[0]; + return (scopeType: ScopeType) => { + return this.getIntersectionIcon(editor, selection, scopeType); + }; + })(); + + return this.supportLevels + .filter( (supportLevel) => - new ScopeSupportTreeItem( - supportLevel, - isEqual(supportLevel.scopeType, this.scopeVisualizer.scopeType), + supportLevel.support === scopeSupport && + // Skip scope if it doesn't have a spoken form and it's private. That + // is the default state for scopes that are private; we don't want to + // show these to the user. + !( + supportLevel.spokenForm.type === "error" && + supportLevel.spokenForm.isPrivate ), ) - .sort(treeItemComparator); - } - - private getSelectedScopeTypes(): ScopeSupportTreeItem[] { - if (this.selection == null) { - return []; - } - - const editor = ide().activeTextEditor; - const selection = this.selection; - - if (editor == null) { - return []; - } - - return this.getScopeSupportInfo(ScopeSupport.supportedAndPresentInEditor) .map((supportLevel) => { - const scopes = this.scopeProvider.provideScopeRangesForRange( - editor, - supportLevel.scopeType, - selection, - ); - return { - supportLevel, - length: getSmallestTargetLength(editor, selection, scopes), - }; - }) - .filter(({ length }) => length > -1) - .map(({ supportLevel, length }) => { + const intersectionIcon = getIntersectionIcon?.(supportLevel.scopeType); return new ScopeSupportTreeItem( supportLevel, isEqual(supportLevel.scopeType, this.scopeVisualizer.scopeType), - length, + intersectionIcon, ); }) - .sort(treeItemComparator); - } + .sort((a, b) => { + // Scopes with no spoken form are sorted to the bottom + if ( + a.scopeTypeInfo.spokenForm.type !== b.scopeTypeInfo.spokenForm.type + ) { + return a.scopeTypeInfo.spokenForm.type === "error" ? 1 : -1; + } - private getScopeSupportInfo(scopeSupport: ScopeSupport): ScopeSupportInfo[] { - return this.supportLevels.filter( - (supportLevel) => - supportLevel.support === scopeSupport && - // Skip scope if it doesn't have a spoken form and it's private. That - // is the default state for scopes that are private; we don't want to - // show these to the user. - !( - supportLevel.spokenForm.type === "error" && - supportLevel.spokenForm.isPrivate - ), - ); + // Then language-specific scopes are sorted to the top + if ( + a.scopeTypeInfo.isLanguageSpecific !== + b.scopeTypeInfo.isLanguageSpecific + ) { + return a.scopeTypeInfo.isLanguageSpecific ? -1 : 1; + } + + // Then alphabetical by label + return a.label.label.localeCompare(b.label.label); + }); } dispose() { @@ -250,12 +258,8 @@ export class ScopeTreeProvider implements TreeDataProvider { } } -function getSupportCategories(): ( - | SupportCategoryTreeItem - | SelectedCategoryTreeItem -)[] { +function getSupportCategories(): SupportCategoryTreeItem[] { return [ - new SelectedCategoryTreeItem(), new SupportCategoryTreeItem(ScopeSupport.supportedAndPresentInEditor), new SupportCategoryTreeItem(ScopeSupport.supportedButNotPresentInEditor), new SupportCategoryTreeItem(ScopeSupport.unsupported), @@ -274,7 +278,7 @@ class ScopeSupportTreeItem extends TreeItem { constructor( public readonly scopeTypeInfo: ScopeTypeInfo, isVisualized: boolean, - public priority: number = 0, + intersectionIcon: string | undefined, ) { let label: string; let tooltip: string; @@ -300,7 +304,10 @@ class ScopeSupportTreeItem extends TreeItem { ); this.tooltip = tooltip == null ? tooltip : new MarkdownString(tooltip); - this.description = scopeTypeInfo.humanReadableName; + this.description = + intersectionIcon != null + ? `${intersectionIcon} ${scopeTypeInfo.humanReadableName}` + : scopeTypeInfo.humanReadableName; this.command = isVisualized ? { @@ -321,9 +328,8 @@ class ScopeSupportTreeItem extends TreeItem { if (scopeTypeInfo.isLanguageSpecific) { const languageId = window.activeTextEditor?.document.languageId; if (languageId != null) { - const fileExtension = getLanguageExtensionSampleFromLanguageId( - window.activeTextEditor!.document.languageId, - ); + const fileExtension = + getLanguageExtensionSampleFromLanguageId(languageId); if (fileExtension != null) { this.resourceUri = URI.parse( "cursorless-dummy://dummy/dummy" + fileExtension, @@ -364,7 +370,7 @@ class SupportCategoryTreeItem extends TreeItem { case ScopeSupport.supportedButNotPresentInEditor: label = "Supported"; description = "but not present in active editor"; - collapsibleState = TreeItemCollapsibleState.Collapsed; + collapsibleState = TreeItemCollapsibleState.Expanded; break; case ScopeSupport.unsupported: label = "Unsupported"; @@ -378,17 +384,7 @@ class SupportCategoryTreeItem extends TreeItem { } } -class SelectedCategoryTreeItem extends TreeItem { - constructor() { - super("Selected", TreeItemCollapsibleState.Expanded); - this.description = "scopes"; - } -} - -type MyTreeItem = - | ScopeSupportTreeItem - | SupportCategoryTreeItem - | SelectedCategoryTreeItem; +type MyTreeItem = ScopeSupportTreeItem | SupportCategoryTreeItem; /** * Get file extension example from vscode [Language Id](https://code.visualstudio.com/docs/languages/identifiers) @@ -415,51 +411,3 @@ export function getLanguageExtensionSampleFromLanguageId( } } } - -function getSmallestTargetLength( - editor: TextEditor, - selection: Range, - scopes: ScopeRanges[], -): number { - let length: number | null = null; - for (const scope of scopes) { - for (const target of scope.targets) { - // Don't use targets smaller than the selection - if ( - selection.contains(target.contentRange) && - !selection.isRangeEqual(target.contentRange) - ) { - continue; - } - const targetIntersection = target.contentRange.intersection(selection); - if (targetIntersection == null) { - continue; - } - const targetLength = getRangeLength(editor, target.contentRange); - length = length != null ? Math.min(length, targetLength) : targetLength; - } - } - return length ?? -1; -} - -function treeItemComparator(a: ScopeSupportTreeItem, b: ScopeSupportTreeItem) { - // First by priority (lower number is higher priority) - if (a.priority !== b.priority) { - return a.priority - b.priority; - } - - // Scopes with no spoken form are sorted to the bottom - if (a.scopeTypeInfo.spokenForm.type !== b.scopeTypeInfo.spokenForm.type) { - return a.scopeTypeInfo.spokenForm.type === "error" ? 1 : -1; - } - - // Then language-specific scopes are sorted to the top - if ( - a.scopeTypeInfo.isLanguageSpecific !== b.scopeTypeInfo.isLanguageSpecific - ) { - return a.scopeTypeInfo.isLanguageSpecific ? -1 : 1; - } - - // Then alphabetical by label - return a.label.label.localeCompare(b.label.label); -} From fd3512d49ccf4999d9473639cea73d3f761cfeff Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sun, 7 Sep 2025 10:29:00 +0200 Subject: [PATCH 08/13] Clean up --- .../src/ScopeTreeProvider.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/cursorless-vscode/src/ScopeTreeProvider.ts b/packages/cursorless-vscode/src/ScopeTreeProvider.ts index 4b1672ce53..83f1f8dec0 100644 --- a/packages/cursorless-vscode/src/ScopeTreeProvider.ts +++ b/packages/cursorless-vscode/src/ScopeTreeProvider.ts @@ -224,27 +224,27 @@ export class ScopeTreeProvider implements TreeDataProvider { supportLevel.spokenForm.isPrivate ), ) - .map((supportLevel) => { - const intersectionIcon = getIntersectionIcon?.(supportLevel.scopeType); - return new ScopeSupportTreeItem( - supportLevel, - isEqual(supportLevel.scopeType, this.scopeVisualizer.scopeType), - intersectionIcon, - ); - }) + .map( + (supportLevel) => + new ScopeSupportTreeItem( + supportLevel, + isEqual(supportLevel.scopeType, this.scopeVisualizer.scopeType), + getIntersectionIcon?.(supportLevel.scopeType), + ), + ) .sort((a, b) => { - // Scopes with no spoken form are sorted to the bottom if ( a.scopeTypeInfo.spokenForm.type !== b.scopeTypeInfo.spokenForm.type ) { + // Scopes with no spoken form are sorted to the bottom return a.scopeTypeInfo.spokenForm.type === "error" ? 1 : -1; } - // Then language-specific scopes are sorted to the top if ( a.scopeTypeInfo.isLanguageSpecific !== b.scopeTypeInfo.isLanguageSpecific ) { + // Then language-specific scopes are sorted to the top return a.scopeTypeInfo.isLanguageSpecific ? -1 : 1; } From 4503f7baf5cec36f0177a524e133af225a1ac7ad Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sun, 7 Sep 2025 10:31:51 +0200 Subject: [PATCH 09/13] Fix imports --- packages/cursorless-vscode/src/ScopeTreeProvider.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/cursorless-vscode/src/ScopeTreeProvider.ts b/packages/cursorless-vscode/src/ScopeTreeProvider.ts index 83f1f8dec0..e59600d5d1 100644 --- a/packages/cursorless-vscode/src/ScopeTreeProvider.ts +++ b/packages/cursorless-vscode/src/ScopeTreeProvider.ts @@ -1,8 +1,6 @@ import type { CursorlessCommandId, - Range, ScopeProvider, - ScopeRanges, ScopeSupportInfo, ScopeType, ScopeTypeInfo, @@ -21,7 +19,7 @@ import { ide, type CustomSpokenFormGenerator, } from "@cursorless/cursorless-engine"; -import { fromVscodeSelection, type VscodeApi } from "@cursorless/vscode-common"; +import { type VscodeApi } from "@cursorless/vscode-common"; import { isEqual } from "lodash-es"; import type { Disposable, From 447a9a18d0e4101def0733b04bc20a87ac15650a Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sun, 7 Sep 2025 10:37:05 +0200 Subject: [PATCH 10/13] Move private function --- .../src/ScopeTreeProvider.ts | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/packages/cursorless-vscode/src/ScopeTreeProvider.ts b/packages/cursorless-vscode/src/ScopeTreeProvider.ts index e59600d5d1..22020089c6 100644 --- a/packages/cursorless-vscode/src/ScopeTreeProvider.ts +++ b/packages/cursorless-vscode/src/ScopeTreeProvider.ts @@ -166,33 +166,6 @@ export class ScopeTreeProvider implements TreeDataProvider { } } - private getIntersectionIcon( - editor: TextEditor, - selection: Selection, - scopeType: ScopeType, - ): string | undefined { - const scopes = this.scopeProvider.provideScopeRangesForRange( - editor, - scopeType, - selection, - ); - - for (const scope of scopes) { - for (const target of scope.targets) { - // Scope target exactly matches selection - if (target.contentRange.isRangeEqual(selection)) { - return "🎯"; - } - // Scope target contains selection - if (target.contentRange.contains(selection)) { - return "📦"; - } - } - } - - return undefined; - } - private getScopeTypesWithSupport( scopeSupport: ScopeSupport, ): ScopeSupportTreeItem[] { @@ -251,6 +224,33 @@ export class ScopeTreeProvider implements TreeDataProvider { }); } + private getIntersectionIcon( + editor: TextEditor, + selection: Selection, + scopeType: ScopeType, + ): string | undefined { + const scopes = this.scopeProvider.provideScopeRangesForRange( + editor, + scopeType, + selection, + ); + + for (const scope of scopes) { + for (const target of scope.targets) { + // Scope target exactly matches selection + if (target.contentRange.isRangeEqual(selection)) { + return "🎯"; + } + // Scope target contains selection + if (target.contentRange.contains(selection)) { + return "📦"; + } + } + } + + return undefined; + } + dispose() { this.visibleDisposable?.dispose(); } From d9d853355bdba58c430ca68ea999ee7277f3a5af Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sun, 7 Sep 2025 10:43:48 +0200 Subject: [PATCH 11/13] Renamed to containment icon --- .../cursorless-vscode/src/ScopeTreeProvider.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/cursorless-vscode/src/ScopeTreeProvider.ts b/packages/cursorless-vscode/src/ScopeTreeProvider.ts index 22020089c6..df38f06cba 100644 --- a/packages/cursorless-vscode/src/ScopeTreeProvider.ts +++ b/packages/cursorless-vscode/src/ScopeTreeProvider.ts @@ -169,7 +169,7 @@ export class ScopeTreeProvider implements TreeDataProvider { private getScopeTypesWithSupport( scopeSupport: ScopeSupport, ): ScopeSupportTreeItem[] { - const getIntersectionIcon = (() => { + const getContainmentIcon = (() => { if (scopeSupport !== ScopeSupport.supportedAndPresentInEditor) { return null; } @@ -179,7 +179,7 @@ export class ScopeTreeProvider implements TreeDataProvider { } const selection = editor.selections[0]; return (scopeType: ScopeType) => { - return this.getIntersectionIcon(editor, selection, scopeType); + return this.getContainmentIcon(editor, selection, scopeType); }; })(); @@ -200,7 +200,7 @@ export class ScopeTreeProvider implements TreeDataProvider { new ScopeSupportTreeItem( supportLevel, isEqual(supportLevel.scopeType, this.scopeVisualizer.scopeType), - getIntersectionIcon?.(supportLevel.scopeType), + getContainmentIcon?.(supportLevel.scopeType), ), ) .sort((a, b) => { @@ -224,7 +224,7 @@ export class ScopeTreeProvider implements TreeDataProvider { }); } - private getIntersectionIcon( + private getContainmentIcon( editor: TextEditor, selection: Selection, scopeType: ScopeType, @@ -276,7 +276,7 @@ class ScopeSupportTreeItem extends TreeItem { constructor( public readonly scopeTypeInfo: ScopeTypeInfo, isVisualized: boolean, - intersectionIcon: string | undefined, + containmentIcon: string | undefined, ) { let label: string; let tooltip: string; @@ -303,8 +303,8 @@ class ScopeSupportTreeItem extends TreeItem { this.tooltip = tooltip == null ? tooltip : new MarkdownString(tooltip); this.description = - intersectionIcon != null - ? `${intersectionIcon} ${scopeTypeInfo.humanReadableName}` + containmentIcon != null + ? `${containmentIcon} ${scopeTypeInfo.humanReadableName}` : scopeTypeInfo.humanReadableName; this.command = isVisualized From 00a0080759f846a1531e475758b2e7e0c663050e Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 11 Sep 2025 09:46:16 +0200 Subject: [PATCH 12/13] Update docs regarding sidebar --- .../src/docs/user/images/sidebar-scopes.png | Bin 0 -> 72677 bytes .../src/docs/user/scope-sidebar.md | 22 +++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 packages/cursorless-org-docs/src/docs/user/images/sidebar-scopes.png diff --git a/packages/cursorless-org-docs/src/docs/user/images/sidebar-scopes.png b/packages/cursorless-org-docs/src/docs/user/images/sidebar-scopes.png new file mode 100644 index 0000000000000000000000000000000000000000..19e651842f110d04a40bdce8a8aae3d62f5c5cca GIT binary patch literal 72677 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 Riif}7Sx<_rp+2Ve^LZ4LL@SBb0$u2eQ4%m7| z1;ac05gja~6hwgE%|f#O>~#y4x+AxiyZhzjL&T5w>UvkmO36L>UsGc9Ci{|LwK03d z`tsl3t^PV;tm&n=a?j1#L3RAREwStUQ2mKqnfqLDB4hx)a-k`>a*4Ew!P9(yRX>dA zeu?pGE{uH`=F`JDzN6D)^PHdjXsfIHN9`ZpP9;}DtgWqAL1O6_kPB96^d9JSAeS4| zV*-!cFKddVPpRqY5x3ECa%yUzZ=L_xFl3FTE4I`sRS79t-%WBb zv?Kbxz}2Xgvx+RuS4%Umr1sh}(M#_9K=(T-Nx3YW@Ojp%gIFmtAj+^409nQzJK^jL87yRA=WCryYn#aC|v+S2E^}> zMJc*{{b}-Cv+S#Bc#fuwdKm#6^GG1W>yDv8qPqV&V*mzFcQ_0DZ@5@79Mv=`eaha| zN;VOEYd|lDQ1Xw7nwI*2Y0r+0p#mHf>TAnUgad%th@J+A!GD~9^!#y4!?phmu zi=VoK8f8GuY4SWqZmu$t=qV>WI$k2@2!N53iu0jf zYn@YZ_`T39s{x~QRAz`x6Mc42-NjXV1)L#kAd=7E5@2L-Y{K*3Cv&^*|J@z%;$;pD zuQ&s1XD87cOnXf#B~^P&=TiSC*7N*-!+PH5k8ROU+RrM9>9v{kU`$nsahz!qJuh^s zi;F|aq;ID~;rQc!w(puZv<~U10g@h;bab#M);P~wDrxOjYVkUpX@!)_9mogxUUx?2 z*a6-EblOZFU=lCQPdErtGz!E#t&o0-4tiT$-1KYe!f4@QqBk`wE2t@ml+xuxfbKA_ zoYd*8JbdU7Ah~Eds$L62EGq~w&{6)1n6e}@z6Nv&X|F>bUl)1H$}pXE-5hxL65*#` z!RLm*2p)vVxPGwl-~9_3QQ_!J`L@Vk^6kI*p|3z5hji(%a|zK)6c8lC)g-D^y-lP5P=>Hw5-8+MD!AO1GhoOj< z_yuoeTAc#{J#QdbRMG4=J;aWr1gH6XLB!+oKrA0uy7e0i1ZNFQ=D*P*n8Zl~H2N=| zO?YX=Eb2!d71IO+m2qVdqHCk5jN!GC-}XYV_)iEX^Iz;Gfpdi5J#a>kK}}^`JuZIp zBId;sm>u%Nq}W>DLcN6*Xlue^sD@xZ4$+%IwBS%!*!w_9S(@5jDHQ7E;7$tvt>gLX z(~kREz9P8nq0<)ghaG>otO^!`^E?N+Y^)7LusS^{)%wq>(GgHK<2}xE$R_iOfsM&z%?LCc4WuH;-hHCW;toX?e(cm)pXYGcQKs zo7-cJ@ot6I!7A@CqbSx{_j^1Ew3KI{hU|!I6Q71eygmO7;oB4J7ts{2u?F zVmg~QyYvJ*jLFnt+w+}KTfobGX-7vkYwMyFXPSU5&yD9KSC?OeGFpFY^gWHTkp@TT zF0HJqs7=i$X5C`&nyDupmCTj?DT`ZU8)sq90wx({oR#&SNQGT<`Psk(Ku?PDeFMx2Rt|>QsNlzqWaigL9tEEM4E2`km5h zY0AEQ`R(en^fhwPQvt!mTrbYl?eTO-d+_wehUx5uzQV)oz*I#E-4qJH4<1C17o8;_ zUysuILvAt9bzx8O+2P}_F|3`{Q~X}y%%dHUg&wse>fOTxt03QXT*wkWWnt#vhEmbr z2<@QUUs?`dXI6B6*J>DQ5k2EypuAq$C!Gx8t=fR8dzC5REc&S>F=r+h*`}L z%uBvOj+hV5R=BbjYSBN-nHVuWwa0C!R0uw|zrD5elzJvE1U37!bWF)iW}N?gGmPnM z>L9#Op+)dZ`y^_UpDfNJf;simA>zcl`z3GVDQ%n?)0ag<&U6Iuzp=?*)nd`T3&+as zW|1zzg&ql1zDH$x`Bbbnnb@@6w)je*SJa}_AoKy}l-*~4=Uil(z=zh$*k8RiH@9Sb z zYUcnk<2SBy(KOuLW68l=x=Im@cCqgt6z|U;_s_+!S?q(Qo$=s_Q?x;r6@b3>#6)-u?kUkj=qB845DWO zbYN7AR?HJ<;GUza5T3*4yV1k$aoqEfuaqE?WJIbwPVzWCNHL5=mSKjAVT3XuR#w=pbh_%() z)YN1aXd8pfqR)Nj6=An_nN9~A$N$sbTSrCNwSB{iD6I@BAOb^oON+$N5&}w!($bAc z2`U}ZNDhM1UD6`mJ#=?UNY}eZyzlFJ?&o^rd)NBb_s{22*K#dq=H#4x?tL7;IuM4O zirNx{cNo9R(R8^32`1mdAPdLtr#~gllQu|6qOToY{vu#58H#xe89w*~wPD%)GJ)j1WyFVIZIZ zrljA~doeUi6QqzdYWnN>{EP%DZ?jQ`Av(+k?s;ufUwdUgNj5fm#P`!c#K*X;(F8#O zTTDS~KL&VJ3$xdgTl7uzbE|qaUrV7IwUQlZxe!5nf^HUKL~F>&+%(W_LVlt8lE4S60dQR>mEz9Ui&HtSc^(geTa28bJ8_JB zsoBg@p{T;W*Lbp?FTj+Q#4YW$2`zEz(;VW9asiLa@svza#qISYM_8}kd*xiRHf6-C zV77RH)>+mm^7b(NsD{4#`+2ai z((?R$@jx4rah{)fEmQe^=!vCc^-iTd=ty{kU{PJ$qCXfB+11C{l@k)LPOs|@80)Eo}!g+-UQn!5J{sL%6AaDD&xidpU6K|bJgMRnBy_;v2xVX^o@Qk$u9KKWAr$jvO}h`-*tU?~d}=}t zn~Osjm$*$Zb(fdI_qG-pIy?LxL3Dlt{Ko15@LnHb4m4w}z$!&ug-^hEy+YPL8D%x!kp*ENg0QSN=HwO4E;DQ|{^WGX6{3bZ zRHmF>*GxbS>@Dl2Ip1EL^OD^{K6V@!8&|WMAGIPMJLOOdU?<2HQrkP>Ue+hg3}A{NzX%u?nPuoo&yAnWja z7=^+n#dk~F&z1Wt*7eB3M~vRPbx9fU$oins%oTh{{dE$I4c5yZu77C($OCTO{v{7l zXugwe)O`PvT^HgYIK4!0y(r*gH$r(-dmpApr+s`hABr=pYB^EuG9bfGo%((a%Y%d3 zThL^8!!K@8QZ9Rf)Lev`F_~LFNw-9a6VNHq&)yp8A}ph_?oe;$Pu}gk=h)39m`Ghh zSH9zXphR1b;oyEWM#|Je%w%;T`f^^?8_NH1VwCCXrX3+~nyvGX|D*Jx1^wv_&7pAqZQ}*o#jQQpT9dFI19-4KNuI1M` zfj95z;M7=dW=abv)iqYJC(^9SNh$JJA93tpti2<8#_`7*mo@jpEg zh5U57lMUAS9)KZHOnAb>1^)I_TKr<-oKb8fwEg40FdOcKo}@-|nmbzetenm$ckW~4qjDn2iuRcuyuykM1EGpX!E zQagXE<=_BC&CD2Q$lu}3$^!gIT1eR;zd)yLO@TI`R{IO@4KE@setPl`9mJ`rx#XFF z!_eoFr(^6j7|#^4k&}00jZT)y1so??V>>xHJoj!cTZKG@KdC>Ycfs&XaBU7ky$>5k|78B*+wxX@VN z9n!06f~!t#sVXm_-F$hyRgJLewWh#L;hw{tSNnJaD?lh(`@Kw@Ea-Myf>f&*rg zK>xPu4|0G+gwnoAkB(^d>_a4mpYRVYQ=kP?UDnJmG}*9F6Gv0*_r z9)veER(?&<;TBLx3l{1>A;VejaqE)hD9@G}oA6cjWb~fpA=|*ZTK^irtrNmKONmH6 z6lGR95FjS?IA)MLJk5%QnttULY5I`Av^=+z{IL1gFPcW$#+ldiIMsGvIsJ+ovZMe) z9lLdSyRL70vG@&W;oc-@053F>S12CYZ>Xq){ zt1ua3MN+5p_$fk2SP>7MxF{p&|7|fd2g;l8~&8`#leSZSMDXD6lngFO286TDpz*s0@9QTNz;4fn@chC|cVH_kcmLX$a z9ub-=%G!KxFZnq(t=>0?hTS^lphMG3%^f51cjotRROH8E|Kr>JGsQ-E{SNXac>jJ0wKu+P*>6mQOXV;E8hP$9hiwBy zP;RSxo<5@0&Lo%Rv+w-5IYNLVmy!h>@D2K;BN$hh1TGSCf$yo5kWVIVTFK z*&snR3pNQ$KRRee*Lxk*mZa&p>YEx>ZjiZoeH^vr`23mufnd|4Z8_yWn7$F1+A&h? zQrx$X%=&L938V9s&8VCdu1)UPE7B%W`lmfy+woYw8 z?`_ZZb+%3)%^ypD;7P?LbVjoFD50P zy9;eEW7Vb&Df0B{LY9+UyXl+hlU}2Meu&>@<{Bv(*-wUS{sbZCZd}HFd(c8!PCD%i zw8dsI1J%WNo)&XG0l(&Okx|FJ7XGAli_hhcAqgwoojX@pW9)AnN$f}xRS%V(zHxT8 zXX(X$zl+2<*)=Wg@tr>3;a>KOgjn3rpsImhp-ke_(1iL$=FRKDFuB%=3VUDo+2<8@ zw1_-ibIGAIS=mRyAK(Re`9P}nI#t$A`1trZ14w*mI<=ZlP77DQTG)f!z1Lv=h_64q z!jkdX#e(#+k7*OSl@4tk->2=1s@*;k1gbwgIGmjngAZ zkQc+g=&M0{B)|2i;WhR-zWl_Y`=N>G?Y@aq!+HEcm|Ct|wDOW9?#FH6kpf_xktv?kZO3~5A6hr&XR$lKh5UZ8iD$13%j@ba%;Z%Xw3`df6iD(lFl`vi~&WmDB6m4JqA0 zswAIdeRfK@yjbM@#w>7MRcA+PA%8!0-SkyO$n~A&wut#K0cOHinW{cRe6OCcoHRxE zT^?3Uo0ZSgxYY-3m^vF{V~)YlH>b$fcgFnTx}t8ddI#zlv#NYY=B)h6MAU9QwQj}F z=OWbUn(YRzuC9%iqW9m?k~*nBL2a;-8~H=ha3WbZZy^Q~BnTB7AX;ln+SU4F_tZWt zY|y`QIzi%qm}q{COxiY&^|~hWZdiQSTg0;t<=uHfC9FJ@$j#A!YBehV^pKR=q2Tud z9bt7WH!yNb8JIKQrW@jpkrLGVsItahX7cE~-n%F%NrC_|CaFUm2)MSg2$>u>Ur-tqSv z+k01MLzw!x2wlLkbc6~u|GC+qsyc)2^+A!nyX_&^7;4?1D>^3TwgL1l=4^N(iMuw3 zq_UrG6>e?*9gO50k>&h@&vSLg@cl#3g5mcSSA4EXCW5Y~HjG^4iby~d81pU}8cVf< zUn-+UjN6>8>PIH`qwswemqT4_cuY|Rb0{LLNKW!ySZHfl3k*#TO1d4kAxVR>`g0xT zd1)zpK8LaSU=!-?BrBTpbJS?H#8nZN{^re_YG}q@PLBEU?&8z}lU%JLaNsJ_$&S`e z(7k$iYE18q7n_K#c}mrhX&VO{$yC&Xnhx{P%I?-7_C5)!@Qm^?zTMRvP2XQ1p5LBr zm`dZSHB^5VHxz5AYsZ$0VRKdpZkwlY%NI(RjZQ(a%GLQ}i}e4Xh=OJ7t3qS=N@gCj zaBrBll`yOPkXcB1Gq>Xdt7!g@lPWUhV^hW~4vnohNG!(+@|dSaM|<01=uMixW%}+K zWr^xVvzC)*1Vy+9gPP*=m)o7!0RX|bPf|Rs!iSq_Zut1+{9Vv{4bkYXj4*i zac(@hc4`H5gqNE&!4&u0RL!NL&VWIttfT}x0ymx8!B`-oO>NBXtZ*$V>ceB6w>wbt^EMFa43IKStNP@)?zY*xSgV~OrW}l0~)n^EoDI7 z!kw4xD}`HL7gK}QwHr*oHgS%{*G{PJ+&sV{sh*k)jBL{v$~fBV9OcF(7Q(36x=)SC zPf4rX20yYsu4_Nap6Vcls6mAH$gLE$&pX2%cIIHz0~xxF-D5rx*EBmaPsZPGoK&*a z56f)6Z;(YzL~V}ig&AMEooS5i6Q=2;@Y^h(*f7Cl?Fj(^df=SBbk&|QtA@ifv=BY`cD#MQKHb_q;3DaQQ+ z&Y%_Xsy(ze$}66H;@qFO#QiZhrFN`;l^Le&Tqdp;0=@fY$s+8k^gBbMOe3LByLA}y zG9*>HH-*BRCPvRp$4T+Tfa8zcvxaQ{Ik7`Z!P(77mSBqZTb24kOvzd}G5ityESjC< zUsUeJ{I3ok*;VJj-%Y#buGW$LMoyyIk2<< zbqynH2{aTZI>r>&mXhSantM(1#30`GY{-624&62vmh=rVw}twx$)#~q-!+}SB@^qW z(q=t^FcaF5@l9x3O+>4*Xa@@prsmyDNW`n^h9(v+~sQ&x)rF2eDCWquPY=x zyt9+XY{yV~OK!gzUl6=I9rt6F2jheJYc zS0BvBO2U7*L<)#qXRo!=&9@KrT+hQNNGj-&fo_1O1NKf6fjY(b>6#O=>Z96+CCVbgx zjn>41ZkY)-I7aFD8`^~)t1~wbwjS8}&WdfEq%3}RO@m&>3n`y{pK02YYI$Ga@r%gJ zzB};Ox;!MorfNsr2rNm)wz88v2)YH#;GPrin;BK1b$xQxS$%JadoUPn%?6SYO zIbC|pKZMLLI_P@kUq`bufoHX_=`R5QB$0AG0A6QKlvC%7r$$&YRHLmDGN9!QCvHE* z6Z7=9N6~__+ynD7>OWGOyu0da=YgV}KV*Bf#1p0XYx;{l&*RALe$D+;XTw{ z-q59V=Ncw&-}CbN6=oy>(C+_uR9iwtv~R!t?sFP5lhn2&qwrQH2`Xt*gmt+-On)PT z>K80=ixS&NDJMJ?0Ex>*@q4 z>s{N?o!TVoyYY>Zw!KfPbH=sbjY`u-7O|#R?V^Y*6I*Jg7dG_X>Tl?sGkoQNJ&bxYC5+ z)`PbJyZ(mY)uDrQi(RLl|ARGv3?`Tjw415r>Oao_g1oi#3hzJ)aR1%w|L^p#e?uhz z-tjLW1DUDxFMI&#PXH$H2RiuAxBEx*;yT_o@+HiFFrYZ3{^Ku2F@Qz>Ndk-%0RQ>; zK4~BTUWvC4`D5bqxmNCDr}>v&?*GaQ_dgfF%)IU@`!CWFvzBa%R8wcrK({7w-|#h? zZtluUQWmU&|V{ zZkR&rP~P7Jv)#2qEql;RUj9!7B(8?y-|QCU7M7P&%gTs=B?((mT0*S@xQIBwkiL8T zHrIlKgHz3Tg@nk+_^c#VRq?@)KGCeoANw!cn5n66AT#@-n4kS9jo)}ifYcw&rrEb5 z*BWjUSWNS@-eMXK6?)_Txp(O2UaK%3#I2J_K-?CJ1vDbcUu2YCpJ`7Xvps(IfRgT3 zOuqBniqKCqVZi`68VStCbX9n#5no15?lZ@&vsZIBsDbt29;3j+D<34TKXUD|cpodN zr#Qd@>38{>q2C@^TtDA^C(kKj6j1&pNxQtyVTVajP($!c3`t3vY*KwO;ZFS+48 zH^5{LQ(cay5Vdf(rA`t6<)D_-YQ&;CfMw~(P>6li6G;M9TN?U_PKPQT{fvCFp0`n; zv1`ZwiQFU%Ve$dEHG;UB%8EVy{BV%qLK;Eh|##1uZD5IDM+ zH~$zh%a!EtubzC6Lv0|)&ElA%Nu!wZMjmRI35gt+X&n9F&wv_#BajLz?VT$?jy)93owd&jE`N9iqc^YK2}dA^nVgOsEq3dQfO+eL{|dLVPq&PS(*kNr!T0c=6!h0qW5%PFGYbj@E zz9iQzfq9pTn<}+HPFm=+sYU&RQ4d)5>Zo4^ZlqUq29xpGY>x4E#_^hK6x|*2(AWR0 z@=Qr7)RK#2w9F;vXZ7A~A~u~BZp|Qk=JW@KcG1B6=AsESCO4T`S-Z3m?Sq10cDI2= z>tRsU9CJrpM15-!kb839Z8b40iv8o|^U@#e@1IM==Vf0*>Pe1#?+(O$tmBIrp0!-0 zTvj~b6c8Ad>7_1Dh-b9|rx*X_Cs(wRZDFm2V7Y;s*Vj0!^r%w!+^${z20i z$T0Jm3U=;Zb9bdG*z4PC;dAzUGv2fXDWV6-*H!hjy0{qXg4)@$jdzNpvD8q%7{=E1 zGWYS6n;#~b4YNF33oPuU%;NHFf0+LD>l>q9#n?`}7l${Wd+~VgJUuVYR;uB1*P0R`v%WjK9qg{Kg7R`= z3Xh`{=k7tb_+{~0`Y*Cw*=wrW+VRiy0mpVX%hTfH&#oj9G7$9jUePl*XLF%bC!NJQ zkv-9=(=M}75EoM7LAQZO*>W>KhjBIos`@Vj&U-*Zz!47E0W;FPuXoq0MEdv!vNPr= z1MIbIzpVgUChEvQ-g~q7M=C-So&<7ffkYR<`j!cHoebQU$fT!*^tU}nWEh7*Jq0jFT>@!X(_}M(4s128x z>D_FVg%jE}s2vn7HCl}6xrN#(9u3f1)e*_?f#)2U7;oowOFah`$n4@^;s*=KtbyJU}`35uh z#|P9{BoSWkX?iq8fk?mvvgq#9@Z8zY&{Wb!cZ$BSaU`7Yt=ENz!~QSQ$v80UuDQ8s zCh54iZ^#4x{Ji`FY5)Tq_tCMQ1S)>`_PeZkt@rMU5FXD@>f@N3TX62cUeJYE{+!<{ z6mqdZKwF&x2x7h2Gt~47bp3u%D0d@awb!iu(4O1=T%mYahj!3 z>j^>`sHJP6s#jW&)}?=GL6TC6lQ7hF^O%1UXvl-!Rmh~?W8>?*^112?T|HUFNxK`llQluqgkl19OZJtB zi>!;)?s9})lYtJ9K{UVB6uGK(HGUslEkGz77F8@`cyzD!lv_Sw+Lh?$z;94aOWmQ@ zF1mL=*hFQ&DW?^`zK7+<+$V1AReU<|!wbT3#pg|^yfp`GPs?Pm=c52GyxSvw%OG6m z=rm?(5V`^KgX|!Tcu!D=lDpaj45^JF)i$%L zE5K(LP6|Bokt2wC`f@-C*4+D{D|*kHX|zMb0>^Rw=uV)@MJ3`93fr$pY0uG`s8OEZbwP&07D2h#{}$-Ul9qZhoV0Qu#Z)HNK=&8;@>*M*@sSd0KDfn_q{ zz3{F8V-%t|Gpjc2VAzVqIHVSeSkp}J2^lc?K`NrQK8wiJZYatX3|24A7Mi|(xyW~Fp{9Z)2V*4$i^4xj8K2t;VW72PJbG+c$1SEF9%VcMRxM-$! z>}@6AR10s~%bgq9oTXKdC5xWCBL&&*_V3i8*MqeuR5a|DNQpXEBv%z3=~EEjOa3p+ z7C8GxYD5p{A%z{dYmvNzac?Cp=ZlRIk>f;N+NTbZ%2E`$`;3`k&$wfYeFWQd0ILu} zA<9;U$@d07BZGaY=;Be4&(iOT9>M&qh#kT0U0dW67ctKd%XEM#%SI81Wp8&S3K5f& zlfT|+B2IyRtJHr1g2`ngZ(DP_@>&;JgRy%LND zHE;IK)Sf;it+N~Je$|C*ZZ&KrGGc@iwLiWO=Ev|_XP2HT#Bvv9^#tn@OrP8#beWzC zEJACV4r8}$DD&J+TVP@$_Wu?gkge;n!{BtLRU7dkJb6oe_Uysn7J>GT(yR0R&$@ol zjNpKqSDQQ?7?TD^&}4CYSE)?>x>n{vMcnMZ>uyzofgyBMY&W0|5nymLBYWUZhsEYX zuB%5Dr#Bh8xp^~Vu#+?^o<-_8v&va2IYTAi&|TQ_CpiN5ayfI!{{xW+x!b=*9&&Vs zYI0J1XOng5wQsYe9JW>?OunRLwB`nYfU6TZB7^9=dx>tl_afaGGnGjmZFYNW;zz|c zDIus@D1T~c7aIp3hRfa;mx)d$u=NM$89vC$hGe`nm(kd7thXd(X^p z&|UL-kvJZxMo>SIN1BPPR>$Za_VC??=+(B0v<7IEFnAj)45;cHnR&2TU91`?C@G2S zh+~KFO?_uJAuTm6!o_%x44kP%R3L2PiVN4UU^Y3eMbEFcETr0CFpd!9AAbGI{k*CR zN%;`>L58vsd;o!%6sp8oiotJ>Eb#6iQ&V_Bq93aQ@L)jzlq3VXHmMo37~MYsIzZ|V zClUvLej7X~z0m2vJca)s_y8i7|1W?Ki>qkX@4#cTAbtgv{SH?%DEr@G9028URl>Lzdw*~fL07F1UMJY;4OZi=P4Hxb(C`fGsb1Ee_H@=97$R7zr zLz)SYV!x*ctM7F;mMaYD{i5M`02UR%D!jUv8DJcL+CDisOvMC#uednUmivY#CI%qZ zzxk&G6>cT;8<1;sCJOPk&j+%n?H;H8E zfDhu6DwmT}kt0aFOabaQ7-wsn^wWllOG$mE@Upf3kV5ZW0(Q(!T2(HIqpwrS*%zOb zIOD%tU#7PHW=zr1|7}dEbl%n+>bBZ2JGU1;{$P>T>~xjuRP$7t9E-7jeRGo+HNUEA z{DIjsW#zX<9nmD?M2p-6^?kM!q3Mj?^J0mXil_h;_2&q@cNGNvmJRuAW{Qac!hkhd zBcLSEkst9IM@IMU>9Yk&-V_|i9t*M2FvZjd)T5mfiad2L*!#IwG@Y6uXGSD4ruZb{ zf$BcW6wr||%!#~~V$UkF(r2ZDE9D>Y#2DX#8oSJ7^DIh5TT0#F%E;jtHUq<4016(T z{f30JMmH%HLbF$|I1LA@d3pHwhGlG^dI%bNaAa2YWFDyP6725@{j7fX0gL#@Fp(Us zx=D9}7+^~Zjh@y3t&XV0`WL47*w_`;)MnGIcgI2Sd#X}Ww=TAQMQ4!QiHZVeX}nt1 zgU<7U?y#JOF(vz&+9kEgcY5N|P7?u!P(6 zMU2eTSW6*#EO_4JyKPhbu}p{yy*uOzgcIDV!cH)j4eMWKmq^zQCl-SIA|@=trxUO< zPivD!?fPtVm@u|wHo3GmnD?nNJPXW%6e{aqGvqAYEx;EW^~q;|Z-mCxJ%zBA(Y-CS z1Hr#lLq>@MVco$w+P+HLPx;NEUVTVWWD=G6pKAqC8DhB=q!XlCHcg3We~stjf_YKWLinbK6UY0N!o=~xtqnB%R!YTZF54=1SU(iTrk zZTD<2knB$!#i>qK6rTF}oSnI~Y?P#GcDO~OHiLftzm)<}^|>j;St>MKZnvk4K7Gi& zJYvE8$_L-=ds33M0$*fFStslHycVeWlGa;}%}`C*7?U@dghxIGq`%Uu3PM-p(+J+^ zj-pFLA}7-BcG!;5DquvT-x7kF67uco#P;m3b_csnRo{G8)C z@~+}7_wiwXuB>-IB8RAv(1Wkf5$`s)lkVtUfAlpA`yp8(1CU?J;_?JN7it z{(T)`2@r=k;`M~J?%)Kap_aob*Phm9rqsf4Q9T-5MzHyZaj9<5Ulm#$NJNg|4G8U` z!A6S9r}+cy=R&8Q@NtlwBrlPZLx}Y7=B%F+Mc9%d{+Kq6kt>?Gx>)R#PN=vG2QuJxcp6?^P&F zMjl>|Q0kGM+RywBeXcG8H%3lA=-fSQJFMuVV+#=dk19S-L-Q+6*I!EQDB@w?Ik2}@ zMgp+l!OZ@!9^cjtHTLXY%?P+mg^Kg^r+hRHy^a^PXuVq(Rgdt)gLt4B&6$l#7c8ev zzzLV5SuC}FzaCjq=RA4x1Y2@Rh=7nA#HI?=f-yezZq_q0)ZSS*#c#6@JH5^BXXl?n zj06$|aMVyY)AZJc#r8sy_-z*+Ydoq2&7Kb;g4WO~T%JDV83C@ti{cn_kFIZCQWNw1iP zBfzDY?W6;z2Ihb`mt3h~8(uX?7M;4krbEY$rHZ;wufCjxdp@QSjw=4&na$D>Rq)Xt zUQt!;`4?p)iXELM)6q|o)zTZqE|Lu~@X*=8j<6kf=fLL*-nBrUH%FlU_;b0E9H2fz zRl5IY+8HkLpWj=Lu8iB?B|(9n;8)zvd8G{H2S|#Y<<+C|@h!5bPNqTb(`a4DS`T`b10w!je-u*)dK#1A-9Ns)}#ux>NYX-RGb%KJt;IR-EkeM zjQK7Dz7c7;ke;hsEC~fW5U2T{;2&g+5D{(Fom3ksa;66{DG%^W95s?HCAX1TKi4y! z*WtKQut*G*@AcZ5ij;{^?0}gZSE+!%h0IaRVWMyjl6rh4VWh4Q48NH9?sWsqF2IgR zx>-*4Uyax^PrJu{fJAYQjdVZqKVS*C7@!<{0nV&wP=DGiv_cYuUHWZhfrbJPcwb@C z($X>u3eQlDDk|)krE&%{1We7$eq5ZNzB_vMBT-!zLjly*ZD+x&k9e5C3V#&$9fW}Y zM@lK9H^_7YLg$i_l9`I-B>OYdOqqT_v!LMlhj~z?qu)BTu29A_v2oq1dvF{|aHSvK zzc6T)8BV-JXh8A6!$*r zh6(FmxWA=)BLXVZhYk0VKY^9H{%>v>JR8R2_T6lIga1(YW_*td{9HHYy`S7BZr1yU-77oSo&Tev+_-X)APlZTC!jf*TRNo%?Q8yn2Z0cUj4w_BHefU%&CA(0D4{F*vEpVgfDdqsmsCtSntMTO_S(|A~nVi;QrLNDVJH|BLAX+SRC{@oAk%0_o1a{yprGhe2#H0whvx$b zm89oHe&h3+a?nCCAmt8|1R{foShaX^?kr8mdvX#PL||wGy^~4j2bYVS^P&S;+l>5t zzV7tq=4Mh|^~DWD=*c;-wAQrg_xJZ#&;MLsFBZA8p3m+SlxuIPE037g9!GbGFGc9t zvKWFR&5r6XodC$yxnn%#zz5IzMiBM&Q38E9acHx|I1KUFdP8Y&O}t*!wZQQM7S3s) z%8C3=uZ$$esAcB(habDd%0)c)aq8R%zHlkE>h#qm2QWhg*9%K|I0<7jE4^Da|?%+hgj)jhCwP^)Z^rFYd#;TWb63cwD z4y`9E7bne_rnPI{@G>zo2j`?bdESYahKFcXh{<{^1X1gU*ZMKP{3tI8f*{hlE-*Z})k)3nK0QR?@nhbiFSX$KdHAdh@3FlnA^mZ+ z;325v)u4Af3)^INf&W*;LRa=xxPjVu*ya*qODBsoU$e+95fW){C4IbpOO@ z&`jQD+G+lyeatGi-4pjCyB@};k_$`&Dpqj*{RXm-hYp7rvWTFw19HeoD}ty`QJs4p z2yNG$l>@W*IN?M5rG0Yg!)R%6;n2rG-C$P5B`Gt8ivY5oZQ9f8vz%ekq znbgQX2cR)v-1ZflAtS)NIl#s7Y3sQtTMf!$HudOMvUNuqT+?PkVUf~l)oDiZcaS3;U zpraqj+E`rWkvEL0qL@rx8fcrQmhr|gp?S-E^38H-^ayiYgqwXbAE>o+CF@E4;3M@q z!1XXuTO###s}5a68&9X2C?kJQ*Ff^kbBH+k7E?OOoA-r~v}Iy^3k-{Jy@v7yoEmQwq%&WHE()QY>-7RS(%Rl(gskwWzC$O~JJ+R>0C>sQA*MG3#DEI{G?_H%vvr9>RN%L(ml+opc{#_0VYF|ZT@#>_ALtV}tp&KWMpSSf^3Y(?uF>z5{7_M&Ej40#wtzYz)=GpXj RaIb-1vM@!-Jn Date: Thu, 11 Sep 2025 10:01:41 +0200 Subject: [PATCH 13/13] Update docs --- .../src/docs/user/scope-sidebar.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/cursorless-org-docs/src/docs/user/scope-sidebar.md b/packages/cursorless-org-docs/src/docs/user/scope-sidebar.md index 7d64df9484..bf015bb95e 100644 --- a/packages/cursorless-org-docs/src/docs/user/scope-sidebar.md +++ b/packages/cursorless-org-docs/src/docs/user/scope-sidebar.md @@ -4,17 +4,19 @@ You can say `"bar cursorless"` to show the Cursorless sidebar. ## Scopes -- List showing all scopes, organized by whether they are present and supported in the active text editor. -- As you type or move your selection, the list of present scopes will update in real time. -- Clicking on a scope will visualize it using the [scope visualizer](scope-visualizer.md). -- Display the users custom spoken forms +- Displays all available scopes, grouped by whether they are currently present and supported in the active text editor. +- The list updates in real time as you type or move your selection. +- Clicking a scope highlights it using the [scope visualizer](scope-visualizer.md). +- Shows your custom spoken forms for scopes. ### Scope icons -To find a scope that matches your current selection look for the following icons: +To identify the scope for a piece of code: -🎯 scope is equal to selection\ -📦 scope contains selection +1. First select the code in your editor. +2. Then look in the sidebar for the following icons:\ + 🎯 The scope exactly matches your selection\ + 📦 The scope contains your selection ![sidebar scopes](./images/sidebar-scopes.png)