提交 2a01db15 编写于 作者: R Rob Lourens

Fix #59801 - use shared extension tsconfig for search-rg

上级 92a9a072
......@@ -17,42 +17,47 @@ export function activate(): void {
}
}
type SearchEngine = RipgrepFileSearchEngine | RipgrepTextSearchEngine;
class RipgrepSearchProvider implements vscode.FileIndexProvider, vscode.TextSearchProvider {
private inProgress: Set<SearchEngine> = new Set();
private inProgress: Set<vscode.CancellationTokenSource> = new Set();
constructor(private outputChannel: vscode.OutputChannel) {
process.once('exit', () => this.dispose());
}
provideTextSearchResults(query: vscode.TextSearchQuery, options: vscode.TextSearchOptions, progress: vscode.Progress<vscode.TextSearchResult>, token: vscode.CancellationToken): Thenable<vscode.TextSearchComplete> {
provideTextSearchResults(query: vscode.TextSearchQuery, options: vscode.TextSearchOptions, progress: vscode.Progress<vscode.TextSearchResult>, token: vscode.CancellationToken): Promise<vscode.TextSearchComplete> {
const engine = new RipgrepTextSearchEngine(this.outputChannel);
return this.withEngine(engine, () => engine.provideTextSearchResults(query, options, progress, token));
return this.withToken(token, token => engine.provideTextSearchResults(query, options, progress, token));
}
provideFileIndex(options: vscode.FileSearchOptions, token: vscode.CancellationToken): Thenable<vscode.Uri[]> {
const engine = new RipgrepFileSearchEngine(this.outputChannel);
const results: vscode.Uri[] = [];
const onResult = relativePathMatch => {
const onResult = (relativePathMatch: string) => {
results.push(vscode.Uri.file(options.folder.fsPath + '/' + relativePathMatch));
};
return this.withEngine(engine, () => engine.provideFileSearchResults(options, { report: onResult }, token))
return this.withToken(token, token => engine.provideFileSearchResults(options, { report: onResult }, token))
.then(() => results);
}
private withEngine<T>(engine: SearchEngine, fn: () => Thenable<T>): Thenable<T> {
this.inProgress.add(engine);
return fn().then(result => {
this.inProgress.delete(engine);
private async withToken<T>(token: vscode.CancellationToken, fn: (token: vscode.CancellationToken) => Thenable<T>): Promise<T> {
const merged = mergedTokenSource(token);
this.inProgress.add(merged);
const result = await fn(merged.token);
this.inProgress.delete(merged);
return result;
});
return result;
}
private dispose() {
this.inProgress.forEach(engine => engine.cancel());
}
}
\ No newline at end of file
}
function mergedTokenSource(token: vscode.CancellationToken): vscode.CancellationTokenSource {
const tokenSource = new vscode.CancellationTokenSource();
token.onCancellationRequested(() => tokenSource.cancel());
return tokenSource;
}
......@@ -10,7 +10,7 @@ import * as vscode from 'vscode';
import { normalizeNFC, normalizeNFD } from './normalization';
import { rgPath } from './ripgrep';
import { rgErrorMsgForDisplay } from './ripgrepTextSearch';
import { anchorGlob } from './utils';
import { anchorGlob, Maybe } from './utils';
const isMac = process.platform === 'darwin';
......@@ -18,18 +18,8 @@ const isMac = process.platform === 'darwin';
const rgDiskPath = rgPath.replace(/\bnode_modules\.asar\b/, 'node_modules.asar.unpacked');
export class RipgrepFileSearchEngine {
private rgProc: cp.ChildProcess;
private isDone: boolean;
constructor(private outputChannel: vscode.OutputChannel) { }
cancel() {
this.isDone = true;
if (this.rgProc) {
this.rgProc.kill();
}
}
provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress<string>, token: vscode.CancellationToken): Thenable<void> {
this.outputChannel.appendLine(`provideFileSearchResults ${JSON.stringify({
...options,
......@@ -39,7 +29,16 @@ export class RipgrepFileSearchEngine {
})}`);
return new Promise((resolve, reject) => {
token.onCancellationRequested(() => this.cancel());
let isDone = false;
const cancel = () => {
isDone = true;
if (rgProc) {
rgProc.kill();
}
};
token.onCancellationRequested(() => cancel());
const rgArgs = getRgArgs(options);
......@@ -50,22 +49,22 @@ export class RipgrepFileSearchEngine {
.join(' ');
this.outputChannel.appendLine(`rg ${escapedArgs}\n - cwd: ${cwd}\n`);
this.rgProc = cp.spawn(rgDiskPath, rgArgs, { cwd });
let rgProc: Maybe<cp.ChildProcess> = cp.spawn(rgDiskPath, rgArgs, { cwd });
this.rgProc.on('error', e => {
rgProc.on('error', e => {
console.log(e);
reject(e);
});
let leftover = '';
this.collectStdout(this.rgProc, (err, stdout, last) => {
this.collectStdout(rgProc, (err, stdout, last) => {
if (err) {
reject(err);
return;
}
// Mac: uses NFD unicode form on disk, but we want NFC
const normalized = leftover + (isMac ? normalizeNFC(stdout) : stdout);
const normalized = leftover + (isMac ? normalizeNFC(stdout || '') : stdout);
const relativeFiles = normalized.split('\n');
if (last) {
......@@ -75,7 +74,7 @@ export class RipgrepFileSearchEngine {
relativeFiles.pop();
}
} else {
leftover = relativeFiles.pop();
leftover = <string>relativeFiles.pop();
}
if (relativeFiles.length && relativeFiles[0].indexOf('\n') !== -1) {
......@@ -88,11 +87,11 @@ export class RipgrepFileSearchEngine {
});
if (last) {
if (this.isDone) {
if (isDone) {
resolve();
} else {
// Trigger last result
this.rgProc = null;
rgProc = null;
if (err) {
reject(err);
} else {
......@@ -104,8 +103,8 @@ export class RipgrepFileSearchEngine {
});
}
private collectStdout(cmd: cp.ChildProcess, cb: (err: Error, stdout?: string, last?: boolean) => void): void {
let onData = (err: Error, stdout?: string, last?: boolean) => {
private collectStdout(cmd: cp.ChildProcess, cb: (err?: Error, stdout?: string, last?: boolean) => void): void {
let onData = (err?: Error, stdout?: string, last?: boolean) => {
if (err || last) {
onData = () => { };
}
......@@ -136,19 +135,19 @@ export class RipgrepFileSearchEngine {
cmd.on('close', (code: number) => {
// ripgrep returns code=1 when no results are found
let stderrText, displayMsg: string;
let stderrText, displayMsg: Maybe<string>;
if (!gotData && (stderrText = this.decodeData(stderr)) && (displayMsg = rgErrorMsgForDisplay(stderrText))) {
onData(new Error(`command failed with error code ${code}: ${displayMsg}`));
} else {
onData(null, '', true);
onData(undefined, '', true);
}
});
}
private forwardData(stream: Readable, cb: (err: Error, stdout?: string) => void): NodeStringDecoder {
private forwardData(stream: Readable, cb: (err?: Error, stdout?: string) => void): NodeStringDecoder {
const decoder = new StringDecoder();
stream.on('data', (data: Buffer) => {
cb(null, decoder.write(data));
cb(undefined, decoder.write(data));
});
return decoder;
}
......
......@@ -9,31 +9,14 @@ import * as path from 'path';
import { NodeStringDecoder, StringDecoder } from 'string_decoder';
import * as vscode from 'vscode';
import { rgPath } from './ripgrep';
import { anchorGlob, createTextSearchResult } from './utils';
import { anchorGlob, createTextSearchResult, Maybe } from './utils';
// If vscode-ripgrep is in an .asar file, then the binary is unpacked.
const rgDiskPath = rgPath.replace(/\bnode_modules\.asar\b/, 'node_modules.asar.unpacked');
export class RipgrepTextSearchEngine {
private isDone = false;
private rgProc: cp.ChildProcess;
private ripgrepParser: RipgrepParser;
constructor(private outputChannel: vscode.OutputChannel) { }
cancel() {
this.isDone = true;
if (this.rgProc) {
this.rgProc.kill();
}
if (this.ripgrepParser) {
this.ripgrepParser.cancel();
}
}
provideTextSearchResults(query: vscode.TextSearchQuery, options: vscode.TextSearchOptions, progress: vscode.Progress<vscode.TextSearchResult>, token: vscode.CancellationToken): Thenable<vscode.TextSearchComplete> {
this.outputChannel.appendLine(`provideTextSearchResults ${query.pattern}, ${JSON.stringify({
...options,
......@@ -43,7 +26,7 @@ export class RipgrepTextSearchEngine {
})}`);
return new Promise((resolve, reject) => {
token.onCancellationRequested(() => this.cancel());
token.onCancellationRequested(() => cancel());
const rgArgs = getRgArgs(query, options);
......@@ -54,51 +37,64 @@ export class RipgrepTextSearchEngine {
.join(' ');
this.outputChannel.appendLine(`rg ${escapedArgs}\n - cwd: ${cwd}`);
this.rgProc = cp.spawn(rgDiskPath, rgArgs, { cwd });
this.rgProc.on('error', e => {
let rgProc: Maybe<cp.ChildProcess> = cp.spawn(rgDiskPath, rgArgs, { cwd });
rgProc.on('error', e => {
console.error(e);
this.outputChannel.append('Error: ' + (e && e.message));
reject(e);
});
let gotResult = false;
this.ripgrepParser = new RipgrepParser(options.maxResults, cwd, options.previewOptions);
this.ripgrepParser.on('result', (match: vscode.TextSearchResult) => {
const ripgrepParser = new RipgrepParser(options.maxResults, cwd, options.previewOptions);
ripgrepParser.on('result', (match: vscode.TextSearchResult) => {
gotResult = true;
progress.report(match);
});
let isDone = false;
const cancel = () => {
isDone = true;
if (rgProc) {
rgProc.kill();
}
if (ripgrepParser) {
ripgrepParser.cancel();
}
};
let limitHit = false;
this.ripgrepParser.on('hitLimit', () => {
ripgrepParser.on('hitLimit', () => {
limitHit = true;
this.cancel();
cancel();
});
this.rgProc.stdout.on('data', data => {
this.ripgrepParser.handleData(data);
rgProc.stdout.on('data', data => {
ripgrepParser.handleData(data);
});
let gotData = false;
this.rgProc.stdout.once('data', () => gotData = true);
rgProc.stdout.once('data', () => gotData = true);
let stderr = '';
this.rgProc.stderr.on('data', data => {
rgProc.stderr.on('data', data => {
const message = data.toString();
this.outputChannel.append(message);
stderr += message;
});
this.rgProc.on('close', code => {
rgProc.on('close', () => {
this.outputChannel.appendLine(gotData ? 'Got data from stdout' : 'No data from stdout');
this.outputChannel.appendLine(gotResult ? 'Got result from parser' : 'No result from parser');
this.outputChannel.appendLine('');
if (this.isDone) {
if (isDone) {
resolve({ limitHit });
} else {
// Trigger last result
this.ripgrepParser.flush();
this.rgProc = null;
let displayMsg: string;
ripgrepParser.flush();
rgProc = null;
let displayMsg: Maybe<string>;
if (stderr && !gotData && (displayMsg = rgErrorMsgForDisplay(stderr))) {
reject(new Error(displayMsg));
} else {
......@@ -115,7 +111,7 @@ export class RipgrepTextSearchEngine {
* Ripgrep produces stderr output which is not from a fatal error, and we only want the search to be
* "failed" when a fatal error was produced.
*/
export function rgErrorMsgForDisplay(msg: string): string | undefined {
export function rgErrorMsgForDisplay(msg: string): Maybe<string> {
const firstLine = msg.split('\n')[0].trim();
if (firstLine.startsWith('Error parsing regex')) {
......@@ -150,9 +146,9 @@ export class RipgrepParser extends EventEmitter {
public static readonly MATCH_START_MARKER = '\u001b[0m\u001b[31m';
public static readonly MATCH_END_MARKER = '\u001b[0m';
private currentFile: string;
private remainder: string;
private isDone: boolean;
private currentFile = '';
private remainder = '';
private isDone = false;
private stringDecoder: NodeStringDecoder;
private numResults = 0;
......@@ -182,7 +178,7 @@ export class RipgrepParser extends EventEmitter {
decodedData;
const dataLines: string[] = dataStr.split(/\r\n|\n/);
this.remainder = dataLines[dataLines.length - 1] ? dataLines.pop() : null;
this.remainder = dataLines[dataLines.length - 1] ? <string>dataLines.pop() : '';
for (let l = 0; l < dataLines.length; l++) {
const outputLine = dataLines[l].trim();
......@@ -190,7 +186,7 @@ export class RipgrepParser extends EventEmitter {
break;
}
let r: RegExpMatchArray;
let r: Maybe<RegExpMatchArray>;
if (r = outputLine.match(RipgrepParser.RESULT_REGEX)) {
const lineNum = parseInt(r[1]) - 1;
let matchText = r[2];
......@@ -202,7 +198,7 @@ export class RipgrepParser extends EventEmitter {
}
// Line is a result - add to collected results for the current file path
this.handleMatchLine(outputLine, lineNum, matchText);
this.handleMatchLine(lineNum, matchText);
} else if (r = outputLine.match(RipgrepParser.FILE_REGEX)) {
this.currentFile = r[1];
} else {
......@@ -211,7 +207,7 @@ export class RipgrepParser extends EventEmitter {
}
}
private handleMatchLine(outputLine: string, lineNum: number, lineText: string): void {
private handleMatchLine(lineNum: number, lineText: string): void {
if (lineNum === 0) {
lineText = stripUTF8BOM(lineText);
}
......@@ -332,9 +328,9 @@ function getRgArgs(query: vscode.TextSearchQuery, options: vscode.TextSearchOpti
query.pattern = '\\-\\-';
}
let searchPatternAfterDoubleDashes: string;
let searchPatternAfterDoubleDashes: Maybe<string>;
if (query.isWordMatch) {
const regexp = createRegExp(query.pattern, query.isRegExp, { wholeWord: query.isWordMatch });
const regexp = createRegExp(query.pattern, !!query.isRegExp, { wholeWord: query.isWordMatch });
const regexpStr = regexp.source.replace(/\\\//g, '/'); // RegExp.source arbitrarily returns escaped slashes. Search and destroy.
args.push('--regexp', regexpStr);
} else if (query.isRegExp) {
......@@ -407,10 +403,8 @@ function escapeRegExpCharacters(value: string): string {
const UTF8_BOM = 65279;
const UTF8_BOM_CHARACTER = String.fromCharCode(UTF8_BOM);
function startsWithUTF8BOM(str: string): boolean {
return (str && str.length > 0 && str.charCodeAt(0) === UTF8_BOM);
return !!(str && str.length > 0 && str.charCodeAt(0) === UTF8_BOM);
}
function stripUTF8BOM(str: string): string {
......
......@@ -4,16 +4,17 @@
*--------------------------------------------------------------------------------------------*/
import 'mocha';
import * as assert from 'assert';
import * as vscode from 'vscode';
import * as path from 'path';
import { createTextSearchResult } from '../utils';
// TODO./
// import * as assert from 'assert';
// import * as vscode from 'vscode';
// import * as path from 'path';
// import { createTextSearchResult } from '../utils';
function createOneLineRange(lineNumber: number, startCol: number, endCol: number): vscode.Range {
return new vscode.Range(lineNumber, startCol, lineNumber, endCol);
}
// function createOneLineRange(lineNumber: number, startCol: number, endCol: number): vscode.Range {
// return new vscode.Range(lineNumber, startCol, lineNumber, endCol);
// }
const uri = vscode.Uri.file('/foo/bar');
// const uri = vscode.Uri.file('/foo/bar');
suite('search-rg', () => {
......
......@@ -6,6 +6,8 @@
import * as path from 'path';
import * as vscode from 'vscode';
export type Maybe<T> = T | null | undefined;
export function fixDriveC(_path: string): string {
const root = path.parse(_path).root;
return root.toLowerCase() === 'c:/' ?
......
{
"extends": "../shared.tsconfig.json",
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"lib": [
"es6",
"es2015.promise"
],
"outDir": "./out",
"experimentalDecorators": true
"experimentalDecorators": true,
},
"include": [
"src/**/*"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册