misc: change how commands are defined and executed (fixes #258)

This commit is contained in:
Grégoire Geis 2022-05-07 13:44:36 +02:00
parent cd345d577f
commit 7f1963cef0
21 changed files with 1706 additions and 1637 deletions

View File

@ -50,6 +50,13 @@ const valueConverter: Record<keyof Builder.AdditionalCommand, (x: string) => str
commands(commands) {
return commands
.replace(/^`+|`+$/g, "")
.replace(/ +/g, " ")
.replace(/\.{3}(?= })/g, () =>
"-" + [...commands.matchAll(/(?<=\+)([a-zA-Z,]+)/g)].map((x) => x[0]).join(","))
.replace(/-([a-zA-Z,]*)(?= })/g, (_, exclude) =>
`$exclude: ${exclude === "" ? "[]" : JSON.stringify(exclude.split(","))}`)
.replace(/\+([a-zA-Z,]+)(?= })/g, (_, include) =>
`$include: ${JSON.stringify(include.split(","))}`)
.replace(/MAX_INT/g, `${2 ** 31 - 1}`); // Max integer supported in JSON.
},
identifier(identifier) {
@ -525,7 +532,7 @@ function getKeybindings(module: Omit<Builder.ParsedModule, "keybindings">) {
}
const parsedCommands =
JSON.parse("[" + commands!.replace(/(\w+):/g, "\"$1\":") + "]") as any[];
JSON.parse("[" + commands!.replace(/([$\w]+):/g, "\"$1\":") + "]") as any[];
if (parsedCommands.length === 1) {
let [command]: [string] = parsedCommands[0];

16
package.json generated
View File

@ -2989,7 +2989,10 @@
{
"key": "Shift+;",
"when": "editorTextFocus && dance.mode == 'normal'",
"command": "workbench.action.showCommands"
"command": "workbench.action.showCommands",
"args": {
"$exclude": []
}
},
{
"key": "A",
@ -3694,7 +3697,8 @@
"when": "editorTextFocus && dance.mode == 'normal'",
"command": "dance.selections.restore.withCurrent",
"args": {
"reverse": true
"reverse": true,
"$exclude": []
}
},
{
@ -3775,7 +3779,8 @@
"title": "Show view menu",
"command": "dance.openMenu",
"args": {
"input": "view"
"menu": "view",
"$exclude": []
}
},
{
@ -3784,8 +3789,9 @@
"title": "Show view menu (locked)",
"command": "dance.openMenu",
"args": {
"input": "view",
"locked": true
"menu": "view",
"locked": true,
"$exclude": []
}
},
{

View File

@ -3,9 +3,9 @@ import { Builder } from "../../../meta";
export async function build(builder: Builder) {
const modules = await builder.getCommandModules(),
keybindings = modules.flatMap((module) => module.keybindings),
keybindingsCode = JSON.stringify(keybindings, undefined, 2)
keybindingsCode = (JSON.stringify(keybindings, undefined, 2) + ";")
.replace(/"(\w+)":/g, "$1:")
.replace(/([0-9a-z}"])$/gm, "$1,");
.replace(/([0-9a-z}\]"])$/gm, "$1,");
return `\nconst builtinKeybindings = ${keybindingsCode};\n`;
return `\nconst builtinKeybindings = ${keybindingsCode}\n`;
}

View File

@ -454,6 +454,9 @@ const builtinKeybindings = [
key: "Shift+;",
when: "editorTextFocus && dance.mode == 'normal'",
command: "workbench.action.showCommands",
args: {
"$exclude": [],
},
},
{
key: "A",
@ -1159,6 +1162,7 @@ const builtinKeybindings = [
command: "dance.selections.restore.withCurrent",
args: {
reverse: true,
"$exclude": [],
},
},
{
@ -1239,7 +1243,8 @@ const builtinKeybindings = [
title: "Show view menu",
command: "dance.openMenu",
args: {
input: "view",
menu: "view",
"$exclude": [],
},
},
{
@ -1248,8 +1253,9 @@ const builtinKeybindings = [
title: "Show view menu (locked)",
command: "dance.openMenu",
args: {
input: "view",
menu: "view",
locked: true,
"$exclude": [],
},
},
];

View File

@ -2,7 +2,6 @@ import * as vscode from "vscode";
import { Context } from "./context";
import { set as setSelections } from "./selections";
import type { Input, SetInput } from "../commands";
import { ArgumentError, CancellationError } from "../utils/errors";
const actionEvent = new vscode.EventEmitter<Parameters<typeof notifyPromptActionRequested>[0]>();
@ -310,17 +309,17 @@ export function promptInteractively<T>(
*
* @internal
*/
export async function manipulateSelectionsInteractively<I, R>(
export async function manipulateSelectionsInteractively<R extends object, N extends keyof R>(
_: Context,
input: Input<I>,
setInput: SetInput<R>,
inputName: N,
argument: R,
interactive: boolean,
options: prompt.Options,
f: (input: string | I, selections: readonly vscode.Selection[]) => Thenable<R>,
f: (input: string | Exclude<R[N], undefined>, selections: readonly vscode.Selection[]) => Thenable<R[N]>,
) {
const selections = _.selections;
function execute(input: string | I) {
function execute(input: string | Exclude<R[N], undefined>) {
return _.runAsync(() => f(input, selections));
}
@ -328,10 +327,10 @@ export async function manipulateSelectionsInteractively<I, R>(
setSelections(selections);
}
if (input === undefined) {
setInput(await promptInteractively(execute, undo, options, interactive));
if (argument[inputName] === undefined) {
argument[inputName] = await promptInteractively(execute, undo, options, interactive);
} else {
await execute(input);
await execute(argument[inputName] as Exclude<R[N], undefined>);
}
}

View File

@ -1,7 +1,7 @@
import * as vscode from "vscode";
import { Context } from "./context";
import type { CommandDescriptor } from "../commands";
import type { CommandDescriptor, Commands } from "../commands";
import { parseRegExpWithReplacement } from "../utils/regexp";
/**
@ -191,20 +191,82 @@ export function clearCompiledFunctionsCache(olderThanMs = 1000 * 60 * 5) {
}
}
/**
* Runs the VS Code command with the given identifier and optional arguments.
*/
export function command(commandName: string, ...args: readonly any[]): Thenable<any> {
return commands([commandName, ...args]).then((x) => x[0]);
interface ArgumentAssignment {
baseValue: Record<string, any>;
include?: readonly string[];
exclude?: ReadonlySet<string>;
}
type ArgumentAssignments = readonly ArgumentAssignment[];
function assignArgument(
assignment: ArgumentAssignment,
argument: Record<string, any>,
): Record<string, any> {
const ownedArgument = Object.assign({}, assignment.baseValue);
if ("include" in assignment) {
for (const propName of assignment.include!) {
const propValue = argument[propName];
if (propValue !== undefined) {
ownedArgument[propName] = propValue;
}
}
} else if ("exclude" in assignment) {
const excluded = assignment.exclude!;
for (const propName in argument) {
if (excluded.has(propName)) {
continue;
}
ownedArgument[propName] = argument[propName];
}
}
return ownedArgument;
}
function buildAssignment(argument: Record<string, any>): ArgumentAssignment {
if (argument == null) {
return { baseValue: {} };
}
const { $include, $exclude, ...baseValue } = argument,
assignment: ArgumentAssignment = { baseValue };
if (Array.isArray($include)) {
assignment.include = $include;
} else if (Array.isArray($exclude)) {
assignment.exclude = new Set<string>($exclude);
}
return assignment;
}
function assignArguments(
assignment: ArgumentAssignments,
argument: Record<string, any>,
): readonly Record<string, any>[] {
return assignment.map((assignment) => assignArgument(assignment, argument));
}
function buildAssignments(args: readonly Record<string, any>[]): ArgumentAssignments {
return args.map(buildAssignment);
}
/**
* Runs the VS Code commands with the given identifiers and optional arguments.
* Builds a function which can be called with an `argument` object, which will
* execute the list of given commands.
*/
export async function commands(...commands: readonly command.Any[]): Promise<any[]> {
const extension = Context.WithoutActiveEditor.current.extension,
batches = [] as ([CommandDescriptor, any][] | [string, any])[],
currentBatch = [] as [CommandDescriptor, any][];
export function buildCommands(
commands: readonly command.Any[],
extension: { readonly commands: Commands } = Context.WithoutActiveEditor.current.extension,
) {
const batches = [] as (
[CommandDescriptor, ArgumentAssignment][] | [string, ArgumentAssignments])[],
currentBatch = [] as [CommandDescriptor, ArgumentAssignment][];
// Build and validate commands.
for (let i = 0, len = commands.length; i < len; i++) {
@ -247,15 +309,16 @@ export async function commands(...commands: readonly command.Any[]): Promise<any
throw new Error(`command ${JSON.stringify(commandName)} does not exist`);
}
const argument = Array.isArray(commandArguments) ? commandArguments[0] : commandArguments;
const argument = Array.isArray(commandArguments) ? commandArguments[0] : commandArguments,
assignment = buildAssignment(argument);
currentBatch.push([descriptor, argument]);
currentBatch.push([descriptor, assignment]);
} else {
if (currentBatch.length > 0) {
batches.push(currentBatch.splice(0));
}
batches.push([commandName, commandArguments]);
batches.push([commandName, buildAssignments(commandArguments)]);
}
}
@ -263,66 +326,92 @@ export async function commands(...commands: readonly command.Any[]): Promise<any
batches.push(currentBatch);
}
// Execute all commands.
const results = [];
for (const batch of batches) {
if (typeof batch[0] === "string") {
results.push(await vscode.commands.executeCommand(batch[0], batch[1]));
} else {
const context = Context.WithoutActiveEditor.current;
let { currentCount, currentRegister } = context.extension;
for (const pair of batch as [CommandDescriptor, any][]) {
const [descriptor, argument] = pair,
ownedArgument = pair[1] = Object.assign({}, argument);
if (currentCount !== context.extension.currentCount) {
currentCount = ownedArgument["count"] = context.extension.currentCount;
context.extension.currentCount = 0;
}
if (currentRegister !== context.extension.currentRegister) {
currentRegister = ownedArgument["register"] = context.extension.currentRegister;
context.extension.currentRegister = undefined;
}
if (ownedArgument.try) {
delete ownedArgument.try;
try {
results.push(await descriptor.handler(context, ownedArgument));
} catch {
results.push(undefined);
}
} else {
results.push(await descriptor.handler(context, ownedArgument));
}
}
}
}
if (Context.WithoutActiveEditor.currentOrUndefined?.shouldRecord() ?? true) {
const recorder = extension.recorder;
return async (argument: Record<string, any>, context = Context.WithoutActiveEditor.current) => {
// Execute all commands.
const results = [],
ownedArguments: any[] = [];
for (const batch of batches) {
if (typeof batch[0] === "string") {
recorder.recordExternalCommand(batch[0], batch[1]);
const ownedArgument = assignArguments(batch[1] as ArgumentAssignments, argument);
results.push(await vscode.commands.executeCommand(batch[0], ...ownedArgument));
ownedArguments.push(ownedArgument);
} else {
for (const [descriptor, argument] of batch as [CommandDescriptor, any][]) {
if (argument.record === false) {
continue;
const context = Context.WithoutActiveEditor.current;
let { currentCount, currentRegister } = context.extension;
for (const [descriptor, assignment] of batch as [CommandDescriptor, ArgumentAssignment][]) {
const ownedArgument = assignArgument(assignment, argument);
if (currentCount !== context.extension.currentCount) {
currentCount = ownedArgument["count"] = context.extension.currentCount;
context.extension.currentCount = 0;
}
recorder.recordCommand(descriptor, argument);
if (currentRegister !== context.extension.currentRegister) {
currentRegister = ownedArgument["register"] = context.extension.currentRegister;
context.extension.currentRegister = undefined;
}
if (ownedArgument["try"]) {
delete ownedArgument["try"];
try {
results.push(await descriptor.handler(context, ownedArgument));
} catch {
results.push(undefined);
}
} else {
results.push(await descriptor.handler(context, ownedArgument));
}
ownedArguments.push(ownedArgument);
}
}
}
}
return results;
if (context.shouldRecord()) {
const recorder = context.extension.recorder;
let i = 0;
for (const batch of batches) {
if (typeof batch[0] === "string") {
const ownedArgument = ownedArguments[i++];
recorder.recordExternalCommand(batch[0], ownedArgument);
} else {
for (const [descriptor] of batch as [CommandDescriptor, never][]) {
const ownedArgument = ownedArguments[i++];
if (ownedArgument["record"] === false) {
continue;
}
recorder.recordCommand(descriptor, ownedArgument);
}
}
}
}
return results;
};
}
/**
* Runs the VS Code command with the given identifier and optional arguments.
*/
export async function command(commandName: string, ...args: readonly any[]): Promise<any> {
return (await commands([commandName, ...args]))[0];
}
/**
* Runs the VS Code commands with the given identifiers and optional arguments.
*/
export async function commands(...commands: readonly command.Any[]): Promise<any[]> {
return await buildCommands(commands)({});
}
export declare namespace command {

391
src/commands/README.md generated
View File

@ -76,10 +76,10 @@
<tr><td><a href="./search.ts#L22"><code>search.backward</code></a></td><td>Search backward</td><td><code>Alt+/</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./search.ts#L23"><code>search.backward.extend</code></a></td><td>Search backward (extend)</td><td><code>Shift+Alt+/</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./search.ts#L21"><code>search.extend</code></a></td><td>Search (extend)</td><td><code>Shift+/</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./search.ts#L157"><code>search.next.add</code></a></td><td>Add next match</td><td><code>Shift+N</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./search.ts#L158"><code>search.previous</code></a></td><td>Select previous match</td><td><code>Alt+N</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./search.ts#L159"><code>search.previous.add</code></a></td><td>Add previous match</td><td><code>Shift+Alt+N</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./search.ts#L89"><code>search.selection.smart</code></a></td><td>Search current selection (smart)</td><td><code>Shift+8</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)<code>NumPad_Multiply</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./search.ts#L156"><code>search.next.add</code></a></td><td>Add next match</td><td><code>Shift+N</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./search.ts#L157"><code>search.previous</code></a></td><td>Select previous match</td><td><code>Alt+N</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./search.ts#L158"><code>search.previous.add</code></a></td><td>Add previous match</td><td><code>Shift+Alt+N</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./search.ts#L88"><code>search.selection.smart</code></a></td><td>Search current selection (smart)</td><td><code>Shift+8</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)<code>NumPad_Multiply</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="#search.selection"><code>search.selection</code></a></td><td>Search current selection</td><td><code>Shift+Alt+8</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td rowspan=35><a href="#seek"><code>seek</code></a></td><td><a href="#seek.enclosing"><code>seek.enclosing</code></a></td><td>Select to next enclosing character</td><td><code>M</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="#seek.object"><code>seek.object</code></a></td><td>Select object</td><td></td></tr>
@ -172,17 +172,17 @@
<tr><td><a href="#selections.select"><code>selections.select</code></a></td><td>Select within selections</td><td><code>S</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./selections.ts#L319"><code>selections.clear.main</code></a></td><td>Clear main selections</td><td><code>Alt+Space</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./selections.ts#L318"><code>selections.clear.secondary</code></a></td><td>Clear secondary selections</td><td><code>Space</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./selections.ts#L702"><code>selections.copy.above</code></a></td><td>Copy selections above</td><td><code>Shift+Alt+C</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./selections.ts#L666"><code>selections.faceBackward</code></a></td><td>Backward selections</td><td></td></tr>
<tr><td><a href="./selections.ts#L665"><code>selections.faceForward</code></a></td><td>Forward selections</td><td><code>Shift+Alt+;</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./selections.ts#L699"><code>selections.copy.above</code></a></td><td>Copy selections above</td><td><code>Shift+Alt+C</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./selections.ts#L663"><code>selections.faceBackward</code></a></td><td>Backward selections</td><td></td></tr>
<tr><td><a href="./selections.ts#L662"><code>selections.faceForward</code></a></td><td>Forward selections</td><td><code>Shift+Alt+;</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./selections.ts#L316"><code>selections.filter.regexp</code></a></td><td>Keep matching selections</td><td><code>Alt+K</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./selections.ts#L317"><code>selections.filter.regexp.inverse</code></a></td><td>Clear matching selections</td><td><code>Shift+Alt+K</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./selections.ts#L781"><code>selections.hideIndices</code></a></td><td>Hide selection indices</td><td></td></tr>
<tr><td><a href="./selections.ts#L778"><code>selections.hideIndices</code></a></td><td>Hide selection indices</td><td></td></tr>
<tr><td><a href="./selections.ts#L254"><code>selections.pipe.append</code></a></td><td>Pipe and append</td><td><code>Shift+1</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./selections.ts#L255"><code>selections.pipe.prepend</code></a></td><td>Pipe and prepend</td><td><code>Shift+Alt+1</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./selections.ts#L253"><code>selections.pipe.replace</code></a></td><td>Pipe and replace</td><td><code>Shift+\</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./selections.ts#L584"><code>selections.reduce.edges</code></a></td><td>Reduce selections to their ends</td><td><code>Shift+Alt+S</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./selections.ts#L780"><code>selections.showIndices</code></a></td><td>Show selection indices</td><td></td></tr>
<tr><td><a href="./selections.ts#L581"><code>selections.reduce.edges</code></a></td><td>Reduce selections to their ends</td><td><code>Shift+Alt+S</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./selections.ts#L777"><code>selections.showIndices</code></a></td><td>Show selection indices</td><td></td></tr>
<tr><td><a href="#selections.split"><code>selections.split</code></a></td><td>Split selections</td><td><code>Shift+S</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="#selections.splitLines"><code>selections.splitLines</code></a></td><td>Split selections at line boundaries</td><td><code>Alt+S</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="#selections.toggleIndices"><code>selections.toggleIndices</code></a></td><td>Toggle selection indices</td><td><code>Enter</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
@ -248,18 +248,18 @@ current selections.
#### Additional commands
| Title | Identifier | Keybinding | Commands |
| ---------------------------------- | ----------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------- |
| Pick register and replace | `selectRegister-insert` | `c-r` (normal), `c-r` (insert) | `[".selectRegister"], [".edit.insert"]` |
| Paste before | `paste.before` | `s-p` (normal) | `[".edit.insert", { handleNewLine: true, where: "start" }]` |
| Paste after | `paste.after` | `p` (normal) | `[".edit.insert", { handleNewLine: true, where: "end" }]` |
| Paste before and select | `paste.before.select` | `s-a-p` (normal) | `[".edit.insert", { handleNewLine: true, where: "start", shift: "select" }]` |
| Paste after and select | `paste.after.select` | `a-p` (normal) | `[".edit.insert", { handleNewLine: true, where: "end" , shift: "select" }]` |
| Delete | `delete` | `a-d` (normal) | `[".edit.insert", { register: "_" }]` |
| Delete and switch to Insert | `delete-insert` | `a-c` (normal) | `[".modes.set", { input: "insert" }], [".edit.insert", { register: "_" }]` |
| Copy and delete | `yank-delete` | `d` (normal) | `[".selections.saveText"], [".edit.insert", { register: "_" }]` |
| Copy, delete and switch to Insert | `yank-delete-insert` | `c` (normal) | `[".selections.saveText"], [".modes.set", { input: "insert" }], [".edit.insert", { register: "_" }]` |
| Copy and replace | `yank-replace` | `s-r` (normal) | `[".selections.saveText", { register: "tmp" }], [".edit.insert"], [".updateRegister", { copyFrom: "tmp" }]` |
| Title | Identifier | Keybinding | Commands |
| ---------------------------------- | ----------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
| Pick register and replace | `selectRegister-insert` | `c-r` (normal), `c-r` (insert) | `[".selectRegister", { +register }], [".edit.insert", { ... }]` |
| Paste before | `paste.before` | `s-p` (normal) | `[".edit.insert", { handleNewLine: true, where: "start", ... }]` |
| Paste after | `paste.after` | `p` (normal) | `[".edit.insert", { handleNewLine: true, where: "end" , ... }]` |
| Paste before and select | `paste.before.select` | `s-a-p` (normal) | `[".edit.insert", { handleNewLine: true, where: "start", shift: "select", ... }]` |
| Paste after and select | `paste.after.select` | `a-p` (normal) | `[".edit.insert", { handleNewLine: true, where: "end" , shift: "select", ... }]` |
| Delete | `delete` | `a-d` (normal) | `[".edit.insert", { register: "_", ... }]` |
| Delete and switch to Insert | `delete-insert` | `a-c` (normal) | `[".modes.set", { mode: "insert", +mode }], [".edit.insert", { register: "_", ... }]` |
| Copy and delete | `yank-delete` | `d` (normal) | `[".selections.saveText", { +register }], [".edit.insert", { register: "_", ... }]` |
| Copy, delete and switch to Insert | `yank-delete-insert` | `c` (normal) | `[".selections.saveText", { +register }], [".modes.set", { mode: "insert", +mode }], [".edit.insert", { register: "_", ... }]` |
| Copy and replace | `yank-replace` | `s-r` (normal) | `[".selections.saveText", { register: "tmp" }], [".edit.insert"], [".updateRegister", { copyFrom: "tmp", ... }]` |
This command:
- accepts a register (by default, it uses `dquote`).
@ -384,7 +384,6 @@ Replace characters.
This command:
- may be repeated with a given number of repetitions.
- takes an input of type `string`.
Default keybinding: `r` (normal)
@ -432,9 +431,9 @@ keep the current selections.
#### Additional keybindings
| Title | Identifier | Keybinding | Commands |
| ------------------------------------------ | ---------------------- | -------------- | ------------------------------------------------------------------------ |
| Insert new line above and switch to insert | `newLine.above.insert` | `s-o` (normal) | `[".edit.newLine.above", { shift: "select" }], [".modes.insert.before"]` |
| Title | Identifier | Keybinding | Commands |
| ------------------------------------------ | ---------------------- | -------------- | --------------------------------------------------------------------------------- |
| Insert new line above and switch to insert | `newLine.above.insert` | `s-o` (normal) | `[".edit.newLine.above", { shift: "select" }], [".modes.insert.before", { ... }]` |
This command:
- may be repeated with a given number of repetitions.
@ -457,9 +456,9 @@ keep the current selections.
#### Additional keybindings
| Title | Identifier | Keybinding | Commands |
| ------------------------------------------ | ---------------------- | ------------ | ------------------------------------------------------------------------ |
| Insert new line below and switch to insert | `newLine.below.insert` | `o` (normal) | `[".edit.newLine.below", { shift: "select" }], [".modes.insert.before"]` |
| Title | Identifier | Keybinding | Commands |
| ------------------------------------------ | ---------------------- | ------------ | --------------------------------------------------------------------------------- |
| Insert new line below and switch to insert | `newLine.below.insert` | `o` (normal) | `[".edit.newLine.below", { shift: "select" }], [".modes.insert.before", { ... }]` |
This command:
- may be repeated with a given number of repetitions.
@ -527,14 +526,14 @@ Default keybinding: `s-a-u` (normal)
Repeat last change.
| Title | Identifier | Keybinding | Commands |
| ---------------------------- | ------------------ | -------------- | --------------------------------------------------------------------------- |
| Repeat last selection change | `repeat.selection` | | `[".history.repeat", { include: "dance\\.(seek\|select\|selections)\\..+" }]` |
| Repeat last seek | `repeat.seek` | `a-.` (normal) | `[".history.repeat", { include: "dance\\.seek\\..+" }]` |
| Title | Identifier | Keybinding | Commands |
| ---------------------------- | ------------------ | -------------- | -------------------------------------------------------------------------- |
| Repeat last selection change | `repeat.selection` | | `[".history.repeat", { filter: "dance\\.(seek\|select\|selections)\\..+" }]` |
| Repeat last seek | `repeat.seek` | `a-.` (normal) | `[".history.repeat", { filter: "dance\\.seek\\..+" }]` |
This command:
- may be repeated with a given number of repetitions.
- takes an argument `include` of type `string | RegExp`.
- takes an argument `filter` of type `string | RegExp`.
<a name="history.repeat.edit" />
@ -606,9 +605,9 @@ Miscellaneous commands that don't deserve their own category.
By default, Dance also exports the following keybindings for existing
commands:
| Keybinding | Command |
| -------------- | ----------------------------------- |
| `s-;` (normal) | `["workbench.action.showCommands"]` |
| Keybinding | Command |
| -------------- | -------------------------------------------- |
| `s-;` (normal) | `["workbench.action.showCommands", { ... }]` |
<a name=".cancel" />
@ -646,7 +645,7 @@ instance,
{
"command": "dance.run",
"args": {
"input": "Selections.set(Selections.filter(text => text.includes('foo')))",
"code": "Selections.set(Selections.filter(text => text.includes('foo')))",
},
},
```
@ -659,7 +658,7 @@ easier to read:
{
"command": "dance.run",
"args": {
"input": [
"code": [
"for (const selection of Selections.current) {",
" console.log(text(selection));",
"}",
@ -690,7 +689,7 @@ But arguments can also be provided by passing an array:
"command": "dance.run",
"args": {
"commands": [
["dance.modes.set", { "input": "normal" }],
["dance.modes.set", { "mode": "normal" }],
],
},
},
@ -705,7 +704,7 @@ Or by passing an object, like regular VS Code key bindings:
"commands": [
{
"command": "dance.modes.set",
"args": { "input": "normal" },
"args": { "mode": "normal" },
},
],
},
@ -722,7 +721,7 @@ These values can be mixed:
["dance.selections.saveText", { "register": "^" }],
{
"command": "dance.modes.set",
"args": { "input": "normal" },
"args": { "mode": "normal" },
},
"hideSuggestWidget",
],
@ -730,19 +729,19 @@ These values can be mixed:
},
```
If both `input` and `commands` are given, Dance will use `run` if arbitrary
command execution is enabled, or `commands` otherwise.
If both `code` and `commands` are given, Dance will use `code` if arbitrary
code execution is enabled, or `commands` otherwise.
This command:
- accepts a register (by default, it uses `null`).
- accepts an argument of type `Record<"code" | "input", string | readonly string[]>`.
- may be repeated with a given number of repetitions.
- may be repeated with a given number of repetitions.
- takes an argument `commands` of type `command.Any[]`.
- takes an input of type `string`.
<a name=".selectRegister" />
### [`selectRegister`](./misc.ts#L184-L195)
### [`selectRegister`](./misc.ts#L186-L200)
Select register for next command.
@ -757,18 +756,17 @@ Default keybinding: `"` (normal)
<a name=".updateRegister" />
### [`updateRegister`](./misc.ts#L211-L222)
### [`updateRegister`](./misc.ts#L216-L227)
Update the contents of a register.
This command:
- accepts a register (by default, it uses `dquote`).
- takes an input of type `string`.
<a name=".updateCount" />
### [`updateCount`](./misc.ts#L248-L277)
### [`updateCount`](./misc.ts#L253-L282)
Update Dance count.
@ -793,11 +791,10 @@ Update the current counter used to repeat the next command.
This command:
- may be repeated with a given number of repetitions.
- takes an argument `addDigits` of type `number`.
- takes an input of type `number`.
<a name=".openMenu" />
### [`openMenu`](./misc.ts#L305-L328)
### [`openMenu`](./misc.ts#L310-L332)
Open menu.
@ -814,15 +811,13 @@ This command:
- does not require an active text editor.
- takes an argument `delay` of type `number`.
- takes an argument `locked` of type `boolean`.
- takes an argument `menu` of type `Menu`.
- takes an argument `pass` of type `any[]`.
- takes an argument `prefix` of type `string`.
- takes an argument `title` of type `string`.
- takes an input of type `string`.
<a name=".changeInput" />
### [`changeInput`](./misc.ts#L368-L382)
### [`changeInput`](./misc.ts#L373-L387)
Change current input.
@ -849,23 +844,19 @@ Set Dance mode.
#### Variants
| Title | Identifier | Keybinding | Command |
| ------------------ | ------------ | ----------------- | ------------------------------------------------------------ |
| Set mode to Normal | `set.normal` | `escape` (insert) | `[".modes.set", { input: "normal" }], ["hideSuggestWidget"]` |
| Set mode to Insert | `set.insert` | | `[".modes.set", { input: "insert" }]` |
| Title | Identifier | Keybinding | Command |
| ------------------ | ------------ | ----------------- | ----------------------------------------------------------- |
| Set mode to Normal | `set.normal` | `escape` (insert) | `[".modes.set", { mode: "normal" }], ["hideSuggestWidget"]` |
| Set mode to Insert | `set.insert` | | `[".modes.set", { mode: "insert" }]` |
Other variants are provided to switch to insert mode:
| Title | Identifier | Keybinding | Commands |
| -------------------- | ------------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Insert before | `insert.before` | `i` (normal) | `[".selections.faceBackward", { record: false }], [".modes.set", { input: "insert" }], [".selections.reduce", { where: "start", record: false, empty: true }]` |
| Insert after | `insert.after` | `a` (normal) | `[".selections.faceForward" , { record: false }], [".modes.set", { input: "insert" }], [".selections.reduce", { where: "end" , record: false, empty: true }]` |
| Insert at line start | `insert.lineStart` | `s-i` (normal) | `[".select.lineStart", { shift: "jump", skipBlank: true }], [".modes.set", { input: "insert" }], [".selections.reduce", { where: "start", record: false, empty: true }]` |
| Insert at line end | `insert.lineEnd` | `s-a` (normal) | `[".select.lineEnd" , { shift: "jump" }], [".modes.set", { input: "insert" }], [".selections.reduce", { where: "end" , record: false, empty: true }]` |
This command:
- takes an input of type `string`.
| Title | Identifier | Keybinding | Commands |
| -------------------- | ------------------ | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Insert before | `insert.before` | `i` (normal) | `[".selections.faceBackward", { record: false }], [".modes.set", { mode: "insert", +mode }], [".selections.reduce", { where: "start", record: false, empty: true, ... }]` |
| Insert after | `insert.after` | `a` (normal) | `[".selections.faceForward" , { record: false }], [".modes.set", { mode: "insert", +mode }], [".selections.reduce", { where: "end" , record: false, empty: true, ... }]` |
| Insert at line start | `insert.lineStart` | `s-i` (normal) | `[".select.lineStart", { shift: "jump", skipBlank: true }], [".modes.set", { mode: "insert", +mode }], [".selections.reduce", { where: "start", record: false, empty: true, ... }]` |
| Insert at line end | `insert.lineEnd` | `s-a` (normal) | `[".select.lineEnd" , { shift: "jump" }], [".modes.set", { mode: "insert", +mode }], [".selections.reduce", { where: "end" , record: false, empty: true, ... }]` |
<a name="modes.set.temporarily" />
@ -875,15 +866,14 @@ Set Dance mode temporarily.
#### Variants
| Title | Identifier | Keybindings | Commands |
| --------------------- | ------------------------ | -------------- | ------------------------------------------------- |
| Temporary Normal mode | `set.temporarily.normal` | `c-v` (insert) | `[".modes.set.temporarily", { input: "normal" }]` |
| Temporary Insert mode | `set.temporarily.insert` | `c-v` (normal) | `[".modes.set.temporarily", { input: "insert" }]` |
| Title | Identifier | Keybindings | Commands |
| --------------------- | ------------------------ | -------------- | ------------------------------------------------ |
| Temporary Normal mode | `set.temporarily.normal` | `c-v` (insert) | `[".modes.set.temporarily", { mode: "normal" }]` |
| Temporary Insert mode | `set.temporarily.insert` | `c-v` (normal) | `[".modes.set.temporarily", { mode: "insert" }]` |
This command:
- may be repeated with a given number of repetitions.
- takes an input of type `string`.
## [`search`](./search.ts)
@ -891,36 +881,36 @@ Search for patterns and replace or add selections.
<a name="search.search" />
### [`search.search`](./search.ts#L14-L37)
### [`search.search`](./search.ts#L14-L36)
Search.
| Title | Identifier | Keybinding | Command |
| ------------------------ | ----------------- | -------------- | ------------------------------------------------- |
| Search (extend) | `extend` | `?` (normal) | `[".search", { shift: "extend" }]` |
| Search backward | `backward` | `a-/` (normal) | `[".search", { direction: -1 }]` |
| Search backward (extend) | `backward.extend` | `a-?` (normal) | `[".search", { direction: -1, shift: "extend" }]` |
| Title | Identifier | Keybinding | Command |
| ------------------------ | ----------------- | -------------- | ------------------------------------------------------ |
| Search (extend) | `extend` | `?` (normal) | `[".search", { shift: "extend", ... }]` |
| Search backward | `backward` | `a-/` (normal) | `[".search", { direction: -1 , ... }]` |
| Search backward (extend) | `backward.extend` | `a-?` (normal) | `[".search", { direction: -1, shift: "extend", ... }]` |
This command:
- accepts a register (by default, it uses `slash`).
- accepts an argument of type `{ input?: string | RegExp }`.
- may be repeated with a given number of repetitions.
- takes an argument `add` of type `boolean`.
- takes an argument `interactive` of type `boolean`.
- takes an input of type `Input<string | RegExp>`.
Default keybinding: `/` (normal), `NumPad_Divide` (normal)
<a name="search.selection" />
### [`search.selection`](./search.ts#L82-L97)
### [`search.selection`](./search.ts#L81-L96)
Search current selection.
| Title | Identifier | Keybinding | Command |
| -------------------------------- | ----------------- | ---------------------------------------- | ---------------------------------------- |
| Search current selection (smart) | `selection.smart` | `*` (normal), `NumPad_Multiply` (normal) | `[".search.selection", { smart: true }]` |
| Title | Identifier | Keybinding | Command |
| -------------------------------- | ----------------- | ---------------------------------------- | --------------------------------------------------- |
| Search current selection (smart) | `selection.smart` | `*` (normal), `NumPad_Multiply` (normal) | `[".search.selection", { smart: true, +register }]` |
This command:
- accepts a register (by default, it uses `slash`).
@ -930,16 +920,16 @@ Default keybinding: `a-*` (normal)
<a name="search.next" />
### [`search.next`](./search.ts#L150-L169)
### [`search.next`](./search.ts#L149-L168)
Select next match.
| Title | Identifier | Keybinding | Command |
| --------------------- | -------------- | ---------------- | ------------------------------------------------ |
| Add next match | `next.add` | `s-n` (normal) | `[".search.next", { add: true }]` |
| Select previous match | `previous` | `a-n` (normal) | `[".search.next", { direction: -1 }]` |
| Add previous match | `previous.add` | `s-a-n` (normal) | `[".search.next", { direction: -1, add: true }]` |
| Title | Identifier | Keybinding | Command |
| --------------------- | -------------- | ---------------- | ----------------------------------------------------- |
| Add next match | `next.add` | `s-n` (normal) | `[".search.next", { add: true, ... }]` |
| Select previous match | `previous` | `a-n` (normal) | `[".search.next", { direction: -1 , ... }]` |
| Add previous match | `previous.add` | `s-a-n` (normal) | `[".search.next", { direction: -1, add: true, ... }]` |
This command:
- accepts a register (by default, it uses `slash`).
@ -961,20 +951,19 @@ Select to character (excluded).
#### Variants
| Title | Identifier | Keybinding | Command |
| ---------------------------------------- | -------------------------- | ---------------- | -------------------------------------------------------------- |
| Extend to character (excluded) | `extend` | `s-t` (normal) | `[".seek", { shift: "extend" }]` |
| Select to character (excluded, backward) | `backward` | `a-t` (normal) | `[".seek", { direction: -1 }]` |
| Extend to character (excluded, backward) | `extend.backward` | `s-a-t` (normal) | `[".seek", { shift: "extend", direction: -1 }]` |
| Select to character (included) | `included` | `f` (normal) | `[".seek", { include: true }]` |
| Extend to character (included) | `included.extend` | `s-f` (normal) | `[".seek", { include: true, shift: "extend" }]` |
| Select to character (included, backward) | `included.backward` | `a-f` (normal) | `[".seek", { include: true, direction: -1 }]` |
| Extend to character (included, backward) | `included.extend.backward` | `s-a-f` (normal) | `[".seek", { include: true, shift: "extend", direction: -1 }]` |
| Title | Identifier | Keybinding | Command |
| ---------------------------------------- | -------------------------- | ---------------- | ------------------------------------------------------------------- |
| Extend to character (excluded) | `extend` | `s-t` (normal) | `[".seek", { shift: "extend" , ... }]` |
| Select to character (excluded, backward) | `backward` | `a-t` (normal) | `[".seek", { direction: -1, ... }]` |
| Extend to character (excluded, backward) | `extend.backward` | `s-a-t` (normal) | `[".seek", { shift: "extend", direction: -1, ... }]` |
| Select to character (included) | `included` | `f` (normal) | `[".seek", { include: true , ... }]` |
| Extend to character (included) | `included.extend` | `s-f` (normal) | `[".seek", { include: true, shift: "extend" , ... }]` |
| Select to character (included, backward) | `included.backward` | `a-f` (normal) | `[".seek", { include: true, direction: -1, ... }]` |
| Extend to character (included, backward) | `included.extend.backward` | `s-a-f` (normal) | `[".seek", { include: true, shift: "extend", direction: -1, ... }]` |
This command:
- may be repeated with a given number of repetitions.
- takes an argument `include` of type `boolean`.
- takes an input of type `string`.
Default keybinding: `t` (normal)
@ -987,11 +976,11 @@ Select to next enclosing character.
#### Variants
| Title | Identifier | Keybinding | Command |
| -------------------------------------- | --------------------------- | ---------------- | --------------------------------------------------------- |
| Extend to next enclosing character | `enclosing.extend` | `s-m` (normal) | `[".seek.enclosing", { shift: "extend" }]` |
| Select to previous enclosing character | `enclosing.backward` | `a-m` (normal) | `[".seek.enclosing", { direction: -1 }]` |
| Extend to previous enclosing character | `enclosing.extend.backward` | `s-a-m` (normal) | `[".seek.enclosing", { shift: "extend", direction: -1 }]` |
| Title | Identifier | Keybinding | Command |
| -------------------------------------- | --------------------------- | ---------------- | -------------------------------------------------------------- |
| Extend to next enclosing character | `enclosing.extend` | `s-m` (normal) | `[".seek.enclosing", { shift: "extend" , ... }]` |
| Select to previous enclosing character | `enclosing.backward` | `a-m` (normal) | `[".seek.enclosing", { direction: -1, ... }]` |
| Extend to previous enclosing character | `enclosing.extend.backward` | `s-a-m` (normal) | `[".seek.enclosing", { shift: "extend", direction: -1, ... }]` |
This command:
- takes an argument `open` of type `boolean`.
@ -1010,19 +999,19 @@ Select the word and following whitespaces on the right of the end of each select
#### Variants
| Title | Identifier | Keybinding | Command |
| -------------------------------------------- | ------------------------- | ---------------- | -------------------------------------------------------------------------------- |
| Extend to next word start | `word.extend` | `s-w` (normal) | `[".seek.word", { shift: "extend" }]` |
| Select to previous word start | `word.backward` | `b` (normal) | `[".seek.word", { direction: -1 }]` |
| Extend to previous word start | `word.extend.backward` | `s-b` (normal) | `[".seek.word", { shift: "extend", direction: -1 }]` |
| Select to next non-whitespace word start | `word.ws` | `a-w` (normal) | `[".seek.word", { ws: true }]` |
| Extend to next non-whitespace word start | `word.ws.extend` | `s-a-w` (normal) | `[".seek.word", { ws: true, shift: "extend" }]` |
| Select to previous non-whitespace word start | `word.ws.backward` | `a-b` (normal) | `[".seek.word", { ws: true, direction: -1 }]` |
| Extend to previous non-whitespace word start | `word.ws.extend.backward` | `s-a-b` (normal) | `[".seek.word", { ws: true, shift: "extend", direction: -1 }]` |
| Select to next word end | `wordEnd` | `e` (normal) | `[".seek.word", { stopAtEnd: true }]` |
| Extend to next word end | `wordEnd.extend` | `s-e` (normal) | `[".seek.word", { stopAtEnd: true , shift: "extend" }]` |
| Select to next non-whitespace word end | `wordEnd.ws` | `a-e` (normal) | `[".seek.word", { stopAtEnd: true , ws: true }]` |
| Extend to next non-whitespace word end | `wordEnd.ws.extend` | `s-a-e` (normal) | `[".seek.word", { stopAtEnd: true , ws: true, shift: "extend" }]` |
| Title | Identifier | Keybinding | Command |
| -------------------------------------------- | ------------------------- | ---------------- | ------------------------------------------------------------------------------------- |
| Extend to next word start | `word.extend` | `s-w` (normal) | `[".seek.word", { shift: "extend" , ... }]` |
| Select to previous word start | `word.backward` | `b` (normal) | `[".seek.word", { direction: -1, ... }]` |
| Extend to previous word start | `word.extend.backward` | `s-b` (normal) | `[".seek.word", { shift: "extend", direction: -1, ... }]` |
| Select to next non-whitespace word start | `word.ws` | `a-w` (normal) | `[".seek.word", { ws: true , ... }]` |
| Extend to next non-whitespace word start | `word.ws.extend` | `s-a-w` (normal) | `[".seek.word", { ws: true, shift: "extend" , ... }]` |
| Select to previous non-whitespace word start | `word.ws.backward` | `a-b` (normal) | `[".seek.word", { ws: true, direction: -1, ... }]` |
| Extend to previous non-whitespace word start | `word.ws.extend.backward` | `s-a-b` (normal) | `[".seek.word", { ws: true, shift: "extend", direction: -1, ... }]` |
| Select to next word end | `wordEnd` | `e` (normal) | `[".seek.word", { stopAtEnd: true , ... }]` |
| Extend to next word end | `wordEnd.extend` | `s-e` (normal) | `[".seek.word", { stopAtEnd: true , shift: "extend" , ... }]` |
| Select to next non-whitespace word end | `wordEnd.ws` | `a-e` (normal) | `[".seek.word", { stopAtEnd: true , ws: true , ... }]` |
| Extend to next non-whitespace word end | `wordEnd.ws.extend` | `s-a-e` (normal) | `[".seek.word", { stopAtEnd: true , ws: true, shift: "extend" , ... }]` |
This command:
- may be repeated with a given number of repetitions.
@ -1048,23 +1037,22 @@ Select object.
#### Variants
| Title | Identifier | Keybinding | Command |
| ---------------------------- | ------------------------------ | ------------------------------ | ---------------------------------------------------------------------------------------------- |
| Select whole object | `askObject` | `a-a` (normal), `a-a` (insert) | `[".openMenu", { input: "object", title: "Select whole object..." }]` |
| Select inner object | `askObject.inner` | `a-i` (normal), `a-i` (insert) | `[".openMenu", { input: "object", pass: [{ inner: true }], title: "Select inner object..." }]` |
| Select to whole object start | `askObject.start` | `[` (normal) | `[".openMenu", { input: "object", pass: [{ where: "start" }] }]` |
| Extend to whole object start | `askObject.start.extend` | `{` (normal) | `[".openMenu", { input: "object", pass: [{ where: "start", shift: "extend" }] }]` |
| Select to inner object start | `askObject.inner.start` | `a-[` (normal) | `[".openMenu", { input: "object", pass: [{ inner: true, where: "start" }] }]` |
| Extend to inner object start | `askObject.inner.start.extend` | `a-{` (normal) | `[".openMenu", { input: "object", pass: [{ inner: true, where: "start", shift: "extend" }] }]` |
| Select to whole object end | `askObject.end` | `]` (normal) | `[".openMenu", { input: "object", pass: [{ where: "end" }] }]` |
| Extend to whole object end | `askObject.end.extend` | `}` (normal) | `[".openMenu", { input: "object", pass: [{ where: "end" , shift: "extend" }] }]` |
| Select to inner object end | `askObject.inner.end` | `a-]` (normal) | `[".openMenu", { input: "object", pass: [{ inner: true, where: "end" }] }]` |
| Extend to inner object end | `askObject.inner.end.extend` | `a-}` (normal) | `[".openMenu", { input: "object", pass: [{ inner: true, where: "end" , shift: "extend" }] }]` |
| Title | Identifier | Keybinding | Command |
| ---------------------------- | ------------------------------ | ------------------------------ | --------------------------------------------------------------------------------------------- |
| Select whole object | `askObject` | `a-a` (normal), `a-a` (insert) | `[".openMenu", { menu: "object", title: "Select whole object..." }]` |
| Select inner object | `askObject.inner` | `a-i` (normal), `a-i` (insert) | `[".openMenu", { menu: "object", pass: [{ inner: true }], title: "Select inner object..." }]` |
| Select to whole object start | `askObject.start` | `[` (normal) | `[".openMenu", { menu: "object", pass: [{ where: "start" }] }]` |
| Extend to whole object start | `askObject.start.extend` | `{` (normal) | `[".openMenu", { menu: "object", pass: [{ where: "start", shift: "extend" }] }]` |
| Select to inner object start | `askObject.inner.start` | `a-[` (normal) | `[".openMenu", { menu: "object", pass: [{ inner: true, where: "start" }] }]` |
| Extend to inner object start | `askObject.inner.start.extend` | `a-{` (normal) | `[".openMenu", { menu: "object", pass: [{ inner: true, where: "start", shift: "extend" }] }]` |
| Select to whole object end | `askObject.end` | `]` (normal) | `[".openMenu", { menu: "object", pass: [{ where: "end" }] }]` |
| Extend to whole object end | `askObject.end.extend` | `}` (normal) | `[".openMenu", { menu: "object", pass: [{ where: "end" , shift: "extend" }] }]` |
| Select to inner object end | `askObject.inner.end` | `a-]` (normal) | `[".openMenu", { menu: "object", pass: [{ inner: true, where: "end" }] }]` |
| Extend to inner object end | `askObject.inner.end.extend` | `a-}` (normal) | `[".openMenu", { menu: "object", pass: [{ inner: true, where: "end" , shift: "extend" }] }]` |
This command:
- takes an argument `inner` of type `boolean`.
- takes an argument `where` of type `"start" | "end"`.
- takes an input of type `string`.
## [`select`](./select.ts)
@ -1089,12 +1077,12 @@ Select vertically.
#### Variants
| Title | Identifier | Keybinding | Command |
| ----------- | ------------- | --------------------------------- | ------------------------------------------------------------ |
| Jump down | `down.jump` | `j` (normal) , `down` (normal) | `[".select.vertically", { direction: 1, shift: "jump" }]` |
| Extend down | `down.extend` | `s-j` (normal), `s-down` (normal) | `[".select.vertically", { direction: 1, shift: "extend" }]` |
| Jump up | `up.jump` | `k` (normal) , `up` (normal) | `[".select.vertically", { direction: -1, shift: "jump" }]` |
| Extend up | `up.extend` | `s-k` (normal), `s-up` (normal) | `[".select.vertically", { direction: -1, shift: "extend" }]` |
| Title | Identifier | Keybinding | Command |
| ----------- | ------------- | --------------------------------- | ----------------------------------------------------------------- |
| Jump down | `down.jump` | `j` (normal) , `down` (normal) | `[".select.vertically", { direction: 1, shift: "jump" , ... }]` |
| Extend down | `down.extend` | `s-j` (normal), `s-down` (normal) | `[".select.vertically", { direction: 1, shift: "extend", ... }]` |
| Jump up | `up.jump` | `k` (normal) , `up` (normal) | `[".select.vertically", { direction: -1, shift: "jump" , ... }]` |
| Extend up | `up.extend` | `s-k` (normal), `s-up` (normal) | `[".select.vertically", { direction: -1, shift: "extend", ... }]` |
The following keybindings are also defined:
@ -1119,12 +1107,12 @@ Select horizontally.
#### Variants
| Title | Identifier | Keybinding | Command |
| ------------ | -------------- | ---------------------------------- | -------------------------------------------------------------- |
| Jump right | `right.jump` | `l` (normal) , `right` (normal) | `[".select.horizontally", { direction: 1, shift: "jump" }]` |
| Extend right | `right.extend` | `s-l` (normal), `s-right` (normal) | `[".select.horizontally", { direction: 1, shift: "extend" }]` |
| Jump left | `left.jump` | `h` (normal) , `left` (normal) | `[".select.horizontally", { direction: -1, shift: "jump" }]` |
| Extend left | `left.extend` | `s-h` (normal), `s-left` (normal) | `[".select.horizontally", { direction: -1, shift: "extend" }]` |
| Title | Identifier | Keybinding | Command |
| ------------ | -------------- | ---------------------------------- | ------------------------------------------------------------------- |
| Jump right | `right.jump` | `l` (normal) , `right` (normal) | `[".select.horizontally", { direction: 1, shift: "jump" , ... }]` |
| Extend right | `right.extend` | `s-l` (normal), `s-right` (normal) | `[".select.horizontally", { direction: 1, shift: "extend", ... }]` |
| Jump left | `left.jump` | `h` (normal) , `left` (normal) | `[".select.horizontally", { direction: -1, shift: "jump" , ... }]` |
| Extend left | `left.extend` | `s-h` (normal), `s-left` (normal) | `[".select.horizontally", { direction: -1, shift: "extend", ... }]` |
This command:
- may be repeated with a given number of repetitions.
@ -1141,10 +1129,10 @@ line. If no count is specified, this command will shift open the `goto` menu.
#### Variants
| Title | Identifier | Keybinding | Command |
| --------- | ----------- | -------------- | ------------------------------------- |
| Go to | `to.jump` | `g` (normal) | `[".select.to", { shift: "jump" }]` |
| Extend to | `to.extend` | `s-g` (normal) | `[".select.to", { shift: "extend" }]` |
| Title | Identifier | Keybinding | Command |
| --------- | ----------- | -------------- | ------------------------------------------ |
| Go to | `to.jump` | `g` (normal) | `[".select.to", { shift: "jump" , ... }]` |
| Extend to | `to.extend` | `s-g` (normal) | `[".select.to", { shift: "extend", ... }]` |
This command:
- accepts an argument of type `object`.
@ -1201,14 +1189,14 @@ Select to line start.
#### Variants
| Title | Identifier | Keybinding | Command |
| -------------------- | ------------------ | ----------------------------------- | ------------------------------------------------------------- |
| Jump to line start | `lineStart.jump` | | `[".select.lineStart", { shift: "jump" }]` |
| Extend to line start | `lineStart.extend` | `s-a-h` (normal), `s-home` (normal) | `[".select.lineStart", { shift: "extend" }]` |
| Jump to line start (skip blank) | `lineStart.skipBlank.jump` | | `[".select.lineStart", { skipBlank: true, shift: "jump" }]` |
| Extend to line start (skip blank) | `lineStart.skipBlank.extend` | | `[".select.lineStart", { skipBlank: true, shift: "extend" }]` |
| Jump to first line | `firstLine.jump` | | `[".select.lineStart", { count: 0, shift: "jump" }]` |
| Extend to first line | `firstLine.extend` | | `[".select.lineStart", { count: 0, shift: "extend" }]` |
| Title | Identifier | Keybinding | Command |
| -------------------- | ------------------ | ----------------------------------- | ------------------------------------------------------------------ |
| Jump to line start | `lineStart.jump` | | `[".select.lineStart", { shift: "jump" , ... }]` |
| Extend to line start | `lineStart.extend` | `s-a-h` (normal), `s-home` (normal) | `[".select.lineStart", { shift: "extend", ... }]` |
| Jump to line start (skip blank) | `lineStart.skipBlank.jump` | | `[".select.lineStart", { skipBlank: true, shift: "jump" , ... }]` |
| Extend to line start (skip blank) | `lineStart.skipBlank.extend` | | `[".select.lineStart", { skipBlank: true, shift: "extend", ... }]` |
| Jump to first line | `firstLine.jump` | | `[".select.lineStart", { count: 0, shift: "jump" , ... }]` |
| Extend to first line | `firstLine.extend` | | `[".select.lineStart", { count: 0, shift: "extend", ... }]` |
This command:
- may be repeated with a given number of repetitions.
@ -1226,11 +1214,11 @@ Select to line end.
#### Variants
| Title | Identifier | Keybinding | Command |
| ------------------------ | -------------------- | ---------------------------------- | ---------------------------------------------------------- |
| Extend to line end | `lineEnd.extend` | `s-a-l` (normal), `s-end` (normal) | `[".select.lineEnd", { shift: "extend" }]` |
| Jump to last character | `documentEnd.jump` | | `[".select.lineEnd", { count: MAX_INT, shift: "jump" }]` |
| Extend to last character | `documentEnd.extend` | | `[".select.lineEnd", { count: MAX_INT, shift: "extend" }]` |
| Title | Identifier | Keybinding | Command |
| ------------------------ | -------------------- | ---------------------------------- | --------------------------------------------------------------- |
| Extend to line end | `lineEnd.extend` | `s-a-l` (normal), `s-end` (normal) | `[".select.lineEnd", { shift: "extend", ... }]` |
| Jump to last character | `documentEnd.jump` | | `[".select.lineEnd", { count: MAX_INT, shift: "jump" , ... }]` |
| Extend to last character | `documentEnd.extend` | | `[".select.lineEnd", { count: MAX_INT, shift: "extend", ... }]` |
This command:
- may be repeated with a given number of repetitions.
@ -1342,9 +1330,9 @@ Combine register selections with current ones.
The following keybinding is also available:
| Keybinding | Command |
| ---------------- | -------------------------------------------------------- |
| `s-a-z` (normal) | `[".selections.restore.withCurrent", { reverse: true }]` |
| Keybinding | Command |
| ---------------- | ------------------------------------------------------------- |
| `s-a-z` (normal) | `[".selections.restore.withCurrent", { reverse: true, ... }]` |
See https://github.com/mawww/kakoune/blob/master/doc/pages/keys.asciidoc#marks
@ -1369,73 +1357,72 @@ See https://github.com/mawww/kakoune/blob/master/doc/pages/keys.asciidoc#changes
#### Additional commands
| Title | Identifier | Keybinding | Commands |
| ------------------- | -------------- | -------------- | --------------------------------------------------------------------------- |
| Pipe and replace | `pipe.replace` | `\|` (normal) | `[".selections.pipe"], [".edit.insert", { register: "\|" }]` |
| Pipe and append | `pipe.append` | `!` (normal) | `[".selections.pipe"], [".edit.insert", { register: "\|", where: "end" }]` |
| Pipe and prepend | `pipe.prepend` | `a-!` (normal) | `[".selections.pipe"], [".edit.insert", { register: "\|", where: "start" }]` |
| Title | Identifier | Keybinding | Commands |
| ------------------- | -------------- | -------------- | ----------------------------------------------------------------------------------------------------- |
| Pipe and replace | `pipe.replace` | `\|` (normal) | `[".selections.pipe", { +input,register }], [".edit.insert", { register: "\|" , ... }]` |
| Pipe and append | `pipe.append` | `!` (normal) | `[".selections.pipe", { +input,register }], [".edit.insert", { register: "\|", where: "end" , ... }]` |
| Pipe and prepend | `pipe.prepend` | `a-!` (normal) | `[".selections.pipe", { +input,register }], [".edit.insert", { register: "\|", where: "start", ... }]` |
This command:
- accepts a register (by default, it uses `pipe`).
- takes an input of type `string`.
Default keybinding: `a-|` (normal)
<a name="selections.filter" />
### [`selections.filter`](./selections.ts#L307-L330)
### [`selections.filter`](./selections.ts#L307-L329)
Filter selections.
#### Variants
| Title | Identifier | Keybinding | Commands |
| -------------------------- | ----------------------- | ------------------ | -------------------------------------------------------------- |
| Keep matching selections | `filter.regexp` | `a-k` (normal) | `[".selections.filter", { defaultInput: "/" }]` |
| Clear matching selections | `filter.regexp.inverse` | `s-a-k` (normal) | `[".selections.filter", { defaultInput: "/", inverse: true }]` |
| Clear secondary selections | `clear.secondary` | `space` (normal) | `[".selections.filter", { input: "i === count" }]` |
| Clear main selections | `clear.main` | `a-space` (normal) | `[".selections.filter", { input: "i !== count" }]` |
| Title | Identifier | Keybinding | Commands |
| -------------------------- | ----------------------- | ------------------ | ------------------------------------------------------------------- |
| Keep matching selections | `filter.regexp` | `a-k` (normal) | `[".selections.filter", { defaultInput: "/" , ... }]` |
| Clear matching selections | `filter.regexp.inverse` | `s-a-k` (normal) | `[".selections.filter", { defaultInput: "/", inverse: true, ... }]` |
| Clear secondary selections | `clear.secondary` | `space` (normal) | `[".selections.filter", { input: "i === count" , ... }]` |
| Clear main selections | `clear.main` | `a-space` (normal) | `[".selections.filter", { input: "i !== count" , ... }]` |
This command:
- accepts an argument of type `{ input?: string }`.
- may be repeated with a given number of repetitions.
- takes an argument `defaultInput` of type `string`.
- takes an argument `interactive` of type `boolean`.
- takes an argument `inverse` of type `boolean`.
- takes an input of type `Input<string>`.
Default keybinding: `$` (normal)
<a name="selections.select" />
### [`selections.select`](./selections.ts#L359-L370)
### [`selections.select`](./selections.ts#L358-L368)
Select within selections.
This command:
- accepts an argument of type `{ input?: string | RegExp }`.
- takes an argument `interactive` of type `boolean`.
- takes an input of type `Input<string | RegExp>`.
Default keybinding: `s` (normal)
<a name="selections.split" />
### [`selections.split`](./selections.ts#L389-L401)
### [`selections.split`](./selections.ts#L387-L398)
Split selections.
This command:
- accepts an argument of type `{ input?: string | RegExp }`.
- takes an argument `excludeEmpty` of type `boolean`.
- takes an argument `interactive` of type `boolean`.
- takes an input of type `Input<string | RegExp>`.
Default keybinding: `s-s` (normal)
<a name="selections.splitLines" />
### [`selections.splitLines`](./selections.ts#L426-L437)
### [`selections.splitLines`](./selections.ts#L423-L434)
Split selections at line boundaries.
@ -1448,7 +1435,7 @@ Default keybinding: `a-s` (normal)
<a name="selections.expandToLines" />
### [`selections.expandToLines`](./selections.ts#L480-L487)
### [`selections.expandToLines`](./selections.ts#L477-L484)
Expand to lines.
@ -1460,7 +1447,7 @@ Default keybinding: `a-x` (normal)
<a name="selections.trimLines" />
### [`selections.trimLines`](./selections.ts#L514-L521)
### [`selections.trimLines`](./selections.ts#L511-L518)
Trim lines.
@ -1472,7 +1459,7 @@ Default keybinding: `s-a-x` (normal)
<a name="selections.trimWhitespace" />
### [`selections.trimWhitespace`](./selections.ts#L546-L553)
### [`selections.trimWhitespace`](./selections.ts#L543-L550)
Trim whitespace.
@ -1484,7 +1471,7 @@ Default keybinding: `_` (normal)
<a name="selections.reduce" />
### [`selections.reduce`](./selections.ts#L572-L591)
### [`selections.reduce`](./selections.ts#L569-L588)
Reduce selections to their cursor.
@ -1492,9 +1479,9 @@ Reduce selections to their cursor.
#### Variant
| Title | Identifier | Keybinding | Command |
| ------------------------------- | -------------- | ---------------- | --------------------------------------------------------- |
| Reduce selections to their ends | `reduce.edges` | `s-a-s` (normal) | `[".selections.reduce", { where: "both", empty: false }]` |
| Title | Identifier | Keybinding | Command |
| ------------------------------- | -------------- | ---------------- | -------------------------------------------------------------- |
| Reduce selections to their ends | `reduce.edges` | `s-a-s` (normal) | `[".selections.reduce", { where: "both", empty: false, ... }]` |
This command:
- takes an argument `empty` of type `boolean`.
@ -1504,7 +1491,7 @@ Default keybinding: `;` (normal)
<a name="selections.changeDirection" />
### [`selections.changeDirection`](./selections.ts#L653-L668)
### [`selections.changeDirection`](./selections.ts#L650-L665)
Change direction of selections.
@ -1522,7 +1509,7 @@ Default keybinding: `a-;` (normal)
<a name="selections.copy" />
### [`selections.copy`](./selections.ts#L693-L711)
### [`selections.copy`](./selections.ts#L690-L708)
Copy selections below.
@ -1540,7 +1527,7 @@ Default keybinding: `s-c` (normal)
<a name="selections.merge" />
### [`selections.merge`](./selections.ts#L745-L750)
### [`selections.merge`](./selections.ts#L742-L747)
Merge contiguous selections.
@ -1550,23 +1537,23 @@ Default keybinding: `a-_` (normal)
<a name="selections.open" />
### [`selections.open`](./selections.ts#L754-L757)
### [`selections.open`](./selections.ts#L751-L754)
Open selected file.
<a name="selections.toggleIndices" />
### [`selections.toggleIndices`](./selections.ts#L771-L788)
### [`selections.toggleIndices`](./selections.ts#L768-L785)
Toggle selection indices.
#### Variants
| Title | Identifier | Command |
| ---------------------- | ------------- | --------------------------------------------------- |
| Show selection indices | `showIndices` | `[".selections.toggleIndices", { display: true }]` |
| Hide selection indices | `hideIndices` | `[".selections.toggleIndices", { display: false }]` |
| Title | Identifier | Command |
| ---------------------- | ------------- | -------------------------------------------------------- |
| Show selection indices | `showIndices` | `[".selections.toggleIndices", { display: true , ... }]` |
| Hide selection indices | `hideIndices` | `[".selections.toggleIndices", { display: false, ... }]` |
This command:
- takes an argument `display` of type `boolean | undefined`.
@ -1638,10 +1625,10 @@ Moving the editor view.
#### Predefined keybindings
| Title | Keybinding | Command |
| ----------------------- | -------------- | ------------------------------------------------ |
| Show view menu | `v` (normal) | `[".openMenu", { input: "view" }]` |
| Show view menu (locked) | `s-v` (normal) | `[".openMenu", { input: "view", locked: true }]` |
| Title | Keybinding | Command |
| ----------------------- | -------------- | ---------------------------------------------------- |
| Show view menu | `v` (normal) | `[".openMenu", { menu: "view", ... }]` |
| Show view menu (locked) | `s-v` (normal) | `[".openMenu", { menu: "view", locked: true, ... }]` |
<a name="view.line" />

View File

@ -29,18 +29,18 @@ declare module "./edit";
*
* #### Additional commands
*
* | Title | Identifier | Keybinding | Commands |
* | ---------------------------------- | ----------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------- |
* | Pick register and replace | `selectRegister-insert` | `c-r` (normal), `c-r` (insert) | `[".selectRegister"], [".edit.insert"]` |
* | Paste before | `paste.before` | `s-p` (normal) | `[".edit.insert", { handleNewLine: true, where: "start" }]` |
* | Paste after | `paste.after` | `p` (normal) | `[".edit.insert", { handleNewLine: true, where: "end" }]` |
* | Paste before and select | `paste.before.select` | `s-a-p` (normal) | `[".edit.insert", { handleNewLine: true, where: "start", shift: "select" }]` |
* | Paste after and select | `paste.after.select` | `a-p` (normal) | `[".edit.insert", { handleNewLine: true, where: "end" , shift: "select" }]` |
* | Delete | `delete` | `a-d` (normal) | `[".edit.insert", { register: "_" }]` |
* | Delete and switch to Insert | `delete-insert` | `a-c` (normal) | `[".modes.set", { input: "insert" }], [".edit.insert", { register: "_" }]` |
* | Copy and delete | `yank-delete` | `d` (normal) | `[".selections.saveText"], [".edit.insert", { register: "_" }]` |
* | Copy, delete and switch to Insert | `yank-delete-insert` | `c` (normal) | `[".selections.saveText"], [".modes.set", { input: "insert" }], [".edit.insert", { register: "_" }]` |
* | Copy and replace | `yank-replace` | `s-r` (normal) | `[".selections.saveText", { register: "tmp" }], [".edit.insert"], [".updateRegister", { copyFrom: "tmp" }]` |
* | Title | Identifier | Keybinding | Commands |
* | ---------------------------------- | ----------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
* | Pick register and replace | `selectRegister-insert` | `c-r` (normal), `c-r` (insert) | `[".selectRegister", { +register }], [".edit.insert", { ... }]` |
* | Paste before | `paste.before` | `s-p` (normal) | `[".edit.insert", { handleNewLine: true, where: "start", ... }]` |
* | Paste after | `paste.after` | `p` (normal) | `[".edit.insert", { handleNewLine: true, where: "end" , ... }]` |
* | Paste before and select | `paste.before.select` | `s-a-p` (normal) | `[".edit.insert", { handleNewLine: true, where: "start", shift: "select", ... }]` |
* | Paste after and select | `paste.after.select` | `a-p` (normal) | `[".edit.insert", { handleNewLine: true, where: "end" , shift: "select", ... }]` |
* | Delete | `delete` | `a-d` (normal) | `[".edit.insert", { register: "_", ... }]` |
* | Delete and switch to Insert | `delete-insert` | `a-c` (normal) | `[".modes.set", { mode: "insert", +mode }], [".edit.insert", { register: "_", ... }]` |
* | Copy and delete | `yank-delete` | `d` (normal) | `[".selections.saveText", { +register }], [".edit.insert", { register: "_", ... }]` |
* | Copy, delete and switch to Insert | `yank-delete-insert` | `c` (normal) | `[".selections.saveText", { +register }], [".modes.set", { mode: "insert", +mode }], [".edit.insert", { register: "_", ... }]` |
* | Copy and replace | `yank-replace` | `s-r` (normal) | `[".selections.saveText", { register: "tmp" }], [".edit.insert"], [".updateRegister", { copyFrom: "tmp", ... }]` |
*/
export async function insert(
_: Context,
@ -216,7 +216,7 @@ export function case_swap(_: Context) {
export async function replaceCharacters(
_: Context,
repetitions: number,
inputOr: InputOr<string>,
inputOr: InputOr<"input", string>,
) {
const input = (await inputOr(() => keypress(_))).repeat(repetitions);
@ -342,9 +342,9 @@ export function copyIndentation(
*
* #### Additional keybindings
*
* | Title | Identifier | Keybinding | Commands |
* | ------------------------------------------ | ---------------------- | -------------- | ------------------------------------------------------------------------ |
* | Insert new line above and switch to insert | `newLine.above.insert` | `s-o` (normal) | `[".edit.newLine.above", { shift: "select" }], [".modes.insert.before"]` |
* | Title | Identifier | Keybinding | Commands |
* | ------------------------------------------ | ---------------------- | -------------- | --------------------------------------------------------------------------------- |
* | Insert new line above and switch to insert | `newLine.above.insert` | `s-o` (normal) | `[".edit.newLine.above", { shift: "select" }], [".modes.insert.before", { ... }]` |
*/
export function newLine_above(
_: Context,
@ -383,9 +383,9 @@ export function newLine_above(
*
* #### Additional keybindings
*
* | Title | Identifier | Keybinding | Commands |
* | ------------------------------------------ | ---------------------- | ------------ | ------------------------------------------------------------------------ |
* | Insert new line below and switch to insert | `newLine.below.insert` | `o` (normal) | `[".edit.newLine.below", { shift: "select" }], [".modes.insert.before"]` |
* | Title | Identifier | Keybinding | Commands |
* | ------------------------------------------ | ---------------------- | ------------ | --------------------------------------------------------------------------------- |
* | Insert new line below and switch to insert | `newLine.below.insert` | `o` (normal) | `[".edit.newLine.below", { shift: "select" }], [".modes.insert.before", { ... }]` |
*/
export function newLine_below(
_: Context,

View File

@ -52,19 +52,19 @@ export function redo_selections() {
*
* @noreplay
*
* | Title | Identifier | Keybinding | Commands |
* | ---------------------------- | ------------------ | -------------- | --------------------------------------------------------------------------- |
* | Repeat last selection change | `repeat.selection` | | `[".history.repeat", { include: "dance\\.(seek|select|selections)\\..+" }]` |
* | Repeat last seek | `repeat.seek` | `a-.` (normal) | `[".history.repeat", { include: "dance\\.seek\\..+" }]` |
* | Title | Identifier | Keybinding | Commands |
* | ---------------------------- | ------------------ | -------------- | -------------------------------------------------------------------------- |
* | Repeat last selection change | `repeat.selection` | | `[".history.repeat", { filter: "dance\\.(seek|select|selections)\\..+" }]` |
* | Repeat last seek | `repeat.seek` | `a-.` (normal) | `[".history.repeat", { filter: "dance\\.seek\\..+" }]` |
*/
export async function repeat(
_: Context,
repetitions: number,
include: Argument<string | RegExp> = /.+/,
filter: Argument<string | RegExp> = /.+/,
) {
if (typeof include === "string") {
include = new RegExp(include, "u");
if (typeof filter === "string") {
filter = new RegExp(filter, "u");
}
let commandDescriptor: CommandDescriptor,
@ -77,7 +77,7 @@ export async function repeat(
const entry = cursor.entry(),
descriptor = entry.descriptor();
if (include.test(descriptor.identifier)) {
if (filter.test(descriptor.identifier)) {
commandDescriptor = descriptor;
commandArgument = entry.argument();
break;
@ -85,7 +85,7 @@ export async function repeat(
}
if (!cursor.previous()) {
throw new Error("no previous command matching " + include);
throw new Error("no previous command matching " + filter);
}
}

View File

@ -17,24 +17,11 @@ export type RegisterOr<_Default extends keyof Registers,
* function will be used to update the input value in subsequent executions of
* this command.
*/
export interface InputOr<T> {
export interface InputOr<ArgumentName extends string, T> {
(promptDefaultInput: () => T): T;
(promptDefaultInput: () => Thenable<T>): Thenable<T>;
}
/**
* Indicates that an input is expected.
*/
export type Input<T> = T | undefined;
/**
* A function used to update the input value in subsequent executions of this
* command.
*/
export interface SetInput<T> {
(input: T): void;
}
/**
* Indicates that a value passed as a command argument is expected.
*/

View File

@ -2,71 +2,95 @@ import * as assert from "assert";
import { Builder, unindent } from "../../meta";
export async function build(builder: Builder) {
const modules = await builder.getCommandModules();
const modules = await builder.getCommandModules(),
availableCommands = new Set(
modules.flatMap((m) => m.functions.map((f) => "dance." + f.qualifiedName))),
additionalCommands = [] as Builder.AdditionalCommand[];
// Build list of additional commands, only adding new commands when all their
// dependencies have already been added as well.
let unorderedAdditionalCommands = modules.flatMap((module) =>
module.additional
.concat(...module.functions.map((f) => f.additional))
.filter((x) => x.identifier !== undefined && x.commands !== undefined));
while (unorderedAdditionalCommands.length > 0) {
const commandsWithMissingDependencies = [] as Builder.AdditionalCommand[];
outer: for (const command of unorderedAdditionalCommands) {
const dependencies = command.commands!.matchAll(/"(\.[\w.-]+)"/g);
for (const match of dependencies) {
const dependency = "dance" + match[1];
if (!availableCommands.has(dependency)) {
commandsWithMissingDependencies.push(command);
continue outer;
}
}
availableCommands.add(`dance.${command.qualifiedIdentifier}`);
additionalCommands.push(command);
}
if (unorderedAdditionalCommands.length === commandsWithMissingDependencies.length) {
throw new Error(
`cannot resolve dependencies: ${JSON.stringify(commandsWithMissingDependencies)}`);
}
unorderedAdditionalCommands = commandsWithMissingDependencies;
}
return unindent(4, `
${modules.map((module) => unindent(8, `
/**
* Loads the "${module.name}" module and returns its defined commands.
*/
async function load${capitalize(module.name!)}Module(): Promise<CommandDescriptor[]> {
const {${
module.functionNames
.map((name) => "\n" + " ".repeat(16) + name + ",")
.sort()
.join("")}
} = await import("./${module.name}");
return [${
module.functions
.map((f) => `
new CommandDescriptor(
"dance.${f.qualifiedName}",
${determineFunctionExpression(f)},
${determineFunctionFlags(f)},
),`)
.sort()
.join("")}${
module.additional.concat(...module.functions.map((f) => f.additional))
.filter((x) => x.identifier !== undefined && x.commands !== undefined)
.map((x) => `
new CommandDescriptor(
"dance.${x.qualifiedIdentifier}",
${buildCommandsExpression(x)},
CommandDescriptor.Flags.RequiresActiveEditor | CommandDescriptor.Flags.DoNotReplay,
),`)
.sort()
.join("")}
];
}
import {${
module.functions
.map((f) => `\n${" ".repeat(14)}${f.name} as ${f.qualifiedName.replace(/\./g, "_")},`)
.sort()
.join("")}
} from "./${module.name}";
`).trim()).join("\n\n")}
/**
* Loads and returns all defined commands.
* All defined Dance commands.
*/
export async function loadCommands(): Promise<Commands> {
const allModules = await Promise.all([${
modules
.map((module) => `\n${" ".repeat(8)}load${capitalize(module.name!)}Module(),`)
.join("")}
]);
export const commands: Commands = function () {
// Normal commands.
const commands = {
${modules
.flatMap((m) => m.functions)
.map((f) => unindent(4, `"dance.${f.qualifiedName}": new CommandDescriptor(
"dance.${f.qualifiedName}",
${determineFunctionExpression(f)},
${determineFunctionFlags(f)},
),`))
.sort()
.join("\n" + " ".repeat(8))}
};
return Object.freeze(
Object.fromEntries(allModules.flat().map((desc) => [desc.identifier, desc])),
);
}
// Additional commands.
${additionalCommands
.map((x) => unindent(6, `describeAdditionalCommand(
commands,
"dance.${x.qualifiedIdentifier}",
CommandDescriptor.Flags.RequiresActiveEditor | CommandDescriptor.Flags.DoNotReplay,
[${x.commands}],
);`))
.join("\n" + " ".repeat(6))}
// Finalize \`commands\`.
return Object.freeze(commands);
}();
`);
}
function capitalize(text: string) {
return text.replace(/(\.|^)[a-z]/g, (x, dot) => x.slice(dot.length).toUpperCase());
}
function determineFunctionExpression(f: Builder.ParsedFunction) {
const givenParameters: string[] = [];
let takeArgument = false;
for (const [name, type] of f.parameters) {
let match: RegExpExecArray | null;
switch (name) {
// Arguments, input.
@ -75,21 +99,6 @@ function determineFunctionExpression(f: Builder.ParsedFunction) {
givenParameters.push("argument");
break;
case "input":
takeArgument = true;
givenParameters.push("getInput(argument)");
break;
case "setInput":
takeArgument = true;
givenParameters.push("getSetInput(argument)");
break;
case "inputOr":
takeArgument = true;
givenParameters.push("getInputOr(argument)");
break;
case "direction":
takeArgument = true;
givenParameters.push("getDirection(argument)");
@ -135,7 +144,7 @@ function determineFunctionExpression(f: Builder.ParsedFunction) {
case "register":
takeArgument = true;
const match = /^RegisterOr<"(\w+)"(?:, (.+))?>$/.exec(type);
match = /^RegisterOr<"(\w+)"(?:, (.+))?>$/.exec(type);
assert(match !== null);
@ -163,6 +172,9 @@ function determineFunctionExpression(f: Builder.ParsedFunction) {
} else if (type.startsWith("Argument<")) {
takeArgument = true;
givenParameters.push("argument[" + JSON.stringify(name) + "]");
} else if (match = /^InputOr<("\w+"),.+>$/.exec(type)) {
takeArgument = true;
givenParameters.push(`getInputOr(${match[1]}, argument)`);
} else {
throw new Error(`unknown parameter ${JSON.stringify([name, type])}`);
}
@ -170,7 +182,7 @@ function determineFunctionExpression(f: Builder.ParsedFunction) {
}
const inputParameters = ["_", ...(takeArgument ? ["argument"] : [])],
call = `${f.name}(${givenParameters.join(", ")})`;
call = `${f.qualifiedName.replace(/\./g, "_")}(${givenParameters.join(", ")})`;
return `(${inputParameters.join(", ")}) => _.runAsync((_) => ${call})`;
}
@ -194,8 +206,6 @@ function determineFunctionFlags(f: Builder.ParsedFunction) {
return flags.map((flag) => "CommandDescriptor.Flags." + flag).join(" | ");
}
function buildCommandsExpression(f: Builder.AdditionalCommand) {
const commands = f.commands!.replace(/ +/g, " ");
return `(_, argument) => _.runAsync(() => runCommands(argument, ${commands}))`;
function additionalCommandName(f: Builder.AdditionalCommand) {
return f.qualifiedIdentifier!.replace(/[.-]/g, "_");
}

2083
src/commands/load-all.ts generated

File diff suppressed because it is too large Load Diff

View File

@ -10,9 +10,9 @@ import { ArgumentError, CancellationError, InputError } from "../utils/errors";
* By default, Dance also exports the following keybindings for existing
* commands:
*
* | Keybinding | Command |
* | -------------- | ----------------------------------- |
* | `s-;` (normal) | `["workbench.action.showCommands"]` |
* | Keybinding | Command |
* | -------------- | -------------------------------------------- |
* | `s-;` (normal) | `["workbench.action.showCommands", { ... }]` |
*/
declare module "./misc";
@ -48,7 +48,7 @@ const runHistory: string[] = [];
* {
* "command": "dance.run",
* "args": {
* "input": "Selections.set(Selections.filter(text => text.includes('foo')))",
* "code": "Selections.set(Selections.filter(text => text.includes('foo')))",
* },
* },
* ```
@ -61,7 +61,7 @@ const runHistory: string[] = [];
* {
* "command": "dance.run",
* "args": {
* "input": [
* "code": [
* "for (const selection of Selections.current) {",
* " console.log(text(selection));",
* "}",
@ -92,7 +92,7 @@ const runHistory: string[] = [];
* "command": "dance.run",
* "args": {
* "commands": [
* ["dance.modes.set", { "input": "normal" }],
* ["dance.modes.set", { "mode": "normal" }],
* ],
* },
* },
@ -107,7 +107,7 @@ const runHistory: string[] = [];
* "commands": [
* {
* "command": "dance.modes.set",
* "args": { "input": "normal" },
* "args": { "mode": "normal" },
* },
* ],
* },
@ -124,7 +124,7 @@ const runHistory: string[] = [];
* ["dance.selections.saveText", { "register": "^" }],
* {
* "command": "dance.modes.set",
* "args": { "input": "normal" },
* "args": { "mode": "normal" },
* },
* "hideSuggestWidget",
* ],
@ -132,13 +132,13 @@ const runHistory: string[] = [];
* },
* ```
*
* If both `input` and `commands` are given, Dance will use `run` if arbitrary
* command execution is enabled, or `commands` otherwise.
* If both `code` and `commands` are given, Dance will use `code` if arbitrary
* code execution is enabled, or `commands` otherwise.
*/
export async function run(
_: Context,
input: string,
inputOr: InputOr<string | readonly string[]>,
argument: Record<"code" | "input", string | readonly string[]>,
codeOr: InputOr<"code", string | readonly string[]>,
count: number,
repetitions: number,
@ -146,15 +146,17 @@ export async function run(
commands?: Argument<command.Any[]>,
) {
argument["code"] ??= argument["input"];
if (Array.isArray(commands)) {
if (typeof input === "string" && runIsEnabled()) {
if (typeof argument["code"] === "string" && runIsEnabled()) {
// Prefer "input" to the "commands" array.
} else {
return apiCommands(...commands);
}
}
let code = await inputOr(() => prompt({
let code = await codeOr(() => prompt({
prompt: "Code to run",
validateInput(value) {
try {
@ -192,8 +194,11 @@ export async function run(
* @keys `"` (normal)
* @noreplay
*/
export async function selectRegister(_: Context, inputOr: InputOr<string | Register>) {
const input = await inputOr(() => keypressForRegister(_));
export async function selectRegister(
_: Context,
registerOr: InputOr<"register", string | Register>,
) {
const input = await registerOr(() => keypressForRegister(_));
if (typeof input === "string") {
if (input.length === 0) {
@ -218,7 +223,7 @@ export async function updateRegister(
register: RegisterOr<"dquote", Register.Flags.CanWrite>,
copyFrom: Argument<Register | string | undefined>,
inputOr: InputOr<string>,
inputOr: InputOr<"input", string>,
) {
if (copyFrom !== undefined) {
const copyFromRegister: Register = typeof copyFrom === "string"
@ -271,7 +276,7 @@ export async function updateCount(
_: Context,
count: number,
extension: Extension,
inputOr: InputOr<number>,
countOr: InputOr<"count", number>,
addDigits?: Argument<number>,
) {
@ -292,7 +297,7 @@ export async function updateCount(
return;
}
const input = +await inputOr(() => promptNumber({ integer: true, range: [0, 1_000_000] }, _));
const input = +await countOr(() => promptNumber({ integer: true, range: [0, 1_000_000] }, _));
InputError.validateInput(!isNaN(input), "value is not a number");
InputError.validateInput(input >= 0, "value is negative");
@ -318,30 +323,30 @@ const menuHistory: string[] = [];
export async function openMenu(
_: Context.WithoutActiveEditor,
inputOr: InputOr<string>,
menu?: Argument<Menu>,
menuOr: InputOr<"menu", string | Menu>,
prefix?: Argument<string>,
pass: Argument<any[]> = [],
locked: Argument<boolean> = false,
delay: Argument<number> = 0,
title?: Argument<string>,
) {
if (typeof menu !== "object") {
const menus = _.extension.menus;
const input = await inputOr(() => prompt({
prompt: "Menu name",
validateInput(value) {
if (menus.has(value)) {
return;
}
const menus = _.extension.menus;
return `menu ${JSON.stringify(value)} does not exist`;
},
placeHolder: [...menus.keys()].sort().join(", ") || "no menu defined",
history: menuHistory,
}, _));
let menu = await menuOr(() => prompt({
prompt: "Menu name",
validateInput(value) {
if (menus.has(value)) {
return;
}
menu = findMenu(input, _);
return `menu ${JSON.stringify(value)} does not exist`;
},
placeHolder: [...menus.keys()].sort().join(", ") || "no menu defined",
history: menuHistory,
}, _));
if (typeof menu === "string") {
menu = findMenu(menu, _);
}
if (title !== undefined) {

View File

@ -11,24 +11,24 @@ declare module "./modes";
*
* #### Variants
*
* | Title | Identifier | Keybinding | Command |
* | ------------------ | ------------ | ----------------- | ------------------------------------------------------------ |
* | Set mode to Normal | `set.normal` | `escape` (insert) | `[".modes.set", { input: "normal" }], ["hideSuggestWidget"]` |
* | Set mode to Insert | `set.insert` | | `[".modes.set", { input: "insert" }]` |
* | Title | Identifier | Keybinding | Command |
* | ------------------ | ------------ | ----------------- | ----------------------------------------------------------- |
* | Set mode to Normal | `set.normal` | `escape` (insert) | `[".modes.set", { mode: "normal" }], ["hideSuggestWidget"]` |
* | Set mode to Insert | `set.insert` | | `[".modes.set", { mode: "insert" }]` |
*
* Other variants are provided to switch to insert mode:
*
* | Title | Identifier | Keybinding | Commands |
* | -------------------- | ------------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
* | Insert before | `insert.before` | `i` (normal) | `[".selections.faceBackward", { record: false }], [".modes.set", { input: "insert" }], [".selections.reduce", { where: "start", record: false, empty: true }]` |
* | Insert after | `insert.after` | `a` (normal) | `[".selections.faceForward" , { record: false }], [".modes.set", { input: "insert" }], [".selections.reduce", { where: "end" , record: false, empty: true }]` |
* | Insert at line start | `insert.lineStart` | `s-i` (normal) | `[".select.lineStart", { shift: "jump", skipBlank: true }], [".modes.set", { input: "insert" }], [".selections.reduce", { where: "start", record: false, empty: true }]` |
* | Insert at line end | `insert.lineEnd` | `s-a` (normal) | `[".select.lineEnd" , { shift: "jump" }], [".modes.set", { input: "insert" }], [".selections.reduce", { where: "end" , record: false, empty: true }]` |
* | Title | Identifier | Keybinding | Commands |
* | -------------------- | ------------------ | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
* | Insert before | `insert.before` | `i` (normal) | `[".selections.faceBackward", { record: false }], [".modes.set", { mode: "insert", +mode }], [".selections.reduce", { where: "start", record: false, empty: true, ... }]` |
* | Insert after | `insert.after` | `a` (normal) | `[".selections.faceForward" , { record: false }], [".modes.set", { mode: "insert", +mode }], [".selections.reduce", { where: "end" , record: false, empty: true, ... }]` |
* | Insert at line start | `insert.lineStart` | `s-i` (normal) | `[".select.lineStart", { shift: "jump", skipBlank: true }], [".modes.set", { mode: "insert", +mode }], [".selections.reduce", { where: "start", record: false, empty: true, ... }]` |
* | Insert at line end | `insert.lineEnd` | `s-a` (normal) | `[".select.lineEnd" , { shift: "jump" }], [".modes.set", { mode: "insert", +mode }], [".selections.reduce", { where: "end" , record: false, empty: true, ... }]` |
*
* @noreplay
*/
export async function set(_: Context, inputOr: InputOr<string>) {
await toMode(await inputOr(() => prompt(validateModeName())));
export async function set(_: Context, modeOr: InputOr<"mode", string>) {
await toMode(await modeOr(() => prompt(validateModeName())));
}
/**
@ -36,15 +36,15 @@ export async function set(_: Context, inputOr: InputOr<string>) {
*
* #### Variants
*
* | Title | Identifier | Keybindings | Commands |
* | --------------------- | ------------------------ | -------------- | ------------------------------------------------- |
* | Temporary Normal mode | `set.temporarily.normal` | `c-v` (insert) | `[".modes.set.temporarily", { input: "normal" }]` |
* | Temporary Insert mode | `set.temporarily.insert` | `c-v` (normal) | `[".modes.set.temporarily", { input: "insert" }]` |
* | Title | Identifier | Keybindings | Commands |
* | --------------------- | ------------------------ | -------------- | ------------------------------------------------ |
* | Temporary Normal mode | `set.temporarily.normal` | `c-v` (insert) | `[".modes.set.temporarily", { mode: "normal" }]` |
* | Temporary Insert mode | `set.temporarily.insert` | `c-v` (normal) | `[".modes.set.temporarily", { mode: "insert" }]` |
*
* @noreplay
*/
export async function set_temporarily(_: Context, inputOr: InputOr<string>, repetitions: number) {
await toMode(await inputOr(() => prompt(validateModeName())), repetitions);
export async function set_temporarily(_: Context, modeOr: InputOr<"mode", string>, repetitions: number) {
await toMode(await modeOr(() => prompt(validateModeName())), repetitions);
}
const modeHistory: string[] = [];

View File

@ -1,6 +1,6 @@
import * as vscode from "vscode";
import type { Argument, Input, RegisterOr, SetInput } from ".";
import type { Argument, RegisterOr } from ".";
import { search as apiSearch, Context, Direction, EmptySelectionsError, manipulateSelectionsInteractively, Positions, promptRegexpOpts, Selections, Shift } from "../api";
import type { Register } from "../state/registers";
import { CharSet, getCharSetFunction } from "../utils/charset";
@ -16,11 +16,11 @@ declare module "./search";
*
* @keys `/` (normal), `NumPad_Divide` (normal)
*
* | Title | Identifier | Keybinding | Command |
* | ------------------------ | ----------------- | -------------- | ------------------------------------------------- |
* | Search (extend) | `extend` | `?` (normal) | `[".search", { shift: "extend" }]` |
* | Search backward | `backward` | `a-/` (normal) | `[".search", { direction: -1 }]` |
* | Search backward (extend) | `backward.extend` | `a-?` (normal) | `[".search", { direction: -1, shift: "extend" }]` |
* | Title | Identifier | Keybinding | Command |
* | ------------------------ | ----------------- | -------------- | ------------------------------------------------------ |
* | Search (extend) | `extend` | `?` (normal) | `[".search", { shift: "extend", ... }]` |
* | Search backward | `backward` | `a-/` (normal) | `[".search", { direction: -1 , ... }]` |
* | Search backward (extend) | `backward.extend` | `a-?` (normal) | `[".search", { direction: -1, shift: "extend", ... }]` |
*/
export async function search(
_: Context,
@ -32,10 +32,9 @@ export async function search(
interactive: Argument<boolean> = true,
shift: Shift = Shift.Jump,
input: Input<string | RegExp>,
setInput: SetInput<RegExp>,
argument: { input?: string | RegExp },
) {
return manipulateSelectionsInteractively(_, input, setInput, interactive, {
return manipulateSelectionsInteractively(_, "input", argument, interactive, {
...promptRegexpOpts("mu"),
value: (await register.get())?.[0],
}, (input, selections) => {
@ -84,9 +83,9 @@ export async function search(
*
* @keys `a-*` (normal)
*
* | Title | Identifier | Keybinding | Command |
* | -------------------------------- | ----------------- | ---------------------------------------- | ---------------------------------------- |
* | Search current selection (smart) | `selection.smart` | `*` (normal), `NumPad_Multiply` (normal) | `[".search.selection", { smart: true }]` |
* | Title | Identifier | Keybinding | Command |
* | -------------------------------- | ----------------- | ---------------------------------------- | --------------------------------------------------- |
* | Search current selection (smart) | `selection.smart` | `*` (normal), `NumPad_Multiply` (normal) | `[".search.selection", { smart: true, +register }]` |
*/
export function selection(
document: vscode.TextDocument,
@ -152,11 +151,11 @@ export function selection(
*
* @keys `n` (normal)
*
* | Title | Identifier | Keybinding | Command |
* | --------------------- | -------------- | ---------------- | ------------------------------------------------ |
* | Add next match | `next.add` | `s-n` (normal) | `[".search.next", { add: true }]` |
* | Select previous match | `previous` | `a-n` (normal) | `[".search.next", { direction: -1 }]` |
* | Add previous match | `previous.add` | `s-a-n` (normal) | `[".search.next", { direction: -1, add: true }]` |
* | Title | Identifier | Keybinding | Command |
* | --------------------- | -------------- | ---------------- | ----------------------------------------------------- |
* | Add next match | `next.add` | `s-n` (normal) | `[".search.next", { add: true, ... }]` |
* | Select previous match | `previous` | `a-n` (normal) | `[".search.next", { direction: -1 , ... }]` |
* | Add previous match | `previous.add` | `s-a-n` (normal) | `[".search.next", { direction: -1, add: true, ... }]` |
*/
export async function next(
_: Context,

View File

@ -18,19 +18,19 @@ declare module "./seek";
*
* #### Variants
*
* | Title | Identifier | Keybinding | Command |
* | ---------------------------------------- | -------------------------- | ---------------- | -------------------------------------------------------------- |
* | Extend to character (excluded) | `extend` | `s-t` (normal) | `[".seek", { shift: "extend" }]` |
* | Select to character (excluded, backward) | `backward` | `a-t` (normal) | `[".seek", { direction: -1 }]` |
* | Extend to character (excluded, backward) | `extend.backward` | `s-a-t` (normal) | `[".seek", { shift: "extend", direction: -1 }]` |
* | Select to character (included) | `included` | `f` (normal) | `[".seek", { include: true }]` |
* | Extend to character (included) | `included.extend` | `s-f` (normal) | `[".seek", { include: true, shift: "extend" }]` |
* | Select to character (included, backward) | `included.backward` | `a-f` (normal) | `[".seek", { include: true, direction: -1 }]` |
* | Extend to character (included, backward) | `included.extend.backward` | `s-a-f` (normal) | `[".seek", { include: true, shift: "extend", direction: -1 }]` |
* | Title | Identifier | Keybinding | Command |
* | ---------------------------------------- | -------------------------- | ---------------- | ------------------------------------------------------------------- |
* | Extend to character (excluded) | `extend` | `s-t` (normal) | `[".seek", { shift: "extend" , ... }]` |
* | Select to character (excluded, backward) | `backward` | `a-t` (normal) | `[".seek", { direction: -1, ... }]` |
* | Extend to character (excluded, backward) | `extend.backward` | `s-a-t` (normal) | `[".seek", { shift: "extend", direction: -1, ... }]` |
* | Select to character (included) | `included` | `f` (normal) | `[".seek", { include: true , ... }]` |
* | Extend to character (included) | `included.extend` | `s-f` (normal) | `[".seek", { include: true, shift: "extend" , ... }]` |
* | Select to character (included, backward) | `included.backward` | `a-f` (normal) | `[".seek", { include: true, direction: -1, ... }]` |
* | Extend to character (included, backward) | `included.extend.backward` | `s-a-f` (normal) | `[".seek", { include: true, shift: "extend", direction: -1, ... }]` |
*/
export async function seek(
_: Context,
inputOr: InputOr<string>,
inputOr: InputOr<"input", string>,
repetitions: number,
direction = Direction.Forward,
@ -83,11 +83,11 @@ const defaultEnclosingPatterns = [
*
* #### Variants
*
* | Title | Identifier | Keybinding | Command |
* | -------------------------------------- | --------------------------- | ---------------- | --------------------------------------------------------- |
* | Extend to next enclosing character | `enclosing.extend` | `s-m` (normal) | `[".seek.enclosing", { shift: "extend" }]` |
* | Select to previous enclosing character | `enclosing.backward` | `a-m` (normal) | `[".seek.enclosing", { direction: -1 }]` |
* | Extend to previous enclosing character | `enclosing.extend.backward` | `s-a-m` (normal) | `[".seek.enclosing", { shift: "extend", direction: -1 }]` |
* | Title | Identifier | Keybinding | Command |
* | -------------------------------------- | --------------------------- | ---------------- | -------------------------------------------------------------- |
* | Extend to next enclosing character | `enclosing.extend` | `s-m` (normal) | `[".seek.enclosing", { shift: "extend" , ... }]` |
* | Select to previous enclosing character | `enclosing.backward` | `a-m` (normal) | `[".seek.enclosing", { direction: -1, ... }]` |
* | Extend to previous enclosing character | `enclosing.extend.backward` | `s-a-m` (normal) | `[".seek.enclosing", { shift: "extend", direction: -1, ... }]` |
*/
export function enclosing(
_: Context,
@ -178,19 +178,19 @@ export function enclosing(
*
* #### Variants
*
* | Title | Identifier | Keybinding | Command |
* | -------------------------------------------- | ------------------------- | ---------------- | -------------------------------------------------------------------------------- |
* | Extend to next word start | `word.extend` | `s-w` (normal) | `[".seek.word", { shift: "extend" }]` |
* | Select to previous word start | `word.backward` | `b` (normal) | `[".seek.word", { direction: -1 }]` |
* | Extend to previous word start | `word.extend.backward` | `s-b` (normal) | `[".seek.word", { shift: "extend", direction: -1 }]` |
* | Select to next non-whitespace word start | `word.ws` | `a-w` (normal) | `[".seek.word", { ws: true }]` |
* | Extend to next non-whitespace word start | `word.ws.extend` | `s-a-w` (normal) | `[".seek.word", { ws: true, shift: "extend" }]` |
* | Select to previous non-whitespace word start | `word.ws.backward` | `a-b` (normal) | `[".seek.word", { ws: true, direction: -1 }]` |
* | Extend to previous non-whitespace word start | `word.ws.extend.backward` | `s-a-b` (normal) | `[".seek.word", { ws: true, shift: "extend", direction: -1 }]` |
* | Select to next word end | `wordEnd` | `e` (normal) | `[".seek.word", { stopAtEnd: true }]` |
* | Extend to next word end | `wordEnd.extend` | `s-e` (normal) | `[".seek.word", { stopAtEnd: true , shift: "extend" }]` |
* | Select to next non-whitespace word end | `wordEnd.ws` | `a-e` (normal) | `[".seek.word", { stopAtEnd: true , ws: true }]` |
* | Extend to next non-whitespace word end | `wordEnd.ws.extend` | `s-a-e` (normal) | `[".seek.word", { stopAtEnd: true , ws: true, shift: "extend" }]` |
* | Title | Identifier | Keybinding | Command |
* | -------------------------------------------- | ------------------------- | ---------------- | ------------------------------------------------------------------------------------- |
* | Extend to next word start | `word.extend` | `s-w` (normal) | `[".seek.word", { shift: "extend" , ... }]` |
* | Select to previous word start | `word.backward` | `b` (normal) | `[".seek.word", { direction: -1, ... }]` |
* | Extend to previous word start | `word.extend.backward` | `s-b` (normal) | `[".seek.word", { shift: "extend", direction: -1, ... }]` |
* | Select to next non-whitespace word start | `word.ws` | `a-w` (normal) | `[".seek.word", { ws: true , ... }]` |
* | Extend to next non-whitespace word start | `word.ws.extend` | `s-a-w` (normal) | `[".seek.word", { ws: true, shift: "extend" , ... }]` |
* | Select to previous non-whitespace word start | `word.ws.backward` | `a-b` (normal) | `[".seek.word", { ws: true, direction: -1, ... }]` |
* | Extend to previous non-whitespace word start | `word.ws.extend.backward` | `s-a-b` (normal) | `[".seek.word", { ws: true, shift: "extend", direction: -1, ... }]` |
* | Select to next word end | `wordEnd` | `e` (normal) | `[".seek.word", { stopAtEnd: true , ... }]` |
* | Extend to next word end | `wordEnd.extend` | `s-e` (normal) | `[".seek.word", { stopAtEnd: true , shift: "extend" , ... }]` |
* | Select to next non-whitespace word end | `wordEnd.ws` | `a-e` (normal) | `[".seek.word", { stopAtEnd: true , ws: true , ... }]` |
* | Extend to next non-whitespace word end | `wordEnd.ws.extend` | `s-a-e` (normal) | `[".seek.word", { stopAtEnd: true , ws: true, shift: "extend" , ... }]` |
*/
export function word(
_: Context,
@ -264,23 +264,23 @@ let lastObjectInput: string | undefined;
*
* #### Variants
*
* | Title | Identifier | Keybinding | Command |
* | ---------------------------- | ------------------------------ | ------------------------------ | ---------------------------------------------------------------------------------------------- |
* | Select whole object | `askObject` | `a-a` (normal), `a-a` (insert) | `[".openMenu", { input: "object", title: "Select whole object..." }]` |
* | Select inner object | `askObject.inner` | `a-i` (normal), `a-i` (insert) | `[".openMenu", { input: "object", pass: [{ inner: true }], title: "Select inner object..." }]` |
* | Select to whole object start | `askObject.start` | `[` (normal) | `[".openMenu", { input: "object", pass: [{ where: "start" }] }]` |
* | Extend to whole object start | `askObject.start.extend` | `{` (normal) | `[".openMenu", { input: "object", pass: [{ where: "start", shift: "extend" }] }]` |
* | Select to inner object start | `askObject.inner.start` | `a-[` (normal) | `[".openMenu", { input: "object", pass: [{ inner: true, where: "start" }] }]` |
* | Extend to inner object start | `askObject.inner.start.extend` | `a-{` (normal) | `[".openMenu", { input: "object", pass: [{ inner: true, where: "start", shift: "extend" }] }]` |
* | Select to whole object end | `askObject.end` | `]` (normal) | `[".openMenu", { input: "object", pass: [{ where: "end" }] }]` |
* | Extend to whole object end | `askObject.end.extend` | `}` (normal) | `[".openMenu", { input: "object", pass: [{ where: "end" , shift: "extend" }] }]` |
* | Select to inner object end | `askObject.inner.end` | `a-]` (normal) | `[".openMenu", { input: "object", pass: [{ inner: true, where: "end" }] }]` |
* | Extend to inner object end | `askObject.inner.end.extend` | `a-}` (normal) | `[".openMenu", { input: "object", pass: [{ inner: true, where: "end" , shift: "extend" }] }]` |
* | Title | Identifier | Keybinding | Command |
* | ---------------------------- | ------------------------------ | ------------------------------ | --------------------------------------------------------------------------------------------- |
* | Select whole object | `askObject` | `a-a` (normal), `a-a` (insert) | `[".openMenu", { menu: "object", title: "Select whole object..." }]` |
* | Select inner object | `askObject.inner` | `a-i` (normal), `a-i` (insert) | `[".openMenu", { menu: "object", pass: [{ inner: true }], title: "Select inner object..." }]` |
* | Select to whole object start | `askObject.start` | `[` (normal) | `[".openMenu", { menu: "object", pass: [{ where: "start" }] }]` |
* | Extend to whole object start | `askObject.start.extend` | `{` (normal) | `[".openMenu", { menu: "object", pass: [{ where: "start", shift: "extend" }] }]` |
* | Select to inner object start | `askObject.inner.start` | `a-[` (normal) | `[".openMenu", { menu: "object", pass: [{ inner: true, where: "start" }] }]` |
* | Extend to inner object start | `askObject.inner.start.extend` | `a-{` (normal) | `[".openMenu", { menu: "object", pass: [{ inner: true, where: "start", shift: "extend" }] }]` |
* | Select to whole object end | `askObject.end` | `]` (normal) | `[".openMenu", { menu: "object", pass: [{ where: "end" }] }]` |
* | Extend to whole object end | `askObject.end.extend` | `}` (normal) | `[".openMenu", { menu: "object", pass: [{ where: "end" , shift: "extend" }] }]` |
* | Select to inner object end | `askObject.inner.end` | `a-]` (normal) | `[".openMenu", { menu: "object", pass: [{ inner: true, where: "end" }] }]` |
* | Extend to inner object end | `askObject.inner.end.extend` | `a-}` (normal) | `[".openMenu", { menu: "object", pass: [{ inner: true, where: "end" , shift: "extend" }] }]` |
*/
export async function object(
_: Context,
inputOr: InputOr<string>,
inputOr: InputOr<"input", string>,
inner: Argument<boolean> = false,
where?: Argument<"start" | "end">,
shift = Shift.Select,

View File

@ -36,12 +36,12 @@ const preferredColumnsToken =
*
* #### Variants
*
* | Title | Identifier | Keybinding | Command |
* | ----------- | ------------- | --------------------------------- | ------------------------------------------------------------ |
* | Jump down | `down.jump` | `j` (normal) , `down` (normal) | `[".select.vertically", { direction: 1, shift: "jump" }]` |
* | Extend down | `down.extend` | `s-j` (normal), `s-down` (normal) | `[".select.vertically", { direction: 1, shift: "extend" }]` |
* | Jump up | `up.jump` | `k` (normal) , `up` (normal) | `[".select.vertically", { direction: -1, shift: "jump" }]` |
* | Extend up | `up.extend` | `s-k` (normal), `s-up` (normal) | `[".select.vertically", { direction: -1, shift: "extend" }]` |
* | Title | Identifier | Keybinding | Command |
* | ----------- | ------------- | --------------------------------- | ----------------------------------------------------------------- |
* | Jump down | `down.jump` | `j` (normal) , `down` (normal) | `[".select.vertically", { direction: 1, shift: "jump" , ... }]` |
* | Extend down | `down.extend` | `s-j` (normal), `s-down` (normal) | `[".select.vertically", { direction: 1, shift: "extend", ... }]` |
* | Jump up | `up.jump` | `k` (normal) , `up` (normal) | `[".select.vertically", { direction: -1, shift: "jump" , ... }]` |
* | Extend up | `up.extend` | `s-k` (normal), `s-up` (normal) | `[".select.vertically", { direction: -1, shift: "extend", ... }]` |
*
* The following keybindings are also defined:
*
@ -222,12 +222,12 @@ export function vertically(
*
* #### Variants
*
* | Title | Identifier | Keybinding | Command |
* | ------------ | -------------- | ---------------------------------- | -------------------------------------------------------------- |
* | Jump right | `right.jump` | `l` (normal) , `right` (normal) | `[".select.horizontally", { direction: 1, shift: "jump" }]` |
* | Extend right | `right.extend` | `s-l` (normal), `s-right` (normal) | `[".select.horizontally", { direction: 1, shift: "extend" }]` |
* | Jump left | `left.jump` | `h` (normal) , `left` (normal) | `[".select.horizontally", { direction: -1, shift: "jump" }]` |
* | Extend left | `left.extend` | `s-h` (normal), `s-left` (normal) | `[".select.horizontally", { direction: -1, shift: "extend" }]` |
* | Title | Identifier | Keybinding | Command |
* | ------------ | -------------- | ---------------------------------- | ------------------------------------------------------------------- |
* | Jump right | `right.jump` | `l` (normal) , `right` (normal) | `[".select.horizontally", { direction: 1, shift: "jump" , ... }]` |
* | Extend right | `right.extend` | `s-l` (normal), `s-right` (normal) | `[".select.horizontally", { direction: 1, shift: "extend", ... }]` |
* | Jump left | `left.jump` | `h` (normal) , `left` (normal) | `[".select.horizontally", { direction: -1, shift: "jump" , ... }]` |
* | Extend left | `left.extend` | `s-h` (normal), `s-left` (normal) | `[".select.horizontally", { direction: -1, shift: "extend", ... }]` |
*/
export function horizontally(
_: Context,
@ -291,10 +291,10 @@ export function horizontally(
*
* #### Variants
*
* | Title | Identifier | Keybinding | Command |
* | --------- | ----------- | -------------- | ------------------------------------- |
* | Go to | `to.jump` | `g` (normal) | `[".select.to", { shift: "jump" }]` |
* | Extend to | `to.extend` | `s-g` (normal) | `[".select.to", { shift: "extend" }]` |
* | Title | Identifier | Keybinding | Command |
* | --------- | ----------- | -------------- | ------------------------------------------ |
* | Go to | `to.jump` | `g` (normal) | `[".select.to", { shift: "jump" , ... }]` |
* | Extend to | `to.extend` | `s-g` (normal) | `[".select.to", { shift: "extend", ... }]` |
*/
export function to(
_: Context,
@ -456,14 +456,14 @@ export function line_above_extend(_: Context, count: number) {
*
* #### Variants
*
* | Title | Identifier | Keybinding | Command |
* | -------------------- | ------------------ | ----------------------------------- | ------------------------------------------------------------- |
* | Jump to line start | `lineStart.jump` | | `[".select.lineStart", { shift: "jump" }]` |
* | Extend to line start | `lineStart.extend` | `s-a-h` (normal), `s-home` (normal) | `[".select.lineStart", { shift: "extend" }]` |
* | Jump to line start (skip blank) | `lineStart.skipBlank.jump` | | `[".select.lineStart", { skipBlank: true, shift: "jump" }]` |
* | Extend to line start (skip blank) | `lineStart.skipBlank.extend` | | `[".select.lineStart", { skipBlank: true, shift: "extend" }]` |
* | Jump to first line | `firstLine.jump` | | `[".select.lineStart", { count: 0, shift: "jump" }]` |
* | Extend to first line | `firstLine.extend` | | `[".select.lineStart", { count: 0, shift: "extend" }]` |
* | Title | Identifier | Keybinding | Command |
* | -------------------- | ------------------ | ----------------------------------- | ------------------------------------------------------------------ |
* | Jump to line start | `lineStart.jump` | | `[".select.lineStart", { shift: "jump" , ... }]` |
* | Extend to line start | `lineStart.extend` | `s-a-h` (normal), `s-home` (normal) | `[".select.lineStart", { shift: "extend", ... }]` |
* | Jump to line start (skip blank) | `lineStart.skipBlank.jump` | | `[".select.lineStart", { skipBlank: true, shift: "jump" , ... }]` |
* | Extend to line start (skip blank) | `lineStart.skipBlank.extend` | | `[".select.lineStart", { skipBlank: true, shift: "extend", ... }]` |
* | Jump to first line | `firstLine.jump` | | `[".select.lineStart", { count: 0, shift: "jump" , ... }]` |
* | Extend to first line | `firstLine.extend` | | `[".select.lineStart", { count: 0, shift: "extend", ... }]` |
*/
export function lineStart(
_: Context,
@ -506,11 +506,11 @@ export function lineStart(
*
* #### Variants
*
* | Title | Identifier | Keybinding | Command |
* | ------------------------ | -------------------- | ---------------------------------- | ---------------------------------------------------------- |
* | Extend to line end | `lineEnd.extend` | `s-a-l` (normal), `s-end` (normal) | `[".select.lineEnd", { shift: "extend" }]` |
* | Jump to last character | `documentEnd.jump` | | `[".select.lineEnd", { count: MAX_INT, shift: "jump" }]` |
* | Extend to last character | `documentEnd.extend` | | `[".select.lineEnd", { count: MAX_INT, shift: "extend" }]` |
* | Title | Identifier | Keybinding | Command |
* | ------------------------ | -------------------- | ---------------------------------- | --------------------------------------------------------------- |
* | Extend to line end | `lineEnd.extend` | `s-a-l` (normal), `s-end` (normal) | `[".select.lineEnd", { shift: "extend", ... }]` |
* | Jump to last character | `documentEnd.jump` | | `[".select.lineEnd", { count: MAX_INT, shift: "jump" , ... }]` |
* | Extend to last character | `documentEnd.extend` | | `[".select.lineEnd", { count: MAX_INT, shift: "extend", ... }]` |
*/
export function lineEnd(
_: Context,

View File

@ -1,6 +1,6 @@
import * as vscode from "vscode";
import type { Argument, Input, InputOr, RegisterOr, SetInput } from ".";
import type { Argument, InputOr, RegisterOr } from ".";
import { Context, Direction, manipulateSelectionsInteractively, moveWhile, moveWhileBackward, moveWhileForward, Positions, prompt, promptOne, promptRegexpOpts, SelectionBehavior, Selections, switchRun, validateForSwitchRun } from "../api";
import { PerEditorState } from "../state/editors";
import { Mode } from "../state/modes";
@ -117,9 +117,9 @@ export function restore(
*
* The following keybinding is also available:
*
* | Keybinding | Command |
* | ---------------- | -------------------------------------------------------- |
* | `s-a-z` (normal) | `[".selections.restore.withCurrent", { reverse: true }]` |
* | Keybinding | Command |
* | ---------------- | ------------------------------------------------------------- |
* | `s-a-z` (normal) | `[".selections.restore.withCurrent", { reverse: true, ... }]` |
*
* See https://github.com/mawww/kakoune/blob/master/doc/pages/keys.asciidoc#marks
*/
@ -248,16 +248,16 @@ const pipeHistory: string[] = [];
*
* #### Additional commands
*
* | Title | Identifier | Keybinding | Commands |
* | ------------------- | -------------- | -------------- | --------------------------------------------------------------------------- |
* | Pipe and replace | `pipe.replace` | `|` (normal) | `[".selections.pipe"], [".edit.insert", { register: "|" }]` |
* | Pipe and append | `pipe.append` | `!` (normal) | `[".selections.pipe"], [".edit.insert", { register: "|", where: "end" }]` |
* | Pipe and prepend | `pipe.prepend` | `a-!` (normal) | `[".selections.pipe"], [".edit.insert", { register: "|", where: "start" }]` |
* | Title | Identifier | Keybinding | Commands |
* | ------------------- | -------------- | -------------- | ----------------------------------------------------------------------------------------------------- |
* | Pipe and replace | `pipe.replace` | `|` (normal) | `[".selections.pipe", { +input,register }], [".edit.insert", { register: "|" , ... }]` |
* | Pipe and append | `pipe.append` | `!` (normal) | `[".selections.pipe", { +input,register }], [".edit.insert", { register: "|", where: "end" , ... }]` |
* | Pipe and prepend | `pipe.prepend` | `a-!` (normal) | `[".selections.pipe", { +input,register }], [".edit.insert", { register: "|", where: "start", ... }]` |
*/
export async function pipe(
_: Context,
register: RegisterOr<"pipe", Register.Flags.CanWrite>,
inputOr: InputOr<string>,
inputOr: InputOr<"input", string>,
) {
const input = await inputOr(() => prompt({
prompt: "Expression",
@ -311,18 +311,17 @@ const filterHistory: string[] = [];
*
* #### Variants
*
* | Title | Identifier | Keybinding | Commands |
* | -------------------------- | ----------------------- | ------------------ | -------------------------------------------------------------- |
* | Keep matching selections | `filter.regexp` | `a-k` (normal) | `[".selections.filter", { defaultInput: "/" }]` |
* | Clear matching selections | `filter.regexp.inverse` | `s-a-k` (normal) | `[".selections.filter", { defaultInput: "/", inverse: true }]` |
* | Clear secondary selections | `clear.secondary` | `space` (normal) | `[".selections.filter", { input: "i === count" }]` |
* | Clear main selections | `clear.main` | `a-space` (normal) | `[".selections.filter", { input: "i !== count" }]` |
* | Title | Identifier | Keybinding | Commands |
* | -------------------------- | ----------------------- | ------------------ | ------------------------------------------------------------------- |
* | Keep matching selections | `filter.regexp` | `a-k` (normal) | `[".selections.filter", { defaultInput: "/" , ... }]` |
* | Clear matching selections | `filter.regexp.inverse` | `s-a-k` (normal) | `[".selections.filter", { defaultInput: "/", inverse: true, ... }]` |
* | Clear secondary selections | `clear.secondary` | `space` (normal) | `[".selections.filter", { input: "i === count" , ... }]` |
* | Clear main selections | `clear.main` | `a-space` (normal) | `[".selections.filter", { input: "i !== count" , ... }]` |
*/
export function filter(
_: Context,
input: Input<string>,
setInput: SetInput<string>,
argument: { input?: string },
defaultInput?: Argument<string>,
inverse: Argument<boolean> = false,
interactive: Argument<boolean> = true,
@ -331,7 +330,7 @@ export function filter(
const document = _.document,
strings = _.selections.map((selection) => document.getText(selection));
return manipulateSelectionsInteractively(_, input, setInput, interactive, {
return manipulateSelectionsInteractively(_, "input", argument, interactive, {
prompt: "Expression",
validateInput(value) {
try {
@ -365,13 +364,12 @@ export function select(
_: Context,
interactive: Argument<boolean> = true,
input: Input<string | RegExp>,
setInput: SetInput<RegExp>,
argument: { input?: string | RegExp },
) {
return manipulateSelectionsInteractively(
_,
input,
setInput,
"input",
argument,
interactive,
promptRegexpOpts("mu"),
(input, selections) => {
@ -396,13 +394,12 @@ export function split(
excludeEmpty: Argument<boolean> = false,
interactive: Argument<boolean> = true,
input: Input<string | RegExp>,
setInput: SetInput<RegExp>,
argument: { input?: string | RegExp },
) {
return manipulateSelectionsInteractively(
_,
input,
setInput,
"input",
argument,
interactive,
promptRegexpOpts("mu"),
(input, selections) => {
@ -579,9 +576,9 @@ export function trimWhitespace(_: Context) {
*
* #### Variant
*
* | Title | Identifier | Keybinding | Command |
* | ------------------------------- | -------------- | ---------------- | --------------------------------------------------------- |
* | Reduce selections to their ends | `reduce.edges` | `s-a-s` (normal) | `[".selections.reduce", { where: "both", empty: false }]` |
* | Title | Identifier | Keybinding | Command |
* | ------------------------------- | -------------- | ---------------- | -------------------------------------------------------------- |
* | Reduce selections to their ends | `reduce.edges` | `s-a-s` (normal) | `[".selections.reduce", { where: "both", empty: false, ... }]` |
*/
export function reduce(
_: Context,
@ -775,10 +772,10 @@ const indicesToken = PerEditorState.registerState<AutoDisposable>(/* isDisposabl
*
* #### Variants
*
* | Title | Identifier | Command |
* | ---------------------- | ------------- | --------------------------------------------------- |
* | Show selection indices | `showIndices` | `[".selections.toggleIndices", { display: true }]` |
* | Hide selection indices | `hideIndices` | `[".selections.toggleIndices", { display: false }]` |
* | Title | Identifier | Command |
* | ---------------------- | ------------- | -------------------------------------------------------- |
* | Show selection indices | `showIndices` | `[".selections.toggleIndices", { display: true , ... }]` |
* | Hide selection indices | `hideIndices` | `[".selections.toggleIndices", { display: false, ... }]` |
*/
export function toggleIndices(
_: Context,

View File

@ -8,10 +8,10 @@ import { Context, Selections } from "../api";
*
* #### Predefined keybindings
*
* | Title | Keybinding | Command |
* | ----------------------- | -------------- | ------------------------------------------------ |
* | Show view menu | `v` (normal) | `[".openMenu", { input: "view" }]` |
* | Show view menu (locked) | `s-v` (normal) | `[".openMenu", { input: "view", locked: true }]` |
* | Title | Keybinding | Command |
* | ----------------------- | -------------- | ---------------------------------------------------- |
* | Show view menu | `v` (normal) | `[".openMenu", { menu: "view", ... }]` |
* | Show view menu (locked) | `s-v` (normal) | `[".openMenu", { menu: "view", locked: true, ... }]` |
*/
declare module "./view";

View File

@ -1,7 +1,6 @@
import * as vscode from "vscode";
import * as api from "./api";
import { loadCommands } from "./commands/load-all";
import { Extension } from "./state/extension";
import { extensionId, extensionName } from "./utils/constants";
@ -15,7 +14,7 @@ let isActivated = false;
/**
* Function called by VS Code to activate the extension.
*/
export function activate() {
export async function activate() {
isActivated = true;
const extensionData = vscode.extensions.getExtension(extensionId),
@ -31,12 +30,13 @@ export function activate() {
api.disableExecuteFunction();
}
return loadCommands().then((commands) => {
if (isActivated) {
return { api, extension: (extensionState = new Extension(commands)) };
}
const { commands } = await import("./commands/load-all");
if (!isActivated) {
return;
});
}
return { api, extension: (extensionState = new Extension(commands)) };
}
/**

View File

@ -3,7 +3,7 @@
"module": "CommonJS",
"target": "ES2018",
"outDir": "out",
"lib": ["ES2019", "ESNext.WeakRef"],
"lib": ["ES2020", "ESNext.WeakRef"],
"sourceMap": true,
"strict": true,
"noImplicitReturns": true,