Skip to content

Commit

Permalink
Merge pull request #30 from tshino/fix-code-completion-with-delete-right
Browse files Browse the repository at this point in the history
Fix code completion with replacing characters to the right
  • Loading branch information
tshino authored Jan 4, 2022
2 parents aa4233f + 51a5090 commit 7419c36
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ All notable changes to the Keyboard Macro Bata extension will be documented in t
- Update
- Updated keymap wrapper for Awesome Emacs Keymap (v0.37.1).
- Fix
- Some types of code completion were not being reproduced correctly. [#30](https://github.com/tshino/vscode-kb-macro/pull/30)
- Reenabled running tests with macOS runners on GitHub Actions. [#28](https://github.com/tshino/vscode-kb-macro/pull/28)

### [0.8.0] - 2022-01-01
Expand Down
28 changes: 27 additions & 1 deletion src/command_sequence.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,30 @@ const CommandSequence = function() {
continue;
}
}
// Combine cursor motion to the left and successive typing with deleting to the right
if (i + 1 < sequence.length &&
sequence[i].command === 'internal:performCursorMotion' &&
sequence[i + 1].command === 'internal:performType') {
const args1 = sequence[i].args || {};
const args2 = sequence[i + 1].args || {};
const characterDelta1 = args1.characterDelta || 0;
const lineDelta1 = args1.lineDelta || 0;
const selectionLength1 = args1.selectionLength || 0;
const groupSize1 = args1.groupSize || 1;
const deleteLeft2 = args2.deleteLeft || 0;
const deleteRight2 = args2.deleteRight || 0;
if (lineDelta1 === 0 &&
selectionLength1 === 0 &&
groupSize1 === 1 &&
characterDelta1 < 0 &&
characterDelta1 + deleteRight2 === 0) {
sequence[i + 1].args.deleteLeft = deleteLeft2 + deleteRight2;
delete sequence[i + 1].args.deleteRight;
sequence.splice(i, 1);
i--;
continue;
}
}
// Concatenate consecutive direct typing
if (0 < i &&
sequence[i - 1].command === 'internal:performType' &&
Expand All @@ -45,7 +69,9 @@ const CommandSequence = function() {
const text1 = args1.text || '';
const text2 = args2.text || '';
const deleteLeft2 = args2.deleteLeft || 0;
if (text1.length >= deleteLeft2) {
const deleteRight2 = args2.deleteRight || 0;
if (text1.length >= deleteLeft2 &&
deleteRight2 === 0) {
const text = text1.substr(0, text1.length - deleteLeft2) + text2;
sequence[i - 1].args.text = text;
sequence.splice(i, 1);
Expand Down
38 changes: 32 additions & 6 deletions src/typing_detector.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,29 @@ const TypingDetector = function() {
// every change replaces the text of the respective selection
return changes.every((chg, i) => selections[i].isEqual(chg.range));
};
const deletesLeftAndInserts = function(changes, selections) {
const isInsertingWithDeleting = function(changes, selections) {
const emptySelection = selections.every(sel => sel.isEmpty);
if (!emptySelection) {
return false;
}
const uniformRangeLength = changes.every(chg => chg.rangeLength === changes[0].rangeLength);
const cursorAtEndOfRange = selections.every((sel, i) => sel.active.isEqual(changes[i].range.end));
return emptySelection && uniformRangeLength && cursorAtEndOfRange;
if (!uniformRangeLength) {
return false;
}
const sameLine = selections.every((sel, i) => sel.active.line === changes[i].range.start.line);
if (!sameLine) {
return false;
}
const deleteLeft = selections[0].active.character - changes[0].range.start.character;
const deleteRight = changes[0].range.end.character - selections[0].active.character;
if (deleteLeft < 0 || deleteRight < 0) {
return false;
}
const uniformDeletingLength = selections.every((sel, i) => (
deleteLeft === sel.active.character - changes[i].range.start.character &&
deleteRight === changes[i].range.end.character - sel.active.character
));
return uniformDeletingLength;
};
const isBracketCompletionWithSelection = function(selections, changes) {
let uniformPairedText = changes.every(
Expand Down Expand Up @@ -100,7 +118,7 @@ const TypingDetector = function() {
notifyDetectedTyping(TypingType.Direct, { text: changes[0].text });
return true;
}
if (deletesLeftAndInserts(changes, selections)) {
if (isInsertingWithDeleting(changes, selections)) {
// Every change (in possible multi-cursor) is a combination of deleting
// common number of characters to the left and inserting a common text.
// This happens when a code completion occurs.
Expand All @@ -109,12 +127,20 @@ const TypingDetector = function() {
// 2. type 'r', 'Array' is suggested
// 3. accept the suggestion
// 4. then edit event happens, that replaces 'ar' with 'Array'
const deleteLeft = changes[0].rangeLength;
const deleteLeft = selections[0].active.character - changes[0].range.start.character;
const deleteRight = changes[0].range.end.character - selections[0].active.character;
const prediction = util.makeSelectionsAfterTyping(changes);
if (!util.isEqualSelections(selections, prediction)) {
cursorMotionDetector.setPrediction(textEditor, prediction);
}
notifyDetectedTyping(TypingType.Direct, { deleteLeft, text: changes[0].text });
const args = { text: changes[0].text };
if (0 < deleteLeft) {
args.deleteLeft = deleteLeft;
}
if (0 < deleteRight) {
args.deleteRight = deleteRight;
}
notifyDetectedTyping(TypingType.Direct, args);
return true;
}
}
Expand Down
59 changes: 56 additions & 3 deletions test/suite/command_sequence.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ describe('CommandSequence', () => {
seq.optimize();
assert.deepStrictEqual(seq.get(), [ TYPE123 ]);
});
it('should not concatenate direct typing commands with deleting', () => {
it('should not concatenate direct typing commands with deleting (1)', () => {
const TYPE1 = {
command: 'internal:performType',
args: { text: 'X' }
Expand All @@ -84,7 +84,22 @@ describe('CommandSequence', () => {
seq.optimize();
assert.deepStrictEqual(seq.get(), [ TYPE1, TYPE2 ]);
});
it('should concatenate direct typing followed by another typing with deleting', () => {
it('should not concatenate direct typing commands with deleting (2)', () => {
const TYPE1 = {
command: 'internal:performType',
args: { text: 'X' }
};
const TYPE2 = {
command: 'internal:performType',
args: { deleteRight: 2, text: 'ABC' }
};
const seq = CommandSequence();
seq.push(TYPE1);
seq.push(TYPE2);
seq.optimize();
assert.deepStrictEqual(seq.get(), [ TYPE1, TYPE2 ]);
});
it('should concatenate direct typing followed by another typing with deleting to the left', () => {
const TYPE1 = {
command: 'internal:performType',
args: { text: 'a' }
Expand All @@ -108,7 +123,7 @@ describe('CommandSequence', () => {
seq.optimize();
assert.deepStrictEqual(seq.get(), [ TYPE123 ]);
});
it('should concatenate direct typing with deleting followed by another typing without deleting', () => {
it('should concatenate direct typing with deleting followed by another typing without deleting (1)', () => {
const TYPE1 = {
command: 'internal:performType',
args: { deleteLeft: 1, text: 'a' }
Expand All @@ -127,6 +142,25 @@ describe('CommandSequence', () => {
seq.optimize();
assert.deepStrictEqual(seq.get(), [ TYPE12 ]);
});
it('should concatenate direct typing with deleting followed by another typing without deleting (2)', () => {
const TYPE1 = {
command: 'internal:performType',
args: { deleteRight: 1, text: 'a' }
};
const TYPE2 = {
command: 'internal:performType',
args: { text: 'b' }
};
const TYPE12 = {
command: 'internal:performType',
args: { deleteRight: 1, text: 'ab' }
};
const seq = CommandSequence();
seq.push(TYPE1);
seq.push(TYPE2);
seq.optimize();
assert.deepStrictEqual(seq.get(), [ TYPE12 ]);
});
it('should remove a pair of cursor motion that results no effect', () => {
const MOVE1 = {
command: 'internal:performCursorMotion',
Expand Down Expand Up @@ -202,5 +236,24 @@ describe('CommandSequence', () => {
seq.optimize();
assert.deepStrictEqual(seq.get(), [ MOVE1, MOVE2 ]);
});
it('should combine cursor motion to the left and successive typing with deleting to the right', () => {
const TYPE1 = {
command: 'internal:performCursorMotion',
args: { characterDelta: -1 }
};
const TYPE2 = {
command: 'internal:performType',
args: { deleteRight: 1, text: 'a' }
};
const TYPE12 = {
command: 'internal:performType',
args: { deleteLeft: 1, text: 'a' }
};
const seq = CommandSequence();
seq.push(TYPE1);
seq.push(TYPE2);
seq.optimize();
assert.deepStrictEqual(seq.get(), [ TYPE12 ]);
});
});
});
5 changes: 3 additions & 2 deletions test/suite/playback_typing.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ describe('Recording and Playback: Typing', () => {
let textEditor;
const Cmd = CommandsToTest;
const Type = text => ({ command: 'internal:performType', args: { text } });
const ReplaceRight = (deleteRight, text) => ({ command: 'internal:performType', args: { deleteRight, text } });
const DefaultType = text => ({ command: 'default:type', args: { text } });
const MoveLeft = delta => ({ command: 'internal:performCursorMotion', args: { characterDelta: -delta } });
const MoveRight = delta => ({ command: 'internal:performCursorMotion', args: { characterDelta: delta } });
Expand Down Expand Up @@ -279,7 +280,7 @@ describe('Recording and Playback: Typing', () => {
await vscode.commands.executeCommand('type', { text: '10' });
await vscode.commands.executeCommand('type', { text: ')' }); // This overwrites the closing bracket.
keyboardMacro.finishRecording();
assert.deepStrictEqual(getSequence(), [ Type('()'), MoveLeft(1), Type('10'), MoveRight(1) ]);
assert.deepStrictEqual(getSequence(), [ Type('()'), MoveLeft(1), Type('10'), ReplaceRight(1, ')') ]);
assert.strictEqual(textEditor.document.lineAt(5).text, '(10)');
assert.deepStrictEqual(getSelections(), [[5, 4]]);

Expand All @@ -295,7 +296,7 @@ describe('Recording and Playback: Typing', () => {
await vscode.commands.executeCommand('type', { text: '10' });
await vscode.commands.executeCommand('type', { text: ')' }); // This overwrites the closing bracket.
keyboardMacro.finishRecording();
assert.deepStrictEqual(getSequence(), [ Type('()'), MoveLeft(1), Type('10'), MoveRight(1) ]);
assert.deepStrictEqual(getSequence(), [ Type('()'), MoveLeft(1), Type('10'), ReplaceRight(1, ')') ]);
assert.strictEqual(textEditor.document.lineAt(5).text, '(10)');
assert.strictEqual(textEditor.document.lineAt(6).text, '(10)');
assert.deepStrictEqual(getSelections(), [[5, 4], [6, 4]]);
Expand Down
7 changes: 7 additions & 0 deletions test/suite/typing_detector.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,13 @@ describe('TypingDetector', () => {
expectedLogs: [[0, {text:'EFGHI', deleteLeft:4}]],
expectedPrediction: [[20, 9]] });
});
it('should process events, detect code completion and invoke the callback function (3)', async () => {
testDetection({ changes: [
makeContentChange(new vscode.Range(20, 4, 20, 6), '"key": ""')
], precond: [[20, 5]],
expectedLogs: [[0, {text:'"key": ""', deleteLeft:1, deleteRight:1}]],
expectedPrediction: [[20, 13]] });
});
it('should not make prediction if no change is expected', async () => {
testDetection({ changes: [
makeContentChange(new vscode.Range(10, 0, 10, 4), 'Abcd')
Expand Down

0 comments on commit 7419c36

Please sign in to comment.