VS Code 里的 Copilot Chat 为啥一拷贝就只剩纯文本
摘要 本文探讨了VS Code中Copilot Chat复制内容时丢失Markdown格式的问题。关键发现包括: 右键"Copy"和Ctrl+C行为不同,前者保留Markdown源码,后者只复制渲染文本; 目标应用对Markdown的支持程度影响最终显示效果; 官方导出功能仅支持JSON格式,社区已开发转换工具。 提供了解决方案: 优先使用右键菜单复制单条消息; 使用"
你遇到的现象是:在 Visual Studio Code 里和 Copilot Chat 对话,复制出去只能得到纯文本,没有 Markdown
的代码块、列表、标题等结构。这个问题表面像是 复制
操作不保留格式,往里看却牵涉到 VS Code 的 Webview
剪贴板实现、Copy
菜单与快捷键的差异、目标应用如何接收内容、以及 Copilot 提供的导出能力与其缺口。下面用一条顺畅的推理链,把原因与对策掰开揉碎。
现象并不单一:右键 Copy
与 Ctrl+C
背后的差别
在 Copilot Chat 的单条消息卡片上,官方文档明确写过:右键那条消息选择 Copy
,会把内容以 Markdown
形式复制到剪贴板;在侧栏空白处 Copy All
可以复制整段对话。这不是玄学,是产品设计出的有意区分。文档的原文要点是:右键单条消息选 Copy
可得到 Markdown
,Export Chat...
则导出为 JSON
文件,右键 Copy All
可复制当前会话的所有内容。(Visual Studio Code)
一些用户实测也在讨论区反馈:用右键的 Copy
能拿到带 Markdown
的文本,而不是浏览器里渲染后的纯文字。(GitHub)
如果改用键盘 Ctrl+C
,行为就不同了。对于 VS Code 的 Webview
,Ctrl+C
更像对选中的可视化区域取 innerText
,于是复制的是渲染后文字,没有原始 Markdown
语法。这就解释了你看到的 只能以纯文本格式拷贝
:方式不对,拿到的是渲染文本,而不是源 Markdown
。
一句话总结这个分歧:右键 Copy
走的是 Copilot Chat 自己的复制通道,把源 Markdown
放进剪贴板;Ctrl+C
走的是通用剪贴板路径,多半只拿到可见文本。
另一个常见误会:Markdown
本就是纯文本,问题在于 有没有保留 Markdown 语法标记
有时大家说 复制成 Markdown
,潜台词是希望保留 ### 标题
、- 列表
、````` 代码块 ``` 等标记。可 Markdown
本质上是纯文本,不是富文本。真正影响体验的是:你复制出去得到的到底是 带 Markdown 标记的纯文本
,还是 渲染后去掉标记的纯文本
。前者可以在大多数编辑器里保留结构,后者就只剩文字了。
所以当你说 只能以纯文本拷贝
,如果右键 Copy
依然不行,就可能是下面的情况之一:
- 目标应用对
Markdown
没有语法高亮或即时渲染,于是看起来像没格式。 - 你用的是
Ctrl+C
,不是右键Copy
。 - 当前 VS Code 或 Copilot 版本里,
右键 Copy
被某些状态干扰失效。 - 你想要的其实是
富文本
粘贴,例如带RTF
或HTML
的粗体、斜体高亮,那和Markdown
是两码事,需要不同工具链。
VS Code 设置项与剪贴板多格式:为啥编辑器里能彩色,粘贴出去不一定带格式
VS Code 的编辑器在 设置
里有一项 Text Editor › Copy With Syntax Highlighting
,控制复制时是否把语法高亮以 HTML
或 RTF
一类格式也放到剪贴板。社区里常见的建议是关闭或开启这个开关来得到更接近预期的复制效果。(Reddit)
不过这项设置主要影响 编辑器 文本,不直接管 Webview
里的 Copilot Chat。Chat
属于 Webview
,它的复制逻辑是另一个通道,所以你会看到右键 Copy
能拿 Markdown
,而 Ctrl+C
拿不到。这个差异也解释了为什么同样的 VS Code,在常规编辑器里复制能带 HTML
高亮,到 Chat 面板就没了。
官方导出能力与缺口:为什么很多人还在提需求
很多开发者需要把整段对话存档成 Markdown
、PDF
或者便于笔记系统使用的格式。官方现在提供的路径是把会话导出为 JSON
,再由你自己转成文档。社区里有不少 导出为 Markdown 或 PDF
的需求帖子,说明这块仍然是大家痛点。(GitHub)
也因此,社区出现了脚本与扩展,专做 Copilot Chat JSON -> Markdown
的转换,比如 copilot-chat-to-markdown
仓库与 Chat To Markdown
扩展。它们的思路都是:先用命令面板 Chat: Export Chat...
导出 JSON
,再本地转 Markdown
。(GitHub)
一套可落地的排查与操作顺序
为了尽量复现实用语境,用下面这套顺序来定位问题并拿到你想要的 Markdown
:
-
在 Copilot Chat 里,对着你要的那条回答
右键 -> Copy
,粘贴到Markdown
编辑器里确认是否保留了诸如###
、-
、````` 等标记。官方文档明确这是复制为 Markdown
的通道。(Visual Studio Code) -
如果想复制整段会话,试下在 Chat 视图空白处
右键 -> Copy All
,或者用命令面板Chat: Export Chat...
导出JSON
。(Visual Studio Code) -
如果你的粘贴目标是
Word
、飞书文档
、Notion
,请留意这些目标对Markdown
的支持方式不同:- 能理解
Markdown
的,会直接渲染或提供Paste and Match Style
、Paste Markdown
一类入口; - 不能理解的,会显示成纯文本,这不代表你复制错了,而是目标应用不支持
Markdown
渲染。
- 能理解
-
若你确实需要
富文本
样式而非Markdown
,那是另一条路线:要么使用能把Markdown
转HTML
并以HTML
形式入剪贴板的扩展,要么贴到支持Markdown
的文档系统里再导出。VS Code 编辑器侧的Copy With Syntax Highlighting
只对编辑器生效,Chat 面板并不吃这套。(Reddit) -
如果你遇到
右键 Copy
也只是纯文本,结合版本或平台差异,可能暂时命中已知缺口或回归问题。开发者社区里确实有用户反映过无法复制整个内容为 Markdown
或导出能力不足的问题,可以临时走导出 JSON -> 本地转 Markdown
的方案。(Visual Studio Developer Community)
提供一套可运行的 Node.js
转换脚本:把 Copilot Chat 的 JSON
导出转成 Markdown
很多人嫌装扩展麻烦,更偏爱一份 脚本在手、随处可跑
。下面这段 Node.js
脚本支持把 Chat: Export Chat...
得到的 JSON
文件转成 Markdown
。脚本做了容错,兼容不同字段命名,遇到代码片段会用三引号围好,生成一个结构清晰的 Markdown
文件,方便直接存档或提交仓库。
使用步骤
- 在 VS Code 命令面板执行
Chat: Export Chat...
,保存为chat.json
。(GitHub) - 把下面脚本保存为
copilot-json-to-md.js
。 - 在
Node.js 18+
环境运行:node copilot-json-to-md.js chat.json chat.md
。 - 打开生成的
chat.md
检查。
脚本源码
#!/usr/bin/env node
// 简易 Copilot Chat JSON -> Markdown 转换器
// 用法:node copilot-json-to-md.js input.json output.md
const fs = require('fs');
const path = require('path');
function asString(x) {
if (x == null) return '';
if (typeof x === 'string') return x;
if (Array.isArray(x)) {
// Copilot Chat 某些 schema 会把一条消息拆成多段 text / code
return x.map(asString).join('\n');
}
if (typeof x === 'object') {
// 尝试常见字段
if (typeof x.text === 'string') return x.text;
if (Array.isArray(x.text)) return x.text.map(asString).join('\n');
if (typeof x.content === 'string') return x.content;
if (Array.isArray(x.content)) return x.content.map(asString).join('\n');
// 展开一下常见 content 块
if (x.value && typeof x.value === 'string') return x.value;
if (x.message && typeof x.message === 'string') return x.message;
// 兜底序列化
return '';
}
return String(x);
}
function normalizeBlocks(msg) {
// 把一条消息拆成可渲染的文本段和代码段
// 常见 schema:{ role: 'user' | 'assistant', content: [{type:'text', text:'...'}, {type:'code', text:'...'}] }
const blocks = [];
if (!msg) return blocks;
const role = msg.role || msg.author || 'assistant';
const content = msg.content ?? msg.message ?? msg.body ?? msg.parts ?? msg.items;
if (Array.isArray(content)) {
for (const part of content) {
if (part == null) continue;
const t = (part.type || part.kind || '').toLowerCase();
const lang = part.language || part.lang || '';
const text = asString(part);
if (!text.trim()) continue;
if (t.includes('code') || t.includes('snippet')) {
blocks.push({ role, type: 'code', lang, text });
} else {
blocks.push({ role, type: 'text', text });
}
}
} else {
const text = asString(content ?? msg.text ?? msg.prompt ?? msg.response);
if (text.trim()) {
blocks.push({ role, type: 'text', text });
}
}
return blocks;
}
function toMarkdown(chat) {
const title = chat.title || 'Copilot Chat Export';
const messages = chat.messages || chat.items || chat.history || chat.conversation || [];
const lines = [];
lines.push(`# ${title}`);
lines.push('');
for (const msg of messages) {
const role = (msg.role || msg.author || '').toLowerCase();
const who = role === 'user' ? 'User' : 'Copilot';
const blocks = normalizeBlocks(msg);
lines.push(`**${who}:**`);
if (blocks.length === 0) {
// 兜底尝试把整条消息序列化
const raw = asString(msg);
if (raw.trim()) lines.push(raw);
lines.push('');
continue;
}
for (const b of blocks) {
if (b.type === 'code') {
const lang = b.lang || '';
lines.push('```' + lang);
lines.push(b.text.replace(/\r\n/g, '\n'));
lines.push('```');
lines.push('');
} else {
lines.push(b.text.replace(/\r\n/g, '\n'));
lines.push('');
}
}
}
return lines.join('\n');
}
function main() {
const [, , input, output] = process.argv;
if (!input) {
console.error('用法:node copilot-json-to-md.js input.json [output.md]');
process.exit(1);
}
const raw = fs.readFileSync(path.resolve(input), 'utf8');
// 有些导出是数组,有些是对象
const data = JSON.parse(raw);
const doc = Array.isArray(data) ? { title: path.basename(input), messages: data } : data;
const md = toMarkdown(doc);
const out = output ? path.resolve(output) : path.resolve(process.cwd(), path.basename(input, path.extname(input)) + '.md');
fs.writeFileSync(out, md, 'utf8');
console.log('已生成:', out);
}
main();
如果你想跳过脚本,直接用社区现成方案,也可以用这个仓库或扩展:peckjon/copilot-chat-to-markdown
与 Chat To Markdown
扩展,流程与上面脚本一致。(GitHub)
一些看似诡异但常见的边角案例
- 在 Windows 或 macOS 上,某些富文本目标应用会优先读取剪贴板里的
HTML
或RTF
,而不是text/plain
。如果你从编辑器复制,且开启了Copy With Syntax Highlighting
,它们会粘出彩色代码;而从 Chat 右键Copy
出来的Markdown
只有text/plain
,因此看起来变朴素。这种差异是预期行为,不是内容丢了。(Reddit) - 有用户反馈
导出为 Markdown 或 PDF
的能力不足,这并不代表右键 Copy
不工作,而是一键导出整段对话为高质量文档
这个场景目前还不完善。你可以用Copy All
搭配Markdown
编辑器,或走JSON -> Markdown
。(GitHub) - 个别版本或 Insider 版在某些时间点出现过
Export Chat...
命令缺失或行为变化,这时候脚本路线就显得更稳。(GitHub)
结论与一条建议清单
当你说 只能以纯文本拷贝
,大多数情况下只是使用路径的问题:
- 需要
Markdown
源文本时,对着消息右键 -> Copy
,不要依赖Ctrl+C
。(Visual Studio Code) - 想要整段会话,试
右键 -> Copy All
,或Chat: Export Chat...
然后本地转Markdown
。(Visual Studio Code) - 如果目标应用不识别
Markdown
,显示效果会像纯文本,这不等于你没复制到Markdown
。 - 若你追求富文本粘贴效果,那是
HTML/RTF
路线,与Markdown
是两种需求;编辑器里的Copy With Syntax Highlighting
只对编辑器文本生效,不直接影响 Chat。(Reddit) - 需要可复现流程,随手保留上面的
Node.js
转换脚本,或使用社区扩展与仓库。(GitHub)
把这些点串起来,你就能在 VS Code 的 Copilot Chat、不同版本与不同目标应用之间,稳定地得到想要的 Markdown
。当官方未来补全 一键导出为 Markdown 或 PDF
的能力时,链路还会更顺滑;在那之前,右键 Copy
与 JSON -> Markdown
的两套方法足够覆盖绝大多数工作流。
更多推荐
所有评论(0)