Skip to content

Conversation

@martchellop
Copy link

What this PR does / why we need it:

This PR implements the :g[lobal]/[pattern]/[command] feature

Which issue(s) this PR fixes

#3010

Special notes for your reviewer:

This is my first time in this code base and I used Kiro. All the tests are passing, but review it carefully.

@benatagirre
Copy link

I see this also implements the :v command. I've always missed that one.


private static createParser(inverse: boolean): Parser<GlobalCommand> {
return optWhitespace.then(
regexp(/[^\w\s\\|"]{1}/)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{1}... doesn't that mean "1 time"? Is that necessary?

Comment on lines +255 to +257
const position = new Position(lineNumber, 0);
vimState.cursorStopPosition = position;
vimState.cursorStartPosition = position;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const position = new Position(lineNumber, 0);
vimState.cursorStopPosition = position;
vimState.cursorStartPosition = position;
vimState.cursor = Cursor.atPosition(new Position(lineNumber, 0));

const position = new Position(lineNumber, 0);
vimState.cursorStopPosition = position;
vimState.cursorStartPosition = position;
vimState.editor.selection = new vscode.Selection(position, position);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is setting the selection here necessary? Whenever possible we try to just set the cursor(s) on VimState then let ModeHandler.updateView() set the editor selections accordingly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(here and elsewhere)

Comment on lines +269 to +273
StatusBar.displayError(
vimState,
VimError.fromCode(ErrorCode.NotAnEditorCommand, commandToExecute),
);
throw new Error('Command parsing failed');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just throw the VimError?

Comment on lines +348 to +349
vimState.cursorStopPosition = finalPosition;
vimState.cursorStartPosition = finalPosition;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is redundant if we are going to do vimState.cursors = [new Cursor(finalPosition, finalPosition)]; two lines below

vimState.cursorStartPosition = finalPosition;
vimState.editor.selection = new vscode.Selection(finalPosition, finalPosition);
vimState.cursors = [new Cursor(finalPosition, finalPosition)];
vimState.lastVisualSelection = undefined;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

* Updates line tracking based on the executed command and its effects on the document
*/
private updateLineTracking(
parsedCommand: ExCommand | null,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this ever null?

} else if (parsedCommand instanceof SubstituteCommand || commandString.startsWith('s')) {
// Substitute commands: can add or remove lines based on replacement text
executionContext.lineOffset += lineCountChange;
} else if (commandString.startsWith('t') || commandString.startsWith('co')) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not parsedCommand instanceof CopyCommand like above?

executionContext: ExecutionContext,
): void {
// Handle different command types and their line count effects
if (parsedCommand instanceof DeleteCommand || commandString.startsWith('d')) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we bothering with commandString after we parsed the command?

constructor(args: IGlobalCommandArguments) {
super();

// Validate pattern is not empty (Requirement 6.1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume Requirement 6.1 refers to an AI prompt?

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements the :g[lobal]/[pattern]/[command] feature for VSCodeVim, enabling users to execute commands on lines matching (or not matching) a pattern across the entire document or a specified range. This is a fundamental Vim feature that has been requested in issue #3010.

Key Changes:

  • New GlobalCommand class supporting :g, :global, :g!, :v, and :vglobal variants
  • Comprehensive line tracking to handle document modifications during command execution
  • Atomic undo/redo support treating the entire global operation as a single change
  • Pattern-based line selection with support for multiple delimiter characters

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/cmd_line/commands/global.ts New implementation of the global command with pattern matching, line tracking, and command execution on matching lines
src/vimscript/exCommandParser.ts Registration of global command variants (g, global, g!, v, vglobal) with their respective parsers
test/cmd_line/global.test.ts Comprehensive test suite covering parser variants, pattern delimiters, command execution, ranges, error handling, undo/redo, and edge cases
test/vimscript/exCommandParse.test.ts Parser tests for global command syntax validation
.gitignore Added .kiro directory to ignore list for AI assistant artifacts

Comment on lines +60 to +64
// Validate pattern is not empty (Requirement 6.1)
if (!args.pattern || args.pattern.patternString === '') {
throw VimError.fromCode(ErrorCode.NoPreviousRegularExpression);
}

Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The constructor incorrectly rejects empty patterns. In Vim, when using :g//command with an empty pattern, it should use the last search pattern from globalState.searchState, not throw an error. Similar to how SubstituteCommand handles empty patterns (see substitute.ts lines 531-542), this should check if the pattern is empty and fall back to the previous search state. Currently, this makes the test on line 292-298 of global.test.ts fail to work as expected.

Suggested change
// Validate pattern is not empty (Requirement 6.1)
if (!args.pattern || args.pattern.patternString === '') {
throw VimError.fromCode(ErrorCode.NoPreviousRegularExpression);
}

Copilot uses AI. Check for mistakes.

private handleCommandError(
vimState: VimState,
error: any,
Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error parameter type is any, which loses type safety. Consider using a more specific type such as unknown and then performing type checking within the function, or Error | VimError if those are the expected error types. Using any bypasses TypeScript's type checking.

Suggested change
error: any,
error: unknown,

Copilot uses AI. Check for mistakes.
J-Fields and others added 2 commits December 19, 2025 19:31
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants