project_panel: Show path tooltip on hover (#63299)

# Objective

The project panel doesn't show a file or folder's actual location on
disk anywhere in the UI — you have to rely on the tab title or context
menu ("Copy Path") to find out where a file actually lives, which is
especially unclear for files with the same name in different folders, or
when a project has multiple worktrees.

## Solution
- Adds a tooltip to each file/folder row in the project panel that shows
the entry's path on hover, with the home directory abbreviated to `~`
(via the existing `PathExt::compact()` utility, consistent with how
paths are already displayed elsewhere, e.g. the Recent Projects list).

## Testing

- Manually tested by hovering over files and folders at various nesting
depths in the project panel and confirming the tooltip shows the correct
path.
- Tested with a project both inside and outside the home directory to
confirm the `~` substitution only applies when appropriate.
- Confirmed existing row behavior (selection, click-to-open,
drag-and-drop, right-click context menu) is unaffected by the added
tooltip.
- Reviewers: hover over any row in the project panel and confirm the
tooltip appears after the normal hover delay and shows the expected
path.
- Tested on macOS only — haven't verified on Linux/Windows.

## Self-Review Checklist:

- [ ] I've reviewed my own diff for quality, security, and reliability
- [ ] Unsafe blocks (if any) have justifying comments
- [ ] 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)
- [ ] Tests cover the new/changed behavior
- [ ] Performance impact has been considered and is acceptable

## Showcase

<img width="1512" height="871" alt="image"
src="https://github.com/user-attachments/assets/ec86f9ab-0a8e-40f7-b783-8a115203f11d"
/>


- Help others understand the result of this PR by showcasing your
awesome work!
- If this PR includes a visual change, consider adding a screenshot,
GIF, or video
- A before/after comparison is very useful for changes to existing
features!

While a showcase should aim to be brief and digestible, you can use a
toggleable section to save space on longer showcases:

<details>
  <summary>Click to view showcase</summary>

My super cool demos here

</details>

---

Release Notes:

- Added a tooltip showing the file path when hovering over files and
folders in the project panel.

---------

Co-authored-by: Christopher Biscardi <chris@christopherbiscardi.com>
This commit is contained in:
Manoj skandChristopher Biscardi authored and GitHub committed 2026-09-04 11:05:53 +00:00
1 parent c1eda3e88a
commit 9c3e2a071f
7 files changed
+166 -5

No files matched your search

+2
View File
@@ -843,6 +843,8 @@
"button": true,
// Whether to hide the gitignore entries in the project panel.
"hide_gitignore": false,
// Delay in milliseconds before tooltips appear for project panel titles.
"title_tooltip_delay": "default",
// Default width of the project panel.
"default_width": 240,
// Where to dock the project panel. Can be 'left' or 'right'.
+31 -3
View File
@@ -52,8 +52,7 @@ use std::{
cell::OnceCell,
cmp,
collections::HashSet,
ops::Neg,
ops::Range,
ops::{Neg, Range},
path::{Path, PathBuf},
sync::Arc,
time::{Duration, Instant},
@@ -68,7 +67,7 @@ use util::{
ResultExt, TakeUntilExt, TryFutureExt,
markdown::MarkdownInlineCode,
maybe,
paths::{PathStyle, compare_paths},
paths::{PathExt, PathStyle, compare_paths},
rel_path::{RelPath, RelPathBuf},
};
use workspace::{
@@ -5875,10 +5874,39 @@ impl ProjectPanel {
(entry_id.to_proto() as usize).into()
};
let tooltip_path = self
.project
.read(cx)
.worktree_for_id(worktree_id, cx)
.map(|worktree| {
worktree
.read(cx)
.absolutize(&path)
.compact()
.to_string_lossy()
.into_owned()
});
div()
.id(id.clone())
.relative()
.group(GROUP_NAME)
.when_some(
tooltip_path.and_then(|path| {
let delay = match settings.title_tooltip_delay {
settings::ProjectPanelTitleTooltipDelay::Default => 1500,
settings::ProjectPanelTitleTooltipDelay::Custom(delay_ms) => delay_ms.0,
settings::ProjectPanelTitleTooltipDelay::Disabled => {
return None;
}
};
Some((path, delay))
}),
|this, (path, delay)| {
this.tooltip_show_delay(Duration::from_millis(delay))
.tooltip(Tooltip::text(path))
},
)
.cursor_pointer()
.rounded_none()
.bg(bg_color)
@@ -4,7 +4,8 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{
DockSide, FolderIndicator, IntoGpui, ProjectPanelEntrySpacing, ProjectPanelSortMode,
ProjectPanelSortOrder, RegisterSetting, Settings, ShowDiagnostics, ShowIndentGuides,
ProjectPanelSortOrder, ProjectPanelTitleTooltipDelay, RegisterSetting, Settings,
ShowDiagnostics, ShowIndentGuides,
};
use ui::scrollbars::{ScrollbarVisibility, ShowScrollbar};
@@ -13,6 +14,7 @@ pub struct ProjectPanelSettings {
pub button: bool,
pub hide_gitignore: bool,
pub default_width: Pixels,
pub title_tooltip_delay: ProjectPanelTitleTooltipDelay,
pub dock: DockSide,
pub entry_spacing: ProjectPanelEntrySpacing,
pub file_icons: bool,
@@ -99,6 +101,7 @@ impl Settings for ProjectPanelSettings {
hide_gitignore: project_panel.hide_gitignore.unwrap(),
default_width: project_panel.default_width.unwrap().into_gpui(),
dock: project_panel.dock.unwrap(),
title_tooltip_delay: project_panel.title_tooltip_delay.unwrap(),
entry_spacing: project_panel.entry_spacing.unwrap(),
file_icons: project_panel.file_icons.unwrap(),
folder_indicator: project_panel.folder_indicator.unwrap(),
+1
View File
@@ -827,6 +827,7 @@ impl VsCodeSettings {
auto_reveal_entries: self.read_bool("explorer.autoReveal"),
bold_folder_labels: None,
button: None,
title_tooltip_delay: None,
default_width: None,
dock: None,
drag_and_drop: None,
+27
View File
@@ -808,6 +808,8 @@ pub struct ProjectPanelSettingsContent {
///
/// Default: right (Agentic layout), left (Classic layout)
pub dock: Option<DockSide>,
// TODO
pub title_tooltip_delay: Option<ProjectPanelTitleTooltipDelay>,
/// Spacing between worktree entries in the project panel.
///
/// Default: comfortable
@@ -893,6 +895,31 @@ pub struct ProjectPanelSettingsContent {
pub git_status_indicator: Option<bool>,
}
/// Controls the width of the git diff hunk indicators in the gutter.
#[derive(
Clone,
Copy,
Debug,
Default,
Serialize,
Deserialize,
JsonSchema,
MergeFrom,
PartialEq,
strum::EnumDiscriminants,
)]
#[strum_discriminants(derive(strum::VariantArray, strum::VariantNames, strum::FromRepr))]
#[serde(rename_all = "snake_case")]
pub enum ProjectPanelTitleTooltipDelay {
/// Default is 1500ms.
#[default]
Default,
/// A custom offset in milliseconds for the tooltip show delay.
Custom(crate::DelayMs),
/// Disables the tooltip
Disabled,
}
#[derive(
Copy,
Clone,
+100 -1
View File
@@ -5265,7 +5265,7 @@ fn window_and_layout_page() -> SettingsPage {
}
fn panels_page() -> SettingsPage {
fn project_panel_section() -> [SettingsPageItem; 29] {
fn project_panel_section() -> [SettingsPageItem; 30] {
[
SettingsPageItem::SectionHeader("Project Panel"),
SettingsPageItem::SettingItem(SettingItem {
@@ -5305,6 +5305,105 @@ fn panels_page() -> SettingsPage {
metadata: None,
files: USER,
}),
SettingsPageItem::DynamicItem(DynamicItem {
discriminant: SettingItem {
title: "Project Panel Title Tooltips Delay",
description: "Delay in milliseconds before tooltips appear for project panel titles.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("project_panel.title_tooltip_delay$"),
pick: |settings_content| {
Some(
&dynamic_variants::<settings::ProjectPanelTitleTooltipDelay>()
[settings_content
.project_panel
.as_ref()?
.title_tooltip_delay
.as_ref()?
.discriminant()
as usize],
)
},
write: |settings_content, value, _| {
let project_panel =
settings_content.project_panel.get_or_insert_default();
project_panel.title_tooltip_delay = value.map(|value| match value {
settings::ProjectPanelTitleTooltipDelayDiscriminants::Default => {
settings::ProjectPanelTitleTooltipDelay::Default
}
settings::ProjectPanelTitleTooltipDelayDiscriminants::Disabled => {
settings::ProjectPanelTitleTooltipDelay::Disabled
}
settings::ProjectPanelTitleTooltipDelayDiscriminants::Custom => {
let delay = match project_panel.title_tooltip_delay {
Some(settings::ProjectPanelTitleTooltipDelay::Custom(
delay,
)) => settings::DelayMs(delay.0),
_ => settings::DelayMs(1500),
};
settings::ProjectPanelTitleTooltipDelay::Custom(delay)
}
});
},
}),
metadata: None,
files: USER,
},
pick_discriminant: |settings_content| {
Some(
settings_content
.project_panel
.as_ref()?
.title_tooltip_delay
.as_ref()?
.discriminant() as usize,
)
},
fields: dynamic_variants::<settings::ProjectPanelTitleTooltipDelay>()
.into_iter()
.map(|variant| match variant {
settings::ProjectPanelTitleTooltipDelayDiscriminants::Default => vec![],
settings::ProjectPanelTitleTooltipDelayDiscriminants::Disabled => vec![],
settings::ProjectPanelTitleTooltipDelayDiscriminants::Custom => {
vec![SettingItem {
files: USER,
title: "Custom Delay",
description: "Delay in milliseconds of the project panel title tooltips.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("project_panel.title_tooltip_delay"),
pick: |settings_content| match settings_content
.project_panel
.as_ref()
.and_then(|project_panel| {
project_panel.title_tooltip_delay.as_ref()
}) {
Some(settings::ProjectPanelTitleTooltipDelay::Custom(
value,
)) => Some(value),
_ => None,
},
write: |settings_content, value, _| {
let Some(value) = value else {
return;
};
if let Some(
settings::ProjectPanelTitleTooltipDelay::Custom(width),
) = settings_content.project_panel.as_mut().and_then(
|project_panel| {
project_panel.title_tooltip_delay.as_mut()
},
) {
*width = value;
}
},
}),
metadata: None,
}]
}
})
.collect(),
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Hide .gitignore",
description: "Whether to hide the gitignore entries in the project panel.",
+1
View File
@@ -656,6 +656,7 @@ fn init_renderers(cx: &mut App) {
.add_basic_renderer::<settings::IconThemeName>(render_icon_theme_picker)
.add_basic_renderer::<settings::BufferLineHeightDiscriminants>(render_dropdown)
.add_basic_renderer::<settings::GitGutterWidthDiscriminants>(render_dropdown)
.add_basic_renderer::<settings::ProjectPanelTitleTooltipDelayDiscriminants>(render_dropdown)
.add_basic_renderer::<settings::AutosaveSettingDiscriminants>(render_dropdown)
.add_basic_renderer::<settings::WorkingDirectoryDiscriminants>(render_dropdown)
.add_basic_renderer::<settings::IncludeIgnoredContent>(render_dropdown)