Implemented searching.

This commit is contained in:
Grégoire Geis 2019-04-20 02:00:49 +09:00
parent a7619193d0
commit 46cadc7ca9
5 changed files with 267 additions and 17 deletions

View File

@ -116,8 +116,10 @@ Most (but not all) commands defined in [`commands`][commands] are implemented.
- [X] Indent.
- [X] Dedent.
- [X] Change case.
- [ ] Search.
- [ ] History.
- [X] Search.
- [ ] History:
- [X] Undo / redo.
- [X] Forward / backward.
- [ ] Macros.
- [ ] Registers.

View File

@ -1,13 +1,15 @@
import * as vscode from 'vscode'
import { registerCommand, Command } from '.'
// registerCommand(Command.historyUndo, (editor, state) => {
registerCommand(Command.historyUndo, () => {
return vscode.commands.executeCommand('undo')
})
// })
// registerCommand(Command.historyRedo, (editor, state) => {
// })
registerCommand(Command.historyRedo, () => {
return vscode.commands.executeCommand('redo')
})
// registerCommand(Command.historyBackward, (editor, state) => {

View File

@ -4,18 +4,254 @@ import * as vscode from 'vscode'
import { registerCommand, Command } from '.'
// registerCommand(Command.search, editor => {
function isMultilineRegExp(regex: string) {
const len = regex.length
let negate = false
// })
for (let i = 0; i < len; i++) {
const ch = regex[i]
// registerCommand(Command.searchBackwards, editor => {
if (negate) {
if (ch === ']') {
negate = false
} else if (ch === '\\') {
if (regex[i + 1] === 'S')
return true
// })
i++ // Ignore next character
} else {
continue
}
} else if (ch === '[' && regex[i + 1] === '^') {
negate = true
i++
} else if (ch === '\\') {
if (regex[i + 1] === 's' || regex[i + 1] === 'n')
return true
// registerCommand(Command.searchExtend, editor => {
i++ // Ignore next character
}
}
// })
return false
}
// registerCommand(Command.searchBackwardsExtend, editor => {
function findPosition(text: string, position: vscode.Position, offset: number, add = 0) {
let { line, character } = position
// })
for (let i = 0; i < offset; i++) {
const ch = text[i + add]
if (ch === '\n') {
line++
character = 0
} else if (ch === '\r') {
line++
character = 0
i++
} else {
character++
}
}
return new vscode.Position(line, character)
}
function findPositionBackward(text: string, position: vscode.Position, offset: number) {
let { line, character } = position
for (let i = text.length - 1; i >= offset; i--) {
const ch = text[i]
if (ch === '\n') {
line--
character = 0
} else if (ch === '\r') {
line--
character = 0
i--
} else {
character--
}
}
if (line !== position.line) {
// The 'character' is actually the number of characters until the end of the line,
// so we have to find the start of the current line and do a diff
let startOfLine = text.lastIndexOf('\n', offset)
if (startOfLine === -1)
startOfLine = 0
character = offset - startOfLine
}
return new vscode.Position(line, character - 1)
}
// TODO: Should search strings be limited to the range between the current selection
// and the previous/next selection, or only to the bounds of the document?
function search(document: vscode.TextDocument, start: vscode.Position, regex: RegExp) {
if (regex.multiline) {
const text = document.getText(document.lineAt(document.lineCount - 1).range.with(start))
const match = regex.exec(text)
if (match === null)
return undefined
const startPos = findPosition(text, start, match.index),
endPos = findPosition(text, startPos, match[0].length, match.index)
return new vscode.Selection(startPos, endPos)
} else {
{
// Custom processing for first line
const line = document.lineAt(start.line),
match = regex.exec(line.text.substr(start.character))
if (match !== null)
return new vscode.Selection(
new vscode.Position(start.line, start.character + match.index),
new vscode.Position(start.line, start.character + match.index + match[0].length),
)
}
for (let i = start.line + 1; i < document.lineCount; i++) {
const line = document.lineAt(i),
match = regex.exec(line.text)
if (match === null)
continue
return new vscode.Selection(
new vscode.Position(i, match.index),
new vscode.Position(i, match.index + match[0].length),
)
}
return undefined
}
}
function execFromEnd(regex: RegExp, input: string) {
let match = regex.exec(input)
if (match === null || match[0].length === 0)
return null
for (;;) {
const newMatch = regex.exec(input)
if (newMatch === null)
return match
if (newMatch[0].length === 0)
return null
match = newMatch
}
}
function searchBackward(document: vscode.TextDocument, end: vscode.Position, regex: RegExp) {
if (regex.multiline) {
const text = document.getText(new vscode.Range(new vscode.Position(0, 0), end))
const match = execFromEnd(regex, text)
if (match === null)
return undefined
const startPos = findPositionBackward(text, end, match.index),
endPos = findPosition(text, startPos, match[0].length, match.index)
return new vscode.Selection(startPos, endPos)
} else {
{
// Custom processing for first line
const line = document.lineAt(end.line),
match = execFromEnd(regex, line.text.substr(0, end.character))
if (match !== null)
return new vscode.Selection(
new vscode.Position(end.line, match.index),
new vscode.Position(end.line, match.index + match[0].length),
)
}
for (let i = end.line - 1; i >= 0; i--) {
const line = document.lineAt(i),
match = execFromEnd(regex, line.text)
if (match === null)
continue
return new vscode.Selection(
new vscode.Position(i, match.index),
new vscode.Position(i, match.index + match[0].length),
)
}
return undefined
}
}
function registerSearchCommand(command: Command, backward: boolean, extend: boolean) {
registerCommand(command, async editor => {
const initialSelections = editor.selections
const result = await vscode.window.showInputBox({
prompt: 'Search RegExp',
validateInput(input) {
if (input.length === 0)
return 'RegExp cannot be empty.'
let regex: RegExp
let flags = (isMultilineRegExp(input) ? 'm' : '') + (backward ? 'g' : '')
try {
regex = new RegExp(input, flags)
} catch {
return 'Invalid ECMA RegExp.'
}
// TODO: For subsequent searches, first try to match at start of previous
// match by adding a ^, and then fallback to the default search routine
if (extend) {
if (backward)
editor.selections = initialSelections.map(selection => {
const newSelection = searchBackward(editor.document, selection.anchor, regex)
return newSelection === undefined
? selection
: new vscode.Selection(newSelection.start, selection.end)
})
else
editor.selections = initialSelections.map(selection => {
const newSelection = search(editor.document, selection.active, regex)
return newSelection === undefined
? selection
: new vscode.Selection(selection.start, newSelection.end)
})
} else {
editor.selections = initialSelections.map(selection => {
return (backward
? searchBackward(editor.document, selection.anchor, regex)
: search(editor.document, selection.active, regex))
|| selection
})
}
return undefined
}
})
if (result === undefined)
editor.selections = initialSelections
})
}
registerSearchCommand(Command.search , false, false)
registerSearchCommand(Command.searchBackwards , true , false)
registerSearchCommand(Command.searchExtend , false, true )
registerSearchCommand(Command.searchBackwardsExtend, true , true )

View File

@ -19,7 +19,7 @@ export function promptRegex(flags?: string) {
return undefined
} catch {
return 'The given RegExp is not a valid ECMA RegExp.'
return 'Invalid ECMA RegExp.'
}
}
}).then(x => x === undefined ? undefined : new RegExp(x, flags))

View File

@ -41,10 +41,20 @@ export function getOppositePosition(document: vscode.TextDocument, text: string,
}
}
export function getOppositePositionFromString(document: string, text: string, position: vscode.Position, isStart: boolean) {
}
/**
* Returns the `Selection` starting at the given position, and ending at the end of the given text.
*/
export function getSelectionFromStart(document: vscode.TextDocument, text: string, position: vscode.Position) {
return new vscode.Selection(position, getOppositePosition(document, text, position, true))
}
/**
* Returns the `Selection` ending at the given position, and starting at the start of the given text.
*/
export function getSelectionFromEnd(document: vscode.TextDocument, text: string, position: vscode.Position) {
return new vscode.Selection(getOppositePosition(document, text, position, false), position)
}