diff --git a/README.md b/README.md index 803504e..e9e1225 100644 --- a/README.md +++ b/README.md @@ -4,16 +4,43 @@ This extension can create command sequence as one command and bind a key, or cal ## Features -* create command sequence as one command and bind a key. -* call command sequence manually. -* set interval between each command execution. +- create command sequence as one command and bind a key. +- call command sequence manually. +- set interval between each command execution. ## Extension Settings -Settings has 2 steps. +There is simple usage that uses only keybindings.json and a usage that uses settings.json. + +### Simple Usage with keybindings.json + +In keybindings.json, bind a key to `extension.multiCommand.execute` with passing a command sequence you want to execute as the argument. +For example: + +```json +{ + "key": "alt+x", + "command": "extension.multiCommand.execute", + "args": { + "sequence": [ + "cursorDown", + "cursorDown", + "cursorDown" + ] + } +} +``` +This command sequence executes "cursorDown" command 3 times. + +### Usage with settings.json. + +This usage is useful for resusing the defined command sequence in another command sequnce or executing the sequence manually. + +In case using settings.json, the settings has 2 steps. 1. Create command sequence as one command in settings.json. For example: + ```json "multiCommand.commands": [ { @@ -36,22 +63,38 @@ Settings has 2 steps. } ] ``` + First sequence is named "multiCommand.down3Lines" and executes "cursorDown" command 3 times. Second sequence is named "multiCommand.swapChar". This sequence swaps cursor's left character and the right character. If a command is executed asynchronousely, you can set time interval between each command execution using "interval" configuration(milliseconds). + You can also use an object style that uses the command name as a key instead of an array. + ```json + "multiCommand.commands": { + "multiCommand.down3Lines": { + "sequence": [ + "cursorDown", + "cursorDown", + "cursorDown" + ] + } + } + ``` + This style is useful when you want to merge user settings and the workspace settings. + 2. Bind a key to created command sequence in keybindings.json. - For example: + For example: + ```json - { - "key": "F1", - "command": "extension.multiCommand.execute" , + { + "key": "F1", + "command": "extension.multiCommand.execute", "args": { "command": "multiCommand.down3Lines" }, "when": "editorTextFocus" }, - { - "key": "F21", - "command": "extension.multiCommand.execute" , + { + "key": "F21", + "command": "extension.multiCommand.execute", "args": { "command": "multiCommand.swapChar" }, "when": "editorTextFocus" } @@ -60,10 +103,20 @@ Settings has 2 steps. You can bind a key to the command directly. For example: + ```json - { "key": "F1", "command": "multiCommand.down3Lines", "when": "editorTextFocus"}, - { "key": "F2", "command": "multiCommand.swapChar", "when": "editorTextFocus"} + { + "key": "F1", + "command": "multiCommand.down3Lines", + "when": "editorTextFocus" + }, + { + "key": "F2", + "command": "multiCommand.swapChar", + "when": "editorTextFocus" + } ``` + But when you use this key bind style, Visual Studio Code may warn about the command name. see: https://github.com/ryuta46/vscode-multi-command/issues/16 ### Manual Execution @@ -77,17 +130,19 @@ You can call a defined command sequence from command palette. If you want to call a command sequence in shorter steps, bind a key to "extension.multiCommand.execute". For example: + ```json - { - "key": "cmd+shift+m", - "command": "extension.multiCommand.execute" - } +{ + "key": "cmd+shift+m", + "command": "extension.multiCommand.execute" +} ``` If you set `label` and `description` parameters in settings.json, they are displayed when you choose a command sequence. Both parameters are optional. For example: + ```json "multiCommand.commands": [ { @@ -113,23 +168,21 @@ For Example: { "command": "multiCommand.cutAndType", "sequence": [ - "editor.action.clipboardCutAction", - {"command": "type", "args": {"text": "CUT !!"}} + "editor.action.clipboardCutAction", + { "command": "type", "args": { "text": "CUT !!" } } ] } ``` This sequence cut selected text and type "CUT !!". - ### Find the name of the command you want to execute 1. Execute "Developer: Set Log Level..." and select "trace" in the command palette. 2. Execute command of you want to know the name. - 3. You can see the name in output panel for Log(Window) process( you can set the process for output in the rightside of the output panel). -![command-name-output.png](assets/command-name-output.png) + ![command-name-output.png](assets/command-name-output.png) ### Using shell commands in a command sequence @@ -141,7 +194,10 @@ With Command Runner extension, you can write a command sequence with shell comma { "command": "multiCommand.checkoutDevelop", "sequence": [ - { "command": "command-runner.run", "args": {"command": "git checkout develop"} }, + { + "command": "command-runner.run", + "args": { "command": "git checkout develop" } + }, "git.sync" ] } @@ -151,6 +207,10 @@ See the [Command Runner document](https://marketplace.visualstudio.com/items?ite ## Release Notes +### 1.5.0 +New Feature: Simple usage only with keybindings.json +New Feature: Object style settings for merging user settings and workspace settings. + ### 1.4.0 Added new style for binding a key to created commands. @@ -171,4 +231,3 @@ Now, you can use a custom multi-command immediately after adding it in the setti ### 1.0.0 Initial release. - diff --git a/package.json b/package.json index 0a175e7..f0e572d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "multi-command", "displayName": "multi-command", "description": "Invoke multiple commands as one command.", - "version": "1.4.0", + "version": "1.5.0", "publisher": "ryuta46", "repository": { "type": "git", @@ -31,7 +31,10 @@ "title": "multi-command", "properties": { "multiCommand.commands": { - "type": "array", + "type": [ + "array", + "object" + ], "items": { "type": "object", "title": "command sequence", @@ -69,10 +72,10 @@ "test": "npm run compile && node ./node_modules/vscode/bin/test" }, "devDependencies": { - "typescript": "^2.0.3", + "typescript": "^3.9.7", "vscode": "^1.0.0", "mocha": "^2.3.3", - "@types/node": "^6.0.40", + "@types/node": "^7.0.7", "@types/mocha": "^2.2.32" } } diff --git a/src/command.ts b/src/command.ts index a17b4f9..7cbfaa2 100644 --- a/src/command.ts +++ b/src/command.ts @@ -1,13 +1,16 @@ import * as vscode from "vscode"; export class Command { - constructor(private readonly exe: string, private readonly args: object | null) {} + constructor( + private readonly exe: string, + private readonly args: object | null + ) {} public execute() { if (this.args === null) { - return vscode.commands.executeCommand(this.exe) + return vscode.commands.executeCommand(this.exe); } else { - return vscode.commands.executeCommand(this.exe, this.args) + return vscode.commands.executeCommand(this.exe, this.args); } } } diff --git a/src/extension.ts b/src/extension.ts index eebe481..38f88ee 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,105 +1,138 @@ // The module 'vscode' contains the VS Code extensibility API // Import the module and reference it with the alias vscode in your code below -import * as vscode from 'vscode'; -import {Command} from "./command"; -import {MultiCommand} from "./multiCommand"; - -interface CommandSettings{ - command: string, - label: string, - description: string, - interval: number, - sequence: Array +import * as vscode from "vscode"; +import { Command } from "./command"; +import { MultiCommand } from "./multiCommand"; + +interface CommandSettings { + label: string; + description: string; + interval: number; + sequence: Array; +} + +interface CommandSettingsWithKey extends CommandSettings { + command: string; +} + +interface CommandMap { + [key: string]: CommandSettings; } interface ComplexCommand { - command: string, - args: object + command: string; + args: object; +} + +function implementsCommandMap(arg: any): arg is CommandSettings { + return arg !== null && typeof arg === "object"; +} + +function createMultiCommand( + id: string, + settings: CommandSettings +): MultiCommand { + const label = settings.label; + const description = settings.description; + const interval = settings.interval; + const sequence = settings.sequence.map((command) => { + let exe: string; + let args: object | null; + if (typeof command === "string") { + exe = command; + args = null; + } else { + exe = command.command; + args = command.args; + } + return new Command(exe, args); + }); + + return new MultiCommand(id, label, description, interval, sequence); } let multiCommands: Array; function refreshUserCommands(context: vscode.ExtensionContext) { let configuration = vscode.workspace.getConfiguration("multiCommand"); - let commands = configuration.get>("commands"); + + let commands = new Map(); + + let commandList = + configuration.get | CommandMap>( + "commands" + ) || []; // Dispose current settings. for (let element of context.subscriptions) { element.dispose(); } - if (!commands) { - return; + if (Array.isArray(commandList)) { + for (let commandSettingsWithKey of commandList) { + commands.set( + commandSettingsWithKey.command, + commandSettingsWithKey + ); + } + } else if (implementsCommandMap(commandList)) { + let commandObject = commandList as CommandMap; + Object.keys(commandObject).forEach((key: string) => { + commands.set(key, commandObject[key]); + }); } multiCommands = []; - for (let commandSettings of commands) { - const id = commandSettings.command; - const label = commandSettings.label; - const description = commandSettings.description; - const interval = commandSettings.interval; - const sequence = commandSettings.sequence.map(command => { - let exe: string; - let args: object | null; - if (typeof(command) === "string" ) { - exe = command; - args = null; - } else { - exe = command.command; - args = command.args; - } - return new Command(exe, args); - }); - - - const multiCommand = new MultiCommand(id, label, description, interval, sequence); + commands.forEach((value: CommandSettings, key: string) => { + const multiCommand = createMultiCommand(key, value); multiCommands.push(multiCommand); - context.subscriptions.push(vscode.commands.registerCommand(id, async () => { - await multiCommand.execute(); - })); - } - + context.subscriptions.push( + vscode.commands.registerCommand(key, async () => { + await multiCommand.execute(); + }) + ); + }); } // this method is called when your extension is activated // your extension is activated the very first time the command is executed export function activate(context: vscode.ExtensionContext) { - refreshUserCommands(context); vscode.workspace.onDidChangeConfiguration(() => { refreshUserCommands(context); }); - vscode.commands.registerCommand('extension.multiCommand.execute', async (args = {}) => { - try { - if (args.command) { - await vscode.commands.executeCommand(args.command); + vscode.commands.registerCommand( + "extension.multiCommand.execute", + async (args = {}) => { + try { + if (args.command) { + await vscode.commands.executeCommand(args.command); + } else if (args.sequence) { + const multiCommand = createMultiCommand("", args); + await multiCommand.execute(); + } else { + await pickMultiCommand(); + } + } catch (e) { + vscode.window.showErrorMessage(`${e.message}`); } - else { - await pickMultiCommand(); - } - } - catch (e) { - vscode.window.showErrorMessage(`${e.message}`); } - }); - + ); } // this method is called when your extension is deactivated -export function deactivate() { -} - +export function deactivate() {} export async function pickMultiCommand() { - const picks = multiCommands.map(multiCommand => { + const picks = multiCommands.map((multiCommand) => { return { label: multiCommand.label || multiCommand.id, description: multiCommand.description || "", - multiCommand: multiCommand - } + multiCommand: multiCommand, + }; }); const item = await vscode.window.showQuickPick(picks, { @@ -111,4 +144,3 @@ export async function pickMultiCommand() { } await item.multiCommand.execute(); } - diff --git a/src/multiCommand.ts b/src/multiCommand.ts index d51dc3f..5031206 100644 --- a/src/multiCommand.ts +++ b/src/multiCommand.ts @@ -1,12 +1,13 @@ -import {Command} from "./command"; - +import { Command } from "./command"; export class MultiCommand { - constructor(readonly id: string, - readonly label: string | undefined, - readonly description: string | undefined, - readonly interval: number | undefined, - readonly sequence: Array) {} + constructor( + readonly id: string, + readonly label: string | undefined, + readonly description: string | undefined, + readonly interval: number | undefined, + readonly sequence: Array + ) {} public async execute() { for (let command of this.sequence) { @@ -18,6 +19,6 @@ export class MultiCommand { function delay(ms: number) { if (ms > 0) { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise((resolve) => setTimeout(resolve, ms)); } }