代码编辑器多光标完全指南

多光标编辑是现代代码编辑器的核心功能,允许用户同时在多个位置进行输入和编辑。本文详细介绍多光标的创建、管理和同步编辑的实现方案。

多光标数据结构

首先定义多光标的数据结构:

/* 光标位置定义 */
interface Position {
    line: number;
    column: number;
}

/* 单个光标/选区 */
interface Selection {
    start: Position;
    end: Position;
    id: string;
}

/* 多光标管理器 */
class MultiCursorManager {
    constructor(editor) {
        this.editor = editor;
        this.selections = [];  // 所有选区/光标
        this.primaryIndex = 0;  // 主光标索引
    }

    getCursors() {
        return this.selections;
    }

    getPrimaryCursor() {
        return this.selections[this.primaryIndex];
    }
}

创建多光标的方式

用户可以通过多种方式创建多光标:

/* 1. Ctrl+D 添加选中词的光标 */
async addCursorToNextOccurrence(editor) {
    const primary = getPrimarySelection(editor);
    const selectedText = editor.getTextInRange(primary);

    if (!selectedText) return;

    /* 查找所有匹配位置 */
    const matches = findAllMatches(editor.document, selectedText);

    /* 添加光标到匹配位置 */
    for (const match of matches) {
        if (!isOverlapping(match, this.selections)) {
            this.addSelection({
                start: match.start,
                end: match.end
            });
        }
    }
}

/* 2. Alt+Click 添加光标 */
function addCursorAtClick(editor, clickPosition) {
    this.addSelection({
        start: clickPosition,
        end: clickPosition
    });
}

/* 3. 列选择模式(Alt+Shift+拖动)*/
function addColumnSelections(editor, startPos, endPos) {
    const startLine = Math.min(startPos.line, endPos.line);
    const endLine = Math.max(startPos.line, endPos.line);
    const startCol = Math.min(startPos.column, endPos.column);
    const endCol = Math.max(startPos.column, endPos.column);

    for (let line = startLine; line <= endLine; line++) {
        const lineLength = editor.getLineLength(line);
        const actualEndCol = Math.min(endCol, lineLength);

        this.addSelection({
            line: line,
            column: startCol
        }, {
            line: line,
            column: actualEndCol
        });
    }
}

同步编辑实现

多光标编辑的核心是如何同时修改多个位置:

/* 同步编辑管理器 */
class SyncEditManager {
    constructor(cursorManager) {
        this.cursorManager = cursorManager;
    }

    /* 同步插入文本 */
    insert(text) {
        const cursors = this.cursorManager.getCursors();
        const edits = [];

        /* 为每个光标位置创建编辑操作 */
        for (const cursor of cursors) {
            edits.push({
                range: {
                    start: cursor.start,
                    end: cursor.end
                },
                text: text
            });
        }

        /* 批量执行编辑 */
        return this.batchEdit(edits);
    }

    /* 同步删除 */
    delete(direction = 'backward') {
        const cursors = this.cursorManager.getCursors();
        const edits = [];

        for (const cursor of cursors) {
            const deleteRange = calculateDeleteRange(cursor, direction);
            if (deleteRange) {
                edits.push({
                    range: deleteRange,
                    text: ''
                });
            }
        }

        return this.batchEdit(edits);
    }

    /* 智能大小写转换 */
    changeCase(toCase) {
        const cursors = this.cursorManager.getCursors();
        const edits = [];

        for (const cursor of cursors) {
            const selectedText = editor.getTextInRange(cursor);
            const newText = transformCase(selectedText, toCase);

            edits.push({
                range: { start: cursor.start, end: cursor.end },
                text: newText
            });
        }

        return this.batchEdit(edits);
    }
}

光标同步滚动

当一个光标滚动到可见区域时,其他光标也应该保持可见:

/* 光标同步滚动管理器 */
class CursorScrollManager {
    constructor(editor, cursorManager) {
        this.editor = editor;
        this.cursorManager = cursorManager;
    }

    /* 确保所有光标可见 */
    ensureAllCursorsVisible() {
        const cursors = this.cursorManager.getCursors();
        const visibleRanges = this.editor.getVisibleRanges();

        for (const cursor of cursors) {
            if (!isPositionVisible(cursor.start, visibleRanges)) {
                this.scrollToCursor(cursor);
            }
        }
    }

    /* 滚动到指定光标,保持适当的边距 */
    scrollToCursor(cursor, margin = 3) {
        const cursorPos = this.editor.positionToCoordinates(cursor.start);

        const visibleHeight = this.editor.getVisibleHeight();
        const targetScroll = cursorPos.top - visibleHeight * 0.3;

        this.editor.setScrollTop(targetScroll);
    }

    /* 滚动时同步更新光标位置显示 */
    onScroll(scrollTop) {
        this.editor.updateCursorPositions();
    }
}

光标渲染

在编辑器中渲染多个光标:

/* 多光标渲染器 */
class CursorRenderer {
    render(cursors) {
        const container = this.getCursorLayer();

        /* 清除旧光标 */container.clearChildren();

        /* 渲染每个光标 */
        cursors.forEach((cursor, index) => {
            const cursorEl = document.createElement('div');
            cursorEl.className = `cursor ${index === 0 ? 'primary' : 'secondary'}`;

            /* 光标位置和高度计算 */
            const coords = getCursorCoordinates(cursor);
            cursorEl.style.left = `${coords.left}px`;
            cursorEl.style.top = `${coords.top}px`;
            cursorEl.style.height = `${coords.height}px`;

            /* 选区高亮 */
            if (cursor.start !== cursor.end) {
                const selectionEl = createSelectionElement(cursor);
                container.appendChild(selectionEl);
            }

            container.appendChild(cursorEl);
        });
    }
}

快捷键配置

提供便捷的多光标操作快捷键:

/* 多光标快捷键 */
const MULTI_CURSOR_SHORTCUTS = {
    'Alt+Click': addCursorAtClick,
    'Ctrl+D': addCursorToNextOccurrence,
    'Ctrl+Shift+L': selectAllOccurrences,
    'Alt+Shift+Arrow': columnSelection,
    'Escape': clearSecondaryCursors
};

用户体验

主光标和次光标可以使用不同的样式区分,帮助用户识别当前活动光标。

总结

  • 多光标通过选区数组管理
  • 支持多种创建方式:Ctrl+D、Alt+Click、列选择
  • 同步编辑需要处理光标重叠和相对位置
  • 滚动时需要确保所有光标可见