Skip to content

Issue resolution: :g[lobal] feature not present #9556 #9557

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
118 changes: 118 additions & 0 deletions src/cmd_line/commands/global.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { VimState } from '../../state/vimState';
import { ExCommand } from '../../vimscript/exCommand';
import { optWhitespace, Parser, regexp, string as parsimmonString, seqMap } from 'parsimmon';
import { LineRange } from '../../vimscript/lineRange';
import { ErrorCode, VimError } from '../../error';
import { StatusBar } from '../../statusBar';
import { Address } from '../../vimscript/lineRange';
import { CommandParser } from '../../vimscript/commandParser';
import { Logger } from '../../util/logger';
import * as vscode from 'vscode';
import { getDecorationsForSearchMatchRanges } from '../../util/decorationUtils';
import { decoration } from '../../configuration/decoration';

export class GlobalCommand extends ExCommand {
public static readonly argParser: Parser<GlobalCommand> = optWhitespace
.then(
seqMap(
parsimmonString('/'),
regexp(/[^/]*/),
parsimmonString('/'),
optWhitespace.then(regexp(/.*/)),
(slash1, pattern, slash2, command) => {
Logger.info(`Global command parsed: pattern='${pattern}', command='${command.trim()}'`);
return new GlobalCommand(pattern, command.trim());
},
),
)
.desc('global command');

private readonly pattern: string;
private readonly command: string;
// create a private map inserted with value

constructor(pattern: string, command: string) {
super();
this.pattern = pattern;
this.command = command;
Logger.info(`Global command constructed: pattern='${pattern}', command='${command}'`);
}

async execute(vimState: VimState): Promise<void> {
try {
Logger.info(`Executing global command: pattern='${this.pattern}' command='${this.command}'`);
const document = vimState.editor.document;
const lines = document.getText().split('\n');
const matchingLines: number[] = [];

// Find lines matching the pattern
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (this.matchesPattern(line)) {
matchingLines.push(i);
Logger.info(`Line ${i + 1} matches pattern: ${line}`);
}
}

Logger.info(`Found ${matchingLines.length} matching lines for pattern: ${this.pattern}`);

// Execute command on matching lines (in reverse order for delete)
const rangeList: LineRange[] = [];
for (let i = matchingLines.length - 1; i >= 0; i--) {
const line = matchingLines[i];
const lineRange = new LineRange(
new Address({ type: 'number', num: line + 1 }),
undefined,
new Address({ type: 'number', num: line + 1 }),
);
rangeList.push(lineRange);

try {
// Split command into name and args
const [cmdName, ...cmdArgs] = this.command.split(/\s+/);
const args = cmdArgs.join(' ');

{
if (this.command === 'd') {
for (const currentRange of rangeList) {
Logger.info(
`Executing command '${cmdName}' with args '${args}' on line ${line + 1}`,
);
// Use CommandParser to execute the command
await CommandParser.executeCommand(vimState, cmdName, args, currentRange);
}
} else {
Logger.info(`Command is not implemented yet or incorrect: ${this.command}`);
StatusBar.setText(vimState, `Error executing global command: ${this.command}`, true);
}
}
} catch (e) {
if (e instanceof VimError) {
Logger.error(`VimError executing command: ${e.toString()}`);
StatusBar.setText(vimState, e.toString(), true);
} else {
Logger.error(`Error executing command on line ${line + 1}: ${e}`);
StatusBar.setText(vimState, `Error executing command on line ${line + 1}`, true);
}
}
}
} catch (e) {
Logger.error(`Global command error: ${e}`);
if (e instanceof VimError) {
StatusBar.setText(vimState, e.toString(), true);
} else {
StatusBar.setText(vimState, `Error executing global command: ${e}`, true);
}
}
}

private matchesPattern(line: string): boolean {
try {
const regex = new RegExp(this.pattern);
return regex.test(line);
} catch (e) {
// If pattern is invalid regex, treat it as a literal string
return line.includes(this.pattern);
}
}
}
55 changes: 55 additions & 0 deletions src/vimscript/commandParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { ExCommand } from './exCommand';
import { VimState } from '../state/vimState';
import { LineRange } from './lineRange';
import { StatusBar } from '../statusBar';
import { ErrorCode, VimError } from '../error';
import { DeleteCommand } from '../cmd_line/commands/delete';
import { Parser } from 'parsimmon';

export interface CommandDefinition {
name: string;
parser: Parser<ExCommand>;
}

export class CommandParser {
private static commands: CommandDefinition[] = [];

static registerCommand(name: string, parser: Parser<ExCommand>) {
this.commands.push({ name, parser });
}

static findCommand(cmdName: string): CommandDefinition | undefined {
// Handle single-letter commands like 'd' for delete
if (cmdName === 'd') {
return { name: 'delete', parser: DeleteCommand.argParser };
}
return this.commands.find((cmd) => cmd.name === cmdName);
}

static async executeCommand(
vimState: VimState,
cmdName: string,
cmdArgs: string,
range: LineRange,
): Promise<void> {
const cmd = this.findCommand(cmdName);
if (cmd) {
try {
const result = cmd.parser.tryParse(cmdArgs);
if (result instanceof ExCommand) {
await result.executeWithRange(vimState, range);
} else {
StatusBar.setText(vimState, 'Not an editor command', true);
}
} catch (e) {
if (e instanceof VimError) {
StatusBar.setText(vimState, e.toString(), true);
} else {
StatusBar.setText(vimState, `Error executing command: ${cmdName}`, true);
}
}
} else {
StatusBar.setText(vimState, `Unknown command: ${cmdName}`, true);
}
}
}
11 changes: 10 additions & 1 deletion src/vimscript/exCommandParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ import { LineRange } from './lineRange';
import { nameAbbrevParser } from './parserUtils';
import { LetCommand } from '../cmd_line/commands/let';
import { CallCommand, EvalCommand } from '../cmd_line/commands/eval';
import { GlobalCommand } from '../cmd_line/commands/global';
import { CommandParser } from './commandParser';

type ArgParser = Parser<ExCommand>;

Expand Down Expand Up @@ -245,7 +247,7 @@ export const builtinExCommands: ReadonlyArray<[[string, string], ArgParser | und
[['foldo', 'pen'], undefined],
[['for', ''], undefined],
[['fu', 'nction'], undefined],
[['g', 'lobal'], undefined],
[['g', 'lobal'], GlobalCommand.argParser],
[['go', 'to'], GotoCommand.argParser],
[['gr', 'ep'], undefined],
[['grepa', 'dd'], undefined],
Expand Down Expand Up @@ -617,6 +619,13 @@ export const builtinExCommands: ReadonlyArray<[[string, string], ArgParser | und
[['~', ''], undefined],
];

// Register all commands with the CommandParser
builtinExCommands.forEach(([[abbr, rest], parser]) => {
if (parser) {
CommandParser.registerCommand(abbr + rest, parser);
}
});

class UnimplementedCommand extends ExCommand {
name: string;

Expand Down