g? (rot13) support (#4367)

Fixes #4363
This commit is contained in:
Jason Fields 2019-12-26 01:36:27 -05:00 committed by GitHub
parent 7a148f519d
commit 5f6ef5fada
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 139 additions and 15 deletions

View File

@ -355,8 +355,8 @@ moving around:
| :white_check_mark: | g~{motion} | switch case for the text that is moved over with {motion} |
| :white_check_mark: | gu{motion} | make the text that is moved over with {motion} lowercase |
| :white_check_mark: | gU{motion} | make the text that is moved over with {motion} uppercase |
| :arrow_down: | {visual}g? | perform rot13 encoding on highlighted text |
| :arrow_down: | g?{motion} | perform rot13 encoding on the text that is moved over with {motion} |
| :white_check_mark: | {visual}g? | perform rot13 encoding on highlighted text |
| :white_check_mark: | g?{motion} | perform rot13 encoding on the text that is moved over with {motion} |
| :white_check_mark: | :1234: CTRL-A | add N to the number at or after the cursor |
| :white_check_mark: | :1234: CTRL-X | subtract N from the number at or after the cursor |
| :white_check_mark: | :1234: <{motion} | move the lines that are moved over with {motion} one shiftwidth left |

View File

@ -1397,6 +1397,13 @@ export class CommandSearchBackwards extends BaseCommand {
isMotion = true;
isJump = true;
public doesActionApply(vimState: VimState, keysPressed: string[]): boolean {
// Prevent collision with `g?` (rot13 operator)
return (
super.doesActionApply(vimState, keysPressed) && vimState.recordedState.operator === undefined
);
}
public async exec(position: Position, vimState: VimState): Promise<VimState> {
globalState.searchState = new SearchState(
SearchDirection.Backward,

View File

@ -3,7 +3,7 @@ import * as vscode from 'vscode';
import { Position, PositionDiff } from './../common/motion/position';
import { Range } from './../common/motion/range';
import { configuration } from './../configuration/configuration';
import { Mode } from './../mode/mode';
import { Mode, isVisualMode } from './../mode/mode';
import { Register, RegisterMode } from './../register/register';
import { VimState } from './../state/vimState';
import { TextEditor } from './../textEditor';
@ -783,6 +783,59 @@ export class CommentOperator extends BaseOperator {
}
}
@RegisterAction
export class ROT13Operator extends BaseOperator {
public keys = ['g', '?'];
public modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];
public async run(vimState: VimState, start: Position, end: Position): Promise<VimState> {
let selections: vscode.Selection[];
if (isVisualMode(vimState.currentMode)) {
selections = vimState.editor.selections;
} else if (vimState.currentRegisterMode === RegisterMode.LineWise) {
selections = [new vscode.Selection(start.getLineBegin(), end.getLineEnd())];
} else {
selections = [new vscode.Selection(start, end.getRight())];
}
for (const range of selections) {
const original = TextEditor.getText(range);
vimState.recordedState.transformations.push({
type: 'replaceText',
text: ROT13Operator.rot13(original),
start: Position.FromVSCodePosition(range.start),
end: Position.FromVSCodePosition(range.end),
});
}
return vimState;
}
/**
* https://en.wikipedia.org/wiki/ROT13
*/
public static rot13(str: string) {
return str
.split('')
.map((char: string) => {
let charCode = char.charCodeAt(0);
if (char >= 'a' && char <= 'z') {
const a = 'a'.charCodeAt(0);
charCode = ((charCode - a + 13) % 26) + a;
}
if (char >= 'A' && char <= 'Z') {
const A = 'A'.charCodeAt(0);
charCode = ((charCode - A + 13) % 26) + A;
}
return String.fromCharCode(charCode);
})
.join('');
}
}
@RegisterAction
export class CommentBlockOperator extends BaseOperator {
public keys = ['g', 'C'];

View File

@ -49,7 +49,8 @@ export class SneakForward extends BaseMovement {
const ignorecase =
configuration.sneakUseIgnorecaseAndSmartcase &&
configuration.ignorecase && !(configuration.smartcase && /[A-Z]/.test(searchString));
configuration.ignorecase &&
!(configuration.smartcase && /[A-Z]/.test(searchString));
// Check for matches
if (ignorecase) {
@ -112,7 +113,8 @@ export class SneakBackward extends BaseMovement {
const ignorecase =
configuration.sneakUseIgnorecaseAndSmartcase &&
configuration.ignorecase && !(configuration.smartcase && /[A-Z]/.test(searchString));
configuration.ignorecase &&
!(configuration.smartcase && /[A-Z]/.test(searchString));
// Check for matches
if (ignorecase) {

View File

@ -45,10 +45,7 @@ class VimrcImpl {
const mappings = (() => {
switch (remap.keyRemappingType) {
case 'map':
return [
config.normalModeKeyBindings,
config.visualModeKeyBindings,
];
return [config.normalModeKeyBindings, config.visualModeKeyBindings];
case 'nmap':
return [config.normalModeKeyBindings];
case 'vmap':

View File

@ -113,11 +113,11 @@ export class RecordedState {
*/
public get operator(): BaseOperator {
let list = this.actionsRun.filter(a => a instanceof BaseOperator).reverse();
return list[0] as any;
return list[0] as BaseOperator;
}
public get operators(): BaseOperator[] {
return this.actionsRun.filter(a => a instanceof BaseOperator).reverse() as any;
return this.actionsRun.filter(a => a instanceof BaseOperator).reverse() as BaseOperator[];
}
/**
@ -128,7 +128,7 @@ export class RecordedState {
// TODO - disregard <Esc>, then assert this is of length 1.
return list[0] as any;
return list[0] as BaseCommand;
}
public get hasRunAMovement(): boolean {
@ -167,7 +167,8 @@ export class RecordedState {
mode !== Mode.SearchInProgressMode &&
mode !== Mode.CommandlineInProgress &&
(this.hasRunAMovement ||
mode === Mode.Visual || mode === Mode.VisualLine ||
mode === Mode.Visual ||
mode === Mode.VisualLine ||
(this.operators.length > 1 &&
this.operators.reverse()[0].constructor === this.operators.reverse()[1].constructor))
);

View File

@ -21,7 +21,11 @@ suite('Horizontal split', () => {
await commandLine.Run(cmd, modeHandler.vimState);
await WaitForEditorsToClose(2);
assert.strictEqual(vscode.window.visibleTextEditors.length, 2, 'Editor did not split in 1 sec');
assert.strictEqual(
vscode.window.visibleTextEditors.length,
2,
'Editor did not split in 1 sec'
);
});
}
});

View File

@ -21,7 +21,11 @@ suite('Vertical split', () => {
await commandLine.Run(cmd, modeHandler.vimState);
await WaitForEditorsToClose(2);
assert.strictEqual(vscode.window.visibleTextEditors.length, 2, 'Editor did not split in 1 sec');
assert.strictEqual(
vscode.window.visibleTextEditors.length,
2,
'Editor did not split in 1 sec'
);
});
}
});

View File

@ -0,0 +1,56 @@
import * as assert from 'assert';
import { getTestingFunctions } from '../testSimplifier';
import { setupWorkspace, cleanUpWorkspace } from '../testUtils';
import { ROT13Operator } from '../../src/actions/operator';
suite('rot13 operator', () => {
const { newTest, newTestOnly, newTestSkip } = getTestingFunctions();
setup(async () => {
await setupWorkspace();
});
teardown(cleanUpWorkspace);
test('rot13() unit test', () => {
const testCases = [
['abcdefghijklmnopqrstuvwxyz', 'nopqrstuvwxyzabcdefghijklm'],
['ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'NOPQRSTUVWXYZABCDEFGHIJKLM'],
['!@#$%^&*()', '!@#$%^&*()'],
['âéü', 'âéü'],
];
for (const [input, output] of testCases) {
assert.strictEqual(ROT13Operator.rot13(input), output);
}
});
newTest({
title: 'g?j works',
start: ['a|bc', 'def', 'ghi'],
keysPressed: 'g?j',
end: ['n|op', 'qrs', 'ghi'],
});
newTest({
title: 'g? in visual mode works',
start: ['a|bc', 'def', 'ghi'],
keysPressed: 'vj$g?',
end: ['a|op', 'qrs', 'ghi'],
});
newTest({
title: 'g? in visual line mode works',
start: ['a|bc', 'def', 'ghi'],
keysPressed: 'Vj$g?',
end: ['|nop', 'qrs', 'ghi'],
});
newTest({
title: 'g? in visual block mode works',
start: ['a|bc', 'def', 'ghi'],
keysPressed: '<C-v>j$g?',
end: ['a|op', 'drs', 'ghi'],
});
});