diff --git a/.vscode/tasks.json b/.vscode/tasks.json index d18fb11e..c38a1946 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -15,7 +15,7 @@ }, { "type": "gulp", - "task": "build", + "task": "build-dev", "problemMatcher": [] } ] diff --git a/src/actions/commands/actions.ts b/src/actions/commands/actions.ts index a5dcb277..2c568715 100644 --- a/src/actions/commands/actions.ts +++ b/src/actions/commands/actions.ts @@ -1529,17 +1529,35 @@ export class CommandInsertNewLineAbove extends BaseCommand { await vimState.setCurrentMode(Mode.Insert); const count = vimState.recordedState.count || 1; + const charPos = position.getLineBeginRespectingIndent(vimState.document).character; + for (let i = 0; i < count; i++) { await vscode.commands.executeCommand('editor.action.insertLineBefore'); } vimState.cursors = getCursorsAfterSync(vimState.editor); - for (let i = 0; i < count; i++) { - const newPos = new Position( - vimState.cursors[0].start.line + i, - vimState.cursors[0].start.character - ); + const indentAmt = Math.max(charPos - vimState.cursors[0].start.character, 0); + + const firstPos = new Position(vimState.cursors[0].start.line, charPos); + vimState.cursors[0] = new Cursor(firstPos, firstPos); + vimState.recordedState.transformer.addTransformation({ + type: 'insertText', + text: TextEditor.setIndentationLevel('', indentAmt), + position: firstPos, + cursorIndex: 0, + manuallySetCursorPositions: true, + }); + + for (let i = 1; i < count; i++) { + const newPos = new Position(vimState.cursors[0].start.line + i, charPos); vimState.cursors.push(new Cursor(newPos, newPos)); + vimState.recordedState.transformer.addTransformation({ + type: 'insertText', + text: TextEditor.setIndentationLevel('', indentAmt), + position: newPos, + cursorIndex: i, + manuallySetCursorPositions: true, + }); } vimState.cursors = vimState.cursors.reverse(); vimState.isFakeMultiCursor = true; diff --git a/test/actions/insertLine.test.ts b/test/actions/insertLine.test.ts new file mode 100644 index 00000000..596599a5 --- /dev/null +++ b/test/actions/insertLine.test.ts @@ -0,0 +1,95 @@ +import * as assert from 'assert'; +import { Position, window } from 'vscode'; +import { getCurrentParagraphBeginning, getCurrentParagraphEnd } from '../../src/textobject/paragraph'; +import { WordType } from '../../src/textobject/word'; +import { TextEditor } from '../../src/textEditor'; +import { assertEqualLines, cleanUpWorkspace, setupWorkspace } from '../testUtils'; +import { ModeHandler } from '../../src/mode/modeHandler'; +import { getAndUpdateModeHandler } from '../../extension'; +import * as vscode from 'vscode'; +import { Configuration } from '../testConfiguration'; + +suite('insertLineBefore', () => { + let modeHandler: ModeHandler; + + suiteSetup(async () => { + const configuration = new Configuration(); + configuration.tabstop = 4; + configuration.expandtab = true; + + await setupWorkspace(configuration); + await setupWorkspace(); + modeHandler = (await getAndUpdateModeHandler())!; + }); + + suiteTeardown(cleanUpWorkspace); + + test('tabs are added to match previous line even if line above does not match', async () => { + // Setup the test + await modeHandler.handleMultipleKeyEvents(['', 'g', 'g', 'd', 'G']); + await modeHandler.handleMultipleKeyEvents('i\na'.split('')); + await modeHandler.handleMultipleKeyEvents(['']); + await modeHandler.handleMultipleKeyEvents('2G>>ob\nc'.split('')); + + // This is the current state of the document + // + // a + // b + // c + await modeHandler.handleMultipleKeyEvents(['', '2', 'G', 'O', 'a']); + const text = vscode.window.activeTextEditor?.document.getText().split('\n'); + assert.ok(text); + assert.strictEqual(text[1].replace(/[\n\r]/g, ''), text[2].replace(/[\n\r]/g, '')); + }); + + test('no extra whitespace added when insertLineBefore inserts correct amount', async () => { + await modeHandler.handleMultipleKeyEvents(['', 'g', 'g', 'd', 'G']); + await modeHandler.handleMultipleKeyEvents('i\na'.split('')); + await modeHandler.handleMultipleKeyEvents(['']); + await modeHandler.handleMultipleKeyEvents('2G>>ob\nc'.split('')); + + // This is the current state of the document + // + // a + // b + // c + await modeHandler.handleMultipleKeyEvents(['', '3', 'G', 'O', 'b']); + const text = vscode.window.activeTextEditor?.document.getText().split('\n'); + assert.ok(text); + assert.strictEqual(text[2].replace(/[\n\r]/g, ''), text[3].replace(/[\n\r]/g, '')); + }); + + test('works at the top of the document', async () => { + await modeHandler.handleMultipleKeyEvents(['', 'g', 'g', 'd', 'G']); + await modeHandler.handleMultipleKeyEvents('ia'.split('')); + await modeHandler.handleMultipleKeyEvents(['']); + await modeHandler.handleMultipleKeyEvents('gg>>'.split('')); + + // This is the current state of the document + // a + await modeHandler.handleMultipleKeyEvents(['', 'g', 'g', 'O', 'a']); + const text = vscode.window.activeTextEditor?.document.getText().split('\n'); + assert.ok(text); + assert.strictEqual(text[0].replace(/[\n\r]/g, ''), text[1].replace(/[\n\r]/g, '')); + }); + + test('works with multiple cursors', async () => { + await modeHandler.handleMultipleKeyEvents(['', 'g', 'g', 'd', 'G']); + await modeHandler.handleMultipleKeyEvents('oa'.split('')); + await modeHandler.handleMultipleKeyEvents(['']); + await modeHandler.handleMultipleKeyEvents('2G>>'.split('')); + // This is the current state of the document + // + // a + await modeHandler.handleMultipleKeyEvents(['', '2', 'G', '2', 'O', 'a']); + // After + // + // a + // a + // a + const text = vscode.window.activeTextEditor?.document.getText().split('\n'); + assert.ok(text); + assert.strictEqual(text[1].replace(/[\n\r]/g, ''), text[2].replace(/[\n\r]/g, '')); + assert.strictEqual(text[2].replace(/[\n\r]/g, ''), text[3].replace(/[\n\r]/g, '')); + }); +});