代码编辑器查找与替换完全指南

查找与替换是代码编辑器最常用的功能之一。本文详细介绍增量搜索、正则匹配、模糊搜索与高效替换的实现方案。

增量搜索实现

增量搜索(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(); }); } }

总结

  • 增量搜索:输入时实时更新结果,高亮显示匹配
  • 正则搜索:支持完整正则表达式语法
  • 模糊匹配:部分关键词也能匹配到目标
  • 多光标替换:同时修改多个位置的文本