diff --git a/cursorless-talon/src/spoken_forms.json b/cursorless-talon/src/spoken_forms.json index 886a5d38b6..47bbfa6081 100644 --- a/cursorless-talon/src/spoken_forms.json +++ b/cursorless-talon/src/spoken_forms.json @@ -76,7 +76,8 @@ "trailing": "trailing", "content": "keepContentFilter", "empty": "keepEmptyFilter", - "its": "inferPreviousMark" + "its": "inferPreviousMark", + "visible": "visible" }, "simple_scope_modifier": { "every": "every" }, "interior_modifier": { diff --git a/packages/common/src/types/command/PartialTargetDescriptor.types.ts b/packages/common/src/types/command/PartialTargetDescriptor.types.ts index b6a35d243e..0a5293046e 100644 --- a/packages/common/src/types/command/PartialTargetDescriptor.types.ts +++ b/packages/common/src/types/command/PartialTargetDescriptor.types.ts @@ -232,6 +232,10 @@ export interface ExcludeInteriorModifier { type: "excludeInterior"; } +export interface VisibleModifier { + type: "visible"; +} + export interface ContainingScopeModifier { type: "containingScope"; scopeType: ScopeType; @@ -375,6 +379,7 @@ export type Modifier = | EndOfModifier | InteriorOnlyModifier | ExcludeInteriorModifier + | VisibleModifier | ContainingScopeModifier | EveryScopeModifier | OrdinalScopeModifier diff --git a/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts b/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts index 7f8c7ae2f5..24e0bd3a70 100644 --- a/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts +++ b/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts @@ -30,6 +30,7 @@ import { RangeModifierStage } from "./modifiers/RangeModifierStage"; import { RawSelectionStage } from "./modifiers/RawSelectionStage"; import { RelativeScopeStage } from "./modifiers/RelativeScopeStage"; import { SurroundingPairStage } from "./modifiers/SurroundingPairStage"; +import { VisibleStage } from "./modifiers/VisibleStage"; import { ScopeHandlerFactory } from "./modifiers/scopeHandlers/ScopeHandlerFactory"; import { BoundedNonWhitespaceSequenceStage } from "./modifiers/scopeTypeStages/BoundedNonWhitespaceStage"; import { @@ -68,6 +69,8 @@ export class ModifierStageFactoryImpl implements ModifierStageFactory { return new LeadingStage(this, modifier); case "trailing": return new TrailingStage(this, modifier); + case "visible": + return new VisibleStage(modifier); case "containingScope": return new ContainingScopeStage( this, diff --git a/packages/cursorless-engine/src/processTargets/modifiers/VisibleStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/VisibleStage.ts new file mode 100644 index 0000000000..f5cd56fdc1 --- /dev/null +++ b/packages/cursorless-engine/src/processTargets/modifiers/VisibleStage.ts @@ -0,0 +1,19 @@ +import { VisibleModifier } from "@cursorless/common"; +import { Target } from "../../typings/target.types"; +import { ModifierStage } from "../PipelineStages.types"; +import { PlainTarget } from "../targets"; + +export class VisibleStage implements ModifierStage { + constructor(private modifier: VisibleModifier) {} + + run(target: Target): Target[] { + return target.editor.visibleRanges.map( + (range) => + new PlainTarget({ + editor: target.editor, + isReversed: target.isReversed, + contentRange: range, + }), + ); + } +} diff --git a/packages/cursorless-engine/src/spokenForms/defaultSpokenFormMapCore.ts b/packages/cursorless-engine/src/spokenForms/defaultSpokenFormMapCore.ts index e5ab822428..15b4e5ee2b 100644 --- a/packages/cursorless-engine/src/spokenForms/defaultSpokenFormMapCore.ts +++ b/packages/cursorless-engine/src/spokenForms/defaultSpokenFormMapCore.ts @@ -117,6 +117,7 @@ export const defaultSpokenFormMapCore: DefaultSpokenFormMapDefinition = { startOf: "start of", endOf: "end of", interiorOnly: "inside", + visible: "visible", extendThroughStartOf: "head", extendThroughEndOf: "tail", everyScope: "every", diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/changeVisible.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/changeVisible.yml new file mode 100644 index 0000000000..8f827d6216 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/changeVisible.yml @@ -0,0 +1,29 @@ +languageId: typescript +command: + version: 6 + spokenForm: change visible + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - {type: visible} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + // Hello + + function helloWorld() { + // Hello + } + + // Hello + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/visible.vscode.test.ts b/packages/cursorless-vscode-e2e/src/suite/visible.vscode.test.ts new file mode 100644 index 0000000000..0567aa4936 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/visible.vscode.test.ts @@ -0,0 +1,66 @@ +import * as assert from "assert"; +import * as vscode from "vscode"; +import { openNewEditor } from "@cursorless/vscode-common"; +import { endToEndTestSetup } from "../endToEndTestSetup"; +import { runCursorlessCommand } from "@cursorless/vscode-common"; + +suite("visible", async function () { + endToEndTestSetup(this); + + test("visible multiple regions", testMultipleRegions); +}); + +async function testMultipleRegions() { + const editor = await openEditor(); + + await foldRegion(); + + assert.equal(editor.visibleRanges.length, 2); + + await clearVisible(); + + assert.equal(editor.selections.length, 2); + + assert.equal(editor.document.getText(), "\n // 2\n"); +} + +const content = ` +// 1 + +function myFunk() { + // 2 +} + +// 3 +`; + +function openEditor() { + return openNewEditor(content, { + languageId: "typescript", + }); +} + +function foldRegion() { + return vscode.commands.executeCommand("editor.fold", { + levels: 1, + direction: "down", + selectionLines: [3], + }); +} + +function clearVisible() { + return runCursorlessCommand({ + version: 6, + usePrePhraseSnapshot: false, + action: { + name: "clearAndSetSelection", + target: { + type: "primitive", + mark: { + type: "cursor", + }, + modifiers: [{ type: "visible" }], + }, + }, + }); +}