Skip to content

Commit

Permalink
move definition of which actions exit cursorless mode to the config
Browse files Browse the repository at this point in the history
Fixes #2117
  • Loading branch information
josharian committed Jan 5, 2024
1 parent c0b1eaa commit 3af5e0f
Show file tree
Hide file tree
Showing 9 changed files with 190 additions and 149 deletions.
126 changes: 71 additions & 55 deletions packages/cursorless-vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -858,61 +858,77 @@
"description": "Define modal keybindings for actions",
"type": "object",
"additionalProperties": {
"type": "string",
"enum": [
"callAsFunction",
"clearAndSetSelection",
"copyToClipboard",
"cutToClipboard",
"deselect",
"editNew",
"editNewLineAfter",
"editNewLineBefore",
"executeCommand",
"extractVariable",
"findInDocument",
"findInWorkspace",
"foldRegion",
"followLink",
"generateSnippet",
"getText",
"highlight",
"indentLine",
"insertCopyAfter",
"insertCopyBefore",
"insertEmptyLineAfter",
"insertEmptyLineBefore",
"insertEmptyLinesAround",
"insertSnippet",
"moveToTarget",
"outdentLine",
"pasteFromClipboard",
"randomizeTargets",
"remove",
"rename",
"replace",
"replaceWithTarget",
"revealDefinition",
"revealTypeDefinition",
"reverseTargets",
"rewrapWithPairedDelimiter",
"scrollToBottom",
"scrollToCenter",
"scrollToTop",
"setSelection",
"setSelectionAfter",
"setSelectionBefore",
"showDebugHover",
"showHover",
"showQuickFix",
"showReferences",
"sortTargets",
"swapTargets",
"toggleLineBreakpoint",
"toggleLineComment",
"unfoldRegion",
"wrapWithPairedDelimiter",
"wrapWithSnippet"
"type": [
"string",
"object"
],
"properties": {
"actionId": {
"type": "string",
"enum": [
"callAsFunction",
"clearAndSetSelection",
"copyToClipboard",
"cutToClipboard",
"deselect",
"editNew",
"editNewLineAfter",
"editNewLineBefore",
"executeCommand",
"extractVariable",
"findInDocument",
"findInWorkspace",
"foldRegion",
"followLink",
"generateSnippet",
"getText",
"highlight",
"indentLine",
"insertCopyAfter",
"insertCopyBefore",
"insertEmptyLineAfter",
"insertEmptyLineBefore",
"insertEmptyLinesAround",
"insertSnippet",
"moveToTarget",
"outdentLine",
"pasteFromClipboard",
"randomizeTargets",
"remove",
"rename",
"replace",
"replaceWithTarget",
"revealDefinition",
"revealTypeDefinition",
"reverseTargets",
"rewrapWithPairedDelimiter",
"scrollToBottom",
"scrollToCenter",
"scrollToTop",
"setSelection",
"setSelectionAfter",
"setSelectionBefore",
"showDebugHover",
"showHover",
"showQuickFix",
"showReferences",
"sortTargets",
"swapTargets",
"toggleLineBreakpoint",
"toggleLineComment",
"unfoldRegion",
"wrapWithPairedDelimiter",
"wrapWithSnippet"
],
"description": "The cursorless command to run"
},
"exitCursorlessMode": {
"type": "boolean",
"description": "If `true`, indicates that the command should exit cursorless mode after it is run."
}
},
"required": [
"actionId"
]
}
},
Expand Down
12 changes: 10 additions & 2 deletions packages/cursorless-vscode/src/keyboard/KeyboardActionType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,22 @@ type ExtraKeyboardActionType = (typeof extraKeyboardActionNames)[number];
type ExcludedKeyboardActionType = (typeof excludedKeyboardActionNames)[number];
type ComplexKeyboardActionType = (typeof complexKeyboardActionTypes)[number];
export type SimpleKeyboardActionType = Exclude<
KeyboardActionType,
KeyboardActionDescriptor,
ComplexKeyboardActionType
>;

export type KeyboardActionDescriptor =
| KeyboardActionType
| {
actionId: KeyboardActionType;
exitCursorlessMode?: boolean;
};

export type KeyboardActionType =
| Exclude<ActionType, ExcludedKeyboardActionType>
| ExtraKeyboardActionType;

const keyboardActionNames: KeyboardActionType[] = [
const keyboardActionNames: KeyboardActionDescriptor[] = [
...actionNames.filter(
(
actionName,
Expand Down
25 changes: 16 additions & 9 deletions packages/cursorless-vscode/src/keyboard/KeyboardCommandHandler.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { ScopeType, SurroundingPairName } from "@cursorless/common";
import * as vscode from "vscode";
import { HatColor, HatShape } from "../ide/vscode/hatStyles.types";
import { SimpleKeyboardActionType } from "./KeyboardActionType";
import {
KeyboardActionDescriptor,
SimpleKeyboardActionType,
} from "./KeyboardActionType";
import KeyboardCommandsTargeted from "./KeyboardCommandsTargeted";
import { ModalVscodeCommandDescriptor } from "./TokenTypes";
import { surroundingPairsDelimiters } from "@cursorless/cursorless-engine";
Expand Down Expand Up @@ -79,11 +82,12 @@ export class KeyboardCommandHandler {
}

performSimpleActionOnTarget({
actionName,
actionDescriptor,
}: {
actionName: SimpleKeyboardActionType;
actionDescriptor: KeyboardActionDescriptor;
}) {
this.targeted.performSimpleActionOnTarget(actionName);
console.log("performSimpleActionOnTarget:", actionDescriptor);
this.targeted.performSimpleActionOnTarget(actionDescriptor);
}

modifyTargetContainingScope(arg: { scopeType: ScopeType }) {
Expand All @@ -92,10 +96,13 @@ export class KeyboardCommandHandler {
performWrapActionOnTarget({ delimiter }: { delimiter: SurroundingPairName }) {
const [left, right] = surroundingPairsDelimiters[delimiter]!;
this.targeted.performActionOnTarget((target) => ({
name: "wrapWithPairedDelimiter",
target,
left,
right,
action: {
name: "wrapWithPairedDelimiter",
target,
left,
right,
},
exitCursorlessMode: false,
}));
}

Expand Down Expand Up @@ -166,6 +173,6 @@ interface Offset {
number: number | null;
}

function isString(input: any): input is string {
export function isString(input: any): input is string {
return typeof input === "string" || input instanceof String;
}
147 changes: 82 additions & 65 deletions packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import type { HatColor, HatShape } from "../ide/vscode/hatStyles.types";
import { getStyleName } from "../ide/vscode/hats/getStyleName";
import KeyboardCommandsModal from "./KeyboardCommandsModal";
import KeyboardHandler from "./KeyboardHandler";
import { KeyboardActionDescriptor } from "./KeyboardActionType";
import { isString } from "./KeyboardCommandHandler";

type TargetingMode = "replace" | "extend" | "append";

Expand Down Expand Up @@ -179,59 +181,81 @@ export default class KeyboardCommandsTargeted {
* @param name The action to run
* @returns A promise that resolves to the result of the cursorless command
*/
performSimpleActionOnTarget = async (name: ActionType) => {
return this.performActionOnTarget((target) => {
switch (name) {
case "wrapWithPairedDelimiter":
case "rewrapWithPairedDelimiter":
case "insertSnippet":
case "wrapWithSnippet":
case "executeCommand":
case "replace":
case "editNew":
case "getText":
throw Error(`Unsupported keyboard action: ${name}`);
case "replaceWithTarget":
case "moveToTarget":
return {
name,
source: target,
destination: { type: "implicit" },
};
case "swapTargets":
return {
name,
target1: target,
target2: { type: "implicit" },
};
case "callAsFunction":
return {
name,
callee: target,
argument: { type: "implicit" },
};
case "pasteFromClipboard":
return {
name,
destination: {
type: "primitive",
insertionMode: "to",
performSimpleActionOnTarget = async (
actionDescription: KeyboardActionDescriptor,
) => {
let name = "";
let exitCursorlessMode = false;
if (isString(actionDescription)) {
name = actionDescription;
} else {
name = actionDescription.actionId;
exitCursorlessMode = actionDescription.exitCursorlessMode ?? false;
}
console.log("performSimpleActionOnTarget", name, exitCursorlessMode);

return this.performActionOnTarget(
(target: PartialPrimitiveTargetDescriptor) => {
let action: ActionDescriptor;
switch (name) {
case "wrapWithPairedDelimiter":
case "rewrapWithPairedDelimiter":
case "insertSnippet":
case "wrapWithSnippet":
case "executeCommand":
case "replace":
case "editNew":
case "getText":
throw Error(`Unsupported keyboard action: ${name}`);
case "replaceWithTarget":
case "moveToTarget":
action = {
name,
source: target,
destination: { type: "implicit" },
};
break;
case "swapTargets":
action = {
name,
target1: target,
target2: { type: "implicit" },
};
break;
case "callAsFunction":
action = {
name,
callee: target,
argument: { type: "implicit" },
};
break;
case "pasteFromClipboard":
action = {
name,
destination: {
type: "primitive",
insertionMode: "to",
target,
},
};
break;
case "generateSnippet":
case "highlight":
action = {
name,
target,
},
};
case "generateSnippet":
case "highlight":
return {
name,
target,
};
default:
return {
name,
target,
};
}
});
};
break;
default:
action = {
name: name as any,
target,
};
break;
}
return { action, exitCursorlessMode };
},
);
};

/**
Expand All @@ -240,19 +264,20 @@ export default class KeyboardCommandsTargeted {
* @returns A promise that resolves to the result of the cursorless command
*/
performActionOnTarget = async (
constructActionPayload: (
target: PartialPrimitiveTargetDescriptor,
) => ActionDescriptor,
constructActionPayload: (target: PartialPrimitiveTargetDescriptor) => {
action: ActionDescriptor;
exitCursorlessMode: boolean;
},
) => {
const action = constructActionPayload({
const { action, exitCursorlessMode } = constructActionPayload({
type: "primitive",
mark: {
type: "that",
},
});
const returnValue = await executeCursorlessCommand(action);

if (EXIT_CURSORLESS_MODE_ACTIONS.includes(action.name)) {
if (exitCursorlessMode) {
// For some Cursorless actions, it is more convenient if we automatically
// exit modal mode
await this.modal.modeOff();
Expand Down Expand Up @@ -355,11 +380,3 @@ function executeCursorlessCommand(action: ActionDescriptor) {
usePrePhraseSnapshot: false,
});
}

const EXIT_CURSORLESS_MODE_ACTIONS: ActionType[] = [
"setSelectionBefore",
"setSelectionAfter",
"editNewLineBefore",
"editNewLineAfter",
"clearAndSetSelection",
];
Loading

0 comments on commit 3af5e0f

Please sign in to comment.