多光标编辑是现代代码编辑器的核心功能,允许用户同时在多个位置进行输入和编辑。本文详细介绍多光标的创建、管理和同步编辑的实现方案。
多光标数据结构
首先定义多光标的数据结构:
/* 光标位置定义 */
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、列选择
- 同步编辑需要处理光标重叠和相对位置
- 滚动时需要确保所有光标可见