debugger: Fix breakpoint list not showing breakpoints in single files (#63348)

# Objective

- Currently there is a bug where breakpoints in single files (not in
folder/worktree) are not shown in the debug panel breakpoint list. They
visually make the breakpoint list empty
- This is because we try to create a `RelPath` from `breakpoint.path`,
which is an absolute path

## Solution

- Create the fallback directory string in a valid way
- Because breakpoint directories can now be absolute, the first part of
the path isn't always the worktree root, so we can't blindly skip this
in the UI code anymore
- Instead, only prepend the worktree root if there multiple worktrees,
matching behavior of other UI elements. This simplifies the code overall

## Testing

- Manually tested

## Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

## Showcase
Before:
<img width="978" height="645" alt="image"
src="https://github.com/user-attachments/assets/334176fd-3ad3-4bc7-b7bc-bf1568be6d76"
/>

After:
<img width="962" height="755" alt="image"
src="https://github.com/user-attachments/assets/e4259c7f-679b-4ac9-807e-d8787d43d067"
/>



---

Release Notes:

- Fixed breakpoint list not showing breakpoints in single files
This commit is contained in:
Casey Avila authored and GitHub committed 2026-08-28 18:59:38 +00:00
1 parent 9cdbece495
commit 47df871ecc
1 file changed
+19 -28
@@ -1,9 +1,4 @@
use std::{
ops::Range,
path::{Path, PathBuf},
sync::Arc,
time::Duration,
};
use std::{ops::Range, path::Path, sync::Arc, time::Duration};
use dap::{Capabilities, ExceptionBreakpointsFilter, adapters::DebugAdapterName};
use db::kvp::KeyValueStore;
@@ -27,7 +22,7 @@ use ui::{
Divider, DividerColor, FluentBuilder as _, Indicator, IntoElement, ListItem, Render,
ScrollAxes, StatefulInteractiveElement, Tooltip, WithScrollbar, prelude::*,
};
use util::rel_path::RelPath;
use util::paths::PathExt;
use workspace::Workspace;
use zed_actions::{ToggleEnableBreakpoint, UnsetBreakpoint};
@@ -668,7 +663,7 @@ impl Render for BreakpointList {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement {
let breakpoints = self.breakpoint_store.read(cx).all_source_breakpoints(cx);
self.breakpoints.clear();
let path_style = self.worktree_store.read(cx).path_style();
let multiple_worktrees = self.worktree_store.read(cx).visible_worktrees(cx).count() > 1;
let weak = cx.weak_entity();
let breakpoints = breakpoints.into_iter().flat_map(|(path, mut breakpoints)| {
let relative_worktree_path = self
@@ -676,23 +671,26 @@ impl Render for BreakpointList {
.read(cx)
.find_worktree(&path, cx)
.and_then(|(worktree, relative_path)| {
worktree
.read(cx)
.is_visible()
.then(|| worktree.read(cx).root_name().join(&relative_path))
worktree.read(cx).is_visible().then(|| {
if multiple_worktrees {
worktree.read(cx).root_name().join(&relative_path)
} else {
relative_path.to_rel_path_buf()
}
})
});
breakpoints.sort_by_key(|breakpoint| breakpoint.row);
let weak = weak.clone();
breakpoints.into_iter().filter_map(move |breakpoint| {
debug_assert_eq!(&path, &breakpoint.path);
let file_name = breakpoint.path.file_name()?;
let breakpoint_path = RelPath::new(&breakpoint.path, path_style).ok();
let dir = relative_worktree_path
.as_deref()
.or(breakpoint_path.as_deref())?
.map(|rel_path| rel_path.as_std_path())
.unwrap_or(&breakpoint.path.as_ref().compact())
.parent()
.map(|parent| SharedString::from(parent.display(path_style).to_string()));
.map(|parent| SharedString::from(parent.display().to_string()));
let name = file_name
.to_str()
.map(ToOwned::to_owned)
@@ -928,19 +926,12 @@ impl LineBreakpoint {
.size(LabelSize::Small)
.line_height_style(ui::LineHeightStyle::UiLabel),
)
.children(self.dir.as_ref().and_then(|dir| {
let path_without_root = Path::new(dir.as_ref())
.components()
.skip(1)
.collect::<PathBuf>();
path_without_root.components().next()?;
Some(
Label::new(path_without_root.to_string_lossy().into_owned())
.color(Color::Muted)
.size(LabelSize::Small)
.line_height_style(ui::LineHeightStyle::UiLabel)
.truncate(),
)
.children(self.dir.as_ref().map(|dir| {
Label::new(dir)
.color(Color::Muted)
.size(LabelSize::Small)
.line_height_style(ui::LineHeightStyle::UiLabel)
.truncate()
}))
.when_some(self.dir.as_ref(), |this, parent_dir| {
this.tooltip(Tooltip::text(format!(