查找与替换是代码编辑器最常用的功能之一。本文详细介绍增量搜索、正则匹配、模糊搜索与高效替换的实现方案。
增量搜索实现
增量搜索(Incremental Search)在用户输入时实时更新搜索结果:
/* 增量搜索状态管理 */
class IncrementalSearch {
constructor(editor) {
this.editor = editor;
this.query = '';
this.matches = [];
this.currentIndex = -1;
}
/* 实时搜索 */
search(query) {
this.query = query;
if (!query) {
this.matches = [];
this.clearHighlights();
return;
}
this.matches = this.findMatches(query);
this.highlightMatches();
this.jumpToMatch(0);
}
findMatches(query) {
const text = this.editor.getText();
const matches = [];
let pos = 0;
/* 高效字符串搜索 */
while ((pos = text.indexOf(query, pos)) !== -1) {
matches.push({
start: pos,
end: pos + query.length,
line: this.getLineNumber(pos)
});
pos += 1;
}
return matches;
}
/* 高亮所有匹配 */
highlightMatches() {
this.editor.removeAllDecorations('search-match');
this.matches.forEach((match, i) => {
const clasee = i === this.currentIndex
? 'search-match current'
: 'search-match';
this.editor.addDecoration(match.start, match.end, clasee);
});
}
/* 跳转到下一个匹配 */
next() {
if (this.matches.length === 0) return;
this.currentIndex = (this.currentIndex + 1) % this.matches.length;
this.jumpToMatch(this.currentIndex);
}
/* 跳转到上一个匹配 */
previous() {
if (this.matches.length === 0) return;
this.currentIndex = (this.currentIndex - 1 + this.matches.length) % this.matches.length;
this.jumpToMatch(this.currentIndex);
}
}
正则表达式搜索
支持正则表达式的强大搜索功能:
/* 正则表达式搜索 */
class RegexSearch {
constructor() {
this.flags = gmis; // 全局、大小写不敏感、多行
}
/* 使用正则搜索 */
search(text, pattern, flags = 'g') {
try {
const regex = new RegExp(pattern, flags + this.flags);
const matches = [];
let match;
while ((match = regex.exec(text)) !== null) {
matches.push({
start: match.index,
end: match.index + match[0].length,
text: match[0],
groups: match.slice(1)
});
// 防止无限循环(零宽匹配)*/
if (match[0].length === 0) {
regex.lastIndex++;
}
}
return { success: true, matches };
} catch (e) {
return { success: false, error: e.message };
}
}
/* 替换功能 */
replace(text, pattern, replacement, flags = 'g') {
const regex = new RegExp(pattern, flags + this.flags);
/* 支持捕获组引用 */
return text.replace(regex, (match, ...args) => {
/* $1, $2... 捕获组引用 */
/* $& 整个匹配 */
/* $` 匹配前面的内容 */
/* $' 匹配后面的内容 */
return replacement.replace(\$(\d+|\$&|\`|\'), (ref, p1) => {
if (p1 === '&') return match;
if (p1 === '`') return text.slice(0, args[args.length - 2]);
if (p1 === "'") return text.slice(args[args.length - 2] + match.length);
return args[parseInt(p1) - 1] || '';
});
});
}
/* 全部替换 */
replaceAll(text, pattern, replacement) {
const result = this.replace(text, pattern, replacement, 'g');
const count = (text.match(new RegExp(pattern, 'g')) || []).length;
return { text: result, count };
}
}
模糊匹配
模糊搜索允许用户输入部分关键词匹配:
/* 模糊匹配算法 */
function fuzzyMatch(text, pattern) {
/* 简单实现:检查所有字符是否按顺序出现 */
let patternIdx = 0;
let textLower = text.toLowerCase();
let patternLower = pattern.toLowerCase();
for (let i = 0; i < textLower.length && patternIdx < patternLower.length; i++) {
if (textLower[i] === patternLower[patternIdx]) {
patternIdx++;
}
}
if (patternIdx !== patternLower.length) {
return null; // 不匹配
}
/* 计算匹配分数 */
let score = 0;
let matchedIndices = [];
patternIdx = 0;
for (let i = 0; i < text.length && patternIdx < pattern.length; i++) {
if (text[i] === pattern[patternIdx]) {
matchedIndices.push(i);
/* 首字符匹配加分 */
if (i === 0) score += 15;
/* 连续匹配加分 */
else if (matchedIndices[matchedIndices.length - 2] === i - 1) {
score += 10;
}
/* 单词边界匹配加分 */
else if (" _-./\\".includes(text[i - 1])) {
score += 5;
}
/* 大小写匹配加分 */
else if (text[i] === pattern[patternIdx]) {
score += 2;
}
patternIdx++;
}
}
return { score, indices: matchedIndices };
}
/* 使用模糊搜索的文件搜索 */
function fuzzySearchFiles(files, query) {
return files
.map(file => ({
file,
...fuzzyMatch(file.name, query)
}))
.filter(r => r.score !== null)
.sort((a, b) => b.score - a.score)
.map(r => r.file);
}
搜索性能优化
- 大文件使用 Web Worker 避免阻塞 UI
- 限制搜索结果显示数量(如前 1000 个匹配)
- 使用 Trie 树优化多模式搜索
- 搜索结果缓存,避免重复搜索
多光标搜索与替换
在多个光标位置同时进行搜索替换:
/* 多光标替换 */
class MultiCursorReplace {
constructor(editor) {
this.editor = editor;
}
/* 查找当前选择的内容并在所有光标处替换 */
replaceInCursors(searchText, replaceText) {
const selections = this.editor.getSelections();
const changes = [];
selections.forEach(selection => {
const selectedText = this.editor.getTextInRange(selection);
if (selectedText === searchText) {
changes.push({
range: selection,
newText: replaceText
});
} else {
/* 如果选中文本不匹配,在光标位置搜索并替换 */
const pos = selection.start;
const text = this.editor.getText();
const index = text.indexOf(searchText, pos);
if (index !== -1) {
changes.push({
range: { start: index, end: index + searchText.length },
newText: replaceText
});
}
}
});
/* 批量应用修改 */
this.editor.applyChanges(changes);
}
/* 全部替换(当前文档)*/
replaceAll(searchText, replaceText) {
const text = this.editor.getText();
const regex = new RegExp(escapeRegex(searchText), 'g');
const newText = text.replace(regex, replaceText);
this.editor.setText(newText);
}
}
搜索 UI 设计
直观易用的搜索界面:
/* 搜索面板组件 */
class SearchPanel {
constructor(container, searchEngine) {
this.engine = searchEngine;
this.isVisible = false;
container.innerHTML = `
0 / 0
`;
this.bindEvents();
}
bindEvents() {
const input = this.$el.querySelector('.search-input');
let debounceTimer;
input.addEventListener('input', () => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
this.doSearch(input.value);
}, 150);
});
this.$el.querySelector('.next-btn').addEventListener('click', () => {
this.engine.next();
});
}
}
总结
- 增量搜索:输入时实时更新结果,高亮显示匹配
- 正则搜索:支持完整正则表达式语法
- 模糊匹配:部分关键词也能匹配到目标
- 多光标替换:同时修改多个位置的文本