你遇到的现象是:在 Visual Studio Code 里和 Copilot Chat 对话,复制出去只能得到纯文本,没有 Markdown 的代码块、列表、标题等结构。这个问题表面像是 复制 操作不保留格式,往里看却牵涉到 VS Code 的 Webview 剪贴板实现、Copy 菜单与快捷键的差异、目标应用如何接收内容、以及 Copilot 提供的导出能力与其缺口。下面用一条顺畅的推理链,把原因与对策掰开揉碎。


现象并不单一:右键 CopyCtrl+C 背后的差别

在 Copilot Chat 的单条消息卡片上,官方文档明确写过:右键那条消息选择 Copy,会把内容以 Markdown 形式复制到剪贴板;在侧栏空白处 Copy All 可以复制整段对话。这不是玄学,是产品设计出的有意区分。文档的原文要点是:右键单条消息选 Copy 可得到 MarkdownExport Chat... 则导出为 JSON 文件,右键 Copy All 可复制当前会话的所有内容。(Visual Studio Code)

一些用户实测也在讨论区反馈:用右键的 Copy 能拿到带 Markdown 的文本,而不是浏览器里渲染后的纯文字。(GitHub)

如果改用键盘 Ctrl+C,行为就不同了。对于 VS Code 的 WebviewCtrl+C 更像对选中的可视化区域取 innerText,于是复制的是渲染后文字,没有原始 Markdown 语法。这就解释了你看到的 只能以纯文本格式拷贝:方式不对,拿到的是渲染文本,而不是源 Markdown

一句话总结这个分歧:
右键 Copy 走的是 Copilot Chat 自己的复制通道,把源 Markdown 放进剪贴板;Ctrl+C 走的是通用剪贴板路径,多半只拿到可见文本。


另一个常见误会:Markdown 本就是纯文本,问题在于 有没有保留 Markdown 语法标记

有时大家说 复制成 Markdown,潜台词是希望保留 ### 标题- 列表、````` 代码块 ``` 等标记。可 Markdown 本质上是纯文本,不是富文本。真正影响体验的是:你复制出去得到的到底是 带 Markdown 标记的纯文本,还是 渲染后去掉标记的纯文本。前者可以在大多数编辑器里保留结构,后者就只剩文字了。

所以当你说 只能以纯文本拷贝,如果右键 Copy 依然不行,就可能是下面的情况之一:

  1. 目标应用对 Markdown 没有语法高亮或即时渲染,于是看起来像没格式。
  2. 你用的是 Ctrl+C,不是右键 Copy
  3. 当前 VS Code 或 Copilot 版本里,右键 Copy 被某些状态干扰失效。
  4. 你想要的其实是 富文本 粘贴,例如带 RTFHTML 的粗体、斜体高亮,那和 Markdown 是两码事,需要不同工具链。

VS Code 设置项与剪贴板多格式:为啥编辑器里能彩色,粘贴出去不一定带格式

VS Code 的编辑器在 设置 里有一项 Text Editor › Copy With Syntax Highlighting,控制复制时是否把语法高亮以 HTMLRTF 一类格式也放到剪贴板。社区里常见的建议是关闭或开启这个开关来得到更接近预期的复制效果。(Reddit)

不过这项设置主要影响 编辑器 文本,不直接管 Webview 里的 Copilot Chat。Chat 属于 Webview,它的复制逻辑是另一个通道,所以你会看到右键 Copy 能拿 Markdown,而 Ctrl+C 拿不到。这个差异也解释了为什么同样的 VS Code,在常规编辑器里复制能带 HTML 高亮,到 Chat 面板就没了。


官方导出能力与缺口:为什么很多人还在提需求

很多开发者需要把整段对话存档成 MarkdownPDF 或者便于笔记系统使用的格式。官方现在提供的路径是把会话导出为 JSON,再由你自己转成文档。社区里有不少 导出为 Markdown 或 PDF 的需求帖子,说明这块仍然是大家痛点。(GitHub)

也因此,社区出现了脚本与扩展,专做 Copilot Chat JSON -> Markdown 的转换,比如 copilot-chat-to-markdown 仓库与 Chat To Markdown 扩展。它们的思路都是:先用命令面板 Chat: Export Chat... 导出 JSON,再本地转 Markdown。(GitHub)


一套可落地的排查与操作顺序

为了尽量复现实用语境,用下面这套顺序来定位问题并拿到你想要的 Markdown

  1. 在 Copilot Chat 里,对着你要的那条回答 右键 -> Copy,粘贴到 Markdown 编辑器里确认是否保留了诸如 ###-、````` 等标记。官方文档明确这是 复制为 Markdown 的通道。(Visual Studio Code)

  2. 如果想复制整段会话,试下在 Chat 视图空白处 右键 -> Copy All,或者用命令面板 Chat: Export Chat... 导出 JSON。(Visual Studio Code)

  3. 如果你的粘贴目标是 Word飞书文档Notion,请留意这些目标对 Markdown 的支持方式不同:

    • 能理解 Markdown 的,会直接渲染或提供 Paste and Match StylePaste Markdown 一类入口;
    • 不能理解的,会显示成纯文本,这不代表你复制错了,而是目标应用不支持 Markdown 渲染。
  4. 若你确实需要 富文本 样式而非 Markdown,那是另一条路线:要么使用能把 MarkdownHTML 并以 HTML 形式入剪贴板的扩展,要么贴到支持 Markdown 的文档系统里再导出。VS Code 编辑器侧的 Copy With Syntax Highlighting 只对编辑器生效,Chat 面板并不吃这套。(Reddit)

  5. 如果你遇到 右键 Copy 也只是纯文本,结合版本或平台差异,可能暂时命中已知缺口或回归问题。开发者社区里确实有用户反映过 无法复制整个内容为 Markdown 或导出能力不足的问题,可以临时走 导出 JSON -> 本地转 Markdown 的方案。(Visual Studio Developer Community)


提供一套可运行的 Node.js 转换脚本:把 Copilot Chat 的 JSON 导出转成 Markdown

很多人嫌装扩展麻烦,更偏爱一份 脚本在手、随处可跑。下面这段 Node.js 脚本支持把 Chat: Export Chat... 得到的 JSON 文件转成 Markdown。脚本做了容错,兼容不同字段命名,遇到代码片段会用三引号围好,生成一个结构清晰的 Markdown 文件,方便直接存档或提交仓库。

使用步骤

  1. 在 VS Code 命令面板执行 Chat: Export Chat...,保存为 chat.json。(GitHub)
  2. 把下面脚本保存为 copilot-json-to-md.js
  3. Node.js 18+ 环境运行:node copilot-json-to-md.js chat.json chat.md
  4. 打开生成的 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-markdownChat To Markdown 扩展,流程与上面脚本一致。(GitHub)


一些看似诡异但常见的边角案例

  • 在 Windows 或 macOS 上,某些富文本目标应用会优先读取剪贴板里的 HTMLRTF,而不是 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 的能力时,链路还会更顺滑;在那之前,右键 CopyJSON -> Markdown 的两套方法足够覆盖绝大多数工作流。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐