编辑器与开发协议学习笔记
100 天认知提升计划 | Day 1
目录
第一部分:LSP 语言服务器协议
LSP 概述
什么是 LSP
LSP (Language Server Protocol) 是微软开发的通信协议,现已成为业界标准。它解决了编辑器对编程语言支持的重复开发问题。
核心价值:从 N×M 到 N+M
| 传统模式 | LSP 模式 |
|---|---|
| N 个编辑器 × M 种语言 = N×M 个实现 | N 个编辑器 + M 个语言服务器 = N+M 个实现 |
提供的能力
| 功能 | 说明 |
|---|---|
| 代码补全 | Autocomplete |
| 跳转到定义 | Go to Definition |
| 查找引用 | Find References |
| 悬停提示 | Hover Information |
| 代码诊断 | Diagnostics |
| 重命名重构 | Rename Symbol |
| 代码格式化 | Formatting |
常见语言服务器
| 语言 | 服务器 |
|---|---|
| TypeScript | typescript-language-server |
| Go | gopls |
| Rust | rust-analyzer |
| Python | pylsp、pyright |
通信原理
架构图
┌─────────────┐ LSP (JSON-RPC) ┌─────────────────┐
│ 编辑器 │ ◄────────────────────► │ 语言服务器 │
│ (VS Code等) │ 请求/响应/通知 │ (tsserver/gopls)│
│ │ │ │
│ • UI │ │ • 代码分析 │
│ • 文本编辑 │ │ • 语法解析 │
│ • 渲染 │ │ • 类型推断 │
└─────────────┘ └─────────────────┘解耦设计
- 编辑器(客户端):只负责 UI 和文本编辑
- 语言服务器(服务端):负责代码分析、智能提示等
- 通信协议:JSON-RPC 2.0
JSON-RPC 消息格式
LSP/DAP 基于 JSON-RPC 2.0,有三种消息类型:
1. Request / Response(请求-响应)
请求示例:编辑器请求补全
json
{
"jsonrpc": "2.0",
"id": 1,
"method": "textDocument/completion",
"params": {
"textDocument": { "uri": "file:///path/to/file.ts" },
"position": { "line": 5, "character": 10 }
}
}响应示例:服务器返回补全列表
json
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"items": [
{ "label": "console", "kind": 3, "detail": "var console: Console" },
{ "label": "const", "kind": 14, "detail": "keyword" }
]
}
}2. Notification(通知,单向)
编辑器通知服务器文件已打开
json
{
"jsonrpc": "2.0",
"method": "textDocument/didOpen",
"params": {
"textDocument": {
"uri": "file:///path/to/file.ts",
"languageId": "typescript",
"version": 1,
"text": "const x = 1;"
}
}
}服务器通知编辑器有错误
json
{
"jsonrpc": "2.0",
"method": "textDocument/publishDiagnostics",
"params": {
"uri": "file:///path/to/file.ts",
"diagnostics": [
{
"range": { "start": {"line": 0, "character": 8}, "end": {"line": 0, "character": 9} },
"severity": 1,
"message": "Cannot find name 'x'"
}
]
}
}消息类型对比
| 类型 | 字段特征 | 是否需要响应 | 典型场景 |
|---|---|---|---|
| Request | 有 id | ✅ 是 | 获取补全、跳转定义 |
| Response | 有 id、result | - | 返回请求结果 |
| Notification | 无 id | ❌ 否 | 文件变更、错误通知 |
开发 LSP 服务器
技术选型
| 语言 | 推荐框架 |
|---|---|
| TypeScript | vscode-languageserver、langium |
| Python | pygls |
| Go | gopls/langserver |
| Rust | tower-lsp |
最小实现示例(Python + pygls)
python
from pygls.server import LanguageServer
from pygls.lsp import CompletionItem, CompletionItemKind
# 创建服务器实例
server = LanguageServer()
# 注册补全处理器
@server.feature("textDocument/completion")
def completions(params):
return [
CompletionItem(label="hello", kind=CompletionItemKind.Text),
CompletionItem(label="world", kind=CompletionItemKind.Text),
]
# 启动服务器
server.start_io()开发流程
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 定义语言能力 │ ──► │ 实现处理器 │ ──► │ 测试验证 │ ──► │ 打包发布 │
│ (capabilities)│ │ (handlers) │ │ (VS Code等) │ │ (npm/pip) │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘必须实现的基础能力
| 方法 | 说明 | 优先级 |
|---|---|---|
initialize | 返回服务器能力 | 🔴 必须 |
initialized | 客户端初始化完成通知 | 🔴 必须 |
shutdown | 关闭请求 | 🔴 必须 |
textDocument/didOpen | 文档打开 | 🟡 高 |
textDocument/didChange | 文档变更 | 🟡 高 |
textDocument/completion | 代码补全 | 🟡 高 |
第二部分:DAP 调试适配器协议
DAP 概述
什么是 DAP
DAP (Debug Adapter Protocol) 与 LSP 师出同门,解决编辑器对调试器支持的重复开发问题。
常见 Debug Adapter
| 语言/环境 | Debug Adapter |
|---|---|
| Go | delve |
| Python | debugpy |
| Node.js | node-debug2 |
| C++ | cpptools (gdb/lldb) |
| Rust | lldb-dap / codelldb |
与 LSP 的关系
类比关系
| LSP | DAP |
|---|---|
| 代码编辑支持 | 调试支持 |
| Language Server | Debug Adapter |
| 补全、跳转、诊断 | 断点、单步、变量查看 |
DAP 工作原理
┌─────────────┐ DAP (JSON-RPC) ┌─────────────────┐
│ 编辑器 │ ◄────────────────────► │ Debug Adapter │
│ (VS Code等) │ 请求/响应/事件 │ (gdb/lldb/dlv) │
│ │ │ │
│ • 断点 UI │ │ • 控制执行 │
│ • 变量面板 │ │ • 读取内存 │
│ • 调用栈 │ │ • 单步执行 │
└─────────────┘ └────────┬────────┘
│
▼
┌─────────────────┐
│ 被调试程序 │
│ (目标进程) │
└─────────────────┘调试流程
核心功能
| 功能 | 说明 |
|---|---|
| 断点管理 | 设置/删除断点,支持条件断点 |
| 步进控制 | Step Over、Step Into、Step Out |
| 堆栈追踪 | 查看调用栈,在栈帧间切换 |
| 变量查看 | 局部变量、监视表达式、求值 |
| 异常处理 | 捕获异常时暂停 |
调试流程图
启动阶段
┌─────────────────────────────────────────────────┐
│ launch/attach → initialize → setBreakpoints │
│ ↓ │
│ configurationDone │
└─────────────────────────────────────────────────┘
│
▼
调试循环
┌─────────────────────────────────────────────────┐
│ continue → stopped事件 → 查看状态 → step/continue │
│ ↑ ↓ │
│ └──────────────── 循环 ←──────────────┘ │
└─────────────────────────────────────────────────┘远程调试
架构示意
本地开发环境 远程服务器
│ │
│ DAP over TCP/SSH │
├─────────────────────────────►│
│ ┌───┴───┐
│ │ DAP │
│ │ Server │
│ └───┬───┘
│ ┌───┴────┐
│ │ Target │
│ │ 程序 │
│ └────────┘两种模式
| 模式 | 说明 | 使用场景 |
|---|---|---|
| attach | 连接到已运行的远程进程 | 程序已在服务器运行 |
| remote launch | 启动远程程序并调试 | 从本地启动远程程序 |
VS Code 配置示例
json
{
"type": "python",
"request": "attach",
"name": "Remote Attach",
"connect": { "host": "remote-server.com", "port": 5678 },
"pathMappings": [
{ "localRoot": "${workspaceFolder}", "remoteRoot": "/app" }
]
}关键配置项
| 配置 | 作用 |
|---|---|
connect.host | 远程服务器地址 |
connect.port | DAP Server 监听端口 |
pathMappings | 本地路径 ↔ 远程路径映射 |
reverseListen | 反向连接(远程主动连接本地) |
远程调试三步走
Step 1: 远程启动 DAP Server
└─▪ debugpy --listen 0.0.0.0:5678 --wait-for-client
Step 2: 本地 SSH 隧道转发(可选)
└─▪ ssh -L 5678:localhost:5678 user@remote
Step 3: 编辑器连接
└─▪ attach 到 localhost:5678第三部分:实践与思考
实践记录
LSP 配置
- [ ] 安装 Neovim 0.10+
- [ ] 配置主要语言的 LSP(Go/Python/TypeScript)
- [ ] 测试代码补全、跳转定义、悬停提示
DAP 配置
- [ ] 安装 nvim-dap
- [ ] 安装对应语言的 Debug Adapter
- [ ] 测试断点、单步执行、变量查看
疑问与思考
已解答
- ✅ LSP 使用 JSON-RPC 通信,具体的消息格式是怎样的?
- ✅ 如何为一种新语言开发 LSP 服务器?
- ✅ LSP 和 DAP 的关系是什么?
- ✅ DAP 如何处理远程调试场景?
待探索
- ❓ 如何为自定义调试器开发 DAP Adapter?
- ❓ DAP 与 CRIU (Checkpoint/Restore) 结合可以实现进程快照调试吗?
更新日期:2026-02-10