diff --git a/src/actions/actions.ts b/src/actions/actions.ts index dce026cc128..f80b5f439ac 100644 --- a/src/actions/actions.ts +++ b/src/actions/actions.ts @@ -1,4 +1,4 @@ -import { VimCommandActions, VimState } from './../mode/modeHandler'; +import { VimCommandActions, VimState, SearchState } from './../mode/modeHandler'; import { ModeName } from './../mode/mode'; import { TextEditor } from './../textEditor'; import { Register, RegisterMode } from './../register/register'; @@ -363,87 +363,31 @@ class CommandInsertInSearchMode extends BaseCommand { modes = [ModeName.SearchInProgressMode]; keys = [""]; - public static GetNextSearchMatch(startPosition: Position, searchString: string): Position { - for (let line = startPosition.line; line < TextEditor.getLineCount(); line++) { - const text = TextEditor.getLineAt(new Position(line, 0)).text; - - // TODO: Do better implementation!!! - for (let char = (line === startPosition.line ? startPosition.character + 1 : 0); char < text.length; char++) { - const index = text.indexOf(searchString, char); - - if (index > -1) { - return new Position(line, index); - } - } - } - - return undefined; - } - - public static GetPreviousSearchMatch(startPosition: Position, searchString: string): Position { - for (let line = startPosition.line; line >= 0; line--) { - const text = TextEditor.getLineAt(new Position(line, 0)).text; - - // TODO: Do better implementation!!! - for (let char = (line === startPosition.line ? startPosition.character - 1 : text.length - 1); char >= 0; char--) { - const index = text.lastIndexOf(searchString, char); - - if (index > -1) { - return new Position(line, index); - } - } - } - - return undefined; - } - public async exec(position: Position, vimState: VimState): Promise { const key = this.keysPressed[0]; + const searchState = vimState.searchState; + // handle special keys first if (key === "") { - vimState.searchString = vimState.searchString.slice(0, -1); + searchState.searchString = searchState.searchString.slice(0, -1); } else if (key === "\n") { vimState.currentMode = ModeName.Normal; - - if (vimState.nextSearchMatchPosition) { - vimState.cursorPosition = vimState.nextSearchMatchPosition; - } else { - vimState.cursorPosition = vimState.searchCursorStartPosition; - } + vimState.cursorPosition = vimState.searchState.getNextSearchMatchPosition(searchState.searchCursorStartPosition); return vimState; } else if (key === "") { vimState.currentMode = ModeName.Normal; - vimState.searchString = ""; - vimState.nextSearchMatchPosition = undefined; - vimState.cursorPosition = vimState.searchCursorStartPosition; + vimState.searchState = undefined; return vimState; } else { - vimState.searchString += this.keysPressed[0]; + searchState.searchString += this.keysPressed[0]; } // console.log(vimState.searchString); (TODO: Show somewhere!) - vimState.nextSearchMatchPosition = undefined; - - const startPosition = vimState.searchCursorStartPosition; - - if (vimState.searchDirection === 1) { - vimState.nextSearchMatchPosition = CommandInsertInSearchMode.GetNextSearchMatch( - startPosition, - vimState.searchString); - } else { - vimState.nextSearchMatchPosition = CommandInsertInSearchMode.GetPreviousSearchMatch( - startPosition, - vimState.searchString); - } - if (vimState.nextSearchMatchPosition) { - vimState.cursorPosition = vimState.nextSearchMatchPosition; - } else { - vimState.cursorPosition = vimState.searchCursorStartPosition; - } + vimState.cursorPosition = searchState.getNextSearchMatchPosition(searchState.searchCursorStartPosition); return vimState; } @@ -455,27 +399,13 @@ class CommandNextSearchMatch extends BaseMovement { keys = ["n"]; public async execAction(position: Position, vimState: VimState): Promise { - if (vimState.searchString === "") { - return position; - } - - let nextPosition: Position; - - if (vimState.searchDirection === 1) { - nextPosition = CommandInsertInSearchMode.GetNextSearchMatch( - position, vimState.searchString); - } else { - nextPosition = CommandInsertInSearchMode.GetPreviousSearchMatch( - position, vimState.searchString); - } - - if (!nextPosition) { - // TODO(bell) + const searchState = vimState.searchState; + if (searchState.searchString === "") { return position; } - return nextPosition; + return vimState.searchState.getNextSearchMatchPosition(vimState.cursorPosition); } } @@ -496,24 +426,11 @@ class CommandStar extends BaseCommand { public async exec(position: Position, vimState: VimState): Promise { const currentWord = CommandStar.GetWordAtPosition(position); - vimState.searchString = currentWord; - vimState.searchDirection = 1; + vimState.searchState = new SearchState(+1, vimState.cursorPosition, currentWord); - let result: Position; - - while (true) { - result = CommandInsertInSearchMode.GetNextSearchMatch(vimState.cursorPosition, currentWord); - - if (result === undefined) { - break; - } - - vimState.cursorPosition = result; - - if (CommandStar.GetWordAtPosition(result) === currentWord) { - break; - } - } + do { + vimState.cursorPosition = vimState.searchState.getNextSearchMatchPosition(vimState.cursorPosition); + } while (CommandStar.GetWordAtPosition(vimState.cursorPosition) !== currentWord); return vimState; } @@ -538,24 +455,11 @@ class CommandHash extends BaseCommand { public async exec(position: Position, vimState: VimState): Promise { const currentWord = CommandStar.GetWordAtPosition(position); - vimState.searchString = currentWord; - vimState.searchDirection = -1; - - let result: Position; + vimState.searchState = new SearchState(-1, vimState.cursorPosition, currentWord); - while (true) { - result = CommandInsertInSearchMode.GetPreviousSearchMatch(vimState.cursorPosition, currentWord); - - if (result === undefined) { - break; - } - - vimState.cursorPosition = result; - - if (CommandStar.GetWordAtPosition(result) === currentWord) { - break; - } - } + do { + vimState.cursorPosition = vimState.searchState.getNextSearchMatchPosition(vimState.cursorPosition); + } while (CommandStar.GetWordAtPosition(vimState.cursorPosition) !== currentWord); return vimState; } @@ -567,27 +471,13 @@ class CommandPreviousSearchMatch extends BaseMovement { keys = ["N"]; public async execAction(position: Position, vimState: VimState): Promise { - if (vimState.searchString === "") { - return position; - } - - let prevPosition: Position; - - if (vimState.searchDirection === -1) { - prevPosition = CommandInsertInSearchMode.GetNextSearchMatch( - position, vimState.searchString); - } else { - prevPosition = CommandInsertInSearchMode.GetPreviousSearchMatch( - position, vimState.searchString); - } - - if (!prevPosition) { - // TODO(bell) + const searchState = vimState.searchState; + if (searchState.searchString === "") { return position; } - return prevPosition; + return searchState.getNextSearchMatchPosition(vimState.cursorPosition, -1); } } @@ -632,10 +522,7 @@ export class CommandSearchForwards extends BaseCommand { isMotion = true; public async exec(position: Position, vimState: VimState): Promise { - vimState.searchString = ""; - vimState.searchDirection = 1; - vimState.searchCursorStartPosition = position; - vimState.nextSearchMatchPosition = undefined; + vimState.searchState = new SearchState(+1, vimState.cursorPosition); vimState.currentMode = ModeName.SearchInProgressMode; return vimState; @@ -650,10 +537,7 @@ export class CommandSearchBackwards extends BaseCommand { isMotion = true; public async exec(position: Position, vimState: VimState): Promise { - vimState.searchString = ""; - vimState.searchDirection = -1; - vimState.searchCursorStartPosition = position; - vimState.nextSearchMatchPosition = undefined; + vimState.searchState = new SearchState(-1, vimState.cursorPosition); vimState.currentMode = ModeName.SearchInProgressMode; return vimState; @@ -1849,7 +1733,7 @@ class MovementAWordTextObject extends BaseMovement { // visual mode if you JUST entered visual mode // TODO TODO: I just looked at this again and omg this is awful what was I smoking plz get rid of this asap return { - start: vimState.searchCursorStartPosition, + start: vimState.cursorStartPosition, stop: await new MoveWordBegin().execAction(position, vimState), }; } @@ -1902,7 +1786,7 @@ class MovementIWordTextObject extends BaseMovement { // TODO: This is kind of a bad way to do this, but text objects only work in // visual mode if you JUST entered visual mode return { - start: vimState.searchCursorStartPosition, + start: vimState.cursorStartPosition, stop: await new MoveWordBegin().execAction(position, vimState), }; } diff --git a/src/mode/modeHandler.ts b/src/mode/modeHandler.ts index 22a491d4cf9..d0d84001b76 100644 --- a/src/mode/modeHandler.ts +++ b/src/mode/modeHandler.ts @@ -8,6 +8,7 @@ import { NormalMode } from './modeNormal'; import { InsertMode } from './modeInsert'; import { VisualMode } from './modeVisual'; import { SearchInProgressMode } from './modeSearchInProgress'; +import { TextEditor } from './../textEditor'; import { VisualLineMode } from './modeVisualLine'; import { BaseMovement, BaseCommand, Actions, BaseAction, @@ -76,20 +77,7 @@ export class VimState { */ public cursorStartPosition = new Position(0, 0); - public searchString = ""; - - /** - * The position of the next search, or undefined if there is no match. - */ - public nextSearchMatchPosition: Position = undefined; - - public searchCursorStartPosition: Position = undefined; - - /** - * 1 === forward - * -1 === backward - */ - public searchDirection = 1; + public searchState: SearchState = undefined; /** * The mode Vim will be in once this action finishes. @@ -127,6 +115,95 @@ export class VimState { public whatILastSetTheSelectionTo: vscode.Selection; } +export class SearchState { + /** + * Every range in the document that matches the search string. + */ + public matchRanges: vscode.Range[] = []; + + private _searchString = ""; + public get searchString(): string { + return this._searchString; + } + + public set searchString(search: string){ + this._searchString = search; + + if (this._searchString.length > 0) { + this._recalculateSearchRanges(); + } + } + + private _recalculateSearchRanges(): void { + const search = this.searchString; + + // Calculate and store all matching ranges + this.matchRanges = []; + + for (let lineIdx = 0; lineIdx < TextEditor.getLineCount(); lineIdx++) { + const line = TextEditor.getLineAt(new Position(lineIdx, 0)).text; + + let i = line.indexOf(search); + + for (; i !== -1; i = line.indexOf(search, i + search.length)) { + this.matchRanges.push(new vscode.Range( + new Position(lineIdx, i), + new Position(lineIdx, i + search.length) + )); + } + } + } + + /** + * The position of the next search, or undefined if there is no match. + * + * Pass in -1 as direction to reverse the direction we search. + */ + public getNextSearchMatchPosition(startPosition: Position, direction = 1): Position { + if (this.matchRanges.length === 0) { + // TODO(bell) + return startPosition; + } + + const effectiveDirection = direction * this.searchDirection; + + if (effectiveDirection === 1) { + for (let matchRange of this.matchRanges) { + if (matchRange.start.compareTo(startPosition) > 0) { + return Position.FromVSCodePosition(matchRange.start); + } + } + + // Wrap around + // TODO(bell) + return Position.FromVSCodePosition(this.matchRanges[0].start); + } else { + for (let matchRange of this.matchRanges.slice(0).reverse()) { + if (matchRange.start.compareTo(startPosition) < 0) { + return Position.FromVSCodePosition(matchRange.start); + } + } + + // TODO(bell) + return Position.FromVSCodePosition(this.matchRanges[this.matchRanges.length - 1].start); + } + } + + public searchCursorStartPosition: Position = undefined; + + /** + * 1 === forward + * -1 === backward + */ + public searchDirection = 1; + + constructor(direction: number, startPosition: Position, searchString = "") { + this.searchDirection = direction; + this.searchCursorStartPosition = startPosition; + this.searchString = searchString; + } +} + /** * The RecordedState class holds the current action that the user is * doing. Example: Imagine that the user types: @@ -696,12 +773,16 @@ export class ModeHandler implements vscode.Disposable { // Draw search highlight - if (this.currentMode.name === ModeName.SearchInProgressMode && - vimState.nextSearchMatchPosition !== undefined) { + const searchState = vimState.searchState; + + if (this.currentMode.name === ModeName.SearchInProgressMode) { + rangesToDraw.push.apply(rangesToDraw, searchState.matchRanges); + + const nextSearchMatch = searchState.getNextSearchMatchPosition(vimState.cursorPosition); rangesToDraw.push(new vscode.Range( - vimState.nextSearchMatchPosition, - vimState.nextSearchMatchPosition.getRight(vimState.searchString.length))); + nextSearchMatch, + nextSearchMatch.getRight(searchState.searchString.length))); } vscode.window.activeTextEditor.setDecorations(this._caretDecoration, rangesToDraw);