diff --git a/src/cmd_line/commands/resize.ts b/src/cmd_line/commands/resize.ts new file mode 100644 index 00000000000..ba683e829e6 --- /dev/null +++ b/src/cmd_line/commands/resize.ts @@ -0,0 +1,84 @@ +import { Parser, optWhitespace, seq, regexp, alt } from 'parsimmon'; +import * as vscode from 'vscode'; +import { VimState } from '../../state/vimState'; +import { ExCommand } from '../../vimscript/exCommand'; +import { StatusBar } from '../../statusBar'; + +export interface IResizeCommandArguments { + direction?: '+' | '-'; + value?: number; + absoluteValue?: number; +} + +/** + * Implements :resize command + * The :resize command is used to change the height of the current window + * + * Examples: + * :resize +5 - increase window height by 5 rows + * :resize -3 - decrease window height by 3 rows + */ +export class ResizeCommand extends ExCommand { + public static readonly argParser: Parser = seq( + optWhitespace, + alt( + // Parse absolute values like ":resize 20" + regexp(/\d+/).map((num) => ({ absoluteValue: parseInt(num, 10) })), + // Parse relative values like ":resize +5" or ":resize -3" + seq(regexp(/[+-]/), regexp(/\d+/)).map(([direction, num]) => ({ + direction: direction as '+' | '-', + value: parseInt(num, 10), + })), + // Empty args defaults to maximize + optWhitespace.map(() => ({})), + ), + ).map(([, args]) => new ResizeCommand(args)); + + private readonly arguments: IResizeCommandArguments; + + constructor(args: IResizeCommandArguments) { + super(); + this.arguments = args; + } + + async execute(vimState: VimState): Promise { + const { direction, value, absoluteValue } = this.arguments; + + // Handle absolute resize + if (absoluteValue !== undefined) { + // VSCode doesn't support setting absolute window heights + StatusBar.setText( + vimState, + `VSCode doesn't support setting exact row heights. Use relative resize (+/-) instead.`, + ); + return; + } + + // Handle relative resize + if (direction && value !== undefined) { + // A value of 0 should be a no-op + if (value === 0) { + return; + } + const command = + direction === '+' + ? 'workbench.action.increaseViewHeight' + : 'workbench.action.decreaseViewHeight'; + + // Use runCommands for better performance with multiple executions + if (value > 1) { + const commands = Array(value).fill(command); + await vscode.commands.executeCommand('runCommands', { commands }); + } else { + await vscode.commands.executeCommand(command); + } + return; + } + + // TODO: Default behavior (no arguments) - toggle panel to maximize editor height + StatusBar.setText( + vimState, + 'resize does not currently support running without parameters. Please use :resize +N or :resize -N to adjust the height.', + ); + } +} diff --git a/src/cmd_line/commands/vertical.ts b/src/cmd_line/commands/vertical.ts new file mode 100644 index 00000000000..54fa664e01d --- /dev/null +++ b/src/cmd_line/commands/vertical.ts @@ -0,0 +1,163 @@ +import { Parser, all, optWhitespace } from 'parsimmon'; +import { VimState } from '../../state/vimState'; +import { ExCommand } from '../../vimscript/exCommand'; +import { FileCommand } from './file'; +import * as vscode from 'vscode'; +import { StatusBar } from '../../statusBar'; +import { VimError, ErrorCode } from '../../error'; + +export interface IVerticalCommandArguments { + /** The command following :vertical (e.g., "split", "new filename", "resize +5") */ + command: string; +} + +/** + * Implements :vertical command + * The :vertical command modifier forces the following command to be executed + * in a vertical split manner instead of horizontal. + * + * Currently supported commands: + * - split: Create vertical split instead of horizontal split + * - new: Create new file in vertical split instead of horizontal split + * - resize: Adjust window width instead of height + * + * Examples: + * :vertical split - Create vertical split of current file + * :vertical split filename - Create vertical split and open filename + * :vertical new - Create vertical split with new untitled file + * :vertical new filename - Create vertical split with new file named filename + * :vertical resize +5 - Increase current window width by 5 columns + * :vertical resize -3 - Decrease current window width by 3 columns + * + * Note: For other commands (like help), :vertical sets a modifier flag that + * compatible commands can check, but many commands are not yet implemented + * in this Vim extension. + */ +export class VerticalCommand extends ExCommand { + public static readonly argParser: Parser = optWhitespace + .then(all) + .map((command) => new VerticalCommand({ command })); + + private readonly arguments: IVerticalCommandArguments; + + constructor(args: IVerticalCommandArguments) { + super(); + this.arguments = args; + } + + async execute(vimState: VimState): Promise { + const command = this.arguments.command.trim(); + + if (!command) { + // :vertical without a command is not meaningful + StatusBar.displayError(vimState, VimError.fromCode(ErrorCode.ArgumentRequired)); + return; + } + + // Handle specific commands that we know support vertical modification + if (command === 'split' || command.startsWith('split ')) { + // Parse as a split command but force vertical behavior + const splitArgs = command.substring(5).trim(); // Remove 'split' + let fileCommand: FileCommand; + + if (splitArgs === '') { + // :vertical split (no file) + fileCommand = new FileCommand({ name: 'vsplit', opt: [] }); + } else { + // :vertical split filename + fileCommand = new FileCommand({ name: 'vsplit', opt: [], file: splitArgs }); + } + + await fileCommand.execute(vimState); + return; + } + + if (command === 'new' || command.startsWith('new ')) { + // Parse as a new command but force vertical behavior + const newArgs = command.substring(3).trim(); // Remove 'new' + let fileCommand: FileCommand; + + if (newArgs === '') { + // :vertical new (no file) + fileCommand = new FileCommand({ name: 'vnew', opt: [] }); + } else { + // :vertical new filename + fileCommand = new FileCommand({ name: 'vnew', opt: [], file: newArgs }); + } + + await fileCommand.execute(vimState); + return; + } + + if (command === 'resize' || command.startsWith('resize ')) { + // Handle :vertical resize - change window width instead of height + const resizeArgs = command.substring(6).trim(); // Remove 'resize' + + // Parse resize arguments + let direction: '+' | '-' | undefined; + let value: number | undefined; + let absoluteValue: number | undefined; + + if (resizeArgs === '') { + // :vertical resize (no args) - maximize width + // Use editor group commands instead of panel commands + await vscode.commands.executeCommand('workbench.action.toggleEditorWidths'); + return; + } + + // Parse arguments + const match = resizeArgs.match(/^([+-]?)(\d+)$/); + if (match) { + const [, dir, num] = match; + if (dir) { + direction = dir as '+' | '-'; + value = parseInt(num, 10); + } else { + absoluteValue = parseInt(num, 10); + } + } else { + // Invalid argument (e.g., non-numeric or unexpected chars) + StatusBar.displayError( + vimState, + VimError.fromCode(ErrorCode.InvalidArgument474, resizeArgs), + ); + return; + } + + // Execute width resize commands + if (absoluteValue !== undefined) { + // VSCode doesn't support setting absolute window widths + StatusBar.setText( + vimState, + `VSCode doesn't support setting exact column widths. Use relative resize (+/-) instead.`, + ); + return; + } else if (direction && value !== undefined) { + // A value of 0 should be a no-op + if (value === 0) { + return; + } + const resizeCommand = + direction === '+' + ? 'workbench.action.increaseViewWidth' + : 'workbench.action.decreaseViewWidth'; + + // Use runCommands for better performance with multiple executions + if (value > 1) { + const commands = Array(value).fill(resizeCommand); + await vscode.commands.executeCommand('runCommands', { commands }); + } else { + await vscode.commands.executeCommand(resizeCommand); + } + } + + return; + } + + // For other commands that we don't explicitly support + StatusBar.displayError( + vimState, + VimError.fromCode(ErrorCode.NotAnEditorCommand, `vertical ${command}`), + ); + } +} diff --git a/src/vimscript/exCommandParser.ts b/src/vimscript/exCommandParser.ts index 667ea8d8a98..67b19da89e9 100644 --- a/src/vimscript/exCommandParser.ts +++ b/src/vimscript/exCommandParser.ts @@ -29,6 +29,7 @@ import { QuitCommand } from '../cmd_line/commands/quit'; import { ReadCommand } from '../cmd_line/commands/read'; import { RedoCommand } from '../cmd_line/commands/redo'; import { RegisterCommand } from '../cmd_line/commands/register'; +import { ResizeCommand } from '../cmd_line/commands/resize'; import { RetabCommand } from '../cmd_line/commands/retab'; import { SetCommand } from '../cmd_line/commands/set'; import { ShCommand } from '../cmd_line/commands/sh'; @@ -39,6 +40,7 @@ import { SubstituteCommand } from '../cmd_line/commands/substitute'; import { TabCommand } from '../cmd_line/commands/tab'; import { TerminalCommand } from '../cmd_line/commands/terminal'; import { UndoCommand } from '../cmd_line/commands/undo'; +import { VerticalCommand } from '../cmd_line/commands/vertical'; import { VsCodeCommand } from '../cmd_line/commands/vscode'; import { WallCommand } from '../cmd_line/commands/wall'; import { WriteCommand } from '../cmd_line/commands/write'; @@ -444,7 +446,7 @@ export const builtinExCommands: ReadonlyArray<[[string, string], ArgParser | und [['redraws', 'tatus'], undefined], [['redrawt', 'abline'], undefined], [['reg', 'isters'], RegisterCommand.argParser], - [['res', 'ize'], undefined], + [['res', 'ize'], ResizeCommand.argParser], [['ret', 'ab'], RetabCommand.argParser], [['retu', 'rn'], undefined], [['rew', 'ind'], undefined], @@ -575,7 +577,8 @@ export const builtinExCommands: ReadonlyArray<[[string, string], ArgParser | und [['v', 'global'], undefined], [['ve', 'rsion'], undefined], [['verb', 'ose'], undefined], - [['vert', 'ical'], undefined], + [['vert', 'ical'], VerticalCommand.argParser], + [['vertical-','resize'], VerticalCommand.argParser], [['vi', 'sual'], undefined], [['vie', 'w'], undefined], [['vim', 'grep'], GrepCommand.argParser], diff --git a/test/cmd_line/resize.test.ts b/test/cmd_line/resize.test.ts new file mode 100644 index 00000000000..3b53a10e2cc --- /dev/null +++ b/test/cmd_line/resize.test.ts @@ -0,0 +1,356 @@ +import { strict as assert } from 'assert'; +import * as vscode from 'vscode'; + +import { getAndUpdateModeHandler } from '../../extension'; +import { ExCommandLine } from '../../src/cmd_line/commandLine'; +import { ModeHandler } from '../../src/mode/modeHandler'; +import { StatusBar } from '../../src/statusBar'; +import { cleanUpWorkspace, setupWorkspace } from '../testUtils'; + +suite('Resize command', () => { + let modeHandler: ModeHandler; + type RunCommandsArgs = { commands: string[] }; + + setup(async () => { + await setupWorkspace(); + modeHandler = (await getAndUpdateModeHandler())!; + }); + + teardown(cleanUpWorkspace); + + test('resize without arguments executes default behavior', async () => { + // Test that :resize without arguments doesn't throw an error + // The actual behavior (TODO comment in code) is not fully implemented yet + await new ExCommandLine('resize', modeHandler.vimState.currentMode).run(modeHandler.vimState); + + // Test passes if no error is thrown + assert.ok(true, 'Resize without arguments should not throw error'); + }); + + test('resize +N increases height', async () => { + const originalExecuteCommand = vscode.commands.executeCommand; + const executedCommands: string[] = []; + + vscode.commands.executeCommand = async (command: string): Promise => { + executedCommands.push(command); + return Promise.resolve(); + }; + + try { + await new ExCommandLine('resize +5', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + assert.strictEqual( + executedCommands[0], + 'runCommands', + 'Should execute runCommands for multiple height increases', + ); + } finally { + vscode.commands.executeCommand = originalExecuteCommand; + } + }); + + test('resize -N decreases height', async () => { + const originalExecuteCommand = vscode.commands.executeCommand; + const executedCommands: string[] = []; + + vscode.commands.executeCommand = async (command: string): Promise => { + executedCommands.push(command); + return Promise.resolve(); + }; + + try { + await new ExCommandLine('resize -3', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + assert.strictEqual( + executedCommands[0], + 'runCommands', + 'Should execute runCommands for multiple height decreases', + ); + } finally { + vscode.commands.executeCommand = originalExecuteCommand; + } + }); + + test('resize +1 executes single increase command', async () => { + const originalExecuteCommand = vscode.commands.executeCommand; + let executedCommand: string | undefined; + + vscode.commands.executeCommand = async (command: string): Promise => { + executedCommand = command; + return Promise.resolve(); + }; + + try { + await new ExCommandLine('resize +1', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + assert.strictEqual( + executedCommand, + 'workbench.action.increaseViewHeight', + 'Should execute single height increase command', + ); + } finally { + vscode.commands.executeCommand = originalExecuteCommand; + } + }); + + test('resize -1 executes single decrease command', async () => { + const originalExecuteCommand = vscode.commands.executeCommand; + let executedCommand: string | undefined; + + vscode.commands.executeCommand = async (command: string): Promise => { + executedCommand = command; + return Promise.resolve(); + }; + + try { + await new ExCommandLine('resize -1', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + assert.strictEqual( + executedCommand, + 'workbench.action.decreaseViewHeight', + 'Should execute single height decrease command', + ); + } finally { + vscode.commands.executeCommand = originalExecuteCommand; + } + }); + + test('resize +0 is no-op', async () => { + const originalExecuteCommand = vscode.commands.executeCommand; + const executedCommands: string[] = []; + + vscode.commands.executeCommand = async (command: string): Promise => { + executedCommands.push(command); + return Promise.resolve(); + }; + + try { + await new ExCommandLine('resize +0', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + assert.strictEqual(executedCommands.length, 0, 'No commands should be executed for +0'); + } finally { + vscode.commands.executeCommand = originalExecuteCommand; + } + }); + + test('resize -0 is no-op', async () => { + const originalExecuteCommand = vscode.commands.executeCommand; + const executedCommands: string[] = []; + + vscode.commands.executeCommand = async (command: string): Promise => { + executedCommands.push(command); + return Promise.resolve(); + }; + + try { + await new ExCommandLine('resize -0', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + assert.strictEqual(executedCommands.length, 0, 'No commands should be executed for -0'); + } finally { + vscode.commands.executeCommand = originalExecuteCommand; + } + }); + + test('resize with absolute value shows unsupported message', async () => { + await new ExCommandLine('resize 25', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + + // The status bar should contain message about VSCode not supporting exact row heights + const statusText = StatusBar.getText(); + assert.ok( + statusText.includes("doesn't support setting exact row heights"), + `Expected status bar to contain unsupported message, but got: "${statusText}"`, + ); + }); + + test('resize with large positive value uses runCommands', async () => { + const originalExecuteCommand = vscode.commands.executeCommand; + let executedCommand: string | undefined; + let commandArgs: RunCommandsArgs | undefined; + + vscode.commands.executeCommand = async ( + command: string, + ...args: RunCommandsArgs[] + ): Promise => { + executedCommand = command; + commandArgs = args[0]; + return Promise.resolve(); + }; + + try { + await new ExCommandLine('resize +10', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + assert.strictEqual(executedCommand, 'runCommands', 'Should use runCommands for large values'); + assert.ok(commandArgs, 'Should pass arguments to runCommands'); + assert.strictEqual(commandArgs.commands.length, 10, 'Should have 10 commands in array'); + assert.strictEqual( + commandArgs.commands[0], + 'workbench.action.increaseViewHeight', + 'Commands should be height increase', + ); + } finally { + vscode.commands.executeCommand = originalExecuteCommand; + } + }); + + test('resize with large negative value uses runCommands', async () => { + const originalExecuteCommand = vscode.commands.executeCommand; + let executedCommand: string | undefined; + let commandArgs: RunCommandsArgs | undefined; + + vscode.commands.executeCommand = async ( + command: string, + ...args: RunCommandsArgs[] + ): Promise => { + executedCommand = command; + commandArgs = args[0]; + return Promise.resolve(); + }; + + try { + await new ExCommandLine('resize -8', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + assert.strictEqual(executedCommand, 'runCommands', 'Should use runCommands for large values'); + assert.ok(commandArgs, 'Should pass arguments to runCommands'); + assert.strictEqual(commandArgs.commands.length, 8, 'Should have 8 commands in array'); + assert.strictEqual( + commandArgs.commands[0], + 'workbench.action.decreaseViewHeight', + 'Commands should be height decrease', + ); + } finally { + vscode.commands.executeCommand = originalExecuteCommand; + } + }); + + // Test various spacing scenarios + test('resize with extra spaces works', async () => { + const originalExecuteCommand = vscode.commands.executeCommand; + let executedCommand: string | undefined; + + vscode.commands.executeCommand = async (command: string): Promise => { + executedCommand = command; + return Promise.resolve(); + }; + + try { + await new ExCommandLine('resize +1', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + assert.strictEqual( + executedCommand, + 'workbench.action.increaseViewHeight', + 'Should handle extra spaces', + ); + } finally { + vscode.commands.executeCommand = originalExecuteCommand; + } + }); + + test('resize with tabs and spaces works', async () => { + const originalExecuteCommand = vscode.commands.executeCommand; + let executedCommand: string | undefined; + + vscode.commands.executeCommand = async (command: string): Promise => { + executedCommand = command; + return Promise.resolve(); + }; + + try { + await new ExCommandLine('resize\t\t -2', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + assert.strictEqual(executedCommand, 'runCommands', 'Should handle tabs and spaces'); + } finally { + vscode.commands.executeCommand = originalExecuteCommand; + } + }); + + // Test edge cases with very large numbers + test('resize with very large positive number', async () => { + const originalExecuteCommand = vscode.commands.executeCommand; + let executedCommand: string | undefined; + let commandArgs: RunCommandsArgs | undefined; + + vscode.commands.executeCommand = async ( + command: string, + ...args: RunCommandsArgs[] + ): Promise => { + executedCommand = command; + commandArgs = args[0]; + return Promise.resolve(); + }; + + try { + await new ExCommandLine('resize +999', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + assert.strictEqual(executedCommand, 'runCommands'); + assert.ok(commandArgs, 'Should pass arguments to runCommands'); + assert.strictEqual(commandArgs.commands.length, 999); + } finally { + vscode.commands.executeCommand = originalExecuteCommand; + } + }); + + test('resize with very large negative number', async () => { + const originalExecuteCommand = vscode.commands.executeCommand; + let executedCommand: string | undefined; + let commandArgs: RunCommandsArgs | undefined; + + vscode.commands.executeCommand = async ( + command: string, + ...args: RunCommandsArgs[] + ): Promise => { + executedCommand = command; + commandArgs = args[0]; + return Promise.resolve(); + }; + + try { + await new ExCommandLine('resize -999', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + assert.strictEqual(executedCommand, 'runCommands'); + assert.ok(commandArgs, 'Should pass arguments to runCommands'); + assert.strictEqual(commandArgs.commands.length, 999); + assert.strictEqual(commandArgs.commands[0], 'workbench.action.decreaseViewHeight'); + } finally { + vscode.commands.executeCommand = originalExecuteCommand; + } + }); + + // Error handling tests - Note: resize command parser is quite permissive, + // so most "invalid" inputs are either parsed successfully or ignored rather than throwing errors. + // These tests verify that the command handles edge cases gracefully. + + test('resize with non-numeric absolute value shows unsupported message', async () => { + // Test that non-numeric absolute values are handled (though they may be parsed differently) + await new ExCommandLine('resize abc', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + + // The command should complete without throwing an error + // (The parser may not match this input, so it might be treated as empty args) + assert.ok(true, 'Command should complete without throwing'); + }); + + test('resize with mixed numeric and text handled gracefully', async () => { + // Test commands with mixed content + await new ExCommandLine('resize 5abc', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + + // The command should complete without throwing an error + assert.ok(true, 'Command should complete without throwing'); + }); +}); diff --git a/test/cmd_line/vertical.test.ts b/test/cmd_line/vertical.test.ts new file mode 100644 index 00000000000..0c9794a2323 --- /dev/null +++ b/test/cmd_line/vertical.test.ts @@ -0,0 +1,272 @@ +import { strict as assert } from 'assert'; +import * as vscode from 'vscode'; + +import { getAndUpdateModeHandler } from '../../extension'; +import { ExCommandLine } from '../../src/cmd_line/commandLine'; +import { VimError, ErrorCode } from '../../src/error'; +import { ModeHandler } from '../../src/mode/modeHandler'; +import { StatusBar } from '../../src/statusBar'; +import { cleanUpWorkspace, setupWorkspace, waitForEditorsToClose } from '../testUtils'; + +suite('Vertical command', () => { + let modeHandler: ModeHandler; + + setup(async () => { + await setupWorkspace(); + modeHandler = (await getAndUpdateModeHandler())!; + }); + + teardown(cleanUpWorkspace); + + test('vertical without command shows argument required error', async () => { + await new ExCommandLine('vertical', modeHandler.vimState.currentMode).run(modeHandler.vimState); + const statusText = StatusBar.getText(); + assert.ok(statusText.includes('Argument required')); + }); + + test('vertical with whitespace only shows argument required error', async () => { + await new ExCommandLine('vertical ', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + const statusText = StatusBar.getText(); + assert.ok(statusText.includes('Argument required')); + }); + + test('vertical with unsupported command shows error', async () => { + await new ExCommandLine('vertical help', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + const statusText = StatusBar.getText(); + assert.ok(statusText.includes('Not an editor command: vertical help')); + }); + + // Test vertical split commands + test('vertical split creates vertical split', async () => { + await new ExCommandLine('vertical split', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + await waitForEditorsToClose(2); + + assert.strictEqual( + vscode.window.visibleTextEditors.length, + 2, + 'Vertical split should create a second editor', + ); + }); + + test('vertical split with filename creates vertical split and opens file', async () => { + const initialEditorCount = vscode.window.visibleTextEditors.length; + + await new ExCommandLine('vertical split test.txt', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + await waitForEditorsToClose(2); // Wait for editors to be ready + + const finalEditorCount = vscode.window.visibleTextEditors.length; + assert.ok( + finalEditorCount > initialEditorCount, + 'Vertical split with filename should create a new editor', + ); + }); + + // Test vertical new commands + test('vertical new creates vertical split with new file', async () => { + await new ExCommandLine('vertical new', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + await waitForEditorsToClose(2); + + assert.strictEqual( + vscode.window.visibleTextEditors.length, + 2, + 'Vertical new should create a second editor', + ); + }); + + test('vertical new with filename creates vertical split with new named file', async () => { + const initialEditorCount = vscode.window.visibleTextEditors.length; + + await new ExCommandLine('vertical new newfile.txt', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + await waitForEditorsToClose(2); // Wait for editors to be ready + + const finalEditorCount = vscode.window.visibleTextEditors.length; + assert.ok( + finalEditorCount > initialEditorCount, + 'Vertical new with filename should create a new editor', + ); + }); + + // Test vertical resize commands + test('vertical resize without arguments maximizes width', async () => { + // Create a mock for the VSCode command + const originalExecuteCommand = vscode.commands.executeCommand; + let executedCommand: string | undefined; + + vscode.commands.executeCommand = async (command: string): Promise => { + executedCommand = command; + return Promise.resolve(); + }; + + try { + await new ExCommandLine('vertical resize', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + assert.strictEqual(executedCommand, 'workbench.action.toggleEditorWidths'); + } finally { + vscode.commands.executeCommand = originalExecuteCommand; + } + }); + + test('vertical resize +N increases width', async () => { + const originalExecuteCommand = vscode.commands.executeCommand; + const executedCommands: string[] = []; + + vscode.commands.executeCommand = async (command: string): Promise => { + executedCommands.push(command); + return Promise.resolve(); + }; + + try { + await new ExCommandLine('vertical resize +5', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + assert.strictEqual(executedCommands[0], 'runCommands'); + } finally { + vscode.commands.executeCommand = originalExecuteCommand; + } + }); + + test('vertical resize -N decreases width', async () => { + const originalExecuteCommand = vscode.commands.executeCommand; + const executedCommands: string[] = []; + + vscode.commands.executeCommand = async (command: string): Promise => { + executedCommands.push(command); + return Promise.resolve(); + }; + + try { + await new ExCommandLine('vertical resize -3', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + assert.strictEqual(executedCommands[0], 'runCommands'); + } finally { + vscode.commands.executeCommand = originalExecuteCommand; + } + }); + + test('vertical resize +1 executes single command', async () => { + const originalExecuteCommand = vscode.commands.executeCommand; + let executedCommand: string | undefined; + + vscode.commands.executeCommand = async (command: string): Promise => { + executedCommand = command; + return Promise.resolve(); + }; + + try { + await new ExCommandLine('vertical resize +1', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + assert.strictEqual(executedCommand, 'workbench.action.increaseViewWidth'); + } finally { + vscode.commands.executeCommand = originalExecuteCommand; + } + }); + + test('vertical resize -1 executes single command', async () => { + const originalExecuteCommand = vscode.commands.executeCommand; + let executedCommand: string | undefined; + + vscode.commands.executeCommand = async (command: string): Promise => { + executedCommand = command; + return Promise.resolve(); + }; + + try { + await new ExCommandLine('vertical resize -1', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + assert.strictEqual(executedCommand, 'workbench.action.decreaseViewWidth'); + } finally { + vscode.commands.executeCommand = originalExecuteCommand; + } + }); + + test('vertical resize +0 is no-op', async () => { + const originalExecuteCommand = vscode.commands.executeCommand; + const executedCommands: string[] = []; + + vscode.commands.executeCommand = async (command: string): Promise => { + executedCommands.push(command); + return Promise.resolve(); + }; + + try { + await new ExCommandLine('vertical resize +0', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + assert.strictEqual(executedCommands.length, 0, 'No commands should be executed for +0'); + } finally { + vscode.commands.executeCommand = originalExecuteCommand; + } + }); + + test('vertical resize -0 is no-op', async () => { + const originalExecuteCommand = vscode.commands.executeCommand; + const executedCommands: string[] = []; + + vscode.commands.executeCommand = async (command: string): Promise => { + executedCommands.push(command); + return Promise.resolve(); + }; + + try { + await new ExCommandLine('vertical resize -0', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + assert.strictEqual(executedCommands.length, 0, 'No commands should be executed for -0'); + } finally { + vscode.commands.executeCommand = originalExecuteCommand; + } + }); + + test('vertical resize with absolute value shows unsupported message', async () => { + await new ExCommandLine('vertical resize 80', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + + // The status bar should contain message about VSCode not supporting exact column widths + const statusText = StatusBar.getText(); + assert.ok( + statusText.includes("doesn't support setting exact column widths"), + 'Should show message about unsupported absolute resize', + ); + }); + + test('vertical resize with invalid argument shows error', async () => { + await new ExCommandLine('vertical resize abc', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + const statusText = StatusBar.getText(); + assert.ok(statusText.includes('Invalid argument: abc')); + }); + + test('vertical resize with invalid format shows error', async () => { + await new ExCommandLine('vertical resize +abc', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + const statusText = StatusBar.getText(); + assert.ok(statusText.includes('Invalid argument: +abc')); + }); + + test('vertical resize with mixed format shows error', async () => { + await new ExCommandLine('vertical resize 5+', modeHandler.vimState.currentMode).run( + modeHandler.vimState, + ); + const statusText = StatusBar.getText(); + assert.ok(statusText.includes('Invalid argument: 5+')); + }); +});