Skip to content
84 changes: 84 additions & 0 deletions src/cmd_line/commands/resize.ts
Original file line number Diff line number Diff line change
@@ -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<ResizeCommand> = 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<void> {
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.',
);
}
}
166 changes: 166 additions & 0 deletions src/cmd_line/commands/vertical.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
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 = {
general: optWhitespace.then(all).map((command) => new VerticalCommand({ command })),
resize: optWhitespace
.then(all)
.map((command) => new VerticalCommand({ command: `resize ${command}` })),
};

private readonly arguments: IVerticalCommandArguments;

constructor(args: IVerticalCommandArguments) {
super();
this.arguments = args;
}

async execute(vimState: VimState): Promise<void> {
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}`),
);
}
}
7 changes: 5 additions & 2 deletions src/vimscript/exCommandParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,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';
Expand All @@ -40,6 +41,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';
Expand Down Expand Up @@ -445,7 +447,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],
Expand Down Expand Up @@ -576,7 +578,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.general],
[['vertical-resize', ''], VerticalCommand.argParser.resize],
[['vi', 'sual'], undefined],
[['vie', 'w'], undefined],
[['vim', 'grep'], GrepCommand.argParser],
Expand Down
Loading