Skip to content

Commit

Permalink
Basic keyboard features
Browse files Browse the repository at this point in the history
  • Loading branch information
pokey committed Jan 15, 2024
1 parent e31466b commit 8e046a8
Show file tree
Hide file tree
Showing 10 changed files with 217 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,7 @@ import { CompositeKeyMap } from "@cursorless/common";
import { SpeakableSurroundingPairName } from "../../spokenForms/SpokenFormType";
import { SpokenFormComponentMap } from "../getSpokenFormComponentMap";
import { CustomizableSpokenFormComponentForType } from "../SpokenFormComponent";

const surroundingPairsDelimiters: Record<
SpeakableSurroundingPairName,
[string, string] | null
> = {
curlyBrackets: ["{", "}"],
angleBrackets: ["<", ">"],
escapedDoubleQuotes: ['\\"', '\\"'],
escapedSingleQuotes: ["\\'", "\\'"],
escapedParentheses: ["\\(", "\\)"],
escapedSquareBrackets: ["\\[", "\\]"],
doubleQuotes: ['"', '"'],
parentheses: ["(", ")"],
backtickQuotes: ["`", "`"],
squareBrackets: ["[", "]"],
singleQuotes: ["'", "'"],
whitespace: [" ", " "],

any: null,
string: null,
collectionBoundary: null,
};
import { surroundingPairsDelimiters } from "./surroundingPairsDelimiters";

const surroundingPairDelimiterToName = new CompositeKeyMap<
[string, string],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { SpeakableSurroundingPairName } from "../../spokenForms/SpokenFormType";

export const surroundingPairsDelimiters: Record<
SpeakableSurroundingPairName,
[string, string] | null
> = {
curlyBrackets: ["{", "}"],
angleBrackets: ["<", ">"],
escapedDoubleQuotes: ['\\"', '\\"'],
escapedSingleQuotes: ["\\'", "\\'"],
escapedParentheses: ["\\(", "\\)"],
escapedSquareBrackets: ["\\[", "\\]"],
doubleQuotes: ['"', '"'],
parentheses: ["(", ")"],
backtickQuotes: ["`", "`"],
squareBrackets: ["[", "]"],
singleQuotes: ["'", "'"],
whitespace: [" ", " "],

any: null,
string: null,
collectionBoundary: null,
};
1 change: 1 addition & 0 deletions packages/cursorless-engine/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export * from "./testCaseRecorder/TestCaseRecorder";
export * from "./core/StoredTargets";
export * from "./typings/TreeSitter";
export * from "./cursorlessEngine";
export * from "./generateSpokenForm/defaultSpokenForms/surroundingPairsDelimiters";
export * from "./api/CursorlessEngineApi";
export * from "./CommandRunner";
export * from "./CommandHistory";
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,68 @@ import path from "path";
import { getCursorlessRepoRoot } from "@cursorless/common";
import { readFile } from "node:fs/promises";

interface TestCase {
name: string;
initialContent: string;
// keySequence is the sequence of keypresses that will be sent.
// It can include phantom ";"s for readability.
// They will be not be sent.
keySequence: string;
finalContent: string;
}

const testCases: TestCase[] = [
{
name: "and",
initialContent: "x T y\n",
// change plex and yank
keySequence: "dx;fa;dy;c",
finalContent: "T",
},
{
name: "every",
initialContent: "a a\nb b\n",
// change every token air
keySequence: "da;x;st;c",
finalContent: "b b",
},
{
name: "three",
initialContent: "a b c d e\n",
// change three tokens bat
keySequence: "db;3;st;c",
finalContent: "a e",
},
{
name: "three backwards",
initialContent: "a b c d e\n",
// change three tokens backwards drum
keySequence: "dd;-3;st;c",
finalContent: "a e",
},
{
name: "pair parens",
initialContent: "a + (b + c) + d",
// change parens bat
keySequence: "db;wp;c",
finalContent: "a + + d",
},
{
name: "pair string",
initialContent: 'a + "w" + b',
// change parens bat
keySequence: "dw;wj;c",
finalContent: "a + + b",
},
{
name: "wrap",
initialContent: "a",
// round wrap air
keySequence: "da;aw;wp",
finalContent: "(a)",
},
];

suite("Basic keyboard test", async function () {
endToEndTestSetup(this);

Expand All @@ -22,6 +84,9 @@ suite("Basic keyboard test", async function () {
test("Basic keyboard test", () => basic());
test("No automatic token expansion", () => noAutomaticTokenExpansion());
test("Run vscode command", () => vscodeCommand());
for (const t of testCases) {
test("Sequence " + t.name, () => sequence(t));
}
test("Check that entering and leaving mode is no-op", () =>
enterAndLeaveIsNoOp());
});
Expand Down Expand Up @@ -82,6 +147,22 @@ async function noAutomaticTokenExpansion() {
assert.isTrue(editor.selection.isEqual(new vscode.Selection(1, 0, 1, 0)));
}

/**
* sequence runs a test keyboard sequences.
*/
async function sequence(t: TestCase) {
const { hatTokenMap } = (await getCursorlessApi()).testHelpers!;

const editor = await openNewEditor(t.initialContent, {
languageId: "typescript",
});
await hatTokenMap.allocateHats();
editor.selection = new vscode.Selection(1, 0, 1, 0);
await vscode.commands.executeCommand("cursorless.keyboard.modal.modeOn");
await typeText(t.keySequence.replaceAll(";", ""));
assert.equal(editor.document.getText().trim(), t.finalContent);
}

async function vscodeCommand() {
const { hatTokenMap } = (await getCursorlessApi()).testHelpers!;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { ScopeType } from "@cursorless/common";
import { ScopeType, SurroundingPairName } from "@cursorless/common";
import * as vscode from "vscode";
import { HatColor, HatShape } from "../ide/vscode/hatStyles.types";
import { SimpleKeyboardActionType } from "./KeyboardActionType";
import KeyboardCommandsTargeted from "./KeyboardCommandsTargeted";
import { ModalVscodeCommandDescriptor } from "./TokenTypes";
import { surroundingPairsDelimiters } from "@cursorless/cursorless-engine";

/**
* This class defines the keyboard commands available to our modal keyboard
Expand Down Expand Up @@ -38,6 +39,13 @@ export class KeyboardCommandHandler {
});
}

targetDecoratedMarkAppend({ decoratedMark }: DecoratedMarkArg) {
this.targeted.targetDecoratedMark({
...decoratedMark,
mode: "append",
});
}

async vscodeCommand({
command: commandInfo,
}: {
Expand Down Expand Up @@ -81,6 +89,19 @@ export class KeyboardCommandHandler {
modifyTargetContainingScope(arg: { scopeType: ScopeType }) {
this.targeted.modifyTargetContainingScope(arg);
}
performWrapActionOnTarget({ delimiter }: { delimiter: SurroundingPairName }) {
const [left, right] = surroundingPairsDelimiters[delimiter]!;
this.targeted.performActionOnTarget((target) => ({
name: "wrapWithPairedDelimiter",
target,
left,
right,
}));
}

targetEveryScopeType(arg: { scopeType: ScopeType }) {
this.targeted.modifyTargetContainingScope({ ...arg, type: "everyScope" });
}

targetRelativeExclusiveScope({
offset,
Expand All @@ -95,6 +116,19 @@ export class KeyboardCommandHandler {
scopeType,
});
}

targetRelativeInclusiveScope({
offset,
scopeType,
}: TargetRelativeInclusiveScopeArg) {
this.targeted.targetModifier({
type: "relativeScope",
offset: 0,
direction: offset?.direction ?? "forward",
length: offset?.number ?? 1,
scopeType,
});
}
}

interface DecoratedMarkArg {
Expand All @@ -108,6 +142,10 @@ interface TargetRelativeExclusiveScopeArg {
length: number | null;
scopeType: ScopeType;
}
interface TargetRelativeInclusiveScopeArg {
offset: Offset;
scopeType: ScopeType;
}

interface Offset {
direction: "forward" | "backward" | null;
Expand Down
12 changes: 9 additions & 3 deletions packages/cursorless-vscode/src/keyboard/TokenTypes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SimpleScopeTypeType } from "@cursorless/common";
import { SimpleScopeTypeType, SurroundingPairName } from "@cursorless/common";
import { HatColor, HatShape } from "../ide/vscode/hatStyles.types";
import {
KeyboardActionType,
Expand All @@ -14,17 +14,19 @@ export interface SectionTypes {
color: HatColor;
misc: MiscValue;
scope: SimpleScopeTypeType;
pairedDelimiter: SurroundingPairName;
shape: HatShape;
vscodeCommand: ModalVscodeCommandDescriptor;
modifier: ModifierType;
}
type ModifierType = "nextPrev";
type ModifierType = "nextPrev" | "every";
type MiscValue =
| "combineColorAndShape"
| "makeRange"
| "makeList"
| "forward"
| "backward";
| "backward"
| "wrap"; // TODO: move wrap somewhere out of misc

/**
* Maps from token type used in parser to the type of values that the token type
Expand All @@ -48,17 +50,21 @@ export interface TokenTypeValueMap {
color: HatColor;
shape: HatShape;
vscodeCommand: ModalVscodeCommandDescriptor;
pairedDelimiter: SurroundingPairName;

// action config section
simpleAction: SimpleKeyboardActionType;
wrap: "wrap";

// misc config section
makeRange: "makeRange";
makeList: "makeList";
combineColorAndShape: "combineColorAndShape";
direction: "forward" | "backward";

// modifier config section
nextPrev: "nextPrev";
every: "every";

digit: number;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,19 @@ export function getTokenTypeKeyMaps(
color: config.getTokenKeyMap("color"),
shape: config.getTokenKeyMap("shape"),
vscodeCommand: config.getTokenKeyMap("vscodeCommand"),
pairedDelimiter: config.getTokenKeyMap("pairedDelimiter"),

// action config section
simpleAction: config.getTokenKeyMap(
"simpleAction",
"action",
simpleKeyboardActionNames,
),
wrap: config.getTokenKeyMap("wrap", "misc", ["wrap"]),

// misc config section
makeRange: config.getTokenKeyMap("makeRange", "misc", ["makeRange"]),
makeList: config.getTokenKeyMap("makeList", "misc", ["makeList"]),
combineColorAndShape: config.getTokenKeyMap(
"combineColorAndShape",
"misc",
Expand All @@ -57,6 +60,7 @@ export function getTokenTypeKeyMaps(
]),

// modifier config section
every: config.getTokenKeyMap("every", "modifier", ["every"]),
nextPrev: config.getTokenKeyMap("nextPrev", "modifier", ["nextPrev"]),

digit: Object.fromEntries(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
// @ts-ignore
function id(d: any[]): any { return d[0]; }
declare var makeRange: any;
declare var makeList: any;
declare var every: any;
declare var nextPrev: any;
declare var simpleAction: any;
declare var wrap: any;
declare var pairedDelimiter: any;
declare var vscodeCommand: any;
declare var simpleScopeTypeType: any;
declare var color: any;
Expand Down Expand Up @@ -51,7 +55,11 @@ const grammar: Grammar = {
{"name": "main", "symbols": [(keyboardLexer.has("makeRange") ? {type: "makeRange"} : makeRange), "decoratedMark"], "postprocess":
command("targetDecoratedMarkExtend", [_, "decoratedMark"])
},
{"name": "main", "symbols": [(keyboardLexer.has("makeList") ? {type: "makeList"} : makeList), "decoratedMark"], "postprocess":
command("targetDecoratedMarkAppend", [_, "decoratedMark"])
},
{"name": "main", "symbols": ["scopeType"], "postprocess": command("modifyTargetContainingScope", ["scopeType"])},
{"name": "main", "symbols": [(keyboardLexer.has("every") ? {type: "every"} : every), "scopeType"], "postprocess": command("targetEveryScopeType", [_, "scopeType"])},
{"name": "main$ebnf$1", "symbols": ["offset"], "postprocess": id},
{"name": "main$ebnf$1", "symbols": [], "postprocess": () => null},
{"name": "main$ebnf$2", "symbols": ["number"], "postprocess": id},
Expand All @@ -62,9 +70,18 @@ const grammar: Grammar = {
["offset", _, "length", "scopeType"],
)
},
{"name": "main", "symbols": ["offset", "scopeType"], "postprocess":
command("targetRelativeInclusiveScope", ["offset", "scopeType"])
},
{"name": "main", "symbols": [(keyboardLexer.has("simpleAction") ? {type: "simpleAction"} : simpleAction)], "postprocess": command("performSimpleActionOnTarget", ["actionName"])},
{"name": "main", "symbols": [(keyboardLexer.has("wrap") ? {type: "wrap"} : wrap), (keyboardLexer.has("pairedDelimiter") ? {type: "pairedDelimiter"} : pairedDelimiter)], "postprocess":
command("performWrapActionOnTarget", [_, "delimiter"])
},
{"name": "main", "symbols": [(keyboardLexer.has("vscodeCommand") ? {type: "vscodeCommand"} : vscodeCommand)], "postprocess": command("vscodeCommand", ["command"])},
{"name": "scopeType", "symbols": [(keyboardLexer.has("simpleScopeTypeType") ? {type: "simpleScopeTypeType"} : simpleScopeTypeType)], "postprocess": capture("type")},
{"name": "scopeType", "symbols": [(keyboardLexer.has("pairedDelimiter") ? {type: "pairedDelimiter"} : pairedDelimiter)], "postprocess":
([delimiter]) => ({ type: "surroundingPair", delimiter })
},
{"name": "decoratedMark", "symbols": [(keyboardLexer.has("color") ? {type: "color"} : color)], "postprocess": capture("color")},
{"name": "decoratedMark", "symbols": [(keyboardLexer.has("shape") ? {type: "shape"} : shape)], "postprocess": capture("shape")},
{"name": "decoratedMark", "symbols": [(keyboardLexer.has("combineColorAndShape") ? {type: "combineColorAndShape"} : combineColorAndShape), (keyboardLexer.has("color") ? {type: "color"} : color), (keyboardLexer.has("shape") ? {type: "shape"} : shape)], "postprocess": capture(_, "color", "shape")},
Expand Down
Loading

0 comments on commit 8e046a8

Please sign in to comment.