The Zed Debugger Is Here

2025-06-192:42473188zed.dev

From the Zed Blog: Over 2,000 developers asked, and we delivered. Debugging in Zed is now a reality—and it's a big leap toward Zed 1.0.

Over 2,000 developers asked, and we delivered.

Debugging in Zed is now a reality—and it's a big leap toward Zed 1.0.

Overview

We set out to build a debugger with three primary focuses:

  • Fast: Spend less time context switching and more time debugging
  • Familiar: In line with Zed's design language and supports everything expected from a typical debugger flow
  • Configurable: You're able to customize the UI, keybindings, debug configurations and more

Out of the box, Zed supports debugging popular languages including Rust, C/C++, JavaScript, Go, and Python. With our extension system, Zed can support any debug adapter that implements the Debug Adapter Protocol (DAP).

To simplify the setup process, we've introduced locators, a system that translates build configurations into debug configurations. Meaning that you can write a build task once in tasks.json and reference it from debug.json — or, even better, rely on Zed's automatic configuration.

Zed automatically runs locators on built-in or language server-generated runnables, so in many cases you won't even need to write a debug configuration to get up and running.

We currently support locators for Cargo, Python, JavaScript, and Go, with more coming in the future. For more information on configuring a debug session, see our documentation.

Once in a debug session, Zed makes it easy to inspect your program's state, such as threads, variables, breakpoints, the call stack, and more.

Setting some breakpoints and running the test in a debug session.

The debugger panel is fully customizable too, just drag and rearrange tabs in whatever order you want; you can even move the debug panel around so it fits your workflow.

Zed also supports keyboard-driven debugging for users that prefer to keep their hands on the keyboard. You can step through code, toggle breakpoints, and navigate a debug session without ever touching the mouse.

Navigating through the Debugger surfaces using only the keyboard.

A Special Thanks

The debugger started as a community-led project with some impressive stats: 8 months of development, 977 commits, and 25k+ lines of code. The community built the core foundation that made today’s launch possible.

Special thanks to Remco Smits for driving a lot of the heavy lifting on this project—your contributions have been critical to getting us here.

Under the Hood

Zed's debugger supports debugging a variety of languages through the Debug Adapter Protocol. But simply implementing the protocol wasn't enough—we needed an architecture that could scale to collaborative debugging, support extensions, and efficiently cache and manage responses from debug adapters.

To achieve this, we built a two-layer architecture: a data layer that communicates directly with the debug adapters, and a UI layer that fetches data from the data layer to render the interface.

/// All functions are cheap to call, as they grab current state of the debug session and schedule refreshing on a background
/// thread if that state is outdated.
pub fn modules(&mut self, cx: &mut Context<Self>) -> &[Module] {
 /// Kick off a fresh request to a DAP for modules list if we don't have an up-to-date state.
 /// This is a no-op in case we've ran that request already. In case we did not, it kicks off a background task.
 self.fetch(
 /// We cache request results based on it's arguments. `Modules` request does not take any arguments
 dap_command::ModulesCommand,
 /// Callback invoked with the result of a request.
 |this, result, cx| {
 let Some(result) = result.log_err() else {
 return;
 };
 
 this.modules = result;
 cx.emit(SessionEvent::Modules);
 cx.notify();
 },
 cx,
 );
 
 /// Returns a current list of modules; it might be outdated at the time the new request is underway,
 /// but once it is done, the return value of this function will reflect that.
 &self.modules
}
/// This function is called from the Module list render function in the UI layer whenever the data layer invalidates the module list state.
fn schedule_rebuild(&mut self, cx: &mut Context<Self>) {
 /// Setting the task drops any current work in progress that is out of date
 self._rebuild_task = Some(cx.spawn(async move |this, cx| {
 this.update(cx, |this, cx| {
 /// The UI layer queries the data layer for modules and clones the data
 let modules = this
 .session
 .update(cx, |session, cx| session.modules(cx).to_owned());
 this.entries = modules;
 cx.notify();
 })
 .ok();
 }));
}

This separation means the UI layer only requests what it needs, allowing the data layer to lazily fetch information and avoid unnecessary requests. It also makes the data layer solely responsible for maintaining session state, caching responses, and invalidating stale data. This architecture will make implementing collaborative debugging significantly easier, since the same UI code can be reused across multiplayer sessions—and we only send essential data across the wire, preserving bandwidth.

Supporting every debug adapter out of the box wasn't feasible—there are over 70 DAP implementations, each with its own quirks. To solve this, we extended Zed's extension API to support debugger integration.

 /// Returns the debug adapter binary for the specified adapter name and configuration.
 fn get_dap_binary(
 &mut self,
 _adapter_name: String,
 _config: DebugTaskDefinition,
 _user_provided_debug_adapter_path: Option<String>,
 _worktree: &Worktree,
 ) -> Result<DebugAdapterBinary, String> {
 Err("`get_dap_binary` not implemented".to_string())
 }
 
 /// Determines whether the specified adapter configuration should *launch* a new debuggee process
 /// or *attach* to an existing one. This function should not perform any further validation (outside of determining the kind of a request).
 /// This function should return an error when the kind cannot be determined (rather than fall back to a known default).
 fn dap_request_kind(
 &mut self,
 _adapter_name: String,
 _config: serde_json::Value,
 ) -> Result<StartDebuggingRequestArgumentsRequest, String> {
 Err("`dap_request_kind` not implemented".to_string())
 }
 /// Converts a high-level definition of a debug scenario (originating in a new session UI) to a "low-level" configuration suitable for a particular adapter.
 ///
 /// In layman's terms: given a program, list of arguments, current working directory and environment variables,
 /// create a configuration that can be used to start a debug session.
 fn dap_config_to_scenario(&mut self, _config: DebugConfig) -> Result<DebugScenario, String> {
 Err("`dap_config_to_scenario` not implemented".to_string())
 }
 
 /// Locators are entities that convert a Zed task into a debug scenario.
 ///
 /// They can be provided even by extensions that don't provide a debug adapter.
 /// For all tasks applicable to a given buffer, Zed will query all locators to find one that can turn the task into a debug scenario.
 /// A converted debug scenario can include a build task (it shouldn't contain any configuration in such case); a build task result will later
 /// be resolved with [`Extension::run_dap_locator`].
 ///
 /// To work through a real-world example, take a `cargo run` task and a hypothetical `cargo` locator:
 /// 1. We may need to modify the task; in this case, it is problematic that `cargo run` spawns a binary. We should turn `cargo run` into a debug scenario with
 /// `cargo build` task. This is the decision we make at `dap_locator_create_scenario` scope.
 /// 2. Then, after the build task finishes, we will run `run_dap_locator` of the locator that produced the build task to find the program to be debugged. This function
 /// should give us a debugger-agnostic configuration for launching a debug target (that we end up resolving with [`Extension::dap_config_to_scenario`]). It's almost as if the user
 /// found the artifact path by themselves.
 ///
 /// Note that you're not obliged to use build tasks with locators. Specifically, it is sufficient to provide a debug configuration directly in the return value of
 /// `dap_locator_create_scenario` if you're able to do that. Make sure to not fill out `build` field in that case, as that will prevent Zed from running second phase of resolution in such case.
 /// This might be of particular relevance to interpreted languages.
 fn dap_locator_create_scenario(
 &mut self,
 _locator_name: String,
 _build_task: TaskTemplate,
 _resolved_label: String,
 _debug_adapter_name: String,
 ) -> Option<DebugScenario> {
 None
 }
 
 /// Runs the second phase of locator resolution.
 /// See [`Extension::dap_locator_create_scenario`] for a hefty comment on locators.
 fn run_dap_locator(
 &mut self,
 _locator_name: String,
 _build_task: TaskTemplate,
 ) -> Result<DebugRequest, String> {
 Err("`run_dap_locator` not implemented".to_string())
 }

Adding DAP support via an extension involves defining a custom schema that integrates with our JSON server, implementing logic for downloading and launching the adapter, processing debug configuration to add sane default values, and integrating with locators for automatic configuration. This design follows our approach to LSP extensions, giving extension authors full control to bring their own debug adapters to Zed with minimal friction.

We also wanted inline variable values to work out of the box. Surprisingly, the inline values request is a part of the Language Server Protocol (LSP) instead of the DAP. Using the inline values approach would limit Zed to only showing inline values for DAPs which integrate with LSPs, which isn't many. A naive workaround might be to use regular expressions to match variable names between the source code and debugger values, but that quickly breaks down when dealing with scopes, and comments. Instead, we turned to Tree-sitter. After all Zed is built by the creators of Tree-sitter!

An inline value example.
An inline value example.

Through Tree-sitter queries, we can accurately identify variables within the current execution scope, and easily support any language through .scm files without relying on an LSP server to be tightly integrated with a debug adapter. At launch, inline values are supported for Python, Rust, and Go. More languages will be supported in the coming weeks.

What's Next

When we set out to build the debugger, we wanted to make it seamless to use, out of the way, and in line with Zed's high standard of quality. Now that we've built a strong foundation that is compatible with any debug adapter, we're ready to explore and implement advanced features such as:

  • New views: While we support all the fundamental views, we're planning on adding more advanced views such as a watch list, memory view, disassembly view, and a stack trace view
  • Automatic configuration: We're going to add support for more languages and build systems
  • Polish and more: reach out to us on Discord or on Zed's GitHub repo to let us know!


Read the original article

Comments

  • By laserbeam 2025-06-198:077 reply

    I'm very happy to see work on the debugger. This is the main feature preventing me from switching full time to zed.

    Unfortunately, "here" is not accurate. Not having a watch window, a stack trace view, and no mention of data breakpoints in the announcement still keeps the "beta" tag. I know those features will arrive eventually, but what is described is definitely not sufficient for 97% of my debugging sessions.

    I would also have liked to see more in the announcement of multiple simultaneous debug sessions, and on how multithreaded debugging is planned. There are really cool things that can be done with multithreaded debugging further down the line that I'd be interesting in hearing about (like how RemedyBG has a DAW-like UI for freezing certain threads, or hitting one button to "solo" a thread and freeze all others).

    • By anthony-eid 2025-06-1917:18

      Hey Laserbeam, I'm one of the devs that made the debugger and the one that wrote the article.

      We do have a basic stack trace view that basically all debuggers support. What's coming out in the near future is a stack trace view in our multi buffer system. In fact, you can use the "show stack trace" action while in an active debug session to expand the call stack in a multi buffer, where each excerpt is a frame. It's just not up to the quality that I and several others expect from Zed, so I didn't advertise it.

      There's also a PR for a watching variables/expression that is going to be merged in a couple of days. The functionality is done, but we didn't want to add a feature so close to launch that wasn't fully tested.

      Support for data breakpoints will come in the near future. I can't say when because we're planning on focusing on automatic configuration for a while, but it is a priority.

      We do support simultaneously debugging multiple sessions at the same time and multithreaded debugging too. There's still more to do with it, but the support is definitely there!

    • By happy-dude 2025-06-1910:54

      The blog post mentions[1] that advanced views are in development. This initial release and announcement focuses on the underlying foundation they're building upon.

      > New views: While we support all the fundamental views, we're planning on adding more advanced views such as a watch list, memory view, disassembly view, and a stack trace view

      [1] https://zed.dev/blog/debugger#whats-next

    • By odie5533 2025-06-1910:31

      100% of my debug sessions are with plain breakpoints and stepping. So it's here for me!

    • By keyle 2025-06-1910:081 reply

      I agree but at the rate the Zed team is working at, we're not far off!

      • By laserbeam 2025-06-1910:55

        Oh yeah, of course :). My argument is they're just premature in declaring readiness.

    • By aequitas 2025-06-198:541 reply

      I have to try out the debugger yet. However I share your sentiment but for the Git feature. The basics are there but it is just not complete yet to fully replace my current git workflow. Hope they keep focus on that as well.

      • By koito17 2025-06-1910:191 reply

        Nothing has been able to replace Magit for me, yet. Having a Zed UI for Git like Magit is my dream feature request.

        With that said, Zed has effectively replaced all of Emacs for me, besides Magit. Additionally, typing in other editors feels noticeably higher latency than Zed :)

        I've been daily driving Zed for almost a year now -- works best on TypeScript, Rust, and Go projects, in my opinion.

        There's just so much functionality Zed has to build out to compete with modern editors (agentic coding, collaboration, debugging, edit prediction, task runners, version control). With that said, for pair-programming sessions with friends, Zed has been perfect since Linux gained screenshare support. However, there's a noticeable "pause in development" for collaboration in order to implement major features like Agentic Coding, and smaller-but-essential features like direnv integration, IME support (typing Japanese in the terminal used to be a clunky, error-prone task), dealing with the endless permutations of Python tooling so that Python files aren't a sea of red lines, etc.

        • By no_wizard 2025-06-1910:472 reply

          Zed reminds of the days when Atom was big.

          It was a good time, but it always left me wondering how long it would last as it leaned heavily on community support for nearly everything useful outside a few packages

          Such a situation makes me worry about it keeping up if popularity wanes. With JetBrains for example at least I know that paying for the editor it will keep getting proper updates and support even if it isn’t the most popular (though it is quite popular currently)

          • By hombre_fatal 2025-06-1912:27

            Leaning on community support seems ideal because it means you've built a powerful plugin API and people can implement features.

            As opposed to having a weak plugin API where all progress depends on the tiny internal team.

            The latter suffers more than the first if popularity wanes.

            In Atom's case, its lunch was eaten by VSCode which was similar but just better. Based on web tech but with better performance and just as powerful of a plugin API. It wasn't the fact that they let people implement plugins that killed Atom, and they would have been in an even worse situation had they not had a good plugin API.

          • By drcongo 2025-06-1911:021 reply

            You can pay for Zed too. I am.

            • By no_wizard 2025-06-1911:581 reply

              Paying for zed isn’t the same as paying for extensions to be well maintained. They’re not all in house

              • By drcongo 2025-06-1912:11

                Ahh, I see what you mean now and yeah, I agree.

    • By nixpulvis 2025-06-1916:591 reply

      Is there a tracking issue for watching variables and data breakpoints? I'd like to see that as well.

  • By candrewlee 2025-06-195:392 reply

    Zed is fantastic. I've been making the leap from neovim to zed lately, and it's been an great experience. Everything feels snappy, and I love how well they've integrated Vim bindings. Their agent mode is nice as well. It's clearly an underdog to VSCode, so the extension ecosystem isn't quite there yet... but for a lot of the things I've used it for, it's sufficient. The debugger has been the big missing feature for me and I'm really glad they've built it out now - awesome work.

    • By timeinput 2025-06-1921:091 reply

      I'm curious about how vimmy the vim bindings are?

      Every time I've encountered a vim emulator I've found it is just close enough that my fingers are doing the wrong things so often it's frustrating. Almost to the point where I would prefer a non-vimmy editor since at least then my fingers always do the wrong thing.

      • By esamatti 2025-06-1921:43

        To me it has been the best "vim" that is not a real Vim. Way way better than the vscode plugin. I have used Vim and later Neovim since 2008 or so. Zed is the first non-vim I am truly happy with.

    • By echelon 2025-06-196:143 reply

      How is Zed with auto-completing Rust code?

      I love how fast Windsurf and Cursor are with the "tab-tab-tab" code auto-completion, where nearly everything suggested is spot-on and the suggestions keep on rolling, almost automating the entire task of refactoring for you. This form of autocomplete works really well with TypeScript and other scripting languages.

      IntelliJ / RustRover never got anywhere close to that level of behavior you can get in Cursor and Windsurf, neither in conjunction with JetBrains own models or with Co-pilot. I chalked it up as an IDE / model / language mismatch thing. That Rust just wasn't amenable to this.

      A few questions:

      1) Are we to that magical tab-tab-tab and everything autocompletes fluently with Rust yet? (And does this work in Zed?)

      2) How does Zed compare to Cursor and Windsurf? How does it compare to RustRover, and in particular, JetBrains' command of the Rust AST?

      • By WD-42 2025-06-196:36

        Zed is written in Rust by a bunch of Rust lovers so it's really got first class support for it.

      • By csomar 2025-06-199:551 reply

        > How is Zed with auto-completing Rust code?

        I think they all use LSP, so whether you use neovim or Zed there shouldn't be a difference? (not 100% sure, but that's my basic understanding of LSP).

        • By echelon 2025-06-1910:201 reply

          The LSP support for Rust has trailed JetBrains own Rust plugin, which has long since morphed into the language-specific IDE, RustRover.

          RustRover has the best AST suggestions and refactoring support out there. It works in gigantic workspaces, across build scripts, proc macros, and dynamic dispatch.

          The problem with RustRover has been the lackluster AI support. I've been finding AI autocomplete generally much more useful than AST understanding, though having both would be killer.

          • By no_wizard 2025-06-1910:50

            I know they’re actively working on this, they released a few updates to the AI extension to make it modular now, so you can pick your own model for example. Soon it will let you wire up your own agents, but if I recall correctly the reason it’s a bit slower there is lack of uniform interfaces

      • By lionkor 2025-06-196:19

        It's fantastic for Rust, it's my main IDE which I've written e.g. voltlane.net in. Fantastic software, and the LLM integration is everything you need IMO (in a good way).

  • By ramon156 2025-06-1914:484 reply

    Zed feels like what Lapce, Helix and Neovim couldn't achieve in the time they spent.

    I started using Helix back around 2021-2022 and just couldn't get over the bugs and lack of integration. It was good, but PHP support (I was working at an older company) was bad.

    Neovim felt closest to a nice editor but there were some popular community-driven plugins that were very stubborn, and alternatives were just very slow. I was also just overwhelmed by the choices I needed to make for something stable.

    Lapce just felt like a VSCode clone that didn't do anything special. Looked cool, but I did not feel like it was ready for a daily driver (And it still doesn't).

    Zed became a favorite in a short amount of time, and I'm extremely grateful for it every day. The debugger is a nice addition.

    • By rbits 2025-06-201:07

      This might be off-topic, but I really want to use Helix. I've been using Vim keybindings for a few years now but it's so unintuitive, there's still so many things I can't do efficiently in Vim. Helix just makes so much more sense for my brain. But I don't use Vim/Neovim by itself, I always use an integration with an editor like VSCode or Obsidian (Obsidian's Vim emulation isn't great, but it's good enough). Helix just isn't there yet with VSCode or Obsidian.

      I wish more "Vim successors" would focus more on integrating with existing IDEs, rather than becoming one themselves. I don't want to have to set up an entirely new workflow when I change how I edit text.

      That's also why I haven't tried using Neovim as a standalone IDE. It looks like I'd really like it, but I don't want to be locked in to using Vim.

    • By user3939382 2025-06-1917:151 reply

      > PHP support (I was working at an older company)

      Not sure why PHP needs a qualifier like this.

    • By Bolwin 2025-06-1918:21

      [dead]

    • By hu3 2025-06-1919:091 reply

      > ...like a VSCode clone that didn't do anything special.

      Interesting way to qualify the most popular editor of human history.

      • By quietbritishjim 2025-06-1919:311 reply

        They said that Lapce didn't do anything special (over VSCode). Not that VSCode is nothing special, as you seem to have interpreted it.

        • By hu3 2025-06-1920:02

          Fair enough. I understand what you meant now.

HackerNews