From 69074607c175060bdce8e43faa97d2d97a3f1936 Mon Sep 17 00:00:00 2001 From: tshino Date: Sat, 14 Jan 2023 02:37:01 +0900 Subject: [PATCH 1/3] Not modify input objects in optimize() --- src/command_sequence.js | 10 +++++--- test/suite/command_sequence.test.js | 36 +++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/command_sequence.js b/src/command_sequence.js index 3646a858..f5ae220d 100644 --- a/src/command_sequence.js +++ b/src/command_sequence.js @@ -55,8 +55,10 @@ const CommandSequence = function({ maxLength = null } = {}) { groupSize1 === 1 && characterDelta1 < 0 && characterDelta1 + deleteRight2 === 0) { - sequence[i + 1].args.deleteLeft = deleteLeft2 + deleteRight2; - delete sequence[i + 1].args.deleteRight; + const args = JSON.parse(JSON.stringify(sequence[i + 1].args)); + args.deleteLeft = deleteLeft2 + deleteRight2; + delete args.deleteRight; + sequence[i + 1] = { command: '$type', args }; sequence.splice(i, 1); i--; continue; @@ -76,7 +78,9 @@ const CommandSequence = function({ maxLength = null } = {}) { if (text1.length >= deleteLeft2 && deleteRight2 === 0) { const text = text1.substr(0, text1.length - deleteLeft2) + text2; - sequence[i - 1].args.text = text; + const args = JSON.parse(JSON.stringify(sequence[i - 1].args)); + args.text = text; + sequence[i - 1] = { command: '$type', args }; sequence.splice(i, 1); i--; continue; diff --git a/test/suite/command_sequence.test.js b/test/suite/command_sequence.test.js index 012fe58a..553b750f 100644 --- a/test/suite/command_sequence.test.js +++ b/test/suite/command_sequence.test.js @@ -81,6 +81,17 @@ describe('CommandSequence', () => { seq.optimize(); assert.deepStrictEqual(seq.get(), [ TYPE123 ]); }); + it('should not modify input objects (1: consucutive typing)', () => { + const TYPE1 = { command: '$type', args: { text: 'A' } }; + const TYPE2 = { command: '$type', args: { text: 'B' } }; + const seq = CommandSequence(); + seq.push(TYPE1); + seq.push(TYPE2); + seq.optimize(); + assert.deepStrictEqual(seq.get(), [ { command: '$type', args: { text: 'AB' } } ]); + assert.deepStrictEqual(TYPE1, { command: '$type', args: { text: 'A' } }); + assert.deepStrictEqual(TYPE2, { command: '$type', args: { text: 'B' } }); + }); it('should not concatenate direct typing commands with deleting (1)', () => { const TYPE1 = { command: '$type', @@ -188,6 +199,17 @@ describe('CommandSequence', () => { seq.optimize(); assert.deepStrictEqual(seq.get(), []); }); + it('should not modify input objects (2: pair of cursor motion)', () => { + const MOVE1 = { command: '$moveCursor', args: { characterDelta: -3 } }; + const MOVE2 = { command: '$moveCursor', args: { characterDelta: 3 } }; + const seq = CommandSequence(); + seq.push(MOVE1); + seq.push(MOVE2); + seq.optimize(); + assert.deepStrictEqual(seq.get(), []); + assert.deepStrictEqual(MOVE1, { command: '$moveCursor', args: { characterDelta: -3 } }); + assert.deepStrictEqual(MOVE2, { command: '$moveCursor', args: { characterDelta: 3 } }); + }); it('should retain consecutive cursor motions that have vertical motion', () => { const MOVE1 = { command: '$moveCursor', @@ -269,6 +291,20 @@ describe('CommandSequence', () => { seq.optimize(); assert.deepStrictEqual(seq.get(), [ EXPECTED ]); }); + it('should not modify input objects (3: cursor motion followed by deleting and typing)', () => { + const INPUT = [ + { command: '$moveCursor', args: { characterDelta: -1 } }, + { command: '$type', args: { deleteRight: 1, text: 'a' } } + ]; + const EXPECTED = { command: '$type', args: { deleteLeft: 1, text: 'a' } }; + const seq = CommandSequence(); + seq.push(INPUT[0]); + seq.push(INPUT[1]); + seq.optimize(); + assert.deepStrictEqual(seq.get(), [ EXPECTED ]); + assert.deepStrictEqual(INPUT[0], { command: '$moveCursor', args: { characterDelta: -1 } }); + assert.deepStrictEqual(INPUT[1], { command: '$type', args: { deleteRight: 1, text: 'a' } }); + }); it('should shrink three commands into one', () => { const INPUT = [ { From d68421acf0dabee6f90f9040414459e616fa51fe Mon Sep 17 00:00:00 2001 From: tshino Date: Sat, 14 Jan 2023 02:41:10 +0900 Subject: [PATCH 2/3] Optimize input sequence in playback command --- src/keyboard_macro.js | 10 +++++++++- test/suite/keyboard_macro.test.js | 19 ++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/keyboard_macro.js b/src/keyboard_macro.js index d2954af3..2067a5be 100644 --- a/src/keyboard_macro.js +++ b/src/keyboard_macro.js @@ -244,11 +244,19 @@ const KeyboardMacro = function({ awaitController }) { } return validArgs; }; + const optimizeSequence = function(sequence) { + const s = CommandSequence(); + for (const command of sequence) { + s.push(command); + } + s.optimize(); + return s.get(); + }; const playbackImpl = async function(args, { tillEndOfFile = false } = {}) { args = validatePlaybackArgs(args); const repeat = 'repeat' in args ? args.repeat : 1; - const commands = 'sequence' in args ? args.sequence : sequence.get(); + const commands = 'sequence' in args ? optimizeSequence(args.sequence) : sequence.get(); const wrapMode = active ? 'command' : null; if (recording) { if (!('sequence' in args)) { diff --git a/test/suite/keyboard_macro.test.js b/test/suite/keyboard_macro.test.js index 01a471f3..fc741818 100644 --- a/test/suite/keyboard_macro.test.js +++ b/test/suite/keyboard_macro.test.js @@ -631,7 +631,7 @@ describe('KeybaordMacro', () => { { command: 'internal:log', args: 'hello' } ]); }); - it('should record the commands specified times if repeat option specified', async () => { + it('should record the commands multiple times if repeat option specified', async () => { const sequence = [ { command: 'internal:log', args: 'hello' } ]; @@ -654,6 +654,23 @@ describe('KeybaordMacro', () => { { command: 'internal:log', args: 'hello' } ]); }); + it('should optimize the specified sequence before executing and recording', async () => { + keyboardMacro.registerInternalCommand('$type', async (args) => { + logs.push(`$type:${args.text}`); + }); + const sequence = [ + { command: '$type', args: { text: 'A' } }, + { command: '$type', args: { text: 'B' } } + ]; + keyboardMacro.startRecording(); + await keyboardMacro.playback({ sequence }); + keyboardMacro.finishRecording(); + + assert.deepStrictEqual(logs, [ '$type:AB' ]); + assert.deepStrictEqual(keyboardMacro.getCurrentSequence(), [ + { command: '$type', args: { text: 'AB' } } + ]); + }); }); describe('wrap with playback with sequence option', () => { const logs = []; From 7a30cd6c0800933fce77a0480e75960026e20f7f Mon Sep 17 00:00:00 2001 From: tshino Date: Sat, 14 Jan 2023 03:08:07 +0900 Subject: [PATCH 3/3] Update CHANGELOG --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1605c16..453d6efc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ All notable changes to the Keyboard Macro Bata extension will be documented in this file. ### [Unreleased] +- Feature: + - Added optimization of the input sequence to the playback command. [#195](https://github.com/tshino/vscode-kb-macro/pull/195) + - This optimization can improve playback performance, especially in a sequence containing a long typing. - Update - Changed the activatation context of wrapper keybindings from `kb-macro.recording` to `kb-macro.active`. [#133](https://github.com/tshino/vscode-kb-macro/issues/133) - This change does not require any actions for existing users unless they want to use the extra feature Background Recording API.