mirror of
https://github.com/VSCodeVim/Vim.git
synced 2024-09-20 08:37:45 +03:00
Don't throw exception if a pattern fails to parse
This commit is contained in:
parent
0eb3d1d950
commit
aaee7e2cbe
@ -215,7 +215,7 @@ export class SubstituteCommand extends ExCommand {
|
|||||||
return !this.arguments.flags.confirmEach;
|
return !this.arguments.flags.confirmEach;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getRegex(args: ISubstituteCommandArguments, vimState: VimState) {
|
private getRegex(args: ISubstituteCommandArguments, vimState: VimState): RegExp | undefined {
|
||||||
let jsRegexFlags = '';
|
let jsRegexFlags = '';
|
||||||
if (configuration.gdefault || configuration.substituteGlobalFlag) {
|
if (configuration.gdefault || configuration.substituteGlobalFlag) {
|
||||||
// the gdefault flag is on, then /g if on by default and /g negates that
|
// the gdefault flag is on, then /g if on by default and /g negates that
|
||||||
@ -238,7 +238,7 @@ export class SubstituteCommand extends ExCommand {
|
|||||||
// i.e. :s
|
// i.e. :s
|
||||||
const prevSubstituteState = globalState.substituteState;
|
const prevSubstituteState = globalState.substituteState;
|
||||||
if (
|
if (
|
||||||
prevSubstituteState === undefined ||
|
prevSubstituteState?.searchPattern === undefined ||
|
||||||
prevSubstituteState.searchPattern.patternString === ''
|
prevSubstituteState.searchPattern.patternString === ''
|
||||||
) {
|
) {
|
||||||
throw VimError.fromCode(ErrorCode.NoPreviousSubstituteRegularExpression);
|
throw VimError.fromCode(ErrorCode.NoPreviousSubstituteRegularExpression);
|
||||||
@ -266,7 +266,7 @@ export class SubstituteCommand extends ExCommand {
|
|||||||
vimState.currentMode
|
vimState.currentMode
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return new RegExp(args.pattern.regex.source, jsRegexFlags);
|
return args.pattern ? new RegExp(args.pattern.regex.source, jsRegexFlags) : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -396,6 +396,10 @@ export class SubstituteCommand extends ExCommand {
|
|||||||
|
|
||||||
async execute(vimState: VimState): Promise<void> {
|
async execute(vimState: VimState): Promise<void> {
|
||||||
const regex = this.getRegex(this.arguments, vimState);
|
const regex = this.getRegex(this.arguments, vimState);
|
||||||
|
if (regex === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const selection = vimState.editor.selection;
|
const selection = vimState.editor.selection;
|
||||||
const line = selection.start.isBefore(selection.end)
|
const line = selection.start.isBefore(selection.end)
|
||||||
? selection.start.line
|
? selection.start.line
|
||||||
@ -418,6 +422,10 @@ export class SubstituteCommand extends ExCommand {
|
|||||||
// TODO: Global Setting.
|
// TODO: Global Setting.
|
||||||
// TODO: There are differencies between Vim Regex and JS Regex.
|
// TODO: There are differencies between Vim Regex and JS Regex.
|
||||||
const regex = this.getRegex(this.arguments, vimState);
|
const regex = this.getRegex(this.arguments, vimState);
|
||||||
|
if (regex === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let lines = 0;
|
let lines = 0;
|
||||||
let substitutions = 0;
|
let substitutions = 0;
|
||||||
for (
|
for (
|
||||||
|
@ -17,9 +17,10 @@ export class SearchState {
|
|||||||
) {
|
) {
|
||||||
this._searchString = searchString;
|
this._searchString = searchString;
|
||||||
|
|
||||||
const { pattern, offset } = searchStringParser({ direction, ignoreSmartcase }).tryParse(
|
const result = searchStringParser({ direction, ignoreSmartcase }).parse(searchString);
|
||||||
searchString
|
const { pattern, offset } = result.status
|
||||||
);
|
? result.value
|
||||||
|
: { pattern: undefined, offset: undefined };
|
||||||
this.pattern = pattern;
|
this.pattern = pattern;
|
||||||
this.offset = offset;
|
this.offset = offset;
|
||||||
|
|
||||||
@ -29,7 +30,7 @@ export class SearchState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _searchString: string;
|
private _searchString: string;
|
||||||
public pattern: Pattern;
|
public pattern?: Pattern;
|
||||||
private offset?: SearchOffset;
|
private offset?: SearchOffset;
|
||||||
|
|
||||||
public readonly previousMode: Mode;
|
public readonly previousMode: Mode;
|
||||||
@ -40,11 +41,14 @@ export class SearchState {
|
|||||||
}
|
}
|
||||||
public set searchString(str: string) {
|
public set searchString(str: string) {
|
||||||
this._searchString = str;
|
this._searchString = str;
|
||||||
const { pattern, offset } = searchStringParser({
|
const result = searchStringParser({
|
||||||
direction: this.pattern.direction,
|
direction: this.direction,
|
||||||
ignoreSmartcase: this.ignoreSmartcase,
|
ignoreSmartcase: this.ignoreSmartcase,
|
||||||
}).tryParse(str);
|
}).parse(str);
|
||||||
if (pattern.patternString !== this.pattern.patternString) {
|
const { pattern, offset } = result.status
|
||||||
|
? result.value
|
||||||
|
: { pattern: undefined, offset: undefined };
|
||||||
|
if (pattern?.patternString !== this.pattern?.patternString) {
|
||||||
this.pattern = pattern;
|
this.pattern = pattern;
|
||||||
this.matchRanges.clear();
|
this.matchRanges.clear();
|
||||||
}
|
}
|
||||||
@ -52,7 +56,8 @@ export class SearchState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public get direction(): SearchDirection {
|
public get direction(): SearchDirection {
|
||||||
return this.pattern.direction;
|
// TODO: Defaulting to forward is wrong - I think storing the direction in the pattern is a mistake
|
||||||
|
return this.pattern?.direction ?? SearchDirection.Forward;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -72,7 +77,7 @@ export class SearchState {
|
|||||||
private readonly ignoreSmartcase: boolean;
|
private readonly ignoreSmartcase: boolean;
|
||||||
|
|
||||||
private recalculateSearchRanges(editor: TextEditor): Range[] {
|
private recalculateSearchRanges(editor: TextEditor): Range[] {
|
||||||
if (this.searchString === '') {
|
if (this.searchString === '' || this.pattern === undefined) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,7 +134,7 @@ export class SearchState {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const effectiveDirection = (direction * this.pattern.direction) as SearchDirection;
|
const effectiveDirection = (direction * this.direction) as SearchDirection;
|
||||||
|
|
||||||
if (effectiveDirection === SearchDirection.Forward) {
|
if (effectiveDirection === SearchDirection.Forward) {
|
||||||
for (const [index, range] of matchRanges.entries()) {
|
for (const [index, range] of matchRanges.entries()) {
|
||||||
|
@ -7,14 +7,14 @@ export class SubstituteState {
|
|||||||
/**
|
/**
|
||||||
* The last pattern searched for in the substitution
|
* The last pattern searched for in the substitution
|
||||||
*/
|
*/
|
||||||
public searchPattern: Pattern;
|
public searchPattern: Pattern | undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The last replacement string in the substitution
|
* The last replacement string in the substitution
|
||||||
*/
|
*/
|
||||||
public replaceString: string;
|
public replaceString: string;
|
||||||
|
|
||||||
constructor(searchPattern: Pattern, replaceString: string) {
|
constructor(searchPattern: Pattern | undefined, replaceString: string) {
|
||||||
this.searchPattern = searchPattern;
|
this.searchPattern = searchPattern;
|
||||||
this.replaceString = replaceString;
|
this.replaceString = replaceString;
|
||||||
}
|
}
|
||||||
|
@ -206,20 +206,22 @@ export class Address {
|
|||||||
if (!globalState.substituteState) {
|
if (!globalState.substituteState) {
|
||||||
throw VimError.fromCode(ErrorCode.NoPreviousSubstituteRegularExpression);
|
throw VimError.fromCode(ErrorCode.NoPreviousSubstituteRegularExpression);
|
||||||
}
|
}
|
||||||
const searchState = new SearchState(
|
const searchState = globalState.substituteState.searchPattern
|
||||||
SearchDirection.Forward,
|
? new SearchState(
|
||||||
vimState.cursorStopPosition,
|
SearchDirection.Forward,
|
||||||
globalState.substituteState.searchPattern.patternString,
|
vimState.cursorStopPosition,
|
||||||
{},
|
globalState.substituteState.searchPattern.patternString,
|
||||||
vimState.currentMode
|
{},
|
||||||
);
|
vimState.currentMode
|
||||||
const match = searchState.getNextSearchMatchPosition(
|
)
|
||||||
|
: undefined;
|
||||||
|
const match = searchState?.getNextSearchMatchPosition(
|
||||||
vimState.editor,
|
vimState.editor,
|
||||||
vimState.cursorStopPosition
|
vimState.cursorStopPosition
|
||||||
);
|
);
|
||||||
if (match === undefined) {
|
if (match === undefined) {
|
||||||
// TODO: throw proper errors for nowrapscan
|
// TODO: throw proper errors for nowrapscan
|
||||||
throw VimError.fromCode(ErrorCode.PatternNotFound, searchState.searchString);
|
throw VimError.fromCode(ErrorCode.PatternNotFound, searchState?.searchString);
|
||||||
}
|
}
|
||||||
return match.pos.line;
|
return match.pos.line;
|
||||||
default:
|
default:
|
||||||
|
@ -38,6 +38,7 @@ export class Pattern {
|
|||||||
public readonly ignorecase: boolean | undefined;
|
public readonly ignorecase: boolean | undefined;
|
||||||
|
|
||||||
private static readonly MAX_SEARCH_RANGES = 1000;
|
private static readonly MAX_SEARCH_RANGES = 1000;
|
||||||
|
private static readonly SPECIAL_CHARS_REGEX = /[\-\[\]{}()*+?.,\\\^$|#\s]/g;
|
||||||
|
|
||||||
public nextMatch(document: TextDocument, fromPosition: Position): Range | undefined {
|
public nextMatch(document: TextDocument, fromPosition: Position): Range | undefined {
|
||||||
const haystack = document.getText();
|
const haystack = document.getText();
|
||||||
@ -103,6 +104,16 @@ export class Pattern {
|
|||||||
return matchRanges.afterWrapping.concat(matchRanges.beforeWrapping);
|
return matchRanges.afterWrapping.concat(matchRanges.beforeWrapping);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static compileRegex(regexString: string, ignoreCase?: boolean): RegExp {
|
||||||
|
const flags = ignoreCase ?? configuration.ignorecase ? 'gim' : 'gm';
|
||||||
|
try {
|
||||||
|
return new RegExp(regexString, flags);
|
||||||
|
} catch (err) {
|
||||||
|
// Couldn't compile the regexp, try again with special characters escaped
|
||||||
|
return new RegExp(regexString.replace(Pattern.SPECIAL_CHARS_REGEX, '\\$&'), flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static fromLiteralString(
|
public static fromLiteralString(
|
||||||
input: string,
|
input: string,
|
||||||
direction: SearchDirection,
|
direction: SearchDirection,
|
||||||
@ -110,17 +121,9 @@ export class Pattern {
|
|||||||
): Pattern {
|
): Pattern {
|
||||||
const patternString = input.replace(escapeRegExp(input), '\\$&');
|
const patternString = input.replace(escapeRegExp(input), '\\$&');
|
||||||
if (wordBoundaries) {
|
if (wordBoundaries) {
|
||||||
return new Pattern(
|
return new Pattern(`\\<${patternString}\\>`, direction, Pattern.compileRegex(patternString));
|
||||||
`\\<${patternString}\\>`,
|
|
||||||
direction,
|
|
||||||
new RegExp(`\b${patternString}\b`, configuration.ignorecase ? 'gim' : 'gm')
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
return new Pattern(
|
return new Pattern(patternString, direction, Pattern.compileRegex(patternString));
|
||||||
patternString,
|
|
||||||
direction,
|
|
||||||
new RegExp(patternString, configuration.ignorecase ? 'gim' : 'gm')
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,13 +180,15 @@ export class Pattern {
|
|||||||
};
|
};
|
||||||
})
|
})
|
||||||
.map(({ patternString, caseOverride }) => {
|
.map(({ patternString, caseOverride }) => {
|
||||||
const flags = Pattern.getIgnoreCase(patternString, {
|
const ignoreCase = Pattern.getIgnoreCase(patternString, {
|
||||||
caseOverride,
|
caseOverride,
|
||||||
ignoreSmartcase: args.ignoreSmartcase ?? false,
|
ignoreSmartcase: args.ignoreSmartcase ?? false,
|
||||||
})
|
});
|
||||||
? 'gim'
|
return new Pattern(
|
||||||
: 'gm';
|
patternString,
|
||||||
return new Pattern(patternString, args.direction, RegExp(patternString, flags));
|
args.direction,
|
||||||
|
Pattern.compileRegex(patternString, ignoreCase)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2321,6 +2321,13 @@ suite('Mode Normal', () => {
|
|||||||
end: ['|x end', 'x', 'x', 'start'],
|
end: ['|x end', 'x', 'x', 'start'],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
newTest({
|
||||||
|
title: 'Search for `(`',
|
||||||
|
start: ['|one (two) three'],
|
||||||
|
keysPressed: '/(\n',
|
||||||
|
end: ['one |(two) three'],
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The escaped `/` and `?` the next tests are necessary because otherwise they denote a search offset.
|
* The escaped `/` and `?` the next tests are necessary because otherwise they denote a search offset.
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user