应使用 TextEditor.edit() 执行原子替换:先用 editor.selection 获取选区,editor.document.getText() 读取文本,再通过 editBuilder.replace(selection, newText) 安全写入;需处理空选区、跨行缩进、光标定位及只读文件校验。

vscode.workspace.onDidChangeTextDocument 不是你要用的;替换选中文本的核心动作,必须落在用户触发命令后的编辑器操作上,而不是监听文档变更。
怎么拿到当前选中的文本并安全替换
关键不是“读取”,而是“在正确位置写入”。VS Code 的 API 要求你通过 TextEditor.edit() 构建一个编辑事务,不能直接赋值或拼接字符串后调用 editor.document.setText() —— 那会清空整个文件。
- 先用
editor.selection获取当前选区(Selection对象),它包含start和end两个Position - 用
editor.document.getText(editor.selection)拿到原始选中文本(只读,安全) - 调用
editor.edit(editBuilder => { editBuilder.replace(selection, newText) })执行原子替换 - 注意:如果用户没选中任何内容(即光标在某处),
selection.isEmpty为true,此时replace()会插入而非覆盖——需按需判断
为什么 editor.insert() 和 editor.replace() 行为不一致
这两个方法底层都走 TextEditor.edit(),但语义不同:insert() 总是在光标位置插入,无视选区;replace() 则严格以传入的 Range 为准。插件里常见错误是混用:
- 误用
editor.insert('xxx')替代editBuilder.replace(range, 'xxx')→ 导致文本插在光标处,原选中内容未被清除 - 传给
replace()的Range跨越了多行但未对齐缩进 → 替换后格式错乱(比如把return换成throw new Error()却没补空格) - 没 await
editor.edit()的 Promise → 后续逻辑可能读到旧内容,尤其在连续触发时
如何避免替换后光标乱跳或选区丢失
默认情况下,editBuilder.replace() 执行完,光标会停在新文本末尾,且不保留选区。如果你希望用户能继续操作(比如批量替换后立刻再按一次快捷键),就得手动控制光标位置:
- 用
new Selection(new Position(row, column), new Position(row, column))构造一个空选区 - 在
editor.edit()内部调用editBuilder.setSelection(newSelection) - 更稳妥的做法是:替换完成后,调用
editor.selection = newSelection(注意这是同步赋值,不是 builder 方法) - 若要高亮新文本,可设
new Selection(startPos, endPos),其中endPos由startPos.translate(0, newText.length)计算
实际替换时容易忽略的边界情况
真实场景下,用户选中的可能是跨行内容、含制表符的代码块、或位于注释/字符串内部的文本。API 不做语义判断,全靠你预处理:
- 选区跨越多行时,
editor.document.lineAt(pos).text只返回单行,别用它拼全文本 - 想保留原始缩进?得用
editor.document.lineAt(selection.start.line).firstNonWhitespaceCharacterIndex获取缩进宽度,再对新文本前缀补空格 - 用户双击选中单词,但光标在词尾 →
selection的start和end可能反向(start.isAfter(end)),需用selection.with({ start, end }).normalize() - 在只读文件(如
node_modules下的文件)中执行替换会静默失败,建议提前检查editor.document.isUntitled || editor.document.isClosed || !editor.document.uri.scheme.startsWith('file')
最易被绕开的一点:VS Code 插件里没有“全局查找后批量替换”的现成 API;所有替换必须基于当前活动编辑器的选区。如果真要实现类似“在所有打开文件中替换选中文本”,得自己遍历 vscode.window.visibleTextEditors 并逐个调用 edit() —— 且必须处理并发编辑冲突。


















