展示HN:Dap-mux – 将您的编辑器和REPL连接到同一个调试会话
我已经编程超过四十年,使用过多种语言,参与过许多项目(如果你能记得那么久远的话,包括 Firefox、Final Cut Pro、Newton 和 Fullwrite Professional;这些都是用我的“旧名”完成的)。<p>我写了一个小而简单的工具来解决一个小问题。这就是 UNIX 的哲学:小型的“单一功能马”,每个工具在其特定功能上都非常出色,然后用户可以将它们组合在一起以解决实际问题。我是一个命令行界面(CLI)爱好者,对于几乎所有事情,我已经有了这样的工具。但在调试方面却没有。我解决的这个问题就是为调试提供这种哲学的连接器。这个工具就是 dap-mux。它是一个 DAP 多路复用器,将一对一的协议转变为一个可以协作的会话,使用尽可能多的工具来完成任务!<p>事情是这样开始的:我使用 Helix 和 Python(有时使用 IPython),而我的团队其他成员则使用 PyCharm(我一直很喜欢这个工具!)。我团队的问题在于他们想使用 PyCharm 的调试器,因此不得不满足于 JetBrains 编辑器。*我的*问题是我可以使用一个完整的调试器 *或者* 使用 IPython *或者* 使用 Helix(有时是 Helix 和调试器的一个不太令人满意的组合)。这就是我的“痒点”。<p>DAP(调试适配器协议)是一个令人期待的答案,但实际上并不是。DAP 是那些不想自己编写调试器的编辑器开始采用的东西。DAP 的问题在于它是一对一的。一个编辑器连接到一个调试器。就这样。这并不是我问题的解决方案。然后突然间,它 *成了* 解决方案。我意识到一个 DAP 多路复用器可以让你将任何支持 DAP 的编辑器连接到任何语言的调试器,并同时连接到一个 REPL、另一个编辑器的会话(或不同的编辑器)!另外一个好处是,现在像 screen 或 tmux 一样,由于每个进程都是独立的:会话是持久的。只需重启崩溃的进程,你就能回到之前的状态!<p>其中有一些难点:序列、后加入者、状态管理。不同的端点在不同的序列中执行不同的操作,但消息 ID 是相同的。我解决这些问题的方式有点像 NAT 的工作原理。不过,我不是在翻译网络地址,而是将每个客户端的序列号转换为某种全局有序的东西,然后正确地将回复路由回等待它们的端点,同时将这些回复的序列号映射回该端点的空间。了解调试器的当前状态,并将其作为消息序列重放给后加入者,让你可以以任何顺序启动/连接客户端。我选择了 Python:asyncio 完美契合 I/O 路由器模式,并且它允许 IPython 扩展在进程内运行,而不是通过进程间通信(IPC)。<p>还有一些问题尚未解决:例如,我认为客户端的配置和/或启动序列太复杂。但它能正常运行!我得到了我想要的!<p>我每天使用的组合:Python + debugpy + Helix + IPython,所有工具同时连接。使用 `%n` 或 `%s` 步进,使用 `%eval` 评估表达式,实时观察 Helix 跟踪当前行。Rust 与 codelldb 是第二个确认的组合——我用 Helix 和一个第三方 DAP 观察工具调试了一个 Dijkstra 实现,这两个工具都连接到同一个 codelldb 会话。社区成员 Sean Perry 已经构建了 [dap-observer](<a href="https://github.com/shaleh/dap-observer" rel="nofollow">https://github.com/shaleh/dap-observer</a>),它将当前帧的变量呈现为可导航的终端树。*这*正是我梦寐以求的!小型、专注、可连接的工具共同协作!<p>还有很多东西可以尝试:其他编辑器、其他调试适配器、Windows、其他语言。这些都还没有触及。现在最有帮助的事情是人们用自己的设置进行测试并报告他们的发现。是时候玩了!<p>`uv tool install 'dap-mux[ipython]'` 用于 Python + IPython。`uv tool install dap-mux` 用于无头使用,支持任何语言和适配器。不需要 Python 生态系统的任何部分。<p><a href="https://github.com/dap-mux/dap-mux" rel="nofollow">https://github.com/dap-mux/dap-mux</a>
查看原文
I have been coding over four decades, in many languages, on many projects (including Firefox, Final Cut Pro, the Newton, and Fullwrite Professional if you can remember that far back; all these using my "dead-name").<p>I wrote something small and simple to scratch an itch. It's the UNIX philosophy: small "one-trick ponies", each *really* good at their one trick, then the user can hook them together to solve actual problems. I'm a CLI guy, and for almost everything, I already have this. But not for debugging. The itch I scratched was the connector that enables this philosophy for debugging. That thing is dap-mux. A DAP multiplexer turning a one-to-one protocol into a cooperating session of as many tools as you need to get it done!<p>How it started: Helix and Python for me (and sometimes IPython), with the rest of my team using PyCharm (which I have long loved!). My team's problem is that they want the PyCharm debugger, and so must be satisfied with the JetBrains editor. *My* problem was I could use a full-blown debugger *or* I could have IPython *or* I could have Helix (or sometimes an unsatisfying combination of Helix and the debugger). That was my "itch".<p>DAP (Debug Adapter Protocol) is the tantalizing answer, except it isn't. DAP is what editors (that don't want to write their own debuggers) are starting to adopt. The problem with DAP is it's one-to-one. One editor connects to one debugger. Done. Not a solution to my problem. And then suddenly, it *was* the solution. I realized that a DAP multiplexer would let you connect any DAP-aware editor to any debugger for any language, and simultaneously to a REPL, another session of your editor (or a different editor)! With the side benefit that now, like screen or tmux, since each process is its own thing: sessions are durable. Just restart whatever crashed and you're back where you were!<p>There were hard parts: sequencing, late joiners, state management. Different end-points working on different actions in different sequences but with the same message ids. I solved these problems something like how NAT works. Instead of translating network addresses, though, I'm translating the sequence numbers of each client into something global and ordered, then correctly routing replies back to the end-point awaiting them, while mapping the sequence numbers for those replies back into the space of that end-point. Knowing the current state of the debugger, and replaying that as a message sequence to late joiners lets you start/connect the clients in any order. I chose Python: asyncio fits the I/O-router pattern perfectly, and it lets the IPython extension run in-process rather than over IPC.<p>There are problems not yet solved: for instance, I think configuration in the clients and/or the startup sequence is too complicated. But it functions! I got what I wanted!<p>The combination I use every day: Python + debugpy + Helix + IPython, all connected simultaneously. Step with `%n` or `%s`, evaluate expressions with `%eval`, watch Helix track the current line in real time. Rust with codelldb is the second confirmed combination — I debugged a Dijkstra implementation with Helix and a third-party DAP observer tool both connected to the same codelldb session. A community member, Sean Perry, has already built [dap-observer](<a href="https://github.com/shaleh/dap-observer" rel="nofollow">https://github.com/shaleh/dap-observer</a>), which renders the current frame's variables as a navigable terminal tree. *This* was my exact dream! Small, focused, connectable tools all playing together!<p>There's so much left to try: other editors, other debug adapters, Windows, other languages. None of this has been touched yet. The most helpful thing now is people testing it with their own setup and reporting what they find. It's time to play!<p>`uv tool install 'dap-mux[ipython]'` for Python + IPython. `uv tool install dap-mux` for headless use with any language and adapter. No need for any part of the Python ecosystem.<p><a href="https://github.com/dap-mux/dap-mux" rel="nofollow">https://github.com/dap-mux/dap-mux</a>