Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement normal command #8896

Merged
merged 46 commits into from
Jun 30, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
7cd2f3c
implement normal command temporarily
s-kai273 Feb 19, 2024
07f848a
implement normal command
s-kai273 Feb 19, 2024
f6a7cd6
implement normal command with visual mode
s-kai273 Feb 20, 2024
f384c21
modify error message
s-kai273 Feb 20, 2024
a3e2087
fix bug of undo execution after normal command with multiple lines
s-kai273 Feb 21, 2024
307258a
delete unnecessary code
s-kai273 Feb 22, 2024
8f21e22
fix bug of repeatable dot on normal command
s-kai273 Feb 22, 2024
9c1e490
fix bug of infinite loop in normal command
s-kai273 Feb 23, 2024
b227354
modify normal command to support range line
s-kai273 Feb 23, 2024
dbea00b
fix cursor position without any range
s-kai273 Feb 23, 2024
730386e
remove unnecessary file change
s-kai273 Feb 23, 2024
9bb9a85
remove unnecessary file change
s-kai273 Feb 23, 2024
255b9f4
modify normal command keystroke parser
s-kai273 Feb 24, 2024
1261062
add withRange parameter to ExecuteNormalTransformation
s-kai273 Feb 27, 2024
a01cd25
modify normal command to specify cursorStopPosition for line numbers
s-kai273 Feb 27, 2024
149946b
add simple one liner test
s-kai273 Mar 13, 2024
0ce572d
fix cursor position bug after insert mode with macro
s-kai273 Mar 13, 2024
e8a3afe
add several test cases
s-kai273 Mar 13, 2024
7fea14d
Merge branch 'master' into feature/implement_normal_command
s-kai273 Mar 14, 2024
6f64259
Merge branch 'master' into feature/implement_normal_command
J-Fields Mar 15, 2024
04500b0
modify setup code
s-kai273 Mar 28, 2024
8f09a16
modify comment
s-kai273 Mar 28, 2024
9d4d457
modify declaration of transformation attributes
s-kai273 Mar 28, 2024
283e7c6
modify TextLine list to just number list
s-kai273 Mar 28, 2024
b227f72
add test case of incomplete operation
s-kai273 Mar 28, 2024
3643304
add TODO comment of parsing :normal!
s-kai273 Mar 28, 2024
853fb47
add commented out test of stopping when operation fails
s-kai273 Mar 30, 2024
c574554
add test of undo
s-kai273 Mar 30, 2024
cb8f19f
Merge branch 'master' into feature/implement_normal_command
s-kai273 Apr 6, 2024
766ea77
use newTestSkip instead of commenting out
s-kai273 Apr 7, 2024
507c615
Merge branch 'master' into feature/implement_normal_command
J-Fields Apr 7, 2024
75ca673
Merge branch 'master' into feature/implement_normal_command
s-kai273 Apr 10, 2024
0045ac0
Merge branch 'master' into feature/implement_normal_command
s-kai273 Apr 26, 2024
66ed877
Merge branch 'master' into feature/implement_normal_command
s-kai273 May 12, 2024
a3926ed
Merge branch 'master' of github.com:s-kai273/Vim into feature/impleme…
s-kai273 May 20, 2024
dbd0bd2
Merge branch 'master' of github.com:VSCodeVim/Vim into feature/implem…
s-kai273 May 20, 2024
a67f745
modify multiple dot test not to skip
s-kai273 May 20, 2024
e4532d9
Merge branch 'master' into feature/implement_normal_command
J-Fields May 25, 2024
3149e8c
refactor normal command argparser
s-kai273 May 27, 2024
9a02ae5
refactor pushing line number of normal command execution
s-kai273 May 27, 2024
d7caafe
refactor executing escape of modeHandler
s-kai273 May 27, 2024
e70e7ea
rename keystroke to keystrokes
s-kai273 May 27, 2024
21d94d0
refactor ExecuteNormalTransformation
s-kai273 May 28, 2024
779f7c5
Merge branch 'master' into feature/implement_normal_command
s-kai273 May 28, 2024
4ee4f3d
refacor execute method
s-kai273 May 28, 2024
0838187
Merge branch 'master' into feature/implement_normal_command
J-Fields Jun 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions src/cmd_line/commands/normal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Parser, all, seq, whitespace } from 'parsimmon';
import { VimState } from '../../state/vimState';
import { ExCommand } from '../../vimscript/exCommand';
import { LineRange } from '../../vimscript/lineRange';

export class NormalCommand extends ExCommand {
public static readonly argParser: Parser<NormalCommand> = seq(whitespace, all).map(
J-Fields marked this conversation as resolved.
Show resolved Hide resolved
([_, keystroke]) => new NormalCommand(keystroke),
J-Fields marked this conversation as resolved.
Show resolved Hide resolved
);

private readonly keystroke: string;
constructor(argument: string) {
super();
this.keystroke = argument;
}

override async execute(vimState: VimState): Promise<void> {
const keystroke = this.keystroke;
const lineNumber = vimState.cursorStopPosition.line;
vimState.recordedState.transformer.addTransformation({
J-Fields marked this conversation as resolved.
Show resolved Hide resolved
type: 'executeNormal',
keystroke,
startLineNumber: lineNumber,
endLineNumber: lineNumber,
withRange: false,
});
}

override async executeWithRange(vimState: VimState, lineRange: LineRange): Promise<void> {
const keystroke = this.keystroke;
const { start, end } = lineRange.resolve(vimState);
vimState.recordedState.transformer.addTransformation({
type: 'executeNormal',
keystroke,
startLineNumber: start,
endLineNumber: end,
withRange: true,
});
}
}
4 changes: 3 additions & 1 deletion src/mode/modeHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -773,7 +773,8 @@ export class ModeHandler implements vscode.Disposable, IModeHandler {
this.vimState.currentMode === Mode.Normal &&
prevMode !== Mode.SearchInProgressMode &&
prevMode !== Mode.EasyMotionInputMode &&
prevMode !== Mode.EasyMotionMode
prevMode !== Mode.EasyMotionMode &&
!(prevMode === Mode.CommandlineInProgress && this.vimState.isExecutingNormalCommand)
) {
ranRepeatableAction = true;
}
Expand Down Expand Up @@ -884,6 +885,7 @@ export class ModeHandler implements vscode.Disposable, IModeHandler {
if (
ranRepeatableAction &&
!this.vimState.isReplayingMacro &&
!this.vimState.isExecutingNormalCommand &&
!this.remapState.isCurrentlyPerformingRemapping
) {
this.vimState.historyTracker.finishCurrentStep();
Expand Down
21 changes: 11 additions & 10 deletions src/state/vimState.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import * as vscode from 'vscode';

import { SUPPORT_IME_SWITCHER, SUPPORT_NVIM } from 'platform/constants';
import { Position } from 'vscode';
import { IMovement } from '../actions/baseMotion';
import { configuration } from '../configuration/configuration';
import { IEasyMotion } from '../actions/plugins/easymotion/types';
import { HistoryTracker } from './../history/historyTracker';
import { Logger } from '../util/logger';
import { Mode } from '../mode/mode';
import { Cursor } from '../common/motion/cursor';
import { RecordedState } from './recordedState';
import { RegisterMode } from './../register/register';
import { ReplaceState } from './../state/replaceState';
import { SurroundState } from '../actions/plugins/surround';
import { SUPPORT_NVIM, SUPPORT_IME_SWITCHER } from 'platform/constants';
import { Position } from 'vscode';
import { ExCommandLine, SearchCommandLine } from '../cmd_line/commandLine';
import { Cursor } from '../common/motion/cursor';
import { configuration } from '../configuration/configuration';
import { Mode } from '../mode/mode';
import { ModeData } from '../mode/modeData';
import { Logger } from '../util/logger';
import { SearchDirection } from '../vimscript/pattern';
import { HistoryTracker } from './../history/historyTracker';
import { RegisterMode } from './../register/register';
import { ReplaceState } from './../state/replaceState';
import { globalState } from './globalState';
import { RecordedState } from './recordedState';

interface IInputMethodSwitcher {
switchInputMethod(prevMode: Mode, newMode: Mode): Promise<void>;
Expand Down Expand Up @@ -104,6 +104,7 @@ export class VimState implements vscode.Disposable {

public isRunningDotCommand = false;
public isReplayingMacro: boolean = false;
public isExecutingNormalCommand: boolean = false;

/**
* The last visual selection before running the dot command
Expand Down
49 changes: 47 additions & 2 deletions src/transformations/execute.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as vscode from 'vscode';
import { TextLine } from 'vscode';
import { ExCommandLine } from '../cmd_line/commandLine';
import { Cursor } from '../common/motion/cursor';
import { PositionDiff } from '../common/motion/position';
Expand All @@ -10,7 +11,10 @@ import { RecordedState } from '../state/recordedState';
import { VimState } from '../state/vimState';
import { TextEditor } from '../textEditor';
import { Logger } from '../util/logger';
import { keystrokesExpressionParser } from '../vimscript/expression';
import {
keystrokesExpressionForMacroParser,
keystrokesExpressionParser,
} from '../vimscript/expression';
import {
InsertTextVSCodeTransformation,
TextTransformations,
Expand Down Expand Up @@ -159,7 +163,7 @@ export async function executeTransformations(
return;
} else if (typeof recordedMacro === 'string') {
// A string was set to the register. We need to execute the characters as if they were typed (in normal mode).
const keystrokes = keystrokesExpressionParser.parse(recordedMacro);
const keystrokes = keystrokesExpressionForMacroParser.parse(recordedMacro);
if (!keystrokes.status) {
throw new Error(`Failed to execute macro: ${recordedMacro}`);
}
Expand Down Expand Up @@ -232,6 +236,47 @@ export async function executeTransformations(
vimState.editor.selection = new vscode.Selection(newPos, newPos);
break;

case 'executeNormal':
const keystroke = keystrokesExpressionParser.parse(transformation.keystroke);
J-Fields marked this conversation as resolved.
Show resolved Hide resolved
const startLineNumber = transformation.startLineNumber;
const endLineNumber = transformation.endLineNumber;
const withRange = transformation.withRange;
J-Fields marked this conversation as resolved.
Show resolved Hide resolved
if (!keystroke.status) {
throw new Error(`Failed to execute normal command: ${transformation.keystroke}`);
}

const resultLines: TextLine[] = [];
J-Fields marked this conversation as resolved.
Show resolved Hide resolved
if (withRange) {
for (let i = startLineNumber; i <= endLineNumber; i++) {
resultLines.push(vimState.document.lineAt(i));
}
} else {
const selectionList = vimState.editor.selections;
for (const selection of selectionList) {
const { start, end } = selection;

for (let i = start.line; i <= end.line; i++) {
resultLines.push(vimState.document.lineAt(i));
}
}
}

vimState.isExecutingNormalCommand = true;
vimState.recordedState = new RecordedState();
await vimState.setCurrentMode(Mode.Normal);
for (const line of resultLines) {
if (withRange) {
vimState.cursorStopPosition = vimState.cursorStartPosition =
TextEditor.getFirstNonWhitespaceCharOnLine(vimState.document, line.lineNumber);
}
await modeHandler.handleMultipleKeyEvents(keystroke.value);
if (vimState.currentMode === Mode.Insert) {
await modeHandler.handleMultipleKeyEvents(['<Esc>']);
J-Fields marked this conversation as resolved.
Show resolved Hide resolved
}
}
vimState.isExecutingNormalCommand = false;
break;

case 'vscodeCommand':
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
await vscode.commands.executeCommand(transformation.command, ...transformation.args);
Expand Down
9 changes: 9 additions & 0 deletions src/transformations/transformations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,14 @@ export interface ContentChangeTransformation {
diff: PositionDiff;
}

export interface ExecuteNormalTransformation {
type: 'executeNormal';
keystroke: string;
J-Fields marked this conversation as resolved.
Show resolved Hide resolved
startLineNumber: number;
endLineNumber: number;
withRange: boolean;
}

export type Transformation =
| InsertTextTransformation
| InsertTextVSCodeTransformation
Expand All @@ -194,6 +202,7 @@ export type Transformation =
| Dot
| Macro
| ContentChangeTransformation
| ExecuteNormalTransformation
| VSCodeCommandTransformation;

/**
Expand Down
3 changes: 2 additions & 1 deletion src/vimscript/exCommandParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { CenterCommand, LeftCommand, RightCommand } from '../cmd_line/commands/l
import { DeleteMarksCommand, MarksCommand } from '../cmd_line/commands/marks';
import { MoveCommand } from '../cmd_line/commands/move';
import { NohlCommand } from '../cmd_line/commands/nohl';
import { NormalCommand } from '../cmd_line/commands/normal';
import { OnlyCommand } from '../cmd_line/commands/only';
import { PrintCommand } from '../cmd_line/commands/print';
import { PutExCommand } from '../cmd_line/commands/put';
Expand Down Expand Up @@ -371,7 +372,7 @@ export const builtinExCommands: ReadonlyArray<[[string, string], ArgParser | und
[['noh', 'lsearch'], succeed(new NohlCommand())],
[['norea', 'bbrev'], undefined],
[['noreme', 'nu'], undefined],
[['norm', 'al'], undefined],
[['norm', 'al'], NormalCommand.argParser],
[['nos', 'wapfile'], undefined],
[['nu', 'mber'], PrintCommand.argParser({ printNumbers: true, printText: true })],
[['nun', 'map'], undefined],
Expand Down
6 changes: 6 additions & 0 deletions src/vimscript/expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ const escapedParser = string('\\')
});

export const keystrokesExpressionParser: Parser<string[]> = alt(
escapedParser,
specialCharacterParser,
any,
).many();

export const keystrokesExpressionForMacroParser: Parser<string[]> = alt(
escapedParser,
specialCharacterParser,
noneOf('"'),
Expand Down
74 changes: 74 additions & 0 deletions test/normalCommand.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { newTest } from './testSimplifier';
import { cleanUpWorkspace, setupWorkspace } from './testUtils';

suite('Execute normal command', () => {
setup(async () => {
await setupWorkspace();
});
J-Fields marked this conversation as resolved.
Show resolved Hide resolved

teardown(cleanUpWorkspace);

newTest({
title: 'One liner',
start: ['foo =| bar = 1'],
keysPressed: ':normal f=i!=\n',
end: ['foo = bar !|== 1'],
});

newTest({
title: 'One liner with selection',
start: ['foo =| bar = 1'],
keysPressed: 'V:normal f=i!=\n',
end: ['foo !|== bar = 1'],
});

newTest({
title: 'Multiple liner with selection',
start: ['foo =| bar = 1', 'foo = bar = 2'],
keysPressed: 'Vj:normal f=i!=\n',
end: ['foo !== bar = 1', 'foo !|== bar = 2'],
});

newTest({
title: 'Multiple liner with line range',
start: ['foo =| bar = 1', 'foo = bar = 2', 'foo = bar = 3'],
keysPressed: ':2,3normal f=i!=\n',
end: ['foo = bar = 1', 'foo !== bar = 2', 'foo !|== bar = 3'],
});

newTest({
title: 'One liner with dot',
start: ['foo =| bar = 1', 'foo = bar = 2'],
keysPressed: 'f=i!=<Esc>j^f=:normal .\n',
end: ['foo = bar !== 1', 'foo !|== bar = 2'],
});

// TODO: remove comment out after fixing dot command bug
J-Fields marked this conversation as resolved.
Show resolved Hide resolved
// newTest({
// title: 'One liner with multiple dot',
// start: ['foo =| bar = 1', 'foo = bar = 2'],
// keysPressed: 'f=i!=<Esc>j^f=:normal 2.\n',
// end: ['foo = bar !== 1', 'foo !=!|== bar = 2'],
J-Fields marked this conversation as resolved.
Show resolved Hide resolved
// });

newTest({
title: 'One liner with macro',
start: ['|1. one, 2. two, 3. three, 4. four'],
keysPressed: 'qaf.r)q:normal @a\n',
end: ['1) one, 2|) two, 3. three, 4. four'],
});

newTest({
title: 'One liner with multiple macro',
start: ['|1. one, 2. two, 3. three, 4. four'],
keysPressed: 'qaf.r)q:normal 3@a\n',
end: ['1) one, 2) two, 3) three, 4|) four'],
});

newTest({
title: 'Multiple liner with multiple macro',
start: ['|0. zero', '1. one, 2. two, 3. three, 4. four', '5. five, 6. six, 7. seven, 8. eight'],
keysPressed: 'qaf.r)qjVj:normal 4@a\n',
end: ['0) zero', '1) one, 2) two, 3) three, 4) four', '5) five, 6) six, 7) seven, 8|) eight'],
});
J-Fields marked this conversation as resolved.
Show resolved Hide resolved
});
J-Fields marked this conversation as resolved.
Show resolved Hide resolved
J-Fields marked this conversation as resolved.
Show resolved Hide resolved
Loading