vscode的全局搜索速度非常快,其中的奥妙是什么?
我们常常需要在一大堆文本文件里搜索一个字符串,在Linux
下可以用自带的grep
,不过grep的命令行还是有点难记。ripgrep
是开源工具,使用Rust
编写,全平台支持。看上去比grep要强大不少。如果你使用VS Code
,那么你必定间接的用过这个工具了,因为VS Code的Find In Files功能就是直接调用的ripgrep
。
ripgrep的github地址:https://github.com/BurntSushi/ripgrep
ripgrep
是一种面向行的搜索工具,它递归地在当前目录中搜索正则表达式模式。默认情况下,ripgrep
将尊重 Gitignore
规则,并自动跳过隐藏文件 / 目录和二进制文件。ripgrep
在 Windows
、macOS
和 Linux
上有一流的支持,二进制下载可用于 every release。 ripgrep类似于其他流行的搜索工具,如 Silver Searcher
、ACK
和 Grep
。
有大神整理出黎一份中文文档:https://gitcode.gitcode.host/docs-cn/ripgrep-docs-cn/index.html
vscode为了方便在项目中使用,封装成了一个npm 模块 vscode-regexpp
npm地址:https://www.npmjs.com/package/@vscode/ripgrep
example:
const { rgPath } = require('vscode-ripgrep');
// child_process.spawn(rgPath, ...)
github仓库:https://github.com/microsoft/vscode-ripgrep
我们看下是怎么调用的,下面是npm包
其实最后也是执行的一个rg
的可执行文件
src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts
import * as cp from 'child_process';
import { rgPath } from '@vscode/ripgrep';
// 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 {
constructor(private outputChannel: IOutputChannel) { }
provideTextSearchResults(query: TextSearchQuery, options: TextSearchOptions, progress: Progress<TextSearchResult>, token: CancellationToken): Promise<TextSearchComplete> {
this.outputChannel.appendLine(`provideTextSearchResults ${query.pattern}, ${JSON.stringify({
...options,
...{
folder: options.folder.toString()
}
})}`);
return new Promise((resolve, reject) => {
token.onCancellationRequested(() => cancel());
const rgArgs = getRgArgs(query, options);
const cwd = options.folder.fsPath;
const escapedArgs = rgArgs
.map(arg => arg.match(/^-/) ? arg : `'${arg}'`)
.join(' ');
this.outputChannel.appendLine(`${rgDiskPath} ${escapedArgs}\n - cwd: ${cwd}`);
// console.log('999999999999', rgDiskPath); // /Users/liuchongyang/site/vscode/node_modules/@vscode/ripgrep/bin/rg
let rgProc: Maybe<cp.ChildProcess> = cp.spawn(rgDiskPath, rgArgs, { cwd });
rgProc.on('error', e => {
console.error(e);
this.outputChannel.appendLine('Error: ' + (e && e.message));
reject(serializeSearchError(new SearchError(e && e.message, SearchErrorCode.rgProcessError)));
});
let gotResult = false;
const ripgrepParser = new RipgrepParser(options.maxResults, cwd, options.previewOptions);
ripgrepParser.on('result', (match: TextSearchResult) => {
console.log('match-->', match); //匹配的结果
gotResult = true;
dataWithoutResult = '';
progress.report(match);
});
let isDone = false;
const cancel = () => {
isDone = true;
if (rgProc) {
rgProc.kill();
}
if (ripgrepParser) {
ripgrepParser.cancel();
}
};
let limitHit = false;
ripgrepParser.on('hitLimit', () => {
limitHit = true;
cancel();
});
let dataWithoutResult = '';
rgProc.stdout!.on('data', data => {
console.log('data->', data);
ripgrepParser.handleData(data);
if (!gotResult) {
dataWithoutResult += data;
}
});
let gotData = false;
rgProc.stdout!.once('data', () => gotData = true);
let stderr = '';
rgProc.stderr!.on('data', data => {
const message = data.toString();
this.outputChannel.appendLine(message);
stderr += message;
});
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');
if (dataWithoutResult) {
this.outputChannel.appendLine(`Got data without result: ${dataWithoutResult}`);
}
this.outputChannel.appendLine('');
if (isDone) {
resolve({ limitHit });
} else {
// Trigger last result
ripgrepParser.flush();
rgProc = null;
let searchError: Maybe<SearchError>;
if (stderr && !gotData && (searchError = rgErrorMsgForDisplay(stderr))) {
reject(serializeSearchError(new SearchError(searchError.message, searchError.code)));
} else {
resolve({ limitHit });
}
}
});
});
}
}
打印data 原数据返回的是buffer数组
经过vscode处理后变成了这样的
这样很清晰 我们就可以看懂了。
range 是javascript对象,表示一个包含节点与文本节点的一部分的文档片段。用于文档处理。
https://developer.mozilla.org/zh-CN/docs/Web/API/Range
vscode是模拟range 封装了一个 适合自己使用场景的range。