Skip to content

Commit

Permalink
Add implicit special target
Browse files Browse the repository at this point in the history
  • Loading branch information
pokey committed Jan 11, 2024
1 parent 2ae73a7 commit 1e88a2f
Show file tree
Hide file tree
Showing 28 changed files with 434 additions and 75 deletions.
6 changes: 5 additions & 1 deletion cursorless-talon/src/spoken_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,11 @@ def handle_new_values(csv_name: str, values: list[SpokenFormEntry]):
)

disposables = [
handle_csv("actions.csv"),
handle_csv(
"actions.csv",
extra_allowed_values=["private.setImplicitTarget"],
default_list_name="simple_action",
),
handle_csv("target_connectives.csv"),
handle_csv("modifiers.csv"),
handle_csv("positions.csv"),
Expand Down
1 change: 1 addition & 0 deletions packages/common/src/StoredTargetKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ export const storedTargetKeys = [
"that",
"source",
"instanceReference",
"implicit",
] as const;
export type StoredTargetKey = (typeof storedTargetKeys)[number];
13 changes: 7 additions & 6 deletions packages/common/src/testUtil/TestCaseSnapshot.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
import { StoredTargetKey } from "../StoredTargetKey";
import {
RangePlainObject,
SelectionPlainObject,
SerializedMarks,
TargetPlainObject,
} from "../util/toPlainObject";

export type TestCaseSnapshot = {
type MarkKeys = {
[K in `${StoredTargetKey}Mark`]?: TargetPlainObject[];
};

export interface TestCaseSnapshot extends MarkKeys {
documentContents: string;
selections: SelectionPlainObject[];
clipboard?: string;
// FIXME Visible ranges are not asserted during testing, see:
// https://github.com/cursorless-dev/cursorless/issues/160
visibleRanges?: RangePlainObject[];
marks?: SerializedMarks;
thatMark?: TargetPlainObject[];
sourceMark?: TargetPlainObject[];
instanceReferenceMark?: TargetPlainObject[];
timeOffsetSeconds?: number;

/**
* Extra information about the snapshot. Must be json serializable
*/
metadata?: unknown;
};

}
export type ExtraSnapshotField = keyof TestCaseSnapshot;
export type ExcludableSnapshotField = keyof TestCaseSnapshot;

Expand Down
1 change: 1 addition & 0 deletions packages/common/src/types/command/ActionDescriptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const simpleActionNames = [
"toggleLineBreakpoint",
"toggleLineComment",
"unfoldRegion",
"private.setImplicitTarget",
"private.showParseTree",
"private.getTargets",
] as const;
Expand Down
1 change: 1 addition & 0 deletions packages/cursorless-engine/src/CommandHistory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ function sanitizeActionInPlace(action: ActionDescriptor): void {
case "swapTargets":
case "wrapWithPairedDelimiter":
case "findInDocument":
case "private.setImplicitTarget":
break;

default: {
Expand Down
7 changes: 5 additions & 2 deletions packages/cursorless-engine/src/actions/Actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import Remove from "./Remove";
import Replace from "./Replace";
import Rewrap from "./Rewrap";
import { ScrollToBottom, ScrollToCenter, ScrollToTop } from "./Scroll";
import { SetInstanceReference } from "./SetInstanceReference";
import { SetSpecialTarget } from "./SetSpecialTarget";
import {
SetSelection,
SetSelectionAfter,
Expand Down Expand Up @@ -134,7 +134,10 @@ export class Actions implements ActionRecord {
scrollToBottom = new ScrollToBottom();
scrollToCenter = new ScrollToCenter();
scrollToTop = new ScrollToTop();
["experimental.setInstanceReference"] = new SetInstanceReference();
["private.setImplicitTarget"] = new SetSpecialTarget("implicit");
["experimental.setInstanceReference"] = new SetSpecialTarget(
"instanceReference",
);
setSelection = new SetSelection();
setSelectionAfter = new SetSelectionAfter();
setSelectionBefore = new SetSelectionBefore();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { StoredTargetKey } from "@cursorless/common";
import { Target } from "../typings/target.types";
import { SimpleAction, ActionReturnValue } from "./actions.types";

export class SetInstanceReference implements SimpleAction {
constructor() {
export class SetSpecialTarget implements SimpleAction {
noAutomaticTokenExpansion = true;

constructor(private key: StoredTargetKey) {
this.run = this.run.bind(this);
}

async run(targets: Target[]): Promise<ActionReturnValue> {
return {
thatTargets: targets,
instanceReferenceTargets: targets,
[`${this.key}Targets`]: targets,
};
}
}
12 changes: 12 additions & 0 deletions packages/cursorless-engine/src/actions/actions.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ export interface ActionReturnValue {
* to determine either the range for "every", or the start point for "next"
*/
instanceReferenceTargets?: Target[];

/**
* A list of targets that become the start of the pipeline when the mark is ommitted.
*/
implicitTargets?: Target[];
}

export interface SimpleAction {
Expand All @@ -62,6 +67,13 @@ export interface SimpleAction {
* @param args Extra args to command
*/
getFinalStages?(): ModifierStage[];

/**
* If `true`, don't perform automatic token expansion for "<action> this" with
* empty cursor. Used for actions like `setImplicitTarget` that are just
* loading up the pipeline.
*/
noAutomaticTokenExpansion?: boolean;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { selectionToStoredTarget } from "./selectionToStoredTarget";
export class CommandRunnerImpl implements CommandRunner {
private inferenceContext: InferenceContext;
private finalStages: ModifierStage[] = [];
private noAutomaticTokenExpansion: boolean | undefined;

constructor(
private debug: Debug,
Expand Down Expand Up @@ -53,6 +54,7 @@ export class CommandRunnerImpl implements CommandRunner {
sourceSelections: newSourceSelections,
sourceTargets: newSourceTargets,
instanceReferenceTargets: newInstanceReferenceTargets,
implicitTargets: newImplicitTargets,
} = await this.runAction(action);

this.storedTargets.set(
Expand All @@ -64,6 +66,7 @@ export class CommandRunnerImpl implements CommandRunner {
constructStoredTarget(newSourceTargets, newSourceSelections),
);
this.storedTargets.set("instanceReference", newInstanceReferenceTargets);
this.storedTargets.set("implicit", newImplicitTargets);

return returnValue;
}
Expand Down Expand Up @@ -179,6 +182,8 @@ export class CommandRunnerImpl implements CommandRunner {
default: {
const action = this.actions[actionDescriptor.name];
this.finalStages = action.getFinalStages?.() ?? [];
this.noAutomaticTokenExpansion =
action.noAutomaticTokenExpansion ?? false;
return action.run(this.getTargets(actionDescriptor.target));
}
}
Expand All @@ -191,7 +196,10 @@ export class CommandRunnerImpl implements CommandRunner {
partialTargetsDescriptor,
);

return this.pipelineRunner.run(targetDescriptor, this.finalStages);
return this.pipelineRunner.run(targetDescriptor, {
actionFinalStages: this.finalStages,
noAutomaticTokenExpansion: this.noAutomaticTokenExpansion,
});
}

private getDestinations(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ function inferPrimitiveTarget(
(shouldInferPreviousMark(target)
? getPreviousMark(previousTargets)
: null) ?? {
type: "cursor",
type: "implicit",
};

const modifiers =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const actions = {
joinLines: "join",

["private.showParseTree"]: "parse tree",
["private.setImplicitTarget"]: "implicit",
["experimental.setInstanceReference"]: "from",

editNew: null,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { Mark } from "../typings/TargetDescriptor";
import { MarkStage } from "./PipelineStages.types";

export interface MarkStageFactoryOpts {
/** If there is an `instance` modifier downstream in the pipeline */
isForInstance: boolean;
}

export interface MarkStageFactory {
create(mark: Mark): MarkStage;
create(mark: Mark, opts: MarkStageFactoryOpts): MarkStage;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ReadOnlyHatMap } from "@cursorless/common";
import { TargetPipelineRunner } from ".";
import { StoredTargetMap } from "..";
import { Mark } from "../typings/TargetDescriptor";
import { MarkStageFactory } from "./MarkStageFactory";
import { MarkStageFactory, MarkStageFactoryOpts } from "./MarkStageFactory";
import { MarkStage } from "./PipelineStages.types";
import { CursorStage } from "./marks/CursorStage";
import { DecoratedSymbolStage } from "./marks/DecoratedSymbolStage";
Expand All @@ -12,6 +12,7 @@ import { NothingStage } from "./marks/NothingStage";
import { RangeMarkStage } from "./marks/RangeMarkStage";
import { StoredTargetStage } from "./marks/StoredTargetStage";
import { TargetMarkStage } from "./marks/TargetMarkStage";
import { ImplicitMarkStage } from "./marks/ImplicitMarkStage";

export class MarkStageFactoryImpl implements MarkStageFactory {
private targetPipelineRunner!: TargetPipelineRunner;
Expand All @@ -27,10 +28,12 @@ export class MarkStageFactoryImpl implements MarkStageFactory {
this.create = this.create.bind(this);
}

create(mark: Mark): MarkStage {
create(mark: Mark, opts: MarkStageFactoryOpts): MarkStage {
switch (mark.type) {
case "cursor":
return new CursorStage(mark);
case "implicit":
return new ImplicitMarkStage(this, opts, this.storedTargets);
case "that":
case "source":
return new StoredTargetStage(this.storedTargets, mark.type);
Expand All @@ -39,7 +42,7 @@ export class MarkStageFactoryImpl implements MarkStageFactory {
case "lineNumber":
return new LineNumberStage(mark);
case "range":
return new RangeMarkStage(this, mark);
return new RangeMarkStage(this, opts, mark);
case "nothing":
return new NothingStage(mark);
case "target":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ import { ImplicitStage } from "./marks/ImplicitStage";
import { ContainingTokenIfUntypedEmptyStage } from "./modifiers/ConditionalModifierStages";
import { PlainTarget } from "./targets";

interface TargetPipelineRunnerOpts {
actionFinalStages?: ModifierStage[];
noAutomaticTokenExpansion?: boolean;
}

export class TargetPipelineRunner {
constructor(
private modifierStageFactory: ModifierStageFactory,
Expand All @@ -40,12 +45,18 @@ export class TargetPipelineRunner {
* document containing it, and potentially rich context information such as
* how to remove the target
*/
run(target: TargetDescriptor, actionFinalStages?: ModifierStage[]): Target[] {
run(
target: TargetDescriptor,
{
actionFinalStages = [],
noAutomaticTokenExpansion = false,
}: TargetPipelineRunnerOpts = {},
): Target[] {
return new TargetPipeline(
this.modifierStageFactory,
this.markStageFactory,
target,
actionFinalStages ?? [],
{ actionFinalStages, noAutomaticTokenExpansion },
).run();
}
}
Expand All @@ -55,7 +66,7 @@ class TargetPipeline {
private modifierStageFactory: ModifierStageFactory,
private markStageFactory: MarkStageFactory,
private target: TargetDescriptor,
private actionFinalStages: ModifierStage[],
private opts: Required<TargetPipelineRunnerOpts>,
) {}

/**
Expand Down Expand Up @@ -203,7 +214,9 @@ class TargetPipeline {
markStage = new ImplicitStage();
targetModifierStages = [];
} else {
markStage = this.markStageFactory.create(targetDescriptor.mark);
markStage = this.markStageFactory.create(targetDescriptor.mark, {
isForInstance: targetDescriptor.modifiers.some(isInstanceModifier),
});
targetModifierStages = getModifierStagesFromTargetModifiers(
this.modifierStageFactory,
targetDescriptor.modifiers,
Expand All @@ -218,11 +231,13 @@ class TargetPipeline {
*/
const modifierStages = [
...targetModifierStages,
...this.actionFinalStages,
...this.opts.actionFinalStages,

// This performs auto-expansion to token when you say eg "take this" with an
// empty selection
new ContainingTokenIfUntypedEmptyStage(this.modifierStageFactory),
...(this.opts.noAutomaticTokenExpansion
? []
: [new ContainingTokenIfUntypedEmptyStage(this.modifierStageFactory)]),
];

// Run all targets through the modifier stages
Expand Down Expand Up @@ -368,3 +383,14 @@ function targetsToVerticalTarget(
}
}
}

function isInstanceModifier(modifier: Modifier): boolean {
switch (modifier.type) {
case "everyScope":
case "ordinalScope":
case "relativeScope":
return modifier.scopeType.type === "instance";
default:
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { Target } from "../../typings/target.types";
import type { MarkStage } from "../PipelineStages.types";
import { MarkStageFactory, MarkStageFactoryOpts } from "../MarkStageFactory";
import { StoredTargetMap } from "../..";

export class ImplicitMarkStage implements MarkStage {
private cursorMarkStage: MarkStage;

constructor(
markStageFactory: MarkStageFactory,
private opts: MarkStageFactoryOpts,
private storedTargets: StoredTargetMap,
) {
this.cursorMarkStage = markStageFactory.create(
{ type: "cursor" },
{ isForInstance: this.opts.isForInstance },
);
}

run(): Target[] {
if (this.opts.isForInstance) {
return this.cursorMarkStage.run();
}

return this.storedTargets.get("implicit") ?? this.cursorMarkStage.run();
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
import { RangeMark } from "@cursorless/common";
import { Target } from "../../typings/target.types";
import { MarkStageFactory } from "../MarkStageFactory";
import { MarkStageFactory, MarkStageFactoryOpts } from "../MarkStageFactory";
import { MarkStage } from "../PipelineStages.types";
import { targetsToContinuousTarget } from "../TargetPipelineRunner";

export class RangeMarkStage implements MarkStage {
constructor(
private markStageFactory: MarkStageFactory,
private opts: MarkStageFactoryOpts,
private mark: RangeMark,
) {}

run(): Target[] {
const anchorStage = this.markStageFactory.create(this.mark.anchor);
const activeStage = this.markStageFactory.create(this.mark.active);
const anchorStage = this.markStageFactory.create(
this.mark.anchor,
this.opts,
);
const activeStage = this.markStageFactory.create(
this.mark.active,
this.opts,
);
const anchorTargets = anchorStage.run();
const activeTargets = activeStage.run();

Expand Down
Loading

0 comments on commit 1e88a2f

Please sign in to comment.