From 91e2755beccb5f00a2f6e8cd9ab655015763458f Mon Sep 17 00:00:00 2001 From: knork Date: Tue, 26 Sep 2023 21:35:16 +0200 Subject: [PATCH 01/13] First attempt at making exit keys configurable --- packages/cursorless-vscode/package.json | 8 ++++++++ .../src/keyboard/KeyboardCommandsTargeted.ts | 13 ++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/cursorless-vscode/package.json b/packages/cursorless-vscode/package.json index 584de90030..707ff0e2a5 100644 --- a/packages/cursorless-vscode/package.json +++ b/packages/cursorless-vscode/package.json @@ -902,6 +902,14 @@ ] } }, + "cursorless.experimental.keyboard.modal.exit_actions": { + "description": "Define actions that exit modal mode", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, "cursorless.experimental.keyboard.modal.keybindings.colors": { "description": "Define modal keybindings for colors", "type": "object", diff --git a/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts b/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts index 80d44036c2..69f95fcd66 100644 --- a/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts +++ b/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts @@ -235,6 +235,8 @@ export default class KeyboardCommandsTargeted { await this.highlightTarget(); + const EXIT_CURSORLESS_MODE_ACTIONS:string[]=vscode.workspace.getConfiguration("cursorless.experimental.keyboard.modal").get("exit_actions")??[]; + if (EXIT_CURSORLESS_MODE_ACTIONS.includes(name)) { // For some Cursorless actions, it is more convenient if we automatically // exit modal mode @@ -284,10 +286,7 @@ function executeCursorlessCommand(action: ActionDescriptor) { }); } -const EXIT_CURSORLESS_MODE_ACTIONS: ActionType[] = [ - "setSelectionBefore", - "setSelectionAfter", - "editNewLineBefore", - "editNewLineAfter", - "clearAndSetSelection", -]; + + + + From 8e2ec24650839fd8b17bb6ead9b733c0ca58b91b Mon Sep 17 00:00:00 2001 From: knork Date: Sun, 1 Oct 2023 21:09:36 +0200 Subject: [PATCH 02/13] expansion in working --- .vscode/settings.json | 5 +- packages/cursorless-vscode/package.json | 10 +++ .../src/keyboard/KeyboardCommandsModal.ts | 42 +++++++++++- .../src/keyboard/KeyboardCommandsTargeted.ts | 65 ++++++++++++++++++- .../src/keyboard/defaultKeymaps.ts | 4 +- 5 files changed, 119 insertions(+), 7 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 7b4cac949e..e1ffebcef6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -24,5 +24,8 @@ ], "files.eol": "\n", "typescript.enablePromptUseWorkspaceTsdk": true, - "typescript.tsdk": "node_modules/typescript/lib" + "typescript.tsdk": "node_modules/typescript/lib", + "[typescript]": { + "editor.defaultFormatter": "vscode.typescript-language-features" + } } diff --git a/packages/cursorless-vscode/package.json b/packages/cursorless-vscode/package.json index 707ff0e2a5..68d8126694 100644 --- a/packages/cursorless-vscode/package.json +++ b/packages/cursorless-vscode/package.json @@ -927,6 +927,16 @@ ] } }, + "cursorless.experimental.keyboard.modal.keybindings.modifiers": { + "description": "Define modal keybindings for modifiers", + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "interiorOnly" + ] + } + }, "cursorless.experimental.keyboard.modal.keybindings.shapes": { "description": "Define modal keybindings for shapes", "type": "object", diff --git a/packages/cursorless-vscode/src/keyboard/KeyboardCommandsModal.ts b/packages/cursorless-vscode/src/keyboard/KeyboardCommandsModal.ts index e08f12f716..e906d8cc31 100644 --- a/packages/cursorless-vscode/src/keyboard/KeyboardCommandsModal.ts +++ b/packages/cursorless-vscode/src/keyboard/KeyboardCommandsModal.ts @@ -6,11 +6,13 @@ import { Keymap, DEFAULT_SCOPE_KEYMAP, DEFAULT_SHAPE_KEYMAP, + DEFAULT_MODIFIER_KEYMAP, } from "./defaultKeymaps"; +import { Direction } from "@cursorless/common"; import KeyboardCommandsTargeted from "./KeyboardCommandsTargeted"; import KeyboardHandler from "./KeyboardHandler"; - -type SectionName = "actions" | "scopes" | "colors" | "shapes"; +import { executeCursorlessCommand } from "./KeyboardCommandsTargeted"; +type SectionName = "actions" | "scopes" | "colors" | "shapes" | "modifiers"; interface KeyHandler { sectionName: SectionName; @@ -34,6 +36,7 @@ export default class KeyboardCommandsModal { * colors, etc). */ private mergedKeymap!: Record>; + cursorOffset: vscode.Position; constructor( private extensionContext: vscode.ExtensionContext, @@ -45,6 +48,7 @@ export default class KeyboardCommandsModal { this.handleInput = this.handleInput.bind(this); this.constructMergedKeymap(); + this.cursorOffset = new vscode.Position(0, 0); } init() { @@ -82,6 +86,11 @@ export default class KeyboardCommandsModal { shape: value, }), ); + + this.handleSection("modifiers", DEFAULT_MODIFIER_KEYMAP, (value) => + this.targeted.targetModifierType("interiorOnly"), + + ); } /** @@ -172,9 +181,38 @@ export default class KeyboardCommandsModal { let sequence = text; let keyHandler: KeyHandler | undefined = this.mergedKeymap[sequence]; + const curCursorOffset = vscode.window.activeTextEditor?.selection.active; + + if (curCursorOffset != null && this.cursorOffset != null) { + if (curCursorOffset.line != this.cursorOffset.line) { + await executeCursorlessCommand({ + name: "highlight", + target: { + type: "primitive", + mark: { + type: "cursor", + }, + }, + }); + vscode.window.showInformationMessage("Cursor moved, highlighting new selection"); + } + } + if (curCursorOffset != null) { + this.cursorOffset = curCursorOffset; + } + + if (sequence == "i"){ + vscode.window.showInformationMessage("i pressed"); + this.targeted.expandTarget("forward"); + return; + } + // We handle multi-key sequences by repeatedly awaiting a single keypress // until they've pressed something in the map. while (keyHandler == null) { + + + if (!this.isPrefixOfKey(sequence)) { const errorMessage = `Unknown key sequence "${sequence}"`; vscode.window.showErrorMessage(errorMessage); diff --git a/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts b/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts index 69f95fcd66..3d3685815d 100644 --- a/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts +++ b/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts @@ -1,6 +1,7 @@ import { ActionDescriptor, ActionType, + Direction, LATEST_VERSION, PartialPrimitiveTargetDescriptor, PartialTargetDescriptor, @@ -14,7 +15,7 @@ import KeyboardCommandsModal from "./KeyboardCommandsModal"; import KeyboardHandler from "./KeyboardHandler"; type TargetingMode = "replace" | "extend" | "append"; - +type TargetModifierTypeArgument = "every" | "interiorOnly" | "excludeInterior"; interface TargetDecoratedMarkArgument { color?: HatColor; shape?: HatShape; @@ -151,6 +152,24 @@ export default class KeyboardCommandsTargeted { }, }); + + targetModifierType = async (mod: TargetModifierTypeArgument) => + await executeCursorlessCommand({ + name: "highlight", + target: { + type: "primitive", + modifiers: [ + { + type: "interiorOnly", + + }, + ], + mark: { + type: "that", + }, + }, + }); + private highlightTarget = () => executeCursorlessCommand({ name: "highlight", @@ -258,7 +277,47 @@ export default class KeyboardCommandsTargeted { mark: { type: "cursor", }, - modifiers: [{ type: "toRawSelection" }], + }, + }); + + expandTarget = (direction:Direction) => + + executeCursorlessCommand({ + + name: "highlight", + target: { + type: "range", + anchor: { + type: "primitive", + mark: { + type: "that", + }, + }, + active: { + type: "primitive", + mark: { + type: "that", + + }, + modifiers: [ + { + type: "relativeScope", + direction: direction, + length: 1, + offset: 1, + scopeType: { + type: "token", + }, + + }, + + ], + }, + excludeActive: false, + excludeAnchor: false, + + + }, }); @@ -278,7 +337,7 @@ export default class KeyboardCommandsTargeted { }); } -function executeCursorlessCommand(action: ActionDescriptor) { +export function executeCursorlessCommand(action: ActionDescriptor) { return runCursorlessCommand({ action, version: LATEST_VERSION, diff --git a/packages/cursorless-vscode/src/keyboard/defaultKeymaps.ts b/packages/cursorless-vscode/src/keyboard/defaultKeymaps.ts index a35daad0e7..2fb28e8a76 100644 --- a/packages/cursorless-vscode/src/keyboard/defaultKeymaps.ts +++ b/packages/cursorless-vscode/src/keyboard/defaultKeymaps.ts @@ -1,4 +1,4 @@ -import { ActionType } from "@cursorless/common"; +import { ActionType, ModifierType } from "@cursorless/common"; import { SimpleScopeTypeType } from "@cursorless/common"; import { HatColor, HatShape } from "../ide/vscode/hatStyles.types"; import { isTesting } from "@cursorless/common"; @@ -22,3 +22,5 @@ export const DEFAULT_COLOR_KEYMAP: Keymap = isTesting() : {}; export const DEFAULT_SHAPE_KEYMAP: Keymap = isTesting() ? {} : {}; + +export const DEFAULT_MODIFIER_KEYMAP: Keymap = isTesting() ? {} : {}; From ba9e2a1dc1f448808f4bf53ce9e38473a27a5898 Mon Sep 17 00:00:00 2001 From: knork Date: Tue, 3 Oct 2023 17:33:39 +0200 Subject: [PATCH 03/13] before ripping out the merged keymap --- .../src/keyboard/ActionMap.ts | 105 ++++++++++++ .../src/keyboard/KeyboardCommandsModal.ts | 58 ++++--- .../src/keyboard/KeyboardCommandsTargeted.ts | 16 +- .../cursorless-vscode/src/keyboard/Keymap.ts | 151 ++++++++++++++++++ 4 files changed, 303 insertions(+), 27 deletions(-) create mode 100644 packages/cursorless-vscode/src/keyboard/ActionMap.ts create mode 100644 packages/cursorless-vscode/src/keyboard/Keymap.ts diff --git a/packages/cursorless-vscode/src/keyboard/ActionMap.ts b/packages/cursorless-vscode/src/keyboard/ActionMap.ts new file mode 100644 index 0000000000..9f1e98351a --- /dev/null +++ b/packages/cursorless-vscode/src/keyboard/ActionMap.ts @@ -0,0 +1,105 @@ +import { ActionType } from "@cursorless/common"; +import KeyboardCommandsTargeted, { executeCursorlessCommand } from "./KeyboardCommandsTargeted"; +import * as vscode from "vscode"; + + +export default class ActionMap { + actionMap: Record; + + + constructor(actionMap: Record) { + this.actionMap = actionMap; + } + + getAction(key: string): ActionType | undefined { + return this.actionMap[key]; + } + + isAction(key: string): boolean { + return this.actionMap[key] !== undefined; + } + + async handleInput(key: string, state: KeyboardCommandsTargeted) { + let returnValue: unknown; + if (!this.isAction(key)) { + return; + } + const name: ActionType = this.getAction(key)!; + const target = state.lastTarget; + + 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": + returnValue = await executeCursorlessCommand({ + name, + source: target, + destination: { type: "implicit" }, + }); + break; + case "swapTargets": + returnValue = await executeCursorlessCommand({ + name, + target1: target, + target2: { type: "implicit" }, + }); + break; + case "callAsFunction": + returnValue = await executeCursorlessCommand({ + name, + callee: target, + argument: { type: "implicit" }, + }); + break; + case "pasteFromClipboard": + + if ( target.type !== "primitive" ) { + throw Error(`Unsupported target type: ${target.type} for action: ${name}`); + } + + returnValue = await executeCursorlessCommand({ + name, + destination: { + type: "primitive", + insertionMode: "to", + target, + }, + }); + break; + case "generateSnippet": + case "highlight": + returnValue = await executeCursorlessCommand({ + name, + target, + }); + break; + default: + returnValue = await executeCursorlessCommand({ + name, + target, + }); + } + + await state.highlightTarget(); + + const EXIT_CURSORLESS_MODE_ACTIONS: string[] = vscode.workspace.getConfiguration("cursorless.experimental.keyboard.modal").get("exit_actions") ?? []; + + if (EXIT_CURSORLESS_MODE_ACTIONS.includes(name)) { + // For some Cursorless actions, it is more convenient if we automatically + // exit modal mode + await state.getMode().modeOff(); + } + + return returnValue; + } + + +} \ No newline at end of file diff --git a/packages/cursorless-vscode/src/keyboard/KeyboardCommandsModal.ts b/packages/cursorless-vscode/src/keyboard/KeyboardCommandsModal.ts index e906d8cc31..4e81846261 100644 --- a/packages/cursorless-vscode/src/keyboard/KeyboardCommandsModal.ts +++ b/packages/cursorless-vscode/src/keyboard/KeyboardCommandsModal.ts @@ -1,4 +1,4 @@ -import { keys, merge, toPairs } from "lodash"; +import { List, keys, merge, toPairs } from "lodash"; import * as vscode from "vscode"; import { DEFAULT_ACTION_KEYMAP, @@ -8,12 +8,16 @@ import { DEFAULT_SHAPE_KEYMAP, DEFAULT_MODIFIER_KEYMAP, } from "./defaultKeymaps"; -import { Direction } from "@cursorless/common"; import KeyboardCommandsTargeted from "./KeyboardCommandsTargeted"; import KeyboardHandler from "./KeyboardHandler"; import { executeCursorlessCommand } from "./KeyboardCommandsTargeted"; + type SectionName = "actions" | "scopes" | "colors" | "shapes" | "modifiers"; + + + + interface KeyHandler { sectionName: SectionName; value: T; @@ -37,6 +41,8 @@ export default class KeyboardCommandsModal { */ private mergedKeymap!: Record>; cursorOffset: vscode.Position; + + constructor( private extensionContext: vscode.ExtensionContext, @@ -49,6 +55,7 @@ export default class KeyboardCommandsModal { this.constructMergedKeymap(); this.cursorOffset = new vscode.Position(0, 0); + } init() { @@ -132,6 +139,8 @@ export default class KeyboardCommandsModal { this.mergedKeymap[key] = entry; } + + } modeOn = async () => { @@ -177,6 +186,19 @@ export default class KeyboardCommandsModal { return this.inputDisposable != null; } + + private async setTargetToCursor() { + await executeCursorlessCommand({ + name: "highlight", + target: { + type: "primitive", + mark: { + type: "cursor", + }, + }, + }); + } + async handleInput(text: string) { let sequence = text; let keyHandler: KeyHandler | undefined = this.mergedKeymap[sequence]; @@ -184,34 +206,15 @@ export default class KeyboardCommandsModal { const curCursorOffset = vscode.window.activeTextEditor?.selection.active; if (curCursorOffset != null && this.cursorOffset != null) { - if (curCursorOffset.line != this.cursorOffset.line) { - await executeCursorlessCommand({ - name: "highlight", - target: { - type: "primitive", - mark: { - type: "cursor", - }, - }, - }); - vscode.window.showInformationMessage("Cursor moved, highlighting new selection"); - } - } - if (curCursorOffset != null) { - this.cursorOffset = curCursorOffset; + if (!curCursorOffset.isEqual(this.cursorOffset) ) { + await this.setTargetToCursor(); + } } - if (sequence == "i"){ - vscode.window.showInformationMessage("i pressed"); - this.targeted.expandTarget("forward"); - return; - } // We handle multi-key sequences by repeatedly awaiting a single keypress // until they've pressed something in the map. - while (keyHandler == null) { - - + while (keyHandler == null) { if (!this.isPrefixOfKey(sequence)) { const errorMessage = `Unknown key sequence "${sequence}"`; @@ -234,6 +237,9 @@ export default class KeyboardCommandsModal { } keyHandler.handleValue(); + if (curCursorOffset != null) { + this.cursorOffset = curCursorOffset; + } } isPrefixOfKey(text: string): boolean { @@ -241,7 +247,7 @@ export default class KeyboardCommandsModal { } /** - * This function can be used to deterct if a proposed map entry conflicts with + * This function can be used to detect if a proposed map entry conflicts with * one in the map. Used to detect if the user tries to use two map entries, * one of which is a prefix of the other. * @param text The proposed new map entry diff --git a/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts b/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts index 3d3685815d..4a10fa2550 100644 --- a/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts +++ b/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts @@ -39,6 +39,7 @@ interface TargetScopeTypeArgument { */ export default class KeyboardCommandsTargeted { private modal!: KeyboardCommandsModal; + lastTarget: PartialTargetDescriptor; constructor(private keyboardHandler: KeyboardHandler) { this.targetDecoratedMark = this.targetDecoratedMark.bind(this); @@ -46,12 +47,23 @@ export default class KeyboardCommandsTargeted { this.targetScopeType = this.targetScopeType.bind(this); this.targetSelection = this.targetSelection.bind(this); this.clearTarget = this.clearTarget.bind(this); + this.highlightTarget = this.highlightTarget.bind(this); + this.lastTarget = { + type: "primitive", + mark: { + type: "that", + }, + }; } init(modal: KeyboardCommandsModal) { this.modal = modal; } + getMode() { + return this.modal; + } + /** * Sets the highlighted target to the given decorated mark. If {@link character} is * omitted, then we wait for the user to press a character @@ -76,6 +88,8 @@ export default class KeyboardCommandsTargeted { // Cancelled return; } + + let target: PartialTargetDescriptor = { type: "primitive", @@ -170,7 +184,7 @@ export default class KeyboardCommandsTargeted { }, }); - private highlightTarget = () => + highlightTarget = () => executeCursorlessCommand({ name: "highlight", target: { diff --git a/packages/cursorless-vscode/src/keyboard/Keymap.ts b/packages/cursorless-vscode/src/keyboard/Keymap.ts new file mode 100644 index 0000000000..a3354bc4d9 --- /dev/null +++ b/packages/cursorless-vscode/src/keyboard/Keymap.ts @@ -0,0 +1,151 @@ + +import { ActionType, ModifierType, SimpleScopeTypeType, isTesting } from "@cursorless/common"; +import * as vscode from "vscode"; +import { HatColor, HatShape } from "../ide/vscode/hatStyles.types"; +import { merge, toPairs } from "lodash"; + + +type SectionName = "actions" | "scopes" | "colors" | "shapes" | "modifiers"; +export type SectionKeymap = Record; + +export const DEFAULT_ACTION_KEYMAP: SectionKeymap = isTesting() + ? { t: "setSelection" } + : {}; + +export const DEFAULT_SCOPE_KEYMAP: SectionKeymap = isTesting() + ? { sf: "namedFunction" } + : {}; + +export const DEFAULT_COLOR_KEYMAP: SectionKeymap = isTesting() + ? { d: "default" } + : {}; + +export const DEFAULT_SHAPE_KEYMAP: SectionKeymap = isTesting() ? {} : {}; + +export const DEFAULT_MODIFIER_KEYMAP: SectionKeymap = isTesting() ? {} : {}; + + +export default class Keymap { + + private actionKeymap: SectionKeymap = {}; + private scopeKeymap: SectionKeymap = {}; + private colorKeymap: SectionKeymap = {}; + private shapeKeymap: SectionKeymap = {}; + private modifierKeymap: SectionKeymap = {}; + + private handlerMap: Record = {}; + + constructor() { + this.loadKeymap(); + } + + public loadKeymap() { + this.loadSection("actions", DEFAULT_ACTION_KEYMAP); + this.loadSection("scopes", DEFAULT_SCOPE_KEYMAP); + this.loadSection("colors", DEFAULT_COLOR_KEYMAP); + this.loadSection("shapes", DEFAULT_SHAPE_KEYMAP); + this.loadSection("modifiers", DEFAULT_MODIFIER_KEYMAP); + } + + + + private loadSection(sectionName: SectionName, defaultKeyMap: SectionKeymap) { + const userOverrides: SectionKeymap = + vscode.workspace + .getConfiguration("cursorless.experimental.keyboard.modal.keybindings") + .get>(sectionName) ?? {}; + const keyMap = merge({}, defaultKeyMap, userOverrides); + + for (const [key, value] of toPairs(keyMap)) { + const conflictingEntry = this.getConflictingKeyMapEntry(key); + if (conflictingEntry != null) { + vscode.window.showErrorMessage( + `Conflicting keybindings: \`${sectionName}.${value}\` and \`${conflictingEntry[0]}.${conflictingEntry[1]}\` both want key '${key}'`, + ); + + continue; + } + this.setMap(sectionName,key,value); + } + } + + public getMergeKeys():string[]{ + return merge({}, this.actionKeymap, this.scopeKeymap, this.colorKeymap, this.shapeKeymap, this.modifierKeymap).keys(); + } + + + public setMap(sectionName:SectionName,key:string,value:any){ + switch (sectionName) { + case "actions": + this.actionKeymap[key]=value; + break; + case "scopes": + this.scopeKeymap[key]=value; + break; + case "colors": + this.colorKeymap[key]=value; + break; + case "shapes": + this.shapeKeymap[key]=value; + break; + case "modifiers": + this.modifierKeymap[key]=value; + break; + } + } + + public clearMap(sectionName:SectionName){ + switch (sectionName) { + case "actions": + this.actionKeymap={}; + break; + case "scopes": + this.scopeKeymap={}; + break; + case "colors": + this.colorKeymap={}; + break; + case "shapes": + this.shapeKeymap={}; + break; + case "modifiers": + this.modifierKeymap={}; + break; + } + } + + + + /** + * This function can be used to detect if a proposed map entry conflicts with + * one in the map. Used to detect if the user tries to use two map entries, + * one of which is a prefix of the other. + * @param text The proposed new map entry + * @returns The first map entry that conflicts with {@link text}, if one + * exists + */ + getConflictingKeyMapEntry(text: string): [string, string] | undefined { + const allMap = { + "actions": this.actionKeymap.keys, + "scopes": this.scopeKeymap.keys, + "colors": this.colorKeymap.keys, + "shapes": this.shapeKeymap.keys, + "modifiers": this.modifierKeymap.keys, + }; + + for (const [sectionName, keyMap] of Object.entries(allMap)) { + const conflictingPair = Object.entries(keyMap).find( + ([key]) => text.startsWith(key) || key.startsWith(text) + ); + + if (conflictingPair != null) { + return [sectionName, conflictingPair[0]]; + } + } + + return undefined; + } + + + +} \ No newline at end of file From 6c00936630a83e01c4163651dd44ecb9a0fb839d Mon Sep 17 00:00:00 2001 From: knork Date: Tue, 3 Oct 2023 20:36:55 +0200 Subject: [PATCH 04/13] shape and color can naw be selected at the same time --- .../src/keyboard/KeyboardCommandsModal.ts | 172 ++++++------------ .../src/keyboard/KeyboardCommandsTargeted.ts | 34 +++- .../cursorless-vscode/src/keyboard/Keymap.ts | 70 ++++++- 3 files changed, 143 insertions(+), 133 deletions(-) diff --git a/packages/cursorless-vscode/src/keyboard/KeyboardCommandsModal.ts b/packages/cursorless-vscode/src/keyboard/KeyboardCommandsModal.ts index 4e81846261..fa33ce9e97 100644 --- a/packages/cursorless-vscode/src/keyboard/KeyboardCommandsModal.ts +++ b/packages/cursorless-vscode/src/keyboard/KeyboardCommandsModal.ts @@ -1,29 +1,14 @@ -import { List, keys, merge, toPairs } from "lodash"; import * as vscode from "vscode"; -import { - DEFAULT_ACTION_KEYMAP, - DEFAULT_COLOR_KEYMAP, - Keymap, - DEFAULT_SCOPE_KEYMAP, - DEFAULT_SHAPE_KEYMAP, - DEFAULT_MODIFIER_KEYMAP, -} from "./defaultKeymaps"; + import KeyboardCommandsTargeted from "./KeyboardCommandsTargeted"; import KeyboardHandler from "./KeyboardHandler"; import { executeCursorlessCommand } from "./KeyboardCommandsTargeted"; - -type SectionName = "actions" | "scopes" | "colors" | "shapes" | "modifiers"; +import Keymap from "./Keymap"; -interface KeyHandler { - sectionName: SectionName; - value: T; - handleValue(): Promise; -} - /** * Defines a mode to use with a modal version of Cursorless keyboard. */ @@ -39,9 +24,9 @@ export default class KeyboardCommandsModal { * Merged map from all the different sections of the key map (eg actions, * colors, etc). */ - private mergedKeymap!: Record>; - cursorOffset: vscode.Position; + cursorOffset: vscode.Position; + private keymap: Keymap; constructor( @@ -53,8 +38,9 @@ export default class KeyboardCommandsModal { this.modeOff = this.modeOff.bind(this); this.handleInput = this.handleInput.bind(this); - this.constructMergedKeymap(); + // this.constructMergedKeymap(); this.cursorOffset = new vscode.Position(0, 0); + this.keymap = new Keymap(); } @@ -66,83 +52,14 @@ export default class KeyboardCommandsModal { "cursorless.experimental.keyboard.modal.keybindings", ) ) { - this.constructMergedKeymap(); + this.keymap.loadKeymap(); } }), ); - } - - private constructMergedKeymap() { - this.mergedKeymap = {}; - - this.handleSection("actions", DEFAULT_ACTION_KEYMAP, (value) => - this.targeted.performActionOnTarget(value), - ); - this.handleSection("scopes", DEFAULT_SCOPE_KEYMAP, (value) => - this.targeted.targetScopeType({ - scopeType: value, - }), - ); - this.handleSection("colors", DEFAULT_COLOR_KEYMAP, (value) => - this.targeted.targetDecoratedMark({ - color: value, - }), - ); - this.handleSection("shapes", DEFAULT_SHAPE_KEYMAP, (value) => - this.targeted.targetDecoratedMark({ - shape: value, - }), - ); - - this.handleSection("modifiers", DEFAULT_MODIFIER_KEYMAP, (value) => - this.targeted.targetModifierType("interiorOnly"), - - ); - } - - /** - * Adds a section (eg actions, scopes, etc) to the merged keymap. - * - * @param sectionName The name of the section (eg `"actions"`, `"scopes"`, etc) - * @param defaultKeyMap The default values for this keymap - * @param handleValue The function to call when the user presses the given key - */ - private handleSection( - sectionName: SectionName, - defaultKeyMap: Keymap, - handleValue: (value: T) => Promise, - ) { - const userOverrides: Keymap = - vscode.workspace - .getConfiguration("cursorless.experimental.keyboard.modal.keybindings") - .get>(sectionName) ?? {}; - const keyMap = merge({}, defaultKeyMap, userOverrides); - - for (const [key, value] of toPairs(keyMap)) { - const conflictingEntry = this.getConflictingKeyMapEntry(key); - if (conflictingEntry != null) { - const { sectionName: conflictingSection, value: conflictingValue } = - conflictingEntry; - - vscode.window.showErrorMessage( - `Conflicting keybindings: \`${sectionName}.${value}\` and \`${conflictingSection}.${conflictingValue}\` both want key '${key}'`, - ); - - continue; - } - - const entry: KeyHandler = { - sectionName, - value, - handleValue: () => handleValue(value), - }; - - this.mergedKeymap[key] = entry; - } - } + modeOn = async () => { if (this.isModeOn()) { return; @@ -201,22 +118,19 @@ export default class KeyboardCommandsModal { async handleInput(text: string) { let sequence = text; - let keyHandler: KeyHandler | undefined = this.mergedKeymap[sequence]; + // let keyHandler: KeyHandler | undefined = this.mergedKeymap[sequence]; + + const map = this.keymap.getMergeKeys() + let hasHandler = map.includes(sequence); - const curCursorOffset = vscode.window.activeTextEditor?.selection.active; - if (curCursorOffset != null && this.cursorOffset != null) { - if (!curCursorOffset.isEqual(this.cursorOffset) ) { - await this.setTargetToCursor(); - } - } // We handle multi-key sequences by repeatedly awaiting a single keypress // until they've pressed something in the map. - while (keyHandler == null) { + while (!hasHandler) { - if (!this.isPrefixOfKey(sequence)) { + if (!this.keymap.isPrefixOfMapEntry(sequence)) { const errorMessage = `Unknown key sequence "${sequence}"`; vscode.window.showErrorMessage(errorMessage); throw Error(errorMessage); @@ -233,32 +147,50 @@ export default class KeyboardCommandsModal { } sequence += nextKey; - keyHandler = this.mergedKeymap[sequence]; + hasHandler = this.keymap.getMergeKeys().includes(sequence); } - - keyHandler.handleValue(); + const curCursorOffset = vscode.window.activeTextEditor?.selection.active; + if (curCursorOffset != null && this.cursorOffset != null) { + if (!curCursorOffset.isEqual(this.cursorOffset) ) { + await this.setTargetToCursor(); + } + } + this.handleSequence(sequence); if (curCursorOffset != null) { this.cursorOffset = curCursorOffset; } } - isPrefixOfKey(text: string): boolean { - return keys(this.mergedKeymap).some((key) => key.startsWith(text)); - } - /** - * This function can be used to detect if a proposed map entry conflicts with - * one in the map. Used to detect if the user tries to use two map entries, - * one of which is a prefix of the other. - * @param text The proposed new map entry - * @returns The first map entry that conflicts with {@link text}, if one - * exists - */ - getConflictingKeyMapEntry(text: string): KeyHandler | undefined { - const conflictingPair = toPairs(this.mergedKeymap).find( - ([key]) => text.startsWith(key) || key.startsWith(text), - ); - - return conflictingPair == null ? undefined : conflictingPair[1]; + private handleSequence(sequence: string) { + const sectionName = this.keymap.getKeyValue(sequence); + if (sectionName == null) { + return; + } + const [section, value] = sectionName; + switch (section) { + case "actions": + this.targeted.performActionOnTarget(value); + break; + case "scopes": + this.targeted.targetScopeType({ + scopeType: value, + }); + break; + case "colors": + this.targeted.targetDecoratedMark({ + color: value, + },this.keymap); + break; + case "shapes": + this.targeted.targetDecoratedMark({ + shape: value, + }, this.keymap); + break; + case "modifiers": + this.targeted.targetModifierType("interiorOnly"); + break; + } } + } diff --git a/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts b/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts index 4a10fa2550..1853623cea 100644 --- a/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts +++ b/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts @@ -13,6 +13,7 @@ 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 Keymap from "./Keymap"; type TargetingMode = "replace" | "extend" | "append"; type TargetModifierTypeArgument = "every" | "interiorOnly" | "excludeInterior"; @@ -64,6 +65,9 @@ export default class KeyboardCommandsTargeted { return this.modal; } + + + /** * Sets the highlighted target to the given decorated mark. If {@link character} is * omitted, then we wait for the user to press a character @@ -75,20 +79,36 @@ export default class KeyboardCommandsTargeted { shape = "default", character, mode = "replace", - }: TargetDecoratedMarkArgument) => { - character = - character ?? - (await this.keyboardHandler.awaitSingleKeypress({ + }: TargetDecoratedMarkArgument, + keymap:Keymap) => { + + const getCharacter = async (status:string="Which hat?") => { + return await this.keyboardHandler.awaitSingleKeypress({ cursorStyle: vscode.TextEditorCursorStyle.Underline, whenClauseContext: "cursorless.keyboard.targeted.awaitingHatCharacter", - statusBarText: "Which hat?", - })); + statusBarText: status, + }); + } + character = await getCharacter(); + if (character == null) { // Cancelled return; } - + if (keymap.getColorKeymap()[character.toLowerCase()] && character === character.toUpperCase()) { + color = keymap.getColorKeymap()[character.toLowerCase()]; + character = await getCharacter("Color ${color} selected. Which hat?"); + } + else if (keymap.getShapeKeymap()[character.toLowerCase()] && character === character.toUpperCase()) { + shape = keymap.getShapeKeymap()[character.toLowerCase()]; + character = await getCharacter("Shape ${shape} selected. Which hat?"); + } + if (character == null) { + // Cancelled + return; + } + let target: PartialTargetDescriptor = { diff --git a/packages/cursorless-vscode/src/keyboard/Keymap.ts b/packages/cursorless-vscode/src/keyboard/Keymap.ts index a3354bc4d9..8858bd2b62 100644 --- a/packages/cursorless-vscode/src/keyboard/Keymap.ts +++ b/packages/cursorless-vscode/src/keyboard/Keymap.ts @@ -36,18 +36,53 @@ export default class Keymap { private handlerMap: Record = {}; constructor() { + this.clearAllMaps.bind(this); + this.loadKeymap.bind(this); + this.loadSection.bind(this); + this.getMergeKeys.bind(this); + this.setMap.bind(this); + this.clearMap.bind(this); + this.getConflictingKeyMapEntry.bind(this); this.loadKeymap(); } + public loadKeymap() { + this.clearAllMaps(); this.loadSection("actions", DEFAULT_ACTION_KEYMAP); this.loadSection("scopes", DEFAULT_SCOPE_KEYMAP); this.loadSection("colors", DEFAULT_COLOR_KEYMAP); this.loadSection("shapes", DEFAULT_SHAPE_KEYMAP); this.loadSection("modifiers", DEFAULT_MODIFIER_KEYMAP); } + clearAllMaps() { + this.clearMap("actions"); + this.clearMap("scopes"); + this.clearMap("colors"); + this.clearMap("shapes"); + this.clearMap("modifiers"); + } + + public getKeyValue(key: string): [SectionName, any] | undefined { + if (this.actionKeymap[key] != null) { + return ["actions", this.actionKeymap[key]]; + } + if (this.scopeKeymap[key] != null) { + return ["scopes", this.scopeKeymap[key]]; + } + if (this.colorKeymap[key] != null) { + return ["colors", this.colorKeymap[key]]; + } + if (this.shapeKeymap[key] != null) { + return ["shapes", this.shapeKeymap[key]]; + } + if (this.modifierKeymap[key] != null) { + return ["modifiers", this.modifierKeymap[key]]; + } + return undefined; + } private loadSection(sectionName: SectionName, defaultKeyMap: SectionKeymap) { const userOverrides: SectionKeymap = @@ -70,9 +105,12 @@ export default class Keymap { } public getMergeKeys():string[]{ - return merge({}, this.actionKeymap, this.scopeKeymap, this.colorKeymap, this.shapeKeymap, this.modifierKeymap).keys(); + return Object.keys(merge({}, this.actionKeymap, this.scopeKeymap, this.colorKeymap, this.shapeKeymap, this.modifierKeymap)); } + public isPrefixOfMapEntry(text:string):boolean{ + return this.getMergeKeys().some((key)=>key.startsWith(text)); + } public setMap(sectionName:SectionName,key:string,value:any){ switch (sectionName) { @@ -126,11 +164,11 @@ export default class Keymap { */ getConflictingKeyMapEntry(text: string): [string, string] | undefined { const allMap = { - "actions": this.actionKeymap.keys, - "scopes": this.scopeKeymap.keys, - "colors": this.colorKeymap.keys, - "shapes": this.shapeKeymap.keys, - "modifiers": this.modifierKeymap.keys, + "actions": this.actionKeymap, + "scopes": this.scopeKeymap, + "colors": this.colorKeymap, + "shapes": this.shapeKeymap, + "modifiers": this.modifierKeymap, }; for (const [sectionName, keyMap] of Object.entries(allMap)) { @@ -146,6 +184,26 @@ export default class Keymap { return undefined; } + public getActionKeymap(): SectionKeymap { + return this.actionKeymap; + } + + public getScopeKeymap(): SectionKeymap { + return this.scopeKeymap; + } + + public getColorKeymap(): SectionKeymap { + return this.colorKeymap; + } + public getShapeKeymap(): SectionKeymap { + return this.shapeKeymap; + } + + public getModifierKeymap(): SectionKeymap { + return this.modifierKeymap; + } + + } \ No newline at end of file From 116d76cbbeb8ffc85052a78d96b1ae5928d2e43d Mon Sep 17 00:00:00 2001 From: knork Date: Tue, 17 Oct 2023 15:14:28 +0200 Subject: [PATCH 05/13] extension works --- .../src/keyboard/KeyboardCommandsModal.ts | 62 ++++++++++++++----- 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/packages/cursorless-vscode/src/keyboard/KeyboardCommandsModal.ts b/packages/cursorless-vscode/src/keyboard/KeyboardCommandsModal.ts index fa33ce9e97..67ca879102 100644 --- a/packages/cursorless-vscode/src/keyboard/KeyboardCommandsModal.ts +++ b/packages/cursorless-vscode/src/keyboard/KeyboardCommandsModal.ts @@ -4,6 +4,7 @@ import KeyboardCommandsTargeted from "./KeyboardCommandsTargeted"; import KeyboardHandler from "./KeyboardHandler"; import { executeCursorlessCommand } from "./KeyboardCommandsTargeted"; import Keymap from "./Keymap"; +import { Direction } from "@cursorless/common"; @@ -20,10 +21,7 @@ export default class KeyboardCommandsModal { */ private inputDisposable: vscode.Disposable | undefined; - /** - * Merged map from all the different sections of the key map (eg actions, - * colors, etc). - */ + cursorOffset: vscode.Position; private keymap: Keymap; @@ -121,14 +119,43 @@ export default class KeyboardCommandsModal { // let keyHandler: KeyHandler | undefined = this.mergedKeymap[sequence]; const map = this.keymap.getMergeKeys() - let hasHandler = map.includes(sequence); + // Todo: fix Hacky + let direction: Direction | undefined=undefined; + if (sequence === "u") { + direction = "forward"; + } + if (sequence === "e") { + direction = "backward"; + } + + if (direction != null) { + const curCursorOffset = vscode.window.activeTextEditor?.selection.active; + if (curCursorOffset != null && this.cursorOffset != null) { + if (!curCursorOffset.isEqual(this.cursorOffset) ) { + await this.setTargetToCursor(); + } + } + this.targeted.expandTarget(direction); + + if (curCursorOffset != null) { + this.cursorOffset = curCursorOffset; + } + return; + + } + + + + + + let isValidSequence = map.includes(sequence); // We handle multi-key sequences by repeatedly awaiting a single keypress // until they've pressed something in the map. - while (!hasHandler) { + while (!isValidSequence) { if (!this.keymap.isPrefixOfMapEntry(sequence)) { const errorMessage = `Unknown key sequence "${sequence}"`; @@ -147,22 +174,24 @@ export default class KeyboardCommandsModal { } sequence += nextKey; - hasHandler = this.keymap.getMergeKeys().includes(sequence); + isValidSequence = this.keymap.getMergeKeys().includes(sequence); } + + + await this.handleSequence(sequence); + + } + + + private async handleSequence(sequence: string) { + const curCursorOffset = vscode.window.activeTextEditor?.selection.active; if (curCursorOffset != null && this.cursorOffset != null) { if (!curCursorOffset.isEqual(this.cursorOffset) ) { await this.setTargetToCursor(); } } - this.handleSequence(sequence); - if (curCursorOffset != null) { - this.cursorOffset = curCursorOffset; - } - } - - private handleSequence(sequence: string) { const sectionName = this.keymap.getKeyValue(sequence); if (sectionName == null) { return; @@ -191,6 +220,11 @@ export default class KeyboardCommandsModal { this.targeted.targetModifierType("interiorOnly"); break; } + + if (curCursorOffset != null) { + this.cursorOffset = curCursorOffset; + } + } } From 2ef40537c06931ec7c690f15a11e04aa00de533f Mon Sep 17 00:00:00 2001 From: knork Date: Wed, 18 Oct 2023 18:44:48 +0200 Subject: [PATCH 06/13] extracted handler class --- packages/cursorless-vscode/package.json | 10 + .../src/keyboard/ActionMap.ts | 105 -------- .../cursorless-vscode/src/keyboard/Handler.ts | 129 ++++++++++ .../src/keyboard/Handlers/ActionHandler.ts | 90 +++++++ .../src/keyboard/Handlers/KeyboardActions.ts | 0 .../src/keyboard/Handlers/TargetHandler.ts | 52 ++++ .../src/keyboard/KeyboardCommands.ts | 2 +- .../src/keyboard/KeyboardCommandsModal.ts | 230 ----------------- .../src/keyboard/KeyboardCommandsTargeted.ts | 91 +------ .../src/keyboard/KeyboardVsCodeMode.ts | 241 ++++++++++++++++++ .../cursorless-vscode/src/keyboard/Keymap.ts | 23 +- .../src/keyboard/MarkSelection.ts | 0 12 files changed, 545 insertions(+), 428 deletions(-) delete mode 100644 packages/cursorless-vscode/src/keyboard/ActionMap.ts create mode 100644 packages/cursorless-vscode/src/keyboard/Handler.ts create mode 100644 packages/cursorless-vscode/src/keyboard/Handlers/ActionHandler.ts create mode 100644 packages/cursorless-vscode/src/keyboard/Handlers/KeyboardActions.ts create mode 100644 packages/cursorless-vscode/src/keyboard/Handlers/TargetHandler.ts delete mode 100644 packages/cursorless-vscode/src/keyboard/KeyboardCommandsModal.ts create mode 100644 packages/cursorless-vscode/src/keyboard/KeyboardVsCodeMode.ts create mode 100644 packages/cursorless-vscode/src/keyboard/MarkSelection.ts diff --git a/packages/cursorless-vscode/package.json b/packages/cursorless-vscode/package.json index 327a596ec1..f44d94697b 100644 --- a/packages/cursorless-vscode/package.json +++ b/packages/cursorless-vscode/package.json @@ -936,6 +936,16 @@ "interiorOnly" ] } + }, + "cursorless.experimental.keyboard.modal.keybindings.keyboardActions": { + "description": "", + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "toggleTargetMode" + ] + } }, "cursorless.experimental.keyboard.modal.keybindings.shapes": { "description": "Define modal keybindings for shapes", diff --git a/packages/cursorless-vscode/src/keyboard/ActionMap.ts b/packages/cursorless-vscode/src/keyboard/ActionMap.ts deleted file mode 100644 index 9f1e98351a..0000000000 --- a/packages/cursorless-vscode/src/keyboard/ActionMap.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { ActionType } from "@cursorless/common"; -import KeyboardCommandsTargeted, { executeCursorlessCommand } from "./KeyboardCommandsTargeted"; -import * as vscode from "vscode"; - - -export default class ActionMap { - actionMap: Record; - - - constructor(actionMap: Record) { - this.actionMap = actionMap; - } - - getAction(key: string): ActionType | undefined { - return this.actionMap[key]; - } - - isAction(key: string): boolean { - return this.actionMap[key] !== undefined; - } - - async handleInput(key: string, state: KeyboardCommandsTargeted) { - let returnValue: unknown; - if (!this.isAction(key)) { - return; - } - const name: ActionType = this.getAction(key)!; - const target = state.lastTarget; - - 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": - returnValue = await executeCursorlessCommand({ - name, - source: target, - destination: { type: "implicit" }, - }); - break; - case "swapTargets": - returnValue = await executeCursorlessCommand({ - name, - target1: target, - target2: { type: "implicit" }, - }); - break; - case "callAsFunction": - returnValue = await executeCursorlessCommand({ - name, - callee: target, - argument: { type: "implicit" }, - }); - break; - case "pasteFromClipboard": - - if ( target.type !== "primitive" ) { - throw Error(`Unsupported target type: ${target.type} for action: ${name}`); - } - - returnValue = await executeCursorlessCommand({ - name, - destination: { - type: "primitive", - insertionMode: "to", - target, - }, - }); - break; - case "generateSnippet": - case "highlight": - returnValue = await executeCursorlessCommand({ - name, - target, - }); - break; - default: - returnValue = await executeCursorlessCommand({ - name, - target, - }); - } - - await state.highlightTarget(); - - const EXIT_CURSORLESS_MODE_ACTIONS: string[] = vscode.workspace.getConfiguration("cursorless.experimental.keyboard.modal").get("exit_actions") ?? []; - - if (EXIT_CURSORLESS_MODE_ACTIONS.includes(name)) { - // For some Cursorless actions, it is more convenient if we automatically - // exit modal mode - await state.getMode().modeOff(); - } - - return returnValue; - } - - -} \ No newline at end of file diff --git a/packages/cursorless-vscode/src/keyboard/Handler.ts b/packages/cursorless-vscode/src/keyboard/Handler.ts new file mode 100644 index 0000000000..e90be7fad2 --- /dev/null +++ b/packages/cursorless-vscode/src/keyboard/Handler.ts @@ -0,0 +1,129 @@ +import { PartialPrimitiveTargetDescriptor, PartialTargetDescriptor } from "@cursorless/common"; +import KeyboardHandler from "./KeyboardHandler"; +import Keymap from "./Keymap"; +import { targetDecoratedMark } from "./Handlers/TargetHandler"; +import * as vscode from "vscode"; +import { executeCursorlessCommand } from "./KeyboardCommandsTargeted"; +import KeyboardCommandsModal from "./KeyboardVsCodeMode"; +import { performActionOnTarget } from "./Handlers/ActionHandler"; + + +export interface KeyboardPartialTargetGenerator { + (mode: Handler, keySequence: string): Promise; +} + + + + +export class Handler{ + + + private targets: PartialPrimitiveTargetDescriptor[] = []; + + public readonly keymap: Keymap; + public readonly keyboardHandler: KeyboardHandler; + + + + + constructor( + keyboardHandler: KeyboardHandler, + keymap: Keymap, + private vscodeMode: KeyboardCommandsModal, + ){ + this.keyboardHandler = keyboardHandler; + this.keymap = keymap; + this.handleInput = this.handleInput.bind(this); + this.highlightTarget = this.highlightTarget.bind(this); + this.addTarget = this.addTarget.bind(this); + this.replaceLastTarget = this.replaceLastTarget.bind(this); + + } + + + async handleInput(sequence: string): Promise { + + const map = this.keymap.getMergeKeys(); + let isValidSequence = map.includes(sequence); + // We handle multi-key sequences by repeatedly awaiting a single keypress + // until they've pressed something in the map. + while (!isValidSequence) { + + if (!this.keymap.isPrefixOfMapEntry(sequence)) { + const errorMessage = `Unknown key sequence "${sequence}"`; + vscode.window.showErrorMessage(errorMessage); + throw Error(errorMessage); + } + + const nextKey = await this.keyboardHandler.awaitSingleKeypress({ + cursorStyle: vscode.TextEditorCursorStyle.Underline, + whenClauseContext: "cursorless.keyboard.targeted.awaitingKeys", + statusBarText: "Finish sequence...", + }); + + if (nextKey == null) { + return; + } + + sequence += nextKey; + isValidSequence = this.keymap.getMergeKeys().includes(sequence); + } + await this.getGenerator(sequence)(this,sequence); + await this.highlightTarget(); + } + + private getGenerator(keySequence: string): KeyboardPartialTargetGenerator { + const [section, val] = this.keymap.getSectionAndCommand(keySequence); + switch (section) { + case "colors": + case "shapes": + return targetDecoratedMark; + case "actions": + return performActionOnTarget; + } + return targetDecoratedMark; + } + private async highlightTarget(): Promise { + + await executeCursorlessCommand({ + name: "highlight", + target: this.constructTarget(), + }) + + + } + + public async modeOff(): Promise{ + await this.vscodeMode.modeOff(); + } + + public replaceLastTarget(target: PartialPrimitiveTargetDescriptor): void { + this.targets[this.targets.length - 1] = target; + } + + public addTarget(target: PartialPrimitiveTargetDescriptor): void { + this.targets.push(target); + } + + public getLatestTarget(): PartialPrimitiveTargetDescriptor { + return this.targets[this.targets.length - 1]; + } + + public constructTarget(): PartialTargetDescriptor { + const target: PartialTargetDescriptor= { + type: "range", + anchor: { + type: "primitive", + mark: { + type: "that", + }, + }, + active: this.targets[this.targets.length - 1], + excludeActive: false, + excludeAnchor: false, + }; + return target; + } + + +} \ No newline at end of file diff --git a/packages/cursorless-vscode/src/keyboard/Handlers/ActionHandler.ts b/packages/cursorless-vscode/src/keyboard/Handlers/ActionHandler.ts new file mode 100644 index 0000000000..997e06ee7f --- /dev/null +++ b/packages/cursorless-vscode/src/keyboard/Handlers/ActionHandler.ts @@ -0,0 +1,90 @@ +import * as vscode from "vscode"; + + + +import { executeCursorlessCommand } from "../KeyboardCommandsTargeted"; +import { Handler } from "../Handler"; + + + + +/** + * Performs action {@link name} on the current target + * @param name The action to run + * @returns A promise that resolves to the result of the cursorless command + */ +export async function performActionOnTarget(mode: Handler, keySequence: string): Promise { + + const name = mode.keymap.getActionKeymap()[keySequence.toLowerCase()]; + + const target= mode.constructTarget(); + const lastPrimTarget = mode.getLatestTarget(); + + let returnValue: unknown; + + 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": + returnValue = await executeCursorlessCommand({ + name, + source: target, + destination: { type: "implicit" }, + }); + break; + case "swapTargets": + returnValue = await executeCursorlessCommand({ + name, + target1: target, + target2: { type: "implicit" }, + }); + break; + case "callAsFunction": + returnValue = await executeCursorlessCommand({ + name, + callee: target, + argument: { type: "implicit" }, + }); + break; + case "pasteFromClipboard": + returnValue = await executeCursorlessCommand({ + name, + destination: { + type: "primitive", + insertionMode: "to", + target: lastPrimTarget, + }, + }); + break; + case "generateSnippet": + case "highlight": + returnValue = await executeCursorlessCommand({ + name, + target, + }); + break; + default: + returnValue = await executeCursorlessCommand({ + name, + target, + }); + } + + const EXIT_CURSORLESS_MODE_ACTIONS: string[] = vscode.workspace.getConfiguration("cursorless.experimental.keyboard.modal").get("exit_actions") ?? []; + + if (EXIT_CURSORLESS_MODE_ACTIONS.includes(name)) { + // For some Cursorless actions, it is more convenient if we automatically + // exit modal mode + await mode.modeOff(); + } + + +} diff --git a/packages/cursorless-vscode/src/keyboard/Handlers/KeyboardActions.ts b/packages/cursorless-vscode/src/keyboard/Handlers/KeyboardActions.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/cursorless-vscode/src/keyboard/Handlers/TargetHandler.ts b/packages/cursorless-vscode/src/keyboard/Handlers/TargetHandler.ts new file mode 100644 index 0000000000..00516bca4f --- /dev/null +++ b/packages/cursorless-vscode/src/keyboard/Handlers/TargetHandler.ts @@ -0,0 +1,52 @@ +import * as vscode from "vscode"; +import { getStyleName } from "../../ide/vscode/hats/getStyleName"; +import { PartialTargetDescriptor } from "@cursorless/common"; +import { Handler } from "../Handler"; + + + export async function targetDecoratedMark (mode: Handler, keySequence:string):Promise { + + const keymap= mode.keymap; + const keyboardHandler = mode.keyboardHandler; + + let color = keymap.getColorKeymap()[keySequence.toLowerCase()]??"default"; + let shape = keymap.getShapeKeymap()[keySequence.toLowerCase()]??"default"; + + const getCharacter = async (status:string="Which hat?") => { + return await keyboardHandler.awaitSingleKeypress({ + cursorStyle: vscode.TextEditorCursorStyle.Underline, + whenClauseContext: "cursorless.keyboard.targeted.awaitingHatCharacter", + statusBarText: status, + }); + } + let character = await getCharacter(); + + + if (character == null) { + // Cancelled + return; + } + if (keymap.getColorKeymap()[character.toLowerCase()] && character === character.toUpperCase()) { + color = keymap.getColorKeymap()[character.toLowerCase()]; + character = await getCharacter("Color ${color} selected. Which hat?"); + } + else if (keymap.getShapeKeymap()[character.toLowerCase()] && character === character.toUpperCase()) { + shape = keymap.getShapeKeymap()[character.toLowerCase()]; + character = await getCharacter("Shape ${shape} selected. Which hat?"); + } + if (character == null) { + // Cancelled + return; + } + + const target: PartialTargetDescriptor = { + type: "primitive", + mark: { + type: "decoratedSymbol", + symbolColor: getStyleName(color, shape), + character, + }, + }; + + mode.addTarget(target); + }; \ No newline at end of file diff --git a/packages/cursorless-vscode/src/keyboard/KeyboardCommands.ts b/packages/cursorless-vscode/src/keyboard/KeyboardCommands.ts index 445e5be4e2..7df0cad60d 100644 --- a/packages/cursorless-vscode/src/keyboard/KeyboardCommands.ts +++ b/packages/cursorless-vscode/src/keyboard/KeyboardCommands.ts @@ -1,5 +1,5 @@ import { ExtensionContext } from "vscode"; -import KeyboardCommandsModal from "./KeyboardCommandsModal"; +import KeyboardCommandsModal from "./KeyboardVsCodeMode"; import KeyboardCommandsTargeted from "./KeyboardCommandsTargeted"; import KeyboardHandler from "./KeyboardHandler"; import { StatusBarItem } from "../StatusBarItem"; diff --git a/packages/cursorless-vscode/src/keyboard/KeyboardCommandsModal.ts b/packages/cursorless-vscode/src/keyboard/KeyboardCommandsModal.ts deleted file mode 100644 index 67ca879102..0000000000 --- a/packages/cursorless-vscode/src/keyboard/KeyboardCommandsModal.ts +++ /dev/null @@ -1,230 +0,0 @@ -import * as vscode from "vscode"; - -import KeyboardCommandsTargeted from "./KeyboardCommandsTargeted"; -import KeyboardHandler from "./KeyboardHandler"; -import { executeCursorlessCommand } from "./KeyboardCommandsTargeted"; -import Keymap from "./Keymap"; -import { Direction } from "@cursorless/common"; - - - - - -/** - * Defines a mode to use with a modal version of Cursorless keyboard. - */ -export default class KeyboardCommandsModal { - /** - * This disposable is returned by {@link KeyboardHandler.pushListener}, and is - * used to relinquich control of the keyboard. If this disposable is - * non-null, then our mode is active. - */ - private inputDisposable: vscode.Disposable | undefined; - - - - cursorOffset: vscode.Position; - private keymap: Keymap; - - - constructor( - private extensionContext: vscode.ExtensionContext, - private targeted: KeyboardCommandsTargeted, - private keyboardHandler: KeyboardHandler, - ) { - this.modeOn = this.modeOn.bind(this); - this.modeOff = this.modeOff.bind(this); - this.handleInput = this.handleInput.bind(this); - - // this.constructMergedKeymap(); - this.cursorOffset = new vscode.Position(0, 0); - this.keymap = new Keymap(); - - } - - init() { - this.extensionContext.subscriptions.push( - vscode.workspace.onDidChangeConfiguration((event) => { - if ( - event.affectsConfiguration( - "cursorless.experimental.keyboard.modal.keybindings", - ) - ) { - this.keymap.loadKeymap(); - } - }), - ); - - } - - - modeOn = async () => { - if (this.isModeOn()) { - return; - } - - this.inputDisposable = this.keyboardHandler.pushListener({ - handleInput: this.handleInput, - displayOptions: { - cursorStyle: vscode.TextEditorCursorStyle.BlockOutline, - whenClauseContext: "cursorless.keyboard.modal.mode", - statusBarText: "Listening...", - }, - handleCancelled: this.modeOff, - }); - - // Set target to current selection when we enter the mode - await this.targeted.targetSelection(); - }; - - modeOff = async () => { - if (!this.isModeOn()) { - return; - } - - this.inputDisposable?.dispose(); - this.inputDisposable = undefined; - - // Clear target upon exiting mode; this will remove the highlight - await this.targeted.clearTarget(); - }; - - modeToggle = () => { - if (this.isModeOn()) { - this.modeOff(); - } else { - this.modeOn(); - } - }; - - private isModeOn() { - return this.inputDisposable != null; - } - - - private async setTargetToCursor() { - await executeCursorlessCommand({ - name: "highlight", - target: { - type: "primitive", - mark: { - type: "cursor", - }, - }, - }); - } - - async handleInput(text: string) { - let sequence = text; - // let keyHandler: KeyHandler | undefined = this.mergedKeymap[sequence]; - - const map = this.keymap.getMergeKeys() - - // Todo: fix Hacky - - let direction: Direction | undefined=undefined; - - if (sequence === "u") { - direction = "forward"; - } - - if (sequence === "e") { - direction = "backward"; - } - - if (direction != null) { - const curCursorOffset = vscode.window.activeTextEditor?.selection.active; - if (curCursorOffset != null && this.cursorOffset != null) { - if (!curCursorOffset.isEqual(this.cursorOffset) ) { - await this.setTargetToCursor(); - } - } - this.targeted.expandTarget(direction); - - if (curCursorOffset != null) { - this.cursorOffset = curCursorOffset; - } - return; - - } - - - - - - let isValidSequence = map.includes(sequence); - // We handle multi-key sequences by repeatedly awaiting a single keypress - // until they've pressed something in the map. - while (!isValidSequence) { - - if (!this.keymap.isPrefixOfMapEntry(sequence)) { - const errorMessage = `Unknown key sequence "${sequence}"`; - vscode.window.showErrorMessage(errorMessage); - throw Error(errorMessage); - } - - const nextKey = await this.keyboardHandler.awaitSingleKeypress({ - cursorStyle: vscode.TextEditorCursorStyle.Underline, - whenClauseContext: "cursorless.keyboard.targeted.awaitingKeys", - statusBarText: "Finish sequence...", - }); - - if (nextKey == null) { - return; - } - - sequence += nextKey; - isValidSequence = this.keymap.getMergeKeys().includes(sequence); - } - - - await this.handleSequence(sequence); - - } - - - private async handleSequence(sequence: string) { - - const curCursorOffset = vscode.window.activeTextEditor?.selection.active; - if (curCursorOffset != null && this.cursorOffset != null) { - if (!curCursorOffset.isEqual(this.cursorOffset) ) { - await this.setTargetToCursor(); - } - } - - const sectionName = this.keymap.getKeyValue(sequence); - if (sectionName == null) { - return; - } - const [section, value] = sectionName; - switch (section) { - case "actions": - this.targeted.performActionOnTarget(value); - break; - case "scopes": - this.targeted.targetScopeType({ - scopeType: value, - }); - break; - case "colors": - this.targeted.targetDecoratedMark({ - color: value, - },this.keymap); - break; - case "shapes": - this.targeted.targetDecoratedMark({ - shape: value, - }, this.keymap); - break; - case "modifiers": - this.targeted.targetModifierType("interiorOnly"); - break; - } - - if (curCursorOffset != null) { - this.cursorOffset = curCursorOffset; - } - - } - -} diff --git a/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts b/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts index 1853623cea..739908ce7f 100644 --- a/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts +++ b/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts @@ -1,9 +1,7 @@ import { ActionDescriptor, - ActionType, Direction, LATEST_VERSION, - PartialPrimitiveTargetDescriptor, PartialTargetDescriptor, SimpleScopeTypeType, } from "@cursorless/common"; @@ -11,7 +9,7 @@ import { runCursorlessCommand } from "@cursorless/vscode-common"; import * as vscode from "vscode"; import type { HatColor, HatShape } from "../ide/vscode/hatStyles.types"; import { getStyleName } from "../ide/vscode/hats/getStyleName"; -import KeyboardCommandsModal from "./KeyboardCommandsModal"; +import KeyboardCommandsModal from "./KeyboardVsCodeMode"; import KeyboardHandler from "./KeyboardHandler"; import Keymap from "./Keymap"; @@ -44,7 +42,7 @@ export default class KeyboardCommandsTargeted { constructor(private keyboardHandler: KeyboardHandler) { this.targetDecoratedMark = this.targetDecoratedMark.bind(this); - this.performActionOnTarget = this.performActionOnTarget.bind(this); + this.targetScopeType = this.targetScopeType.bind(this); this.targetSelection = this.targetSelection.bind(this); this.clearTarget = this.clearTarget.bind(this); @@ -194,7 +192,7 @@ export default class KeyboardCommandsTargeted { type: "primitive", modifiers: [ { - type: "interiorOnly", + type: "trailing", }, ], @@ -215,89 +213,6 @@ export default class KeyboardCommandsTargeted { }, }); - /** - * Performs action {@link name} on the current target - * @param name The action to run - * @returns A promise that resolves to the result of the cursorless command - */ - performActionOnTarget = async (name: ActionType) => { - const target: PartialPrimitiveTargetDescriptor = { - type: "primitive", - mark: { - type: "that", - }, - }; - - let returnValue: unknown; - - 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": - returnValue = await executeCursorlessCommand({ - name, - source: target, - destination: { type: "implicit" }, - }); - break; - case "swapTargets": - returnValue = await executeCursorlessCommand({ - name, - target1: target, - target2: { type: "implicit" }, - }); - break; - case "callAsFunction": - returnValue = await executeCursorlessCommand({ - name, - callee: target, - argument: { type: "implicit" }, - }); - break; - case "pasteFromClipboard": - returnValue = await executeCursorlessCommand({ - name, - destination: { - type: "primitive", - insertionMode: "to", - target, - }, - }); - break; - case "generateSnippet": - case "highlight": - returnValue = await executeCursorlessCommand({ - name, - target, - }); - break; - default: - returnValue = await executeCursorlessCommand({ - name, - target, - }); - } - - await this.highlightTarget(); - - const EXIT_CURSORLESS_MODE_ACTIONS:string[]=vscode.workspace.getConfiguration("cursorless.experimental.keyboard.modal").get("exit_actions")??[]; - - if (EXIT_CURSORLESS_MODE_ACTIONS.includes(name)) { - // For some Cursorless actions, it is more convenient if we automatically - // exit modal mode - await this.modal.modeOff(); - } - - return returnValue; - }; /** * Sets the current target to the current selection diff --git a/packages/cursorless-vscode/src/keyboard/KeyboardVsCodeMode.ts b/packages/cursorless-vscode/src/keyboard/KeyboardVsCodeMode.ts new file mode 100644 index 0000000000..de4f971e26 --- /dev/null +++ b/packages/cursorless-vscode/src/keyboard/KeyboardVsCodeMode.ts @@ -0,0 +1,241 @@ +import * as vscode from "vscode"; + +import KeyboardCommandsTargeted from "./KeyboardCommandsTargeted"; +import KeyboardHandler from "./KeyboardHandler"; +import { executeCursorlessCommand } from "./KeyboardCommandsTargeted"; +import Keymap from "./Keymap"; +// import { Direction } from "@cursorless/common"; +// import { performActionOnTarget } from "./Handlers/ActionHandler"; +import { Handler } from "./Handler"; + + + + + +/** + * Defines a mode to use with a modal version of Cursorless keyboard. + */ +export default class KeyboardCommandsModal { + /** + * This disposable is returned by {@link KeyboardHandler.pushListener}, and is + * used to relinquich control of the keyboard. If this disposable is + * non-null, then our mode is active. + */ + private inputDisposable: vscode.Disposable | undefined; + + + + cursorOffset: vscode.Position; + public keymap: Keymap; + private handler: Handler; + + + + + constructor( + private extensionContext: vscode.ExtensionContext, + private targeted: KeyboardCommandsTargeted, + public keyboardHandler: KeyboardHandler, + ) { + this.modeOn = this.modeOn.bind(this); + this.modeOff = this.modeOff.bind(this); + this.handleInput = this.handleInput.bind(this); + + // this.constructMergedKeymap(); + this.cursorOffset = new vscode.Position(0, 0); + this.keymap = new Keymap(); + this.handler = new Handler(keyboardHandler, this.keymap, this); + + } + + init() { + this.extensionContext.subscriptions.push( + vscode.workspace.onDidChangeConfiguration((event) => { + if ( + event.affectsConfiguration( + "cursorless.experimental.keyboard.modal.keybindings", + ) + ) { + this.keymap.loadKeymap(); + } + }), + ); + + } + + + + + modeOn = async () => { + if (this.isModeOn()) { + return; + } + + this.inputDisposable = this.keyboardHandler.pushListener({ + handleInput: this.handleInput, + displayOptions: { + cursorStyle: vscode.TextEditorCursorStyle.BlockOutline, + whenClauseContext: "cursorless.keyboard.modal.mode", + statusBarText: "Listening...", + }, + handleCancelled: this.modeOff, + }); + + // Set target to current selection when we enter the mode + await this.targeted.targetSelection(); + }; + + modeOff = async () => { + if (!this.isModeOn()) { + return; + } + + this.inputDisposable?.dispose(); + this.inputDisposable = undefined; + + // Clear target upon exiting mode; this will remove the highlight + await this.targeted.clearTarget(); + }; + + modeToggle = () => { + if (this.isModeOn()) { + this.modeOff(); + } else { + this.modeOn(); + } + }; + + private isModeOn() { + return this.inputDisposable != null; + } + + + private async setTargetToCursor() { + await executeCursorlessCommand({ + name: "highlight", + target: { + type: "primitive", + mark: { + type: "cursor", + }, + }, + }); + } + + async handleInput(text: string) { + this.handler.handleInput(text); + return; + + // let sequence = text; + // // let keyHandler: KeyHandler | undefined = this.mergedKeymap[sequence]; + + // const map = this.keymap.getMergeKeys() + + // // Todo: fix Hacky + + // let direction: Direction | undefined=undefined; + + // if (sequence === "u") { + // direction = "forward"; + // } + + // if (sequence === "e") { + // direction = "backward"; + // } + + // if (direction != null) { + // const curCursorOffset = vscode.window.activeTextEditor?.selection.active; + // if (curCursorOffset != null && this.cursorOffset != null) { + // if (!curCursorOffset.isEqual(this.cursorOffset) ) { + // await this.setTargetToCursor(); + // } + // } + // this.targeted.expandTarget(direction); + + // if (curCursorOffset != null) { + // this.cursorOffset = curCursorOffset; + // } + // return; + + // } + + + + + + // let isValidSequence = map.includes(sequence); + // // We handle multi-key sequences by repeatedly awaiting a single keypress + // // until they've pressed something in the map. + // while (!isValidSequence) { + + // if (!this.keymap.isPrefixOfMapEntry(sequence)) { + // const errorMessage = `Unknown key sequence "${sequence}"`; + // vscode.window.showErrorMessage(errorMessage); + // throw Error(errorMessage); + // } + + // const nextKey = await this.keyboardHandler.awaitSingleKeypress({ + // cursorStyle: vscode.TextEditorCursorStyle.Underline, + // whenClauseContext: "cursorless.keyboard.targeted.awaitingKeys", + // statusBarText: "Finish sequence...", + // }); + + // if (nextKey == null) { + // return; + // } + + // sequence += nextKey; + // isValidSequence = this.keymap.getMergeKeys().includes(sequence); + // } + + + // await this.handleSequence(sequence); + + } + + + // private async handleSequence(sequence: string) { + + // const curCursorOffset = vscode.window.activeTextEditor?.selection.active; + // if (curCursorOffset != null && this.cursorOffset != null) { + // if (!curCursorOffset.isEqual(this.cursorOffset) ) { + // await this.setTargetToCursor(); + // } + // } + + // const sectionName = this.keymap.getSectionAndCommand(sequence); + // if (sectionName == null) { + // return; + // } + // const [section, value] = sectionName; + // switch (section) { + // case "actions": + // performActionOnTarget(,value); + // break; + // case "scopes": + // this.targeted.targetScopeType({ + // scopeType: value, + // }); + // break; + // case "colors": + // this.targeted.targetDecoratedMark({ + // color: value, + // },this.keymap); + // break; + // case "shapes": + // this.targeted.targetDecoratedMark({ + // shape: value, + // }, this.keymap); + // break; + // case "modifiers": + // this.targeted.targetModifierType("interiorOnly"); + // break; + // } + + // if (curCursorOffset != null) { + // this.cursorOffset = curCursorOffset; + // } + + // } + +} diff --git a/packages/cursorless-vscode/src/keyboard/Keymap.ts b/packages/cursorless-vscode/src/keyboard/Keymap.ts index 8858bd2b62..8272a2296b 100644 --- a/packages/cursorless-vscode/src/keyboard/Keymap.ts +++ b/packages/cursorless-vscode/src/keyboard/Keymap.ts @@ -5,7 +5,8 @@ import { HatColor, HatShape } from "../ide/vscode/hatStyles.types"; import { merge, toPairs } from "lodash"; -type SectionName = "actions" | "scopes" | "colors" | "shapes" | "modifiers"; + +type SectionName = "actions" | "scopes" | "colors" | "shapes" | "modifiers" | "keyboardActions"; export type SectionKeymap = Record; export const DEFAULT_ACTION_KEYMAP: SectionKeymap = isTesting() @@ -32,6 +33,8 @@ export default class Keymap { private colorKeymap: SectionKeymap = {}; private shapeKeymap: SectionKeymap = {}; private modifierKeymap: SectionKeymap = {}; + private keyboardActions: SectionKeymap = {}; + private handlerMap: Record = {}; @@ -63,7 +66,7 @@ export default class Keymap { this.clearMap("modifiers"); } - public getKeyValue(key: string): [SectionName, any] | undefined { + public getSectionAndCommand(key: string): [SectionName, any] { if (this.actionKeymap[key] != null) { return ["actions", this.actionKeymap[key]]; @@ -80,7 +83,12 @@ export default class Keymap { if (this.modifierKeymap[key] != null) { return ["modifiers", this.modifierKeymap[key]]; } - return undefined; + if (this.keyboardActions[key] != null) { + return ["actions", this.keyboardActions[key]]; + } + // TODO How to log this? + return ["actions", undefined]; + } @@ -105,7 +113,7 @@ export default class Keymap { } public getMergeKeys():string[]{ - return Object.keys(merge({}, this.actionKeymap, this.scopeKeymap, this.colorKeymap, this.shapeKeymap, this.modifierKeymap)); + return Object.keys(merge( {},this.actionKeymap, this.scopeKeymap, this.colorKeymap, this.shapeKeymap, this.modifierKeymap,this.keyboardActions)) as string[]; } public isPrefixOfMapEntry(text:string):boolean{ @@ -129,6 +137,9 @@ export default class Keymap { case "modifiers": this.modifierKeymap[key]=value; break; + case "keyboardActions": + this.keyboardActions[key]=value; + break; } } @@ -149,6 +160,9 @@ export default class Keymap { case "modifiers": this.modifierKeymap={}; break; + case "keyboardActions": + this.keyboardActions={}; + break; } } @@ -169,6 +183,7 @@ export default class Keymap { "colors": this.colorKeymap, "shapes": this.shapeKeymap, "modifiers": this.modifierKeymap, + "keyboardActions": this.keyboardActions, }; for (const [sectionName, keyMap] of Object.entries(allMap)) { diff --git a/packages/cursorless-vscode/src/keyboard/MarkSelection.ts b/packages/cursorless-vscode/src/keyboard/MarkSelection.ts new file mode 100644 index 0000000000..e69de29bb2 From 4d097e9187e80f1a1e7d74f1ed40493d03367f9b Mon Sep 17 00:00:00 2001 From: knork Date: Thu, 19 Oct 2023 15:46:42 +0200 Subject: [PATCH 07/13] removed dependence on keymap --- .../cursorless-vscode/src/keyboard/Handler.ts | 126 ++++++++++++------ .../src/keyboard/Handlers/ActionHandler.ts | 3 +- .../src/keyboard/Handlers/KeyboardActions.ts | 0 .../src/keyboard/Handlers/TargetHandler.ts | 37 +++-- .../src/keyboard/fixedKeymap.ts | 18 +++ 5 files changed, 131 insertions(+), 53 deletions(-) delete mode 100644 packages/cursorless-vscode/src/keyboard/Handlers/KeyboardActions.ts create mode 100644 packages/cursorless-vscode/src/keyboard/fixedKeymap.ts diff --git a/packages/cursorless-vscode/src/keyboard/Handler.ts b/packages/cursorless-vscode/src/keyboard/Handler.ts index e90be7fad2..5dfcfb7ec7 100644 --- a/packages/cursorless-vscode/src/keyboard/Handler.ts +++ b/packages/cursorless-vscode/src/keyboard/Handler.ts @@ -6,16 +6,31 @@ import * as vscode from "vscode"; import { executeCursorlessCommand } from "./KeyboardCommandsTargeted"; import KeyboardCommandsModal from "./KeyboardVsCodeMode"; import { performActionOnTarget } from "./Handlers/ActionHandler"; +import { baseLayer, scopeLayer } from "./fixedKeymap"; export interface KeyboardPartialTargetGenerator { (mode: Handler, keySequence: string): Promise; } +const commandMap: Record = { + "default": targetDecoratedMark, + "blue": targetDecoratedMark, + "red": targetDecoratedMark, + "green": targetDecoratedMark, + "yellow": targetDecoratedMark, + "ex": targetDecoratedMark, + "fox": targetDecoratedMark, -export class Handler{ + "clearAndSetSelection": performActionOnTarget, "copyToClipboard": performActionOnTarget, "cutToClipboard": performActionOnTarget, "deselect": performActionOnTarget, "editNewLineAfter": performActionOnTarget, "editNewLineBefore": performActionOnTarget, "extractVariable": performActionOnTarget, "findInWorkspace": performActionOnTarget, "foldRegion": performActionOnTarget, "followLink": performActionOnTarget, "indentLine": performActionOnTarget, "insertCopyAfter": performActionOnTarget, "insertCopyBefore": performActionOnTarget, "insertEmptyLineAfter": performActionOnTarget, "insertEmptyLineBefore": performActionOnTarget, "insertEmptyLinesAround": performActionOnTarget, "outdentLine": performActionOnTarget, "randomizeTargets": performActionOnTarget, "remove": performActionOnTarget, "rename": performActionOnTarget, "revealDefinition": performActionOnTarget, "revealTypeDefinition": performActionOnTarget, "reverseTargets": performActionOnTarget, "scrollToBottom": performActionOnTarget, "scrollToCenter": performActionOnTarget, "scrollToTop": performActionOnTarget, "setSelection": performActionOnTarget, "setSelectionAfter": performActionOnTarget, "setSelectionBefore": performActionOnTarget, "showDebugHover": performActionOnTarget, "showHover": performActionOnTarget, "showQuickFix": performActionOnTarget, "showReferences": performActionOnTarget, "sortTargets": performActionOnTarget, "toggleLineBreakpoint": performActionOnTarget, "toggleLineComment": performActionOnTarget, "unfoldRegion": performActionOnTarget, "callAsFunction": performActionOnTarget, "editNew": performActionOnTarget, "executeCommand": performActionOnTarget, "generateSnippet": performActionOnTarget, "getText": performActionOnTarget, "highlight": performActionOnTarget, "insertSnippet": performActionOnTarget, "moveToTarget": performActionOnTarget, "pasteFromClipboard": performActionOnTarget, "replace": performActionOnTarget, "replaceWithTarget": performActionOnTarget, "rewrapWithPairedDelimiter": performActionOnTarget, "swapTargets": performActionOnTarget, "wrapWithPairedDelimiter": performActionOnTarget, "wrapWithSnippet": performActionOnTarget, + +} + + + +export class Handler { private targets: PartialPrimitiveTargetDescriptor[] = []; @@ -23,14 +38,15 @@ export class Handler{ public readonly keymap: Keymap; public readonly keyboardHandler: KeyboardHandler; + private readonly keybindings: Record[] = [baseLayer, scopeLayer]; + - constructor( keyboardHandler: KeyboardHandler, keymap: Keymap, private vscodeMode: KeyboardCommandsModal, - ){ + ) { this.keyboardHandler = keyboardHandler; this.keymap = keymap; this.handleInput = this.handleInput.bind(this); @@ -38,38 +54,64 @@ export class Handler{ this.addTarget = this.addTarget.bind(this); this.replaceLastTarget = this.replaceLastTarget.bind(this); - } + } + public async run(command: string): Promise { + await commandMap[command](this, command); + await this.highlightTarget(); + } - async handleInput(sequence: string): Promise { + public getFirstKeyMatching(key: string, validCommands: readonly string[]): string | undefined { + key = key.toLowerCase(); + for (const keybinding of this.keybindings) { + let command = keybinding[key]; + if (!command) { + continue; + } + // extract the command from the keybinding + const commandRegex: RegExp = new RegExp(`mode.run\\("(.+?)"\\);`); + const match = command.match(commandRegex); + command = match ? match[1] : ""; + if (command.length >1 && validCommands.includes(command)) { + return command; + } + } + return undefined; + } - const map = this.keymap.getMergeKeys(); - let isValidSequence = map.includes(sequence); - // We handle multi-key sequences by repeatedly awaiting a single keypress - // until they've pressed something in the map. - while (!isValidSequence) { - - if (!this.keymap.isPrefixOfMapEntry(sequence)) { - const errorMessage = `Unknown key sequence "${sequence}"`; - vscode.window.showErrorMessage(errorMessage); - throw Error(errorMessage); - } - - const nextKey = await this.keyboardHandler.awaitSingleKeypress({ - cursorStyle: vscode.TextEditorCursorStyle.Underline, - whenClauseContext: "cursorless.keyboard.targeted.awaitingKeys", - statusBarText: "Finish sequence...", - }); - - if (nextKey == null) { + async handleInput(sequence: string): Promise { + const mode = this; + if (sequence in baseLayer) { + eval(baseLayer[sequence]); + } return; - } + const map = this.keymap.getMergeKeys(); + let isValidSequence = map.includes(sequence); + // We handle multi-key sequences by repeatedly awaiting a single keypress + // until they've pressed something in the map. + while (!isValidSequence) { + + if (!this.keymap.isPrefixOfMapEntry(sequence)) { + const errorMessage = `Unknown key sequence "${sequence}"`; + vscode.window.showErrorMessage(errorMessage); + throw Error(errorMessage); + } - sequence += nextKey; - isValidSequence = this.keymap.getMergeKeys().includes(sequence); - } - await this.getGenerator(sequence)(this,sequence); - await this.highlightTarget(); + const nextKey = await this.keyboardHandler.awaitSingleKeypress({ + cursorStyle: vscode.TextEditorCursorStyle.Underline, + whenClauseContext: "cursorless.keyboard.targeted.awaitingKeys", + statusBarText: "Finish sequence...", + }); + + if (nextKey == null) { + return; + } + + sequence += nextKey; + isValidSequence = this.keymap.getMergeKeys().includes(sequence); + } + await this.getGenerator(sequence)(this, sequence); + await this.highlightTarget(); } private getGenerator(keySequence: string): KeyboardPartialTargetGenerator { @@ -79,21 +121,21 @@ export class Handler{ case "shapes": return targetDecoratedMark; case "actions": - return performActionOnTarget; - } - return targetDecoratedMark; + return performActionOnTarget; + } + return targetDecoratedMark; } private async highlightTarget(): Promise { await executeCursorlessCommand({ name: "highlight", target: this.constructTarget(), - }) + }) } - public async modeOff(): Promise{ + public async modeOff(): Promise { await this.vscodeMode.modeOff(); } @@ -110,19 +152,19 @@ export class Handler{ } public constructTarget(): PartialTargetDescriptor { - const target: PartialTargetDescriptor= { + const target: PartialTargetDescriptor = { type: "range", anchor: { - type: "primitive", - mark: { - type: "that", - }, + type: "primitive", + mark: { + type: "that", + }, }, active: this.targets[this.targets.length - 1], excludeActive: false, excludeAnchor: false, - }; - return target; + }; + return this.targets[this.targets.length - 1]; } diff --git a/packages/cursorless-vscode/src/keyboard/Handlers/ActionHandler.ts b/packages/cursorless-vscode/src/keyboard/Handlers/ActionHandler.ts index 997e06ee7f..6079ac9224 100644 --- a/packages/cursorless-vscode/src/keyboard/Handlers/ActionHandler.ts +++ b/packages/cursorless-vscode/src/keyboard/Handlers/ActionHandler.ts @@ -4,6 +4,7 @@ import * as vscode from "vscode"; import { executeCursorlessCommand } from "../KeyboardCommandsTargeted"; import { Handler } from "../Handler"; +import { ActionType } from "@cursorless/common"; @@ -15,7 +16,7 @@ import { Handler } from "../Handler"; */ export async function performActionOnTarget(mode: Handler, keySequence: string): Promise { - const name = mode.keymap.getActionKeymap()[keySequence.toLowerCase()]; + const name = keySequence as ActionType; const target= mode.constructTarget(); const lastPrimTarget = mode.getLatestTarget(); diff --git a/packages/cursorless-vscode/src/keyboard/Handlers/KeyboardActions.ts b/packages/cursorless-vscode/src/keyboard/Handlers/KeyboardActions.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/cursorless-vscode/src/keyboard/Handlers/TargetHandler.ts b/packages/cursorless-vscode/src/keyboard/Handlers/TargetHandler.ts index 00516bca4f..ad94d2aa8b 100644 --- a/packages/cursorless-vscode/src/keyboard/Handlers/TargetHandler.ts +++ b/packages/cursorless-vscode/src/keyboard/Handlers/TargetHandler.ts @@ -2,15 +2,23 @@ import * as vscode from "vscode"; import { getStyleName } from "../../ide/vscode/hats/getStyleName"; import { PartialTargetDescriptor } from "@cursorless/common"; import { Handler } from "../Handler"; +import { HAT_COLORS, HAT_NON_DEFAULT_SHAPES, HatColor, HatShape } from "../../ide/vscode/hatStyles.types"; export async function targetDecoratedMark (mode: Handler, keySequence:string):Promise { - const keymap= mode.keymap; + const keyboardHandler = mode.keyboardHandler; - let color = keymap.getColorKeymap()[keySequence.toLowerCase()]??"default"; - let shape = keymap.getShapeKeymap()[keySequence.toLowerCase()]??"default"; + let color: HatColor = "default"; + if ( keySequence in HAT_COLORS){ + color = keySequence as HatColor; + } + + let shape: HatShape = "default"; + if ( keySequence in HAT_NON_DEFAULT_SHAPES){ + shape = keySequence as HatShape; + } const getCharacter = async (status:string="Which hat?") => { return await keyboardHandler.awaitSingleKeypress({ @@ -26,14 +34,23 @@ import { Handler } from "../Handler"; // Cancelled return; } - if (keymap.getColorKeymap()[character.toLowerCase()] && character === character.toUpperCase()) { - color = keymap.getColorKeymap()[character.toLowerCase()]; - character = await getCharacter("Color ${color} selected. Which hat?"); - } - else if (keymap.getShapeKeymap()[character.toLowerCase()] && character === character.toUpperCase()) { - shape = keymap.getShapeKeymap()[character.toLowerCase()]; - character = await getCharacter("Shape ${shape} selected. Which hat?"); + + if ( character === character.toUpperCase()){ + const pickedColor = mode.getFirstKeyMatching(character, HAT_COLORS); + if ( pickedColor !== undefined){ + color = pickedColor as HatColor; + } + const pickedShape = mode.getFirstKeyMatching(character, HAT_NON_DEFAULT_SHAPES); + if ( pickedShape !== undefined){ + shape = pickedShape as HatShape; + } + if ( pickedColor !== undefined || pickedShape !== undefined){ + character = await getCharacter(`${color} ${shape} ___?`); + } } + + + if (character == null) { // Cancelled return; diff --git a/packages/cursorless-vscode/src/keyboard/fixedKeymap.ts b/packages/cursorless-vscode/src/keyboard/fixedKeymap.ts new file mode 100644 index 0000000000..6ee7b1a682 --- /dev/null +++ b/packages/cursorless-vscode/src/keyboard/fixedKeymap.ts @@ -0,0 +1,18 @@ + + +export const baseLayer = { + "d": `mode.run("default");`, + "b": `mode.run("blue");`, + "g": `mode.run("green");`, + "r": `mode.run("red");`, + "y": `mode.run("yellow");`, + "x": `mode.run("ex");`, + "f": `mode.run("fox");`, + + "p": `mode.run("paragraph");`, + "i": `mode.run("replaceWithTarget");`, +} + +export const scopeLayer = { + +} \ No newline at end of file From bf9ab707b0f2a13dc684a92c1d6887b671f14494 Mon Sep 17 00:00:00 2001 From: knork Date: Sat, 21 Oct 2023 17:15:17 +0200 Subject: [PATCH 08/13] modes work --- .vscode/settings.json | 2 +- .../cursorless-vscode/src/keyboard/Handler.ts | 375 +++++++++++------- .../src/keyboard/Handlers/ActionHandler.ts | 2 +- .../src/keyboard/Handlers/ScopeHandler.ts | 103 +++++ .../src/keyboard/KeyboardHandler.ts | 8 + .../src/keyboard/KeyboardVsCodeMode.ts | 3 +- .../src/keyboard/fixedKeymap.ts | 19 +- 7 files changed, 361 insertions(+), 151 deletions(-) create mode 100644 packages/cursorless-vscode/src/keyboard/Handlers/ScopeHandler.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index e1ffebcef6..a0cd639275 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -26,6 +26,6 @@ "typescript.enablePromptUseWorkspaceTsdk": true, "typescript.tsdk": "node_modules/typescript/lib", "[typescript]": { - "editor.defaultFormatter": "vscode.typescript-language-features" + "editor.defaultFormatter": "svipas.prettier-plus" } } diff --git a/packages/cursorless-vscode/src/keyboard/Handler.ts b/packages/cursorless-vscode/src/keyboard/Handler.ts index 5dfcfb7ec7..38c8b05845 100644 --- a/packages/cursorless-vscode/src/keyboard/Handler.ts +++ b/packages/cursorless-vscode/src/keyboard/Handler.ts @@ -1,171 +1,258 @@ -import { PartialPrimitiveTargetDescriptor, PartialTargetDescriptor } from "@cursorless/common"; +import { + PartialPrimitiveTargetDescriptor, + PartialTargetDescriptor, +} from "@cursorless/common"; import KeyboardHandler from "./KeyboardHandler"; import Keymap from "./Keymap"; import { targetDecoratedMark } from "./Handlers/TargetHandler"; -import * as vscode from "vscode"; import { executeCursorlessCommand } from "./KeyboardCommandsTargeted"; import KeyboardCommandsModal from "./KeyboardVsCodeMode"; import { performActionOnTarget } from "./Handlers/ActionHandler"; -import { baseLayer, scopeLayer } from "./fixedKeymap"; - +import { layer0, layer1 } from "./fixedKeymap"; +import { targetScope } from "./Handlers/ScopeHandler"; export interface KeyboardPartialTargetGenerator { - (mode: Handler, keySequence: string): Promise; + (mode: Handler, keySequence: string): Promise; } const commandMap: Record = { - "default": targetDecoratedMark, - "blue": targetDecoratedMark, - "red": targetDecoratedMark, - "green": targetDecoratedMark, - "yellow": targetDecoratedMark, - "ex": targetDecoratedMark, - "fox": targetDecoratedMark, - - - - "clearAndSetSelection": performActionOnTarget, "copyToClipboard": performActionOnTarget, "cutToClipboard": performActionOnTarget, "deselect": performActionOnTarget, "editNewLineAfter": performActionOnTarget, "editNewLineBefore": performActionOnTarget, "extractVariable": performActionOnTarget, "findInWorkspace": performActionOnTarget, "foldRegion": performActionOnTarget, "followLink": performActionOnTarget, "indentLine": performActionOnTarget, "insertCopyAfter": performActionOnTarget, "insertCopyBefore": performActionOnTarget, "insertEmptyLineAfter": performActionOnTarget, "insertEmptyLineBefore": performActionOnTarget, "insertEmptyLinesAround": performActionOnTarget, "outdentLine": performActionOnTarget, "randomizeTargets": performActionOnTarget, "remove": performActionOnTarget, "rename": performActionOnTarget, "revealDefinition": performActionOnTarget, "revealTypeDefinition": performActionOnTarget, "reverseTargets": performActionOnTarget, "scrollToBottom": performActionOnTarget, "scrollToCenter": performActionOnTarget, "scrollToTop": performActionOnTarget, "setSelection": performActionOnTarget, "setSelectionAfter": performActionOnTarget, "setSelectionBefore": performActionOnTarget, "showDebugHover": performActionOnTarget, "showHover": performActionOnTarget, "showQuickFix": performActionOnTarget, "showReferences": performActionOnTarget, "sortTargets": performActionOnTarget, "toggleLineBreakpoint": performActionOnTarget, "toggleLineComment": performActionOnTarget, "unfoldRegion": performActionOnTarget, "callAsFunction": performActionOnTarget, "editNew": performActionOnTarget, "executeCommand": performActionOnTarget, "generateSnippet": performActionOnTarget, "getText": performActionOnTarget, "highlight": performActionOnTarget, "insertSnippet": performActionOnTarget, "moveToTarget": performActionOnTarget, "pasteFromClipboard": performActionOnTarget, "replace": performActionOnTarget, "replaceWithTarget": performActionOnTarget, "rewrapWithPairedDelimiter": performActionOnTarget, "swapTargets": performActionOnTarget, "wrapWithPairedDelimiter": performActionOnTarget, "wrapWithSnippet": performActionOnTarget, - -} - - + default: targetDecoratedMark, + blue: targetDecoratedMark, + red: targetDecoratedMark, + green: targetDecoratedMark, + yellow: targetDecoratedMark, + ex: targetDecoratedMark, + fox: targetDecoratedMark, + + clearAndSetSelection: performActionOnTarget, + copyToClipboard: performActionOnTarget, + cutToClipboard: performActionOnTarget, + deselect: performActionOnTarget, + editNewLineAfter: performActionOnTarget, + editNewLineBefore: performActionOnTarget, + extractVariable: performActionOnTarget, + findInWorkspace: performActionOnTarget, + foldRegion: performActionOnTarget, + followLink: performActionOnTarget, + indentLine: performActionOnTarget, + insertCopyAfter: performActionOnTarget, + insertCopyBefore: performActionOnTarget, + insertEmptyLineAfter: performActionOnTarget, + insertEmptyLineBefore: performActionOnTarget, + insertEmptyLinesAround: performActionOnTarget, + outdentLine: performActionOnTarget, + randomizeTargets: performActionOnTarget, + remove: performActionOnTarget, + rename: performActionOnTarget, + revealDefinition: performActionOnTarget, + revealTypeDefinition: performActionOnTarget, + reverseTargets: performActionOnTarget, + scrollToBottom: performActionOnTarget, + scrollToCenter: performActionOnTarget, + scrollToTop: performActionOnTarget, + setSelection: performActionOnTarget, + setSelectionAfter: performActionOnTarget, + setSelectionBefore: performActionOnTarget, + showDebugHover: performActionOnTarget, + showHover: performActionOnTarget, + showQuickFix: performActionOnTarget, + showReferences: performActionOnTarget, + sortTargets: performActionOnTarget, + toggleLineBreakpoint: performActionOnTarget, + toggleLineComment: performActionOnTarget, + unfoldRegion: performActionOnTarget, + callAsFunction: performActionOnTarget, + editNew: performActionOnTarget, + executeCommand: performActionOnTarget, + generateSnippet: performActionOnTarget, + getText: performActionOnTarget, + highlight: performActionOnTarget, + insertSnippet: performActionOnTarget, + moveToTarget: performActionOnTarget, + pasteFromClipboard: performActionOnTarget, + replace: performActionOnTarget, + replaceWithTarget: performActionOnTarget, + rewrapWithPairedDelimiter: performActionOnTarget, + swapTargets: performActionOnTarget, + wrapWithPairedDelimiter: performActionOnTarget, + wrapWithSnippet: performActionOnTarget, + + paragraph: targetScope, + line: targetScope, +}; + +const targetCombinationOptionsList = ["replace", "list", "range"] as const; +type TargetCombinationOptions = typeof targetCombinationOptionsList[number]; export class Handler { - - - private targets: PartialPrimitiveTargetDescriptor[] = []; - - public readonly keymap: Keymap; - public readonly keyboardHandler: KeyboardHandler; - - private readonly keybindings: Record[] = [baseLayer, scopeLayer]; - - - - constructor( - keyboardHandler: KeyboardHandler, - keymap: Keymap, - private vscodeMode: KeyboardCommandsModal, - ) { - this.keyboardHandler = keyboardHandler; - this.keymap = keymap; - this.handleInput = this.handleInput.bind(this); - this.highlightTarget = this.highlightTarget.bind(this); - this.addTarget = this.addTarget.bind(this); - this.replaceLastTarget = this.replaceLastTarget.bind(this); - + private targets: PartialPrimitiveTargetDescriptor[] = []; + + public readonly keymap: Keymap; + public readonly keyboardHandler: KeyboardHandler; + + private currentLayer = 0; + private readonly keybindings: Record[] = [ + layer0, + layer1, + ]; + private combineTargets: TargetCombinationOptions = "replace"; + + constructor( + keyboardHandler: KeyboardHandler, + keymap: Keymap, + private vscodeMode: KeyboardCommandsModal + ) { + this.keyboardHandler = keyboardHandler; + this.keymap = keymap; + this.handleInput = this.handleInput.bind(this); + this.highlightTarget = this.highlightTarget.bind(this); + this.addTarget = this.addTarget.bind(this); + this.replaceLastTarget = this.replaceLastTarget.bind(this); + this.setStatusBarText = this.setStatusBarText.bind(this); + this.toggleCombineTargets = this.toggleCombineTargets.bind(this); + this.setTargetCombinationOption = this.setTargetCombinationOption.bind(this); + this.constructTarget = this.constructTarget.bind(this); + this.getLatestTarget = this.getLatestTarget.bind(this); + this.clearTargets = this.clearTargets.bind(this); + this.getTargets = this.getTargets.bind(this); + this.replaceAllTargets = this.replaceAllTargets.bind(this); + + this.setStatusBarText(); + } + + public async run(command: string): Promise { + await commandMap[command](this, command); + await this.highlightTarget(); + this.currentLayer = 0; + this.setStatusBarText(); + } + + public getFirstKeyMatching( + key: string, + validCommands: readonly string[] + ): string | undefined { + key = key.toLowerCase(); + for (const keybinding of this.keybindings) { + let command = keybinding[key]; + if (!command) { + continue; + } + // extract the command from the keybinding + const commandRegex: RegExp = new RegExp(`mode.run\\("(.+?)"\\);`); + const match = command.match(commandRegex); + command = match ? match[1] : ""; + if (command.length > 1 && validCommands.includes(command)) { + return command; + } } - - public async run(command: string): Promise { - await commandMap[command](this, command); - await this.highlightTarget(); + return undefined; + } + + public setKeybindingLayer(layer: number): void { + this.currentLayer = layer; + this.setStatusBarText(); + } + + + async handleInput(sequence: string): Promise { + // mode variable is necessary for eval + // eslint-disable-next-line @typescript-eslint/no-this-alias, @typescript-eslint/no-unused-vars + const mode = this; + const currentLayer = this.keybindings[this.currentLayer]; + if (sequence in currentLayer) { + eval(currentLayer[sequence]); } + return; + } - public getFirstKeyMatching(key: string, validCommands: readonly string[]): string | undefined { - key = key.toLowerCase(); - for (const keybinding of this.keybindings) { - let command = keybinding[key]; - if (!command) { - continue; - } - // extract the command from the keybinding - const commandRegex: RegExp = new RegExp(`mode.run\\("(.+?)"\\);`); - const match = command.match(commandRegex); - command = match ? match[1] : ""; - if (command.length >1 && validCommands.includes(command)) { - return command; - } - } - return undefined; - } - async handleInput(sequence: string): Promise { - const mode = this; - if (sequence in baseLayer) { - eval(baseLayer[sequence]); - } - return; - const map = this.keymap.getMergeKeys(); - let isValidSequence = map.includes(sequence); - // We handle multi-key sequences by repeatedly awaiting a single keypress - // until they've pressed something in the map. - while (!isValidSequence) { - - if (!this.keymap.isPrefixOfMapEntry(sequence)) { - const errorMessage = `Unknown key sequence "${sequence}"`; - vscode.window.showErrorMessage(errorMessage); - throw Error(errorMessage); - } - - const nextKey = await this.keyboardHandler.awaitSingleKeypress({ - cursorStyle: vscode.TextEditorCursorStyle.Underline, - whenClauseContext: "cursorless.keyboard.targeted.awaitingKeys", - statusBarText: "Finish sequence...", - }); - - if (nextKey == null) { - return; - } - - sequence += nextKey; - isValidSequence = this.keymap.getMergeKeys().includes(sequence); - } - await this.getGenerator(sequence)(this, sequence); - await this.highlightTarget(); - } + private async highlightTarget(): Promise { + await executeCursorlessCommand({ + name: "highlight", + target: this.constructTarget(), + }); + } - private getGenerator(keySequence: string): KeyboardPartialTargetGenerator { - const [section, val] = this.keymap.getSectionAndCommand(keySequence); - switch (section) { - case "colors": - case "shapes": - return targetDecoratedMark; - case "actions": - return performActionOnTarget; - } - return targetDecoratedMark; - } - private async highlightTarget(): Promise { - - await executeCursorlessCommand({ - name: "highlight", - target: this.constructTarget(), - }) + public async modeOff(): Promise { + await this.vscodeMode.modeOff(); + } + public replaceLastTarget(target: PartialPrimitiveTargetDescriptor): void { + this.targets[this.targets.length - 1] = target; + } + public addTarget(target: PartialPrimitiveTargetDescriptor): void { + if ( this.combineTargets === "replace" ) { + this.targets = []; } - - public async modeOff(): Promise { - await this.vscodeMode.modeOff(); + this.targets.push(target); + } + + public getLatestTarget(): PartialPrimitiveTargetDescriptor { + if (this.targets.length === 0) { + return { + type: "primitive", + mark: { + type: "cursor", + }, + }; } - public replaceLastTarget(target: PartialPrimitiveTargetDescriptor): void { - this.targets[this.targets.length - 1] = target; - } + return this.targets[this.targets.length - 1]; + } - public addTarget(target: PartialPrimitiveTargetDescriptor): void { - this.targets.push(target); - } + public clearTargets(): void { + this.targets = []; + this.highlightTarget(); + this.setStatusBarText(); + } - public getLatestTarget(): PartialPrimitiveTargetDescriptor { - return this.targets[this.targets.length - 1]; - } + public getTargets(): PartialPrimitiveTargetDescriptor[] { + return this.targets; + } - public constructTarget(): PartialTargetDescriptor { - const target: PartialTargetDescriptor = { - type: "range", - anchor: { - type: "primitive", - mark: { - type: "that", - }, - }, - active: this.targets[this.targets.length - 1], - excludeActive: false, - excludeAnchor: false, - }; - return this.targets[this.targets.length - 1]; + public replaceAllTargets(targets: PartialPrimitiveTargetDescriptor[]): void { + if (targets.length !== this.targets.length) { + throw Error("Cannot replace targets with different number of targets"); } - - -} \ No newline at end of file + this.targets = targets; + } + + public constructTarget(): PartialTargetDescriptor { + if (this.combineTargets === "list") { + const targetList = []; + for (const target of this.targets) { + targetList.push(target); + } + return { + type: "list", + elements: targetList, + }; + } else if (this.combineTargets === "range" && this.targets.length > 1) { + return { + type: "range", + anchor: this.targets[0], + active: this.targets[this.targets.length - 1], + excludeActive: false, + excludeAnchor: false, + }; + } + return this.getLatestTarget(); + } + + private setStatusBarText(): void { + this.keyboardHandler.setStatusBarText(`M: ${this.combineTargets}, L: ${this.currentLayer}`); + } + + public toggleCombineTargets(): void { + const index = targetCombinationOptionsList.indexOf(this.combineTargets); + this.setTargetCombinationOption( + targetCombinationOptionsList[(index + 1) % targetCombinationOptionsList.length] + ); + this.setStatusBarText(); + } + + public setTargetCombinationOption(option: TargetCombinationOptions): void { + this.combineTargets = option; + } +} diff --git a/packages/cursorless-vscode/src/keyboard/Handlers/ActionHandler.ts b/packages/cursorless-vscode/src/keyboard/Handlers/ActionHandler.ts index 6079ac9224..4fc9e3e37b 100644 --- a/packages/cursorless-vscode/src/keyboard/Handlers/ActionHandler.ts +++ b/packages/cursorless-vscode/src/keyboard/Handlers/ActionHandler.ts @@ -86,6 +86,6 @@ export async function performActionOnTarget(mode: Handler, keySequence: string): // exit modal mode await mode.modeOff(); } - + mode.clearTargets(); } diff --git a/packages/cursorless-vscode/src/keyboard/Handlers/ScopeHandler.ts b/packages/cursorless-vscode/src/keyboard/Handlers/ScopeHandler.ts new file mode 100644 index 0000000000..31b0933246 --- /dev/null +++ b/packages/cursorless-vscode/src/keyboard/Handlers/ScopeHandler.ts @@ -0,0 +1,103 @@ +import { + ContainingScopeModifier, + RelativeScopeModifier, + ScopeType, + SimpleScopeType, + SimpleScopeTypeType, +} from "@cursorless/common"; +import { Handler } from "../Handler"; + +const SimpleScopeTypeArray = [ + "argumentOrParameter", + "anonymousFunction", + "attribute", + "branch", + "class", + "className", + "collectionItem", + "collectionKey", + "comment", + "functionCall", + "functionCallee", + "functionName", + "ifStatement", + "instance", + "list", + "map", + "name", + "namedFunction", + "regularExpression", + "statement", + "string", + "type", + "value", + "condition", + "section", + "sectionLevelOne", + "sectionLevelTwo", + "sectionLevelThree", + "sectionLevelFour", + "sectionLevelFive", + "sectionLevelSix", + "selector", + "switchStatementSubject", + "unit", + "xmlBothTags", + "xmlElement", + "xmlEndTag", + "xmlStartTag", + "notebookCell", + // Latex scope types + "part", + "chapter", + "subSection", + "subSubSection", + "namedParagraph", + "subParagraph", + "environment", + // Text based scopes + "character", + "word", + "token", + "identifier", + "line", + "sentence", + "paragraph", + "document", + "nonWhitespaceSequence", + "boundedNonWhitespaceSequence", + "url", + // Talon + "command", +] as const; + +export async function targetScope( + mode: Handler, + keySequence: string +): Promise { + if (!SimpleScopeTypeArray.includes(keySequence)) { + throw Error(`Unsupported scope: ${keySequence}`); + } + const scopeType: SimpleScopeTypeType = keySequence as SimpleScopeTypeType; + + const modifiedTargets = []; + for ( let curTarget of mode.getTargets()) { + + if (curTarget === undefined) { + return; + } + const scope: ContainingScopeModifier = { + type: "containingScope", + scopeType: { + type: scopeType, + }, + }; + curTarget = { + type: curTarget.type, + modifiers: [scope], + mark: curTarget.mark, + }; + modifiedTargets.push(curTarget); + } + mode.replaceAllTargets(modifiedTargets); +} diff --git a/packages/cursorless-vscode/src/keyboard/KeyboardHandler.ts b/packages/cursorless-vscode/src/keyboard/KeyboardHandler.ts index 80e161fd9c..3e4ebf47c9 100644 --- a/packages/cursorless-vscode/src/keyboard/KeyboardHandler.ts +++ b/packages/cursorless-vscode/src/keyboard/KeyboardHandler.ts @@ -296,6 +296,14 @@ export default class KeyboardHandler { } } + public setStatusBarText(text: string) { + + if (this.activeListener?.listener ) { + this.activeListener.listener.displayOptions.statusBarText = text; + } + this.ensureStatusBarText(); + } + private ensureStatusBarText() { if (!this.isActivated) { return; diff --git a/packages/cursorless-vscode/src/keyboard/KeyboardVsCodeMode.ts b/packages/cursorless-vscode/src/keyboard/KeyboardVsCodeMode.ts index de4f971e26..2c5c084e17 100644 --- a/packages/cursorless-vscode/src/keyboard/KeyboardVsCodeMode.ts +++ b/packages/cursorless-vscode/src/keyboard/KeyboardVsCodeMode.ts @@ -76,13 +76,14 @@ export default class KeyboardCommandsModal { displayOptions: { cursorStyle: vscode.TextEditorCursorStyle.BlockOutline, whenClauseContext: "cursorless.keyboard.modal.mode", - statusBarText: "Listening...", + statusBarText: "", }, handleCancelled: this.modeOff, }); // Set target to current selection when we enter the mode await this.targeted.targetSelection(); + this.handler.clearTargets(); }; modeOff = async () => { diff --git a/packages/cursorless-vscode/src/keyboard/fixedKeymap.ts b/packages/cursorless-vscode/src/keyboard/fixedKeymap.ts index 6ee7b1a682..73b1bedb3d 100644 --- a/packages/cursorless-vscode/src/keyboard/fixedKeymap.ts +++ b/packages/cursorless-vscode/src/keyboard/fixedKeymap.ts @@ -1,18 +1,29 @@ -export const baseLayer = { +export const layer0 = { "d": `mode.run("default");`, "b": `mode.run("blue");`, "g": `mode.run("green");`, "r": `mode.run("red");`, - "y": `mode.run("yellow");`, + "c": `mode.run("yellow");`, "x": `mode.run("ex");`, "f": `mode.run("fox");`, "p": `mode.run("paragraph");`, + ",": `mode.run("line");`, + ".": `mode.run("statement");`, + "i": `mode.run("replaceWithTarget");`, -} + "o": `mode.run("clearAndSetSelection");`, + "m": `mode.run("setSelectionBefore");`, + "n": `mode.run("setSelectionAfter");`, -export const scopeLayer = { + "k": `mode.setKeybindingLayer(1);`, + "a": `mode.toggleCombineTargets();`, + "j": `mode.clearTargets();`, +} +export const layer1 = { + "h": "mode.run('editNewLineBefore');", + "t": "mode.run('editNewLineAfter');", } \ No newline at end of file From 721ffd14e3369a2e9d6d17038b8ef8d571f8844d Mon Sep 17 00:00:00 2001 From: knork Date: Sun, 22 Oct 2023 12:31:38 +0200 Subject: [PATCH 09/13] paired delimiter work with interior/exterior-only --- .../cursorless-vscode/src/keyboard/Handler.ts | 138 ++++++++++++-- .../src/keyboard/Handlers/ModifierHandler.ts | 19 ++ .../Handlers/PairedDelimiterHandler.ts | 22 +++ .../src/keyboard/Handlers/ScopeHandler.ts | 170 ++++++++---------- .../src/keyboard/Handlers/TargetHandler.ts | 6 +- .../src/keyboard/fixedKeymap.ts | 24 ++- 6 files changed, 264 insertions(+), 115 deletions(-) create mode 100644 packages/cursorless-vscode/src/keyboard/Handlers/ModifierHandler.ts create mode 100644 packages/cursorless-vscode/src/keyboard/Handlers/PairedDelimiterHandler.ts diff --git a/packages/cursorless-vscode/src/keyboard/Handler.ts b/packages/cursorless-vscode/src/keyboard/Handler.ts index 38c8b05845..2ea1c5680e 100644 --- a/packages/cursorless-vscode/src/keyboard/Handler.ts +++ b/packages/cursorless-vscode/src/keyboard/Handler.ts @@ -1,4 +1,5 @@ import { + Modifier, PartialPrimitiveTargetDescriptor, PartialTargetDescriptor, } from "@cursorless/common"; @@ -8,8 +9,10 @@ import { targetDecoratedMark } from "./Handlers/TargetHandler"; import { executeCursorlessCommand } from "./KeyboardCommandsTargeted"; import KeyboardCommandsModal from "./KeyboardVsCodeMode"; import { performActionOnTarget } from "./Handlers/ActionHandler"; -import { layer0, layer1 } from "./fixedKeymap"; +import { layer0, layer1, layer2 } from "./fixedKeymap"; import { targetScope } from "./Handlers/ScopeHandler"; +import { targetPairedDelimiter } from "./Handlers/PairedDelimiterHandler"; +import { modifyInteriorExterior } from "./Handlers/ModifierHandler"; export interface KeyboardPartialTargetGenerator { (mode: Handler, keySequence: string): Promise; @@ -77,8 +80,75 @@ const commandMap: Record = { wrapWithPairedDelimiter: performActionOnTarget, wrapWithSnippet: performActionOnTarget, - paragraph: targetScope, - line: targetScope, + + argumentOrParameter:targetScope, + attribute:targetScope, + paragraph:targetScope, + branch:targetScope, + functionCall:targetScope, + functionCallee:targetScope, + notebookCell:targetScope, + chapter:targetScope, + character:targetScope, + class:targetScope, + className:targetScope, + comment:targetScope, + condition:targetScope, + xmlElement:targetScope, + xmlEndTag:targetScope, + environment:targetScope, + document:targetScope, + namedFunction:targetScope, + functionName:targetScope, + identifier:targetScope, + ifStatement:targetScope, + instance:targetScope, + collectionItem:targetScope, + collectionKey:targetScope, + anonymousFunction:targetScope, + line:targetScope, + url:targetScope, + list:targetScope, + map:targetScope, + name:targetScope, + nonWhitespaceSequence:targetScope, + namedParagraph:targetScope, + part:targetScope, + regularExpression:targetScope, + section:targetScope, + selector:targetScope, + sentence:targetScope, + boundedNonWhitespaceSequence:targetScope, + xmlStartTag:targetScope, + statement:targetScope, + string:targetScope, + subParagraph:targetScope, + subSection:targetScope, + subSubSection:targetScope, + xmlBothTags:targetScope, + token:targetScope, + type:targetScope, + unit:targetScope, + value:targetScope, + word:targetScope, + + + squareBrackets: targetPairedDelimiter, + curlyBrackets: targetPairedDelimiter, + angleBrackets: targetPairedDelimiter, + escapedSquareBrackets: targetPairedDelimiter, + escapedDoubleQuotes: targetPairedDelimiter, + escapedParentheses: targetPairedDelimiter, + escapedSingleQuotes: targetPairedDelimiter, + any: targetPairedDelimiter, + doubleQuotes: targetPairedDelimiter, + parentheses: targetPairedDelimiter, + backtickQuotes: targetPairedDelimiter, + singleQuotes: targetPairedDelimiter, + whitespace: targetPairedDelimiter, + + interiorOnly: modifyInteriorExterior, + excludeInterior: modifyInteriorExterior, }; const targetCombinationOptionsList = ["replace", "list", "range"] as const; @@ -91,10 +161,7 @@ export class Handler { public readonly keyboardHandler: KeyboardHandler; private currentLayer = 0; - private readonly keybindings: Record[] = [ - layer0, - layer1, - ]; + private readonly keybindings: Record[] = [layer0, layer1, layer2]; private combineTargets: TargetCombinationOptions = "replace"; constructor( @@ -110,7 +177,9 @@ export class Handler { this.replaceLastTarget = this.replaceLastTarget.bind(this); this.setStatusBarText = this.setStatusBarText.bind(this); this.toggleCombineTargets = this.toggleCombineTargets.bind(this); - this.setTargetCombinationOption = this.setTargetCombinationOption.bind(this); + this.setTargetCombinationOption = this.setTargetCombinationOption.bind( + this + ); this.constructTarget = this.constructTarget.bind(this); this.getLatestTarget = this.getLatestTarget.bind(this); this.clearTargets = this.clearTargets.bind(this); @@ -153,7 +222,6 @@ export class Handler { this.setStatusBarText(); } - async handleInput(sequence: string): Promise { // mode variable is necessary for eval // eslint-disable-next-line @typescript-eslint/no-this-alias, @typescript-eslint/no-unused-vars @@ -165,7 +233,6 @@ export class Handler { return; } - private async highlightTarget(): Promise { await executeCursorlessCommand({ name: "highlight", @@ -182,7 +249,7 @@ export class Handler { } public addTarget(target: PartialPrimitiveTargetDescriptor): void { - if ( this.combineTargets === "replace" ) { + if (this.combineTargets === "replace") { this.targets = []; } this.targets.push(target); @@ -202,8 +269,17 @@ export class Handler { } public clearTargets(): void { - this.targets = []; + this.targets = [ + { + type: "primitive", + mark: { + type: "nothing", + }, + }, + ]; this.highlightTarget(); + this.currentLayer = 0; + this.targets = []; this.setStatusBarText(); } @@ -219,7 +295,7 @@ export class Handler { } public constructTarget(): PartialTargetDescriptor { - if (this.combineTargets === "list") { + if (this.combineTargets === "list" && this.targets.length > 1) { const targetList = []; for (const target of this.targets) { targetList.push(target); @@ -228,10 +304,10 @@ export class Handler { type: "list", elements: targetList, }; - } else if (this.combineTargets === "range" && this.targets.length > 1) { + } else if ( this.combineTargets === "range" && this.targets.length > 1) { return { type: "range", - anchor: this.targets[0], + anchor: { type: "primitive", mark: { type: "that" } }, active: this.targets[this.targets.length - 1], excludeActive: false, excludeAnchor: false, @@ -241,18 +317,42 @@ export class Handler { } private setStatusBarText(): void { - this.keyboardHandler.setStatusBarText(`M: ${this.combineTargets}, L: ${this.currentLayer}`); + this.keyboardHandler.setStatusBarText( + `M: ${this.combineTargets}, L: ${this.currentLayer}` + ); } public toggleCombineTargets(): void { const index = targetCombinationOptionsList.indexOf(this.combineTargets); this.setTargetCombinationOption( - targetCombinationOptionsList[(index + 1) % targetCombinationOptionsList.length] - ); - this.setStatusBarText(); + targetCombinationOptionsList[ + (index + 1) % targetCombinationOptionsList.length + ] + ); + this.setStatusBarText(); } public setTargetCombinationOption(option: TargetCombinationOptions): void { this.combineTargets = option; } + + public addModifier(modifier: Modifier): void { + const modifiedTargets = []; + for (let curTarget of this.getTargets()) { + if (curTarget === undefined) { + return; + } + const mods:Modifier[]=[modifier] + if ( curTarget.modifiers){ + mods.push(...curTarget.modifiers) + } + curTarget = { + type: curTarget.type, + modifiers: mods, + mark: curTarget.mark, + }; + modifiedTargets.push(curTarget); + } + this.replaceAllTargets(modifiedTargets); + } } diff --git a/packages/cursorless-vscode/src/keyboard/Handlers/ModifierHandler.ts b/packages/cursorless-vscode/src/keyboard/Handlers/ModifierHandler.ts new file mode 100644 index 0000000000..bef8a123b0 --- /dev/null +++ b/packages/cursorless-vscode/src/keyboard/Handlers/ModifierHandler.ts @@ -0,0 +1,19 @@ +import { Modifier } from "@cursorless/common"; +import { Handler } from "../Handler"; + +export async function modifyInteriorExterior( + mode: Handler, + keySequence: string + ): Promise { + + if (keySequence !== "interiorOnly" && keySequence !== "excludeInterior") { + throw Error(`Unsupported modifier: ${keySequence}`); + } + + const modifier = { + type: keySequence as "interiorOnly" | "excludeInterior", + } + mode.addModifier(modifier); + } + + diff --git a/packages/cursorless-vscode/src/keyboard/Handlers/PairedDelimiterHandler.ts b/packages/cursorless-vscode/src/keyboard/Handlers/PairedDelimiterHandler.ts new file mode 100644 index 0000000000..e4a08a5c8e --- /dev/null +++ b/packages/cursorless-vscode/src/keyboard/Handlers/PairedDelimiterHandler.ts @@ -0,0 +1,22 @@ +import { + ContainingSurroundingPairModifier, + SurroundingPairName, +} from "@cursorless/common"; +import { Handler } from "../Handler"; + +export async function targetPairedDelimiter( + mode: Handler, + keySequence: string +): Promise { + const scopeType: SurroundingPairName = keySequence as SurroundingPairName; + + const scope: ContainingSurroundingPairModifier = { + type: "containingScope", + scopeType: { + type: "surroundingPair", + delimiter: scopeType, + requireStrongContainment: false, + }, +}; + mode.addModifier(scope); +} diff --git a/packages/cursorless-vscode/src/keyboard/Handlers/ScopeHandler.ts b/packages/cursorless-vscode/src/keyboard/Handlers/ScopeHandler.ts index 31b0933246..c970d6a8c2 100644 --- a/packages/cursorless-vscode/src/keyboard/Handlers/ScopeHandler.ts +++ b/packages/cursorless-vscode/src/keyboard/Handlers/ScopeHandler.ts @@ -1,103 +1,89 @@ import { - ContainingScopeModifier, - RelativeScopeModifier, - ScopeType, - SimpleScopeType, - SimpleScopeTypeType, + ContainingScopeModifier, + RelativeScopeModifier, + ScopeType, + SimpleScopeType, + SimpleScopeTypeType, } from "@cursorless/common"; import { Handler } from "../Handler"; const SimpleScopeTypeArray = [ - "argumentOrParameter", - "anonymousFunction", - "attribute", - "branch", - "class", - "className", - "collectionItem", - "collectionKey", - "comment", - "functionCall", - "functionCallee", - "functionName", - "ifStatement", - "instance", - "list", - "map", - "name", - "namedFunction", - "regularExpression", - "statement", - "string", - "type", - "value", - "condition", - "section", - "sectionLevelOne", - "sectionLevelTwo", - "sectionLevelThree", - "sectionLevelFour", - "sectionLevelFive", - "sectionLevelSix", - "selector", - "switchStatementSubject", - "unit", - "xmlBothTags", - "xmlElement", - "xmlEndTag", - "xmlStartTag", - "notebookCell", - // Latex scope types - "part", - "chapter", - "subSection", - "subSubSection", - "namedParagraph", - "subParagraph", - "environment", - // Text based scopes - "character", - "word", - "token", - "identifier", - "line", - "sentence", - "paragraph", - "document", - "nonWhitespaceSequence", - "boundedNonWhitespaceSequence", - "url", - // Talon - "command", + "argumentOrParameter", + "anonymousFunction", + "attribute", + "branch", + "class", + "className", + "collectionItem", + "collectionKey", + "comment", + "functionCall", + "functionCallee", + "functionName", + "ifStatement", + "instance", + "list", + "map", + "name", + "namedFunction", + "regularExpression", + "statement", + "string", + "type", + "value", + "condition", + "section", + "sectionLevelOne", + "sectionLevelTwo", + "sectionLevelThree", + "sectionLevelFour", + "sectionLevelFive", + "sectionLevelSix", + "selector", + "switchStatementSubject", + "unit", + "xmlBothTags", + "xmlElement", + "xmlEndTag", + "xmlStartTag", + "notebookCell", + // Latex scope types + "part", + "chapter", + "subSection", + "subSubSection", + "namedParagraph", + "subParagraph", + "environment", + // Text based scopes + "character", + "word", + "token", + "identifier", + "line", + "sentence", + "paragraph", + "document", + "nonWhitespaceSequence", + "boundedNonWhitespaceSequence", + "url", + // Talon + "command", ] as const; export async function targetScope( - mode: Handler, - keySequence: string + mode: Handler, + keySequence: string ): Promise { - if (!SimpleScopeTypeArray.includes(keySequence)) { - throw Error(`Unsupported scope: ${keySequence}`); - } - const scopeType: SimpleScopeTypeType = keySequence as SimpleScopeTypeType; - - const modifiedTargets = []; - for ( let curTarget of mode.getTargets()) { - - if (curTarget === undefined) { - return; - } - const scope: ContainingScopeModifier = { - type: "containingScope", - scopeType: { - type: scopeType, - }, - }; - curTarget = { - type: curTarget.type, - modifiers: [scope], - mark: curTarget.mark, + if (!SimpleScopeTypeArray.includes(keySequence)) { + throw Error(`Unsupported scope: ${keySequence}`); + } + const scopeType: SimpleScopeTypeType = keySequence as SimpleScopeTypeType; + const scope: ContainingScopeModifier = { + type: "containingScope", + scopeType: { + type: scopeType, + }, }; - modifiedTargets.push(curTarget); - } - mode.replaceAllTargets(modifiedTargets); + mode.addModifier(scope); } diff --git a/packages/cursorless-vscode/src/keyboard/Handlers/TargetHandler.ts b/packages/cursorless-vscode/src/keyboard/Handlers/TargetHandler.ts index ad94d2aa8b..6b53bd5adc 100644 --- a/packages/cursorless-vscode/src/keyboard/Handlers/TargetHandler.ts +++ b/packages/cursorless-vscode/src/keyboard/Handlers/TargetHandler.ts @@ -2,7 +2,7 @@ import * as vscode from "vscode"; import { getStyleName } from "../../ide/vscode/hats/getStyleName"; import { PartialTargetDescriptor } from "@cursorless/common"; import { Handler } from "../Handler"; -import { HAT_COLORS, HAT_NON_DEFAULT_SHAPES, HatColor, HatShape } from "../../ide/vscode/hatStyles.types"; +import { HAT_COLORS, HAT_NON_DEFAULT_SHAPES, HatColor, HatNonDefaultShape, HatShape } from "../../ide/vscode/hatStyles.types"; export async function targetDecoratedMark (mode: Handler, keySequence:string):Promise { @@ -11,12 +11,12 @@ import { HAT_COLORS, HAT_NON_DEFAULT_SHAPES, HatColor, HatShape } from "../../id const keyboardHandler = mode.keyboardHandler; let color: HatColor = "default"; - if ( keySequence in HAT_COLORS){ + if ( HAT_COLORS.includes(keySequence as HatColor)){ color = keySequence as HatColor; } let shape: HatShape = "default"; - if ( keySequence in HAT_NON_DEFAULT_SHAPES){ + if ( HAT_NON_DEFAULT_SHAPES.includes(keySequence as HatNonDefaultShape)){ shape = keySequence as HatShape; } diff --git a/packages/cursorless-vscode/src/keyboard/fixedKeymap.ts b/packages/cursorless-vscode/src/keyboard/fixedKeymap.ts index 73b1bedb3d..d7abc6b170 100644 --- a/packages/cursorless-vscode/src/keyboard/fixedKeymap.ts +++ b/packages/cursorless-vscode/src/keyboard/fixedKeymap.ts @@ -11,7 +11,7 @@ export const layer0 = { "p": `mode.run("paragraph");`, ",": `mode.run("line");`, - ".": `mode.run("statement");`, + ".": `mode.run("collectionKey");`, "i": `mode.run("replaceWithTarget");`, "o": `mode.run("clearAndSetSelection");`, @@ -21,9 +21,31 @@ export const layer0 = { "k": `mode.setKeybindingLayer(1);`, "a": `mode.toggleCombineTargets();`, "j": `mode.clearTargets();`, + + ";": `mode.setKeybindingLayer(2);`, + } export const layer1 = { "h": "mode.run('editNewLineBefore');", "t": "mode.run('editNewLineAfter');", +} + +export const layer2 = { + "[": "mode.run('squareBrackets');", + "{": "mode.run('curlyBrackets');", + "(": "mode.run('parentheses');", + "<": "mode.run('angleBrackets');", + + "]": "mode.run('squareBrackets');", + "}": "mode.run('curlyBrackets');", + ")": "mode.run('parentheses');", + ">": "mode.run('angleBrackets');", + + ",": "mode.run('any');", + ";": "mode.run('interiorOnly');", + ".": "mode.run('excludeInterior');", + + + } \ No newline at end of file From 6f3eddba5d4954d44e4bc770a0f68816796016d3 Mon Sep 17 00:00:00 2001 From: knork Date: Sun, 22 Oct 2023 16:41:32 +0200 Subject: [PATCH 10/13] first iteration of every works --- .../cursorless-vscode/src/keyboard/Handler.ts | 4 +- .../src/keyboard/Handlers/ModifierHandler.ts | 48 ++++++++++++++++--- .../src/keyboard/Handlers/ScopeHandler.ts | 2 +- .../src/keyboard/fixedKeymap.ts | 1 + 4 files changed, 47 insertions(+), 8 deletions(-) diff --git a/packages/cursorless-vscode/src/keyboard/Handler.ts b/packages/cursorless-vscode/src/keyboard/Handler.ts index 2ea1c5680e..fe1b27c0c6 100644 --- a/packages/cursorless-vscode/src/keyboard/Handler.ts +++ b/packages/cursorless-vscode/src/keyboard/Handler.ts @@ -12,7 +12,7 @@ import { performActionOnTarget } from "./Handlers/ActionHandler"; import { layer0, layer1, layer2 } from "./fixedKeymap"; import { targetScope } from "./Handlers/ScopeHandler"; import { targetPairedDelimiter } from "./Handlers/PairedDelimiterHandler"; -import { modifyInteriorExterior } from "./Handlers/ModifierHandler"; +import { everyModifier, modifyInteriorExterior } from "./Handlers/ModifierHandler"; export interface KeyboardPartialTargetGenerator { (mode: Handler, keySequence: string): Promise; @@ -149,6 +149,8 @@ const commandMap: Record = { interiorOnly: modifyInteriorExterior, excludeInterior: modifyInteriorExterior, + + everyScope: everyModifier }; const targetCombinationOptionsList = ["replace", "list", "range"] as const; diff --git a/packages/cursorless-vscode/src/keyboard/Handlers/ModifierHandler.ts b/packages/cursorless-vscode/src/keyboard/Handlers/ModifierHandler.ts index bef8a123b0..691058b12f 100644 --- a/packages/cursorless-vscode/src/keyboard/Handlers/ModifierHandler.ts +++ b/packages/cursorless-vscode/src/keyboard/Handlers/ModifierHandler.ts @@ -1,19 +1,55 @@ -import { Modifier } from "@cursorless/common"; +import { EveryScopeModifier, Modifier, ScopeType, SimpleScopeType } from "@cursorless/common"; import { Handler } from "../Handler"; +import * as vscode from "vscode"; +import { SimpleScopeTypeArray } from "./ScopeHandler"; export async function modifyInteriorExterior( mode: Handler, keySequence: string - ): Promise { - +): Promise { if (keySequence !== "interiorOnly" && keySequence !== "excludeInterior") { - throw Error(`Unsupported modifier: ${keySequence}`); + throw Error(`Unsupported modifier: ${keySequence}`); } const modifier = { type: keySequence as "interiorOnly" | "excludeInterior", - } + }; mode.addModifier(modifier); - } +} + +export async function everyModifier( + mode: Handler, + keySequence: string +): Promise { + if (keySequence !== "everyScope") { + throw Error(`Unsupported modifier: ${keySequence}`); + } + const keyboardHandler = mode.keyboardHandler; + const getCharacter = async (status: string = "Which hat?") => { + return await keyboardHandler.awaitSingleKeypress({ + cursorStyle: vscode.TextEditorCursorStyle.Underline, + whenClauseContext: + "cursorless.keyboard.targeted.awaitingHatCharacter", + statusBarText: status, + }); + }; + const character = await getCharacter(); + if (character == null) { + // Cancelled + return; + } + const scope = mode.getFirstKeyMatching(character, SimpleScopeTypeArray); + if (scope === undefined) { + throw Error(`No scope found for character : ${character}`); + } + if (!SimpleScopeTypeArray.includes(scope)) { + throw Error(`Unsupported scope: ${scope}`); + } + const modifier: EveryScopeModifier = { + type: "everyScope", + scopeType: {type:scope as SimpleScopeType}, + }; + mode.addModifier(modifier); +} diff --git a/packages/cursorless-vscode/src/keyboard/Handlers/ScopeHandler.ts b/packages/cursorless-vscode/src/keyboard/Handlers/ScopeHandler.ts index c970d6a8c2..7ced48cdae 100644 --- a/packages/cursorless-vscode/src/keyboard/Handlers/ScopeHandler.ts +++ b/packages/cursorless-vscode/src/keyboard/Handlers/ScopeHandler.ts @@ -7,7 +7,7 @@ import { } from "@cursorless/common"; import { Handler } from "../Handler"; -const SimpleScopeTypeArray = [ +export const SimpleScopeTypeArray = [ "argumentOrParameter", "anonymousFunction", "attribute", diff --git a/packages/cursorless-vscode/src/keyboard/fixedKeymap.ts b/packages/cursorless-vscode/src/keyboard/fixedKeymap.ts index d7abc6b170..fe9d676ef7 100644 --- a/packages/cursorless-vscode/src/keyboard/fixedKeymap.ts +++ b/packages/cursorless-vscode/src/keyboard/fixedKeymap.ts @@ -23,6 +23,7 @@ export const layer0 = { "j": `mode.clearTargets();`, ";": `mode.setKeybindingLayer(2);`, + "y": `mode.run("everyScope");`, } From d5aaf00f1619e480985b797b557a176058c7697a Mon Sep 17 00:00:00 2001 From: knork Date: Sun, 22 Oct 2023 16:50:48 +0200 Subject: [PATCH 11/13] fix implicit Target --- packages/cursorless-vscode/src/keyboard/Handler.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/cursorless-vscode/src/keyboard/Handler.ts b/packages/cursorless-vscode/src/keyboard/Handler.ts index fe1b27c0c6..20209b2cd5 100644 --- a/packages/cursorless-vscode/src/keyboard/Handler.ts +++ b/packages/cursorless-vscode/src/keyboard/Handler.ts @@ -340,6 +340,10 @@ export class Handler { public addModifier(modifier: Modifier): void { const modifiedTargets = []; + if (this.targets.length === 0) { + // ensure that there is at least one target + this.targets = [this.getLatestTarget()]; + } for (let curTarget of this.getTargets()) { if (curTarget === undefined) { return; From 889fcddf4e2ce929d3d85ad803d90b0f290b5526 Mon Sep 17 00:00:00 2001 From: knork Date: Sun, 22 Oct 2023 20:11:43 +0200 Subject: [PATCH 12/13] little bit of cleaning --- .../cursorless-vscode/src/keyboard/Handler.ts | 5 +- .../src/keyboard/Handlers/ModifierHandler.ts | 2 +- .../src/keyboard/Handlers/ScopeHandler.ts | 3 - .../src/keyboard/KeyboardVsCodeMode.ts | 8 +- .../cursorless-vscode/src/keyboard/Keymap.ts | 224 ------------------ .../src/keyboard/MarkSelection.ts | 0 .../src/keyboard/fixedKeymap.ts | 1 + 7 files changed, 6 insertions(+), 237 deletions(-) delete mode 100644 packages/cursorless-vscode/src/keyboard/Keymap.ts delete mode 100644 packages/cursorless-vscode/src/keyboard/MarkSelection.ts diff --git a/packages/cursorless-vscode/src/keyboard/Handler.ts b/packages/cursorless-vscode/src/keyboard/Handler.ts index 20209b2cd5..046ea2109f 100644 --- a/packages/cursorless-vscode/src/keyboard/Handler.ts +++ b/packages/cursorless-vscode/src/keyboard/Handler.ts @@ -4,7 +4,7 @@ import { PartialTargetDescriptor, } from "@cursorless/common"; import KeyboardHandler from "./KeyboardHandler"; -import Keymap from "./Keymap"; + import { targetDecoratedMark } from "./Handlers/TargetHandler"; import { executeCursorlessCommand } from "./KeyboardCommandsTargeted"; import KeyboardCommandsModal from "./KeyboardVsCodeMode"; @@ -159,7 +159,6 @@ type TargetCombinationOptions = typeof targetCombinationOptionsList[number]; export class Handler { private targets: PartialPrimitiveTargetDescriptor[] = []; - public readonly keymap: Keymap; public readonly keyboardHandler: KeyboardHandler; private currentLayer = 0; @@ -168,11 +167,9 @@ export class Handler { constructor( keyboardHandler: KeyboardHandler, - keymap: Keymap, private vscodeMode: KeyboardCommandsModal ) { this.keyboardHandler = keyboardHandler; - this.keymap = keymap; this.handleInput = this.handleInput.bind(this); this.highlightTarget = this.highlightTarget.bind(this); this.addTarget = this.addTarget.bind(this); diff --git a/packages/cursorless-vscode/src/keyboard/Handlers/ModifierHandler.ts b/packages/cursorless-vscode/src/keyboard/Handlers/ModifierHandler.ts index 691058b12f..631ebaaaee 100644 --- a/packages/cursorless-vscode/src/keyboard/Handlers/ModifierHandler.ts +++ b/packages/cursorless-vscode/src/keyboard/Handlers/ModifierHandler.ts @@ -1,4 +1,4 @@ -import { EveryScopeModifier, Modifier, ScopeType, SimpleScopeType } from "@cursorless/common"; +import { EveryScopeModifier,SimpleScopeType } from "@cursorless/common"; import { Handler } from "../Handler"; import * as vscode from "vscode"; import { SimpleScopeTypeArray } from "./ScopeHandler"; diff --git a/packages/cursorless-vscode/src/keyboard/Handlers/ScopeHandler.ts b/packages/cursorless-vscode/src/keyboard/Handlers/ScopeHandler.ts index 7ced48cdae..a7f5f5bb21 100644 --- a/packages/cursorless-vscode/src/keyboard/Handlers/ScopeHandler.ts +++ b/packages/cursorless-vscode/src/keyboard/Handlers/ScopeHandler.ts @@ -1,8 +1,5 @@ import { ContainingScopeModifier, - RelativeScopeModifier, - ScopeType, - SimpleScopeType, SimpleScopeTypeType, } from "@cursorless/common"; import { Handler } from "../Handler"; diff --git a/packages/cursorless-vscode/src/keyboard/KeyboardVsCodeMode.ts b/packages/cursorless-vscode/src/keyboard/KeyboardVsCodeMode.ts index 2c5c084e17..f5d0182047 100644 --- a/packages/cursorless-vscode/src/keyboard/KeyboardVsCodeMode.ts +++ b/packages/cursorless-vscode/src/keyboard/KeyboardVsCodeMode.ts @@ -3,7 +3,6 @@ import * as vscode from "vscode"; import KeyboardCommandsTargeted from "./KeyboardCommandsTargeted"; import KeyboardHandler from "./KeyboardHandler"; import { executeCursorlessCommand } from "./KeyboardCommandsTargeted"; -import Keymap from "./Keymap"; // import { Direction } from "@cursorless/common"; // import { performActionOnTarget } from "./Handlers/ActionHandler"; import { Handler } from "./Handler"; @@ -26,7 +25,7 @@ export default class KeyboardCommandsModal { cursorOffset: vscode.Position; - public keymap: Keymap; + private handler: Handler; @@ -43,8 +42,7 @@ export default class KeyboardCommandsModal { // this.constructMergedKeymap(); this.cursorOffset = new vscode.Position(0, 0); - this.keymap = new Keymap(); - this.handler = new Handler(keyboardHandler, this.keymap, this); + this.handler = new Handler(keyboardHandler, this); } @@ -56,7 +54,7 @@ export default class KeyboardCommandsModal { "cursorless.experimental.keyboard.modal.keybindings", ) ) { - this.keymap.loadKeymap(); + return; } }), ); diff --git a/packages/cursorless-vscode/src/keyboard/Keymap.ts b/packages/cursorless-vscode/src/keyboard/Keymap.ts deleted file mode 100644 index 8272a2296b..0000000000 --- a/packages/cursorless-vscode/src/keyboard/Keymap.ts +++ /dev/null @@ -1,224 +0,0 @@ - -import { ActionType, ModifierType, SimpleScopeTypeType, isTesting } from "@cursorless/common"; -import * as vscode from "vscode"; -import { HatColor, HatShape } from "../ide/vscode/hatStyles.types"; -import { merge, toPairs } from "lodash"; - - - -type SectionName = "actions" | "scopes" | "colors" | "shapes" | "modifiers" | "keyboardActions"; -export type SectionKeymap = Record; - -export const DEFAULT_ACTION_KEYMAP: SectionKeymap = isTesting() - ? { t: "setSelection" } - : {}; - -export const DEFAULT_SCOPE_KEYMAP: SectionKeymap = isTesting() - ? { sf: "namedFunction" } - : {}; - -export const DEFAULT_COLOR_KEYMAP: SectionKeymap = isTesting() - ? { d: "default" } - : {}; - -export const DEFAULT_SHAPE_KEYMAP: SectionKeymap = isTesting() ? {} : {}; - -export const DEFAULT_MODIFIER_KEYMAP: SectionKeymap = isTesting() ? {} : {}; - - -export default class Keymap { - - private actionKeymap: SectionKeymap = {}; - private scopeKeymap: SectionKeymap = {}; - private colorKeymap: SectionKeymap = {}; - private shapeKeymap: SectionKeymap = {}; - private modifierKeymap: SectionKeymap = {}; - private keyboardActions: SectionKeymap = {}; - - - private handlerMap: Record = {}; - - constructor() { - this.clearAllMaps.bind(this); - this.loadKeymap.bind(this); - this.loadSection.bind(this); - this.getMergeKeys.bind(this); - this.setMap.bind(this); - this.clearMap.bind(this); - this.getConflictingKeyMapEntry.bind(this); - this.loadKeymap(); - } - - - public loadKeymap() { - this.clearAllMaps(); - this.loadSection("actions", DEFAULT_ACTION_KEYMAP); - this.loadSection("scopes", DEFAULT_SCOPE_KEYMAP); - this.loadSection("colors", DEFAULT_COLOR_KEYMAP); - this.loadSection("shapes", DEFAULT_SHAPE_KEYMAP); - this.loadSection("modifiers", DEFAULT_MODIFIER_KEYMAP); - } - clearAllMaps() { - this.clearMap("actions"); - this.clearMap("scopes"); - this.clearMap("colors"); - this.clearMap("shapes"); - this.clearMap("modifiers"); - } - - public getSectionAndCommand(key: string): [SectionName, any] { - - if (this.actionKeymap[key] != null) { - return ["actions", this.actionKeymap[key]]; - } - if (this.scopeKeymap[key] != null) { - return ["scopes", this.scopeKeymap[key]]; - } - if (this.colorKeymap[key] != null) { - return ["colors", this.colorKeymap[key]]; - } - if (this.shapeKeymap[key] != null) { - return ["shapes", this.shapeKeymap[key]]; - } - if (this.modifierKeymap[key] != null) { - return ["modifiers", this.modifierKeymap[key]]; - } - if (this.keyboardActions[key] != null) { - return ["actions", this.keyboardActions[key]]; - } - // TODO How to log this? - return ["actions", undefined]; - - - } - - private loadSection(sectionName: SectionName, defaultKeyMap: SectionKeymap) { - const userOverrides: SectionKeymap = - vscode.workspace - .getConfiguration("cursorless.experimental.keyboard.modal.keybindings") - .get>(sectionName) ?? {}; - const keyMap = merge({}, defaultKeyMap, userOverrides); - - for (const [key, value] of toPairs(keyMap)) { - const conflictingEntry = this.getConflictingKeyMapEntry(key); - if (conflictingEntry != null) { - vscode.window.showErrorMessage( - `Conflicting keybindings: \`${sectionName}.${value}\` and \`${conflictingEntry[0]}.${conflictingEntry[1]}\` both want key '${key}'`, - ); - - continue; - } - this.setMap(sectionName,key,value); - } - } - - public getMergeKeys():string[]{ - return Object.keys(merge( {},this.actionKeymap, this.scopeKeymap, this.colorKeymap, this.shapeKeymap, this.modifierKeymap,this.keyboardActions)) as string[]; - } - - public isPrefixOfMapEntry(text:string):boolean{ - return this.getMergeKeys().some((key)=>key.startsWith(text)); - } - - public setMap(sectionName:SectionName,key:string,value:any){ - switch (sectionName) { - case "actions": - this.actionKeymap[key]=value; - break; - case "scopes": - this.scopeKeymap[key]=value; - break; - case "colors": - this.colorKeymap[key]=value; - break; - case "shapes": - this.shapeKeymap[key]=value; - break; - case "modifiers": - this.modifierKeymap[key]=value; - break; - case "keyboardActions": - this.keyboardActions[key]=value; - break; - } - } - - public clearMap(sectionName:SectionName){ - switch (sectionName) { - case "actions": - this.actionKeymap={}; - break; - case "scopes": - this.scopeKeymap={}; - break; - case "colors": - this.colorKeymap={}; - break; - case "shapes": - this.shapeKeymap={}; - break; - case "modifiers": - this.modifierKeymap={}; - break; - case "keyboardActions": - this.keyboardActions={}; - break; - } - } - - - - /** - * This function can be used to detect if a proposed map entry conflicts with - * one in the map. Used to detect if the user tries to use two map entries, - * one of which is a prefix of the other. - * @param text The proposed new map entry - * @returns The first map entry that conflicts with {@link text}, if one - * exists - */ - getConflictingKeyMapEntry(text: string): [string, string] | undefined { - const allMap = { - "actions": this.actionKeymap, - "scopes": this.scopeKeymap, - "colors": this.colorKeymap, - "shapes": this.shapeKeymap, - "modifiers": this.modifierKeymap, - "keyboardActions": this.keyboardActions, - }; - - for (const [sectionName, keyMap] of Object.entries(allMap)) { - const conflictingPair = Object.entries(keyMap).find( - ([key]) => text.startsWith(key) || key.startsWith(text) - ); - - if (conflictingPair != null) { - return [sectionName, conflictingPair[0]]; - } - } - - return undefined; - } - - public getActionKeymap(): SectionKeymap { - return this.actionKeymap; - } - - public getScopeKeymap(): SectionKeymap { - return this.scopeKeymap; - } - - public getColorKeymap(): SectionKeymap { - return this.colorKeymap; - } - - public getShapeKeymap(): SectionKeymap { - return this.shapeKeymap; - } - - public getModifierKeymap(): SectionKeymap { - return this.modifierKeymap; - } - - - -} \ No newline at end of file diff --git a/packages/cursorless-vscode/src/keyboard/MarkSelection.ts b/packages/cursorless-vscode/src/keyboard/MarkSelection.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/cursorless-vscode/src/keyboard/fixedKeymap.ts b/packages/cursorless-vscode/src/keyboard/fixedKeymap.ts index fe9d676ef7..8025f9c6cb 100644 --- a/packages/cursorless-vscode/src/keyboard/fixedKeymap.ts +++ b/packages/cursorless-vscode/src/keyboard/fixedKeymap.ts @@ -30,6 +30,7 @@ export const layer0 = { export const layer1 = { "h": "mode.run('editNewLineBefore');", "t": "mode.run('editNewLineAfter');", + "g": `mode.run("character");`, } export const layer2 = { From 9048e7ea4837feeacd683cae298b1a9e60db9cd0 Mon Sep 17 00:00:00 2001 From: "jan.scheffczyk" Date: Mon, 23 Oct 2023 09:49:45 +0200 Subject: [PATCH 13/13] fix compiler errors --- .../command/PartialTargetDescriptor.types.ts | 2 +- .../src/keyboard/Handlers/ModifierHandler.ts | 6 +- .../src/keyboard/Handlers/ScopeHandler.ts | 2 +- .../src/keyboard/KeyboardCommandsTargeted.ts | 164 +++++++++--------- packages/cursorless-vscode/tsconfig.json | 4 +- 5 files changed, 94 insertions(+), 84 deletions(-) diff --git a/packages/common/src/types/command/PartialTargetDescriptor.types.ts b/packages/common/src/types/command/PartialTargetDescriptor.types.ts index ccc45f19ed..0646aedc74 100644 --- a/packages/common/src/types/command/PartialTargetDescriptor.types.ts +++ b/packages/common/src/types/command/PartialTargetDescriptor.types.ts @@ -406,4 +406,4 @@ export type PartialTargetDescriptor = | PartialPrimitiveTargetDescriptor | PartialRangeTargetDescriptor | PartialListTargetDescriptor - | ImplicitTargetDescriptor; + | ImplicitTargetDescriptor; \ No newline at end of file diff --git a/packages/cursorless-vscode/src/keyboard/Handlers/ModifierHandler.ts b/packages/cursorless-vscode/src/keyboard/Handlers/ModifierHandler.ts index 631ebaaaee..b090c4282c 100644 --- a/packages/cursorless-vscode/src/keyboard/Handlers/ModifierHandler.ts +++ b/packages/cursorless-vscode/src/keyboard/Handlers/ModifierHandler.ts @@ -1,4 +1,4 @@ -import { EveryScopeModifier,SimpleScopeType } from "@cursorless/common"; +import { EveryScopeModifier } from "@cursorless/common"; import { Handler } from "../Handler"; import * as vscode from "vscode"; import { SimpleScopeTypeArray } from "./ScopeHandler"; @@ -43,13 +43,13 @@ export async function everyModifier( if (scope === undefined) { throw Error(`No scope found for character : ${character}`); } - if (!SimpleScopeTypeArray.includes(scope)) { + if (!SimpleScopeTypeArray.includes(scope as any)) { throw Error(`Unsupported scope: ${scope}`); } const modifier: EveryScopeModifier = { type: "everyScope", - scopeType: {type:scope as SimpleScopeType}, + scopeType: {type:scope as any}, }; mode.addModifier(modifier); } diff --git a/packages/cursorless-vscode/src/keyboard/Handlers/ScopeHandler.ts b/packages/cursorless-vscode/src/keyboard/Handlers/ScopeHandler.ts index a7f5f5bb21..cf422c08c6 100644 --- a/packages/cursorless-vscode/src/keyboard/Handlers/ScopeHandler.ts +++ b/packages/cursorless-vscode/src/keyboard/Handlers/ScopeHandler.ts @@ -72,7 +72,7 @@ export async function targetScope( mode: Handler, keySequence: string ): Promise { - if (!SimpleScopeTypeArray.includes(keySequence)) { + if (!SimpleScopeTypeArray.includes(keySequence as any)) { throw Error(`Unsupported scope: ${keySequence}`); } const scopeType: SimpleScopeTypeType = keySequence as SimpleScopeTypeType; diff --git a/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts b/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts index 739908ce7f..c82ad86d01 100644 --- a/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts +++ b/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts @@ -11,7 +11,7 @@ import type { HatColor, HatShape } from "../ide/vscode/hatStyles.types"; import { getStyleName } from "../ide/vscode/hats/getStyleName"; import KeyboardCommandsModal from "./KeyboardVsCodeMode"; import KeyboardHandler from "./KeyboardHandler"; -import Keymap from "./Keymap"; + type TargetingMode = "replace" | "extend" | "append"; type TargetModifierTypeArgument = "every" | "interiorOnly" | "excludeInterior"; @@ -77,84 +77,83 @@ export default class KeyboardCommandsTargeted { shape = "default", character, mode = "replace", - }: TargetDecoratedMarkArgument, - keymap:Keymap) => { - - const getCharacter = async (status:string="Which hat?") => { - return await this.keyboardHandler.awaitSingleKeypress({ - cursorStyle: vscode.TextEditorCursorStyle.Underline, - whenClauseContext: "cursorless.keyboard.targeted.awaitingHatCharacter", - statusBarText: status, - }); - } - character = await getCharacter(); + }: TargetDecoratedMarkArgument) => { + + // const getCharacter = async (status:string="Which hat?") => { + // return await this.keyboardHandler.awaitSingleKeypress({ + // cursorStyle: vscode.TextEditorCursorStyle.Underline, + // whenClauseContext: "cursorless.keyboard.targeted.awaitingHatCharacter", + // statusBarText: status, + // }); + // } + // character = await getCharacter(); - if (character == null) { - // Cancelled - return; - } - if (keymap.getColorKeymap()[character.toLowerCase()] && character === character.toUpperCase()) { - color = keymap.getColorKeymap()[character.toLowerCase()]; - character = await getCharacter("Color ${color} selected. Which hat?"); - } - else if (keymap.getShapeKeymap()[character.toLowerCase()] && character === character.toUpperCase()) { - shape = keymap.getShapeKeymap()[character.toLowerCase()]; - character = await getCharacter("Shape ${shape} selected. Which hat?"); - } - if (character == null) { - // Cancelled - return; - } - - - - let target: PartialTargetDescriptor = { - type: "primitive", - mark: { - type: "decoratedSymbol", - symbolColor: getStyleName(color, shape), - character, - }, - }; - - switch (mode) { - case "extend": - target = { - type: "range", - anchor: { - type: "primitive", - mark: { - type: "that", - }, - }, - active: target, - excludeActive: false, - excludeAnchor: false, - }; - break; - case "append": - target = { - type: "list", - elements: [ - { - type: "primitive", - mark: { - type: "that", - }, - }, - target, - ], - }; - break; - case "replace": - break; - } - - return await executeCursorlessCommand({ - name: "highlight", - target, - }); + // if (character == null) { + // // Cancelled + // return; + // } + // if (keymap.getColorKeymap()[character.toLowerCase()] && character === character.toUpperCase()) { + // color = keymap.getColorKeymap()[character.toLowerCase()]; + // character = await getCharacter("Color ${color} selected. Which hat?"); + // } + // else if (keymap.getShapeKeymap()[character.toLowerCase()] && character === character.toUpperCase()) { + // shape = keymap.getShapeKeymap()[character.toLowerCase()]; + // character = await getCharacter("Shape ${shape} selected. Which hat?"); + // } + // if (character == null) { + // // Cancelled + // return; + // } + + + + // let target: PartialTargetDescriptor = { + // type: "primitive", + // mark: { + // type: "decoratedSymbol", + // symbolColor: getStyleName(color, shape), + // character, + // }, + // }; + + // switch (mode) { + // case "extend": + // target = { + // type: "range", + // anchor: { + // type: "primitive", + // mark: { + // type: "that", + // }, + // }, + // active: target, + // excludeActive: false, + // excludeAnchor: false, + // }; + // break; + // case "append": + // target = { + // type: "list", + // elements: [ + // { + // type: "primitive", + // mark: { + // type: "that", + // }, + // }, + // target, + // ], + // }; + // break; + // case "replace": + // break; + // } + + // return await executeCursorlessCommand({ + // name: "highlight", + // target, + // }); }; /** @@ -212,7 +211,16 @@ export default class KeyboardCommandsTargeted { }, }, }); - + performActionOnTarget = () => + executeCursorlessCommand({ + name: "highlight", + target: { + type: "primitive", + mark: { + type: "that", + }, + }, + }); /** * Sets the current target to the current selection diff --git a/packages/cursorless-vscode/tsconfig.json b/packages/cursorless-vscode/tsconfig.json index 7564228e28..6bee976bb7 100644 --- a/packages/cursorless-vscode/tsconfig.json +++ b/packages/cursorless-vscode/tsconfig.json @@ -3,7 +3,9 @@ "compilerOptions": { "target": "es2020", "outDir": "out", - "rootDir": "src" + "rootDir": "src", + "module": "NodeNext", + "moduleResolution": "NodeNext" }, "include": ["src/**/*.ts", "src/**/*.json", "../../typings/**/*.d.ts"], "references": [