git: Fix a couple of bugs with buffer headers in the side-by-side diff UI (#47809)
- Fix sticky buffer header being painted on top of the toolbar - Fix buffer header click handlers and tooltips sometimes not working (element ID collision issue) - Deduplicate buffer header rendering code between split and normal editors Release Notes: - N/A
This commit is contained in:
1 parent
a5d2dc5122
commit
adf2cf9ba1
2 files changed
+462
-905
No files matched your search
+418
-415
@@ -4092,421 +4092,16 @@ impl EditorElement {
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> impl IntoElement {
|
||||
let editor = self.editor.read(cx);
|
||||
let multi_buffer = editor.buffer.read(cx);
|
||||
let is_read_only = self.editor.read(cx).read_only(cx);
|
||||
let editor_handle: &dyn ItemHandle = &self.editor;
|
||||
|
||||
let breadcrumbs = if is_selected {
|
||||
editor.breadcrumbs_inner(cx.theme(), cx)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let file_status = multi_buffer
|
||||
.all_diff_hunks_expanded()
|
||||
.then(|| editor.status_for_buffer_id(for_excerpt.buffer_id, cx))
|
||||
.flatten();
|
||||
let indicator = multi_buffer
|
||||
.buffer(for_excerpt.buffer_id)
|
||||
.and_then(|buffer| {
|
||||
let buffer = buffer.read(cx);
|
||||
let indicator_color = match (buffer.has_conflict(), buffer.is_dirty()) {
|
||||
(true, _) => Some(Color::Warning),
|
||||
(_, true) => Some(Color::Accent),
|
||||
(false, false) => None,
|
||||
};
|
||||
indicator_color.map(|indicator_color| Indicator::dot().color(indicator_color))
|
||||
});
|
||||
|
||||
let include_root = editor
|
||||
.project
|
||||
.as_ref()
|
||||
.map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
|
||||
.unwrap_or_default();
|
||||
let file = for_excerpt.buffer.file();
|
||||
let can_open_excerpts = file.is_none_or(|file| file.can_open());
|
||||
let path_style = file.map(|file| file.path_style(cx));
|
||||
let relative_path = for_excerpt.buffer.resolve_file_path(include_root, cx);
|
||||
let (parent_path, filename) = if let Some(path) = &relative_path {
|
||||
if let Some(path_style) = path_style {
|
||||
let (dir, file_name) = path_style.split(path);
|
||||
(dir.map(|dir| dir.to_owned()), Some(file_name.to_owned()))
|
||||
} else {
|
||||
(None, Some(path.clone()))
|
||||
}
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
let focus_handle = editor.focus_handle(cx);
|
||||
let colors = cx.theme().colors();
|
||||
|
||||
let header = div()
|
||||
.p_1()
|
||||
.w_full()
|
||||
.h(FILE_HEADER_HEIGHT as f32 * window.line_height())
|
||||
.child(
|
||||
h_flex()
|
||||
.size_full()
|
||||
.flex_basis(Length::Definite(DefiniteLength::Fraction(0.667)))
|
||||
.pl_1()
|
||||
.pr_2()
|
||||
.rounded_sm()
|
||||
.gap_1p5()
|
||||
.when(is_sticky, |el| el.shadow_md())
|
||||
.border_1()
|
||||
.map(|border| {
|
||||
let border_color = if is_selected
|
||||
&& is_folded
|
||||
&& focus_handle.contains_focused(window, cx)
|
||||
{
|
||||
colors.border_focused
|
||||
} else {
|
||||
colors.border
|
||||
};
|
||||
border.border_color(border_color)
|
||||
})
|
||||
.bg(colors.editor_subheader_background)
|
||||
.hover(|style| style.bg(colors.element_hover))
|
||||
.map(|header| {
|
||||
let editor = self.editor.clone();
|
||||
let buffer_id = for_excerpt.buffer_id;
|
||||
let toggle_chevron_icon =
|
||||
FileIcons::get_chevron_icon(!is_folded, cx).map(Icon::from_path);
|
||||
let button_size = rems_from_px(28.);
|
||||
|
||||
header.child(
|
||||
div()
|
||||
.hover(|style| style.bg(colors.element_selected))
|
||||
.rounded_xs()
|
||||
.child(
|
||||
ButtonLike::new("toggle-buffer-fold")
|
||||
.style(ButtonStyle::Transparent)
|
||||
.height(button_size.into())
|
||||
.width(button_size)
|
||||
.children(toggle_chevron_icon)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
let is_folded_for_tooltip = is_folded;
|
||||
move |_window, cx| {
|
||||
Tooltip::with_meta_in(
|
||||
if is_folded_for_tooltip {
|
||||
"Unfold Excerpt"
|
||||
} else {
|
||||
"Fold Excerpt"
|
||||
},
|
||||
Some(&ToggleFold),
|
||||
format!(
|
||||
"{} to toggle all",
|
||||
text_for_keystroke(
|
||||
&Modifiers::alt(),
|
||||
"click",
|
||||
cx
|
||||
)
|
||||
),
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click(move |event, window, cx| {
|
||||
if event.modifiers().alt {
|
||||
// Alt+click toggles all buffers
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.toggle_fold_all(
|
||||
&ToggleFoldAll,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// Regular click toggles single buffer
|
||||
if is_folded {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.unfold_buffer(buffer_id, cx);
|
||||
});
|
||||
} else {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.fold_buffer(buffer_id, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
})
|
||||
.children(
|
||||
editor
|
||||
.addons
|
||||
.values()
|
||||
.filter_map(|addon| {
|
||||
addon.render_buffer_header_controls(for_excerpt, window, cx)
|
||||
})
|
||||
.take(1),
|
||||
)
|
||||
.when(!is_read_only, |this| {
|
||||
this.child(
|
||||
h_flex()
|
||||
.size_3()
|
||||
.justify_center()
|
||||
.flex_shrink_0()
|
||||
.children(indicator),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.cursor_pointer()
|
||||
.id("path_header_block")
|
||||
.min_w_0()
|
||||
.size_full()
|
||||
.gap_1()
|
||||
.justify_between()
|
||||
.overflow_hidden()
|
||||
.child(h_flex().min_w_0().flex_1().gap_0p5().overflow_hidden().map(
|
||||
|path_header| {
|
||||
let filename = filename
|
||||
.map(SharedString::from)
|
||||
.unwrap_or_else(|| "untitled".into());
|
||||
|
||||
let full_path = match parent_path.as_deref() {
|
||||
Some(parent) if !parent.is_empty() => {
|
||||
format!("{}{}", parent, filename.as_str())
|
||||
}
|
||||
_ => filename.as_str().to_string(),
|
||||
};
|
||||
|
||||
path_header
|
||||
.child(
|
||||
ButtonLike::new("filename-button")
|
||||
.when(
|
||||
ItemSettings::get_global(cx).file_icons,
|
||||
|this| {
|
||||
let path =
|
||||
path::Path::new(filename.as_str());
|
||||
let icon = FileIcons::get_icon(path, cx)
|
||||
.unwrap_or_default();
|
||||
|
||||
this.child(
|
||||
Icon::from_path(icon)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
},
|
||||
)
|
||||
.child(
|
||||
Label::new(filename)
|
||||
.single_line()
|
||||
.color(file_status_label_color(file_status))
|
||||
.buffer_font(cx)
|
||||
.when(
|
||||
file_status
|
||||
.is_some_and(|s| s.is_deleted()),
|
||||
|label| label.strikethrough(),
|
||||
),
|
||||
)
|
||||
.tooltip(move |_, cx| {
|
||||
Tooltip::with_meta(
|
||||
"Open File",
|
||||
None,
|
||||
full_path.clone(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(window.listener_for(&self.editor, {
|
||||
let jump_data = jump_data.clone();
|
||||
move |editor, e: &ClickEvent, window, cx| {
|
||||
editor.open_excerpts_common(
|
||||
Some(jump_data.clone()),
|
||||
e.modifiers().secondary(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
})),
|
||||
)
|
||||
.when_some(parent_path, |then, path| {
|
||||
then.child(
|
||||
Label::new(path)
|
||||
.buffer_font(cx)
|
||||
.truncate_start()
|
||||
.color(
|
||||
if file_status
|
||||
.is_some_and(FileStatus::is_deleted)
|
||||
{
|
||||
Color::Custom(colors.text_disabled)
|
||||
} else {
|
||||
Color::Custom(colors.text_muted)
|
||||
},
|
||||
),
|
||||
)
|
||||
})
|
||||
.when(!for_excerpt.buffer.capability.editable(), |el| {
|
||||
el.child(
|
||||
Icon::new(IconName::FileLock).color(Color::Muted),
|
||||
)
|
||||
})
|
||||
.when_some(breadcrumbs, |then, breadcrumbs| {
|
||||
then.child(render_breadcrumb_text(
|
||||
breadcrumbs,
|
||||
None,
|
||||
editor_handle,
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
})
|
||||
},
|
||||
))
|
||||
.when(
|
||||
can_open_excerpts && is_selected && relative_path.is_some(),
|
||||
|el| {
|
||||
el.child(
|
||||
Button::new("open-file-button", "Open File")
|
||||
.style(ButtonStyle::OutlinedGhost)
|
||||
.key_binding(KeyBinding::for_action_in(
|
||||
&OpenExcerpts,
|
||||
&focus_handle,
|
||||
cx,
|
||||
))
|
||||
.on_click(window.listener_for(&self.editor, {
|
||||
let jump_data = jump_data.clone();
|
||||
move |editor, e: &ClickEvent, window, cx| {
|
||||
editor.open_excerpts_common(
|
||||
Some(jump_data.clone()),
|
||||
e.modifiers().secondary(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
})),
|
||||
)
|
||||
},
|
||||
)
|
||||
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
|
||||
.on_click(window.listener_for(&self.editor, {
|
||||
let buffer_id = for_excerpt.buffer_id;
|
||||
move |editor, e: &ClickEvent, window, cx| {
|
||||
if e.modifiers().alt {
|
||||
editor.open_excerpts_common(
|
||||
Some(jump_data.clone()),
|
||||
e.modifiers().secondary(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if is_folded {
|
||||
editor.unfold_buffer(buffer_id, cx);
|
||||
} else {
|
||||
editor.fold_buffer(buffer_id, cx);
|
||||
}
|
||||
}
|
||||
})),
|
||||
),
|
||||
);
|
||||
|
||||
let file = for_excerpt.buffer.file().cloned();
|
||||
let editor = self.editor.clone();
|
||||
|
||||
right_click_menu("buffer-header-context-menu")
|
||||
.trigger(move |_, _, _| header)
|
||||
.menu(move |window, cx| {
|
||||
let menu_context = focus_handle.clone();
|
||||
let editor = editor.clone();
|
||||
let file = file.clone();
|
||||
ContextMenu::build(window, cx, move |mut menu, window, cx| {
|
||||
if let Some(file) = file
|
||||
&& let Some(project) = editor.read(cx).project()
|
||||
&& let Some(worktree) =
|
||||
project.read(cx).worktree_for_id(file.worktree_id(cx), cx)
|
||||
{
|
||||
let path_style = file.path_style(cx);
|
||||
let worktree = worktree.read(cx);
|
||||
let relative_path = file.path();
|
||||
let entry_for_path = worktree.entry_for_path(relative_path);
|
||||
let abs_path = entry_for_path.map(|e| {
|
||||
e.canonical_path.as_deref().map_or_else(
|
||||
|| worktree.absolutize(relative_path),
|
||||
Path::to_path_buf,
|
||||
)
|
||||
});
|
||||
let has_relative_path = worktree.root_entry().is_some_and(Entry::is_dir);
|
||||
|
||||
let parent_abs_path = abs_path
|
||||
.as_ref()
|
||||
.and_then(|abs_path| Some(abs_path.parent()?.to_path_buf()));
|
||||
let relative_path = has_relative_path
|
||||
.then_some(relative_path)
|
||||
.map(ToOwned::to_owned);
|
||||
|
||||
let visible_in_project_panel =
|
||||
relative_path.is_some() && worktree.is_visible();
|
||||
let reveal_in_project_panel = entry_for_path
|
||||
.filter(|_| visible_in_project_panel)
|
||||
.map(|entry| entry.id);
|
||||
menu = menu
|
||||
.when_some(abs_path, |menu, abs_path| {
|
||||
menu.entry(
|
||||
"Copy Path",
|
||||
Some(Box::new(zed_actions::workspace::CopyPath)),
|
||||
window.handler_for(&editor, move |_, _, cx| {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(
|
||||
abs_path.to_string_lossy().into_owned(),
|
||||
));
|
||||
}),
|
||||
)
|
||||
})
|
||||
.when_some(relative_path, |menu, relative_path| {
|
||||
menu.entry(
|
||||
"Copy Relative Path",
|
||||
Some(Box::new(zed_actions::workspace::CopyRelativePath)),
|
||||
window.handler_for(&editor, move |_, _, cx| {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(
|
||||
relative_path.display(path_style).to_string(),
|
||||
));
|
||||
}),
|
||||
)
|
||||
})
|
||||
.when(
|
||||
reveal_in_project_panel.is_some() || parent_abs_path.is_some(),
|
||||
|menu| menu.separator(),
|
||||
)
|
||||
.when_some(reveal_in_project_panel, |menu, entry_id| {
|
||||
menu.entry(
|
||||
"Reveal In Project Panel",
|
||||
Some(Box::new(RevealInProjectPanel::default())),
|
||||
window.handler_for(&editor, move |editor, _, cx| {
|
||||
if let Some(project) = &mut editor.project {
|
||||
project.update(cx, |_, cx| {
|
||||
cx.emit(project::Event::RevealInProjectPanel(
|
||||
entry_id,
|
||||
))
|
||||
});
|
||||
}
|
||||
}),
|
||||
)
|
||||
})
|
||||
.when_some(parent_abs_path, |menu, parent_abs_path| {
|
||||
menu.entry(
|
||||
"Open in Terminal",
|
||||
Some(Box::new(OpenInTerminal)),
|
||||
window.handler_for(&editor, move |_, window, cx| {
|
||||
window.dispatch_action(
|
||||
OpenTerminal {
|
||||
working_directory: parent_abs_path.clone(),
|
||||
local: false,
|
||||
}
|
||||
.boxed_clone(),
|
||||
cx,
|
||||
);
|
||||
}),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
menu.context(menu_context)
|
||||
})
|
||||
})
|
||||
render_buffer_header(
|
||||
&self.editor,
|
||||
for_excerpt,
|
||||
is_folded,
|
||||
is_selected,
|
||||
is_sticky,
|
||||
jump_data,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
||||
fn render_blocks(
|
||||
@@ -8441,6 +8036,414 @@ fn header_jump_data_inner(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn render_buffer_header(
|
||||
editor: &Entity<Editor>,
|
||||
for_excerpt: &ExcerptInfo,
|
||||
is_folded: bool,
|
||||
is_selected: bool,
|
||||
is_sticky: bool,
|
||||
jump_data: JumpData,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> impl IntoElement {
|
||||
let editor_read = editor.read(cx);
|
||||
let multi_buffer = editor_read.buffer.read(cx);
|
||||
let is_read_only = editor_read.read_only(cx);
|
||||
let editor_handle: &dyn ItemHandle = editor;
|
||||
|
||||
let breadcrumbs = if is_selected {
|
||||
editor_read.breadcrumbs_inner(cx.theme(), cx)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let file_status = multi_buffer
|
||||
.all_diff_hunks_expanded()
|
||||
.then(|| editor_read.status_for_buffer_id(for_excerpt.buffer_id, cx))
|
||||
.flatten();
|
||||
let indicator = multi_buffer
|
||||
.buffer(for_excerpt.buffer_id)
|
||||
.and_then(|buffer| {
|
||||
let buffer = buffer.read(cx);
|
||||
let indicator_color = match (buffer.has_conflict(), buffer.is_dirty()) {
|
||||
(true, _) => Some(Color::Warning),
|
||||
(_, true) => Some(Color::Accent),
|
||||
(false, false) => None,
|
||||
};
|
||||
indicator_color.map(|indicator_color| Indicator::dot().color(indicator_color))
|
||||
});
|
||||
|
||||
let include_root = editor_read
|
||||
.project
|
||||
.as_ref()
|
||||
.map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
|
||||
.unwrap_or_default();
|
||||
let file = for_excerpt.buffer.file();
|
||||
let can_open_excerpts = file.is_none_or(|file| file.can_open());
|
||||
let path_style = file.map(|file| file.path_style(cx));
|
||||
let relative_path = for_excerpt.buffer.resolve_file_path(include_root, cx);
|
||||
let (parent_path, filename) = if let Some(path) = &relative_path {
|
||||
if let Some(path_style) = path_style {
|
||||
let (dir, file_name) = path_style.split(path);
|
||||
(dir.map(|dir| dir.to_owned()), Some(file_name.to_owned()))
|
||||
} else {
|
||||
(None, Some(path.clone()))
|
||||
}
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
let focus_handle = editor_read.focus_handle(cx);
|
||||
let colors = cx.theme().colors();
|
||||
|
||||
let header = div()
|
||||
.id(("buffer-header", for_excerpt.buffer_id.to_proto()))
|
||||
.p_1()
|
||||
.w_full()
|
||||
.h(FILE_HEADER_HEIGHT as f32 * window.line_height())
|
||||
.child(
|
||||
h_flex()
|
||||
.size_full()
|
||||
.flex_basis(Length::Definite(DefiniteLength::Fraction(0.667)))
|
||||
.pl_1()
|
||||
.pr_2()
|
||||
.rounded_sm()
|
||||
.gap_1p5()
|
||||
.when(is_sticky, |el| el.shadow_md())
|
||||
.border_1()
|
||||
.map(|border| {
|
||||
let border_color =
|
||||
if is_selected && is_folded && focus_handle.contains_focused(window, cx) {
|
||||
colors.border_focused
|
||||
} else {
|
||||
colors.border
|
||||
};
|
||||
border.border_color(border_color)
|
||||
})
|
||||
.bg(colors.editor_subheader_background)
|
||||
.hover(|style| style.bg(colors.element_hover))
|
||||
.map(|header| {
|
||||
let editor = editor.clone();
|
||||
let buffer_id = for_excerpt.buffer_id;
|
||||
let toggle_chevron_icon =
|
||||
FileIcons::get_chevron_icon(!is_folded, cx).map(Icon::from_path);
|
||||
let button_size = rems_from_px(28.);
|
||||
|
||||
header.child(
|
||||
div()
|
||||
.hover(|style| style.bg(colors.element_selected))
|
||||
.rounded_xs()
|
||||
.child(
|
||||
ButtonLike::new("toggle-buffer-fold")
|
||||
.style(ButtonStyle::Transparent)
|
||||
.height(button_size.into())
|
||||
.width(button_size)
|
||||
.children(toggle_chevron_icon)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
let is_folded_for_tooltip = is_folded;
|
||||
move |_window, cx| {
|
||||
Tooltip::with_meta_in(
|
||||
if is_folded_for_tooltip {
|
||||
"Unfold Excerpt"
|
||||
} else {
|
||||
"Fold Excerpt"
|
||||
},
|
||||
Some(&ToggleFold),
|
||||
format!(
|
||||
"{} to toggle all",
|
||||
text_for_keystroke(
|
||||
&Modifiers::alt(),
|
||||
"click",
|
||||
cx
|
||||
)
|
||||
),
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click(move |event, window, cx| {
|
||||
if event.modifiers().alt {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.toggle_fold_all(&ToggleFoldAll, window, cx);
|
||||
});
|
||||
} else {
|
||||
if is_folded {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.unfold_buffer(buffer_id, cx);
|
||||
});
|
||||
} else {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.fold_buffer(buffer_id, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
})
|
||||
.children(
|
||||
editor_read
|
||||
.addons
|
||||
.values()
|
||||
.filter_map(|addon| {
|
||||
addon.render_buffer_header_controls(for_excerpt, window, cx)
|
||||
})
|
||||
.take(1),
|
||||
)
|
||||
.when(!is_read_only, |this| {
|
||||
this.child(
|
||||
h_flex()
|
||||
.size_3()
|
||||
.justify_center()
|
||||
.flex_shrink_0()
|
||||
.children(indicator),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.cursor_pointer()
|
||||
.id("path_header_block")
|
||||
.min_w_0()
|
||||
.size_full()
|
||||
.gap_1()
|
||||
.justify_between()
|
||||
.overflow_hidden()
|
||||
.child(h_flex().min_w_0().flex_1().gap_0p5().overflow_hidden().map(
|
||||
|path_header| {
|
||||
let filename = filename
|
||||
.map(SharedString::from)
|
||||
.unwrap_or_else(|| "untitled".into());
|
||||
|
||||
let full_path = match parent_path.as_deref() {
|
||||
Some(parent) if !parent.is_empty() => {
|
||||
format!("{}{}", parent, filename.as_str())
|
||||
}
|
||||
_ => filename.as_str().to_string(),
|
||||
};
|
||||
|
||||
path_header
|
||||
.child(
|
||||
ButtonLike::new("filename-button")
|
||||
.when(ItemSettings::get_global(cx).file_icons, |this| {
|
||||
let path = path::Path::new(filename.as_str());
|
||||
let icon = FileIcons::get_icon(path, cx)
|
||||
.unwrap_or_default();
|
||||
|
||||
this.child(
|
||||
Icon::from_path(icon).color(Color::Muted),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
Label::new(filename)
|
||||
.single_line()
|
||||
.color(file_status_label_color(file_status))
|
||||
.buffer_font(cx)
|
||||
.when(
|
||||
file_status.is_some_and(|s| s.is_deleted()),
|
||||
|label| label.strikethrough(),
|
||||
),
|
||||
)
|
||||
.tooltip(move |_, cx| {
|
||||
Tooltip::with_meta(
|
||||
"Open File",
|
||||
None,
|
||||
full_path.clone(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(window.listener_for(editor, {
|
||||
let jump_data = jump_data.clone();
|
||||
move |editor, e: &ClickEvent, window, cx| {
|
||||
editor.open_excerpts_common(
|
||||
Some(jump_data.clone()),
|
||||
e.modifiers().secondary(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
})),
|
||||
)
|
||||
.when_some(parent_path, |then, path| {
|
||||
then.child(
|
||||
Label::new(path)
|
||||
.buffer_font(cx)
|
||||
.truncate_start()
|
||||
.color(
|
||||
if file_status
|
||||
.is_some_and(FileStatus::is_deleted)
|
||||
{
|
||||
Color::Custom(colors.text_disabled)
|
||||
} else {
|
||||
Color::Custom(colors.text_muted)
|
||||
},
|
||||
),
|
||||
)
|
||||
})
|
||||
.when(!for_excerpt.buffer.capability.editable(), |el| {
|
||||
el.child(Icon::new(IconName::FileLock).color(Color::Muted))
|
||||
})
|
||||
.when_some(breadcrumbs, |then, breadcrumbs| {
|
||||
then.child(render_breadcrumb_text(
|
||||
breadcrumbs,
|
||||
None,
|
||||
editor_handle,
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
})
|
||||
},
|
||||
))
|
||||
.when(
|
||||
can_open_excerpts && is_selected && relative_path.is_some(),
|
||||
|el| {
|
||||
el.child(
|
||||
Button::new("open-file-button", "Open File")
|
||||
.style(ButtonStyle::OutlinedGhost)
|
||||
.key_binding(KeyBinding::for_action_in(
|
||||
&OpenExcerpts,
|
||||
&focus_handle,
|
||||
cx,
|
||||
))
|
||||
.on_click(window.listener_for(editor, {
|
||||
let jump_data = jump_data.clone();
|
||||
move |editor, e: &ClickEvent, window, cx| {
|
||||
editor.open_excerpts_common(
|
||||
Some(jump_data.clone()),
|
||||
e.modifiers().secondary(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
})),
|
||||
)
|
||||
},
|
||||
)
|
||||
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
|
||||
.on_click(window.listener_for(editor, {
|
||||
let buffer_id = for_excerpt.buffer_id;
|
||||
move |editor, e: &ClickEvent, window, cx| {
|
||||
if e.modifiers().alt {
|
||||
editor.open_excerpts_common(
|
||||
Some(jump_data.clone()),
|
||||
e.modifiers().secondary(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if is_folded {
|
||||
editor.unfold_buffer(buffer_id, cx);
|
||||
} else {
|
||||
editor.fold_buffer(buffer_id, cx);
|
||||
}
|
||||
}
|
||||
})),
|
||||
),
|
||||
);
|
||||
|
||||
let file = for_excerpt.buffer.file().cloned();
|
||||
let editor = editor.clone();
|
||||
|
||||
right_click_menu("buffer-header-context-menu")
|
||||
.trigger(move |_, _, _| header)
|
||||
.menu(move |window, cx| {
|
||||
let menu_context = focus_handle.clone();
|
||||
let editor = editor.clone();
|
||||
let file = file.clone();
|
||||
ContextMenu::build(window, cx, move |mut menu, window, cx| {
|
||||
if let Some(file) = file
|
||||
&& let Some(project) = editor.read(cx).project()
|
||||
&& let Some(worktree) =
|
||||
project.read(cx).worktree_for_id(file.worktree_id(cx), cx)
|
||||
{
|
||||
let path_style = file.path_style(cx);
|
||||
let worktree = worktree.read(cx);
|
||||
let relative_path = file.path();
|
||||
let entry_for_path = worktree.entry_for_path(relative_path);
|
||||
let abs_path = entry_for_path.map(|e| {
|
||||
e.canonical_path
|
||||
.as_deref()
|
||||
.map_or_else(|| worktree.absolutize(relative_path), Path::to_path_buf)
|
||||
});
|
||||
let has_relative_path = worktree.root_entry().is_some_and(Entry::is_dir);
|
||||
|
||||
let parent_abs_path = abs_path
|
||||
.as_ref()
|
||||
.and_then(|abs_path| Some(abs_path.parent()?.to_path_buf()));
|
||||
let relative_path = has_relative_path
|
||||
.then_some(relative_path)
|
||||
.map(ToOwned::to_owned);
|
||||
|
||||
let visible_in_project_panel = relative_path.is_some() && worktree.is_visible();
|
||||
let reveal_in_project_panel = entry_for_path
|
||||
.filter(|_| visible_in_project_panel)
|
||||
.map(|entry| entry.id);
|
||||
menu = menu
|
||||
.when_some(abs_path, |menu, abs_path| {
|
||||
menu.entry(
|
||||
"Copy Path",
|
||||
Some(Box::new(zed_actions::workspace::CopyPath)),
|
||||
window.handler_for(&editor, move |_, _, cx| {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(
|
||||
abs_path.to_string_lossy().into_owned(),
|
||||
));
|
||||
}),
|
||||
)
|
||||
})
|
||||
.when_some(relative_path, |menu, relative_path| {
|
||||
menu.entry(
|
||||
"Copy Relative Path",
|
||||
Some(Box::new(zed_actions::workspace::CopyRelativePath)),
|
||||
window.handler_for(&editor, move |_, _, cx| {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(
|
||||
relative_path.display(path_style).to_string(),
|
||||
));
|
||||
}),
|
||||
)
|
||||
})
|
||||
.when(
|
||||
reveal_in_project_panel.is_some() || parent_abs_path.is_some(),
|
||||
|menu| menu.separator(),
|
||||
)
|
||||
.when_some(reveal_in_project_panel, |menu, entry_id| {
|
||||
menu.entry(
|
||||
"Reveal In Project Panel",
|
||||
Some(Box::new(RevealInProjectPanel::default())),
|
||||
window.handler_for(&editor, move |editor, _, cx| {
|
||||
if let Some(project) = &mut editor.project {
|
||||
project.update(cx, |_, cx| {
|
||||
cx.emit(project::Event::RevealInProjectPanel(entry_id))
|
||||
});
|
||||
}
|
||||
}),
|
||||
)
|
||||
})
|
||||
.when_some(parent_abs_path, |menu, parent_abs_path| {
|
||||
menu.entry(
|
||||
"Open in Terminal",
|
||||
Some(Box::new(OpenInTerminal)),
|
||||
window.handler_for(&editor, move |_, window, cx| {
|
||||
window.dispatch_action(
|
||||
OpenTerminal {
|
||||
working_directory: parent_abs_path.clone(),
|
||||
local: false,
|
||||
}
|
||||
.boxed_clone(),
|
||||
cx,
|
||||
);
|
||||
}),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
menu.context(menu_context)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub struct AcceptEditPredictionBinding(pub(crate) Option<gpui::KeyBinding>);
|
||||
|
||||
impl AcceptEditPredictionBinding {
|
||||
|
||||
@@ -1,33 +1,26 @@
|
||||
use std::{cmp, collections::HashMap, path, path::Path};
|
||||
use std::cmp;
|
||||
|
||||
use collections::HashSet;
|
||||
use file_icons::FileIcons;
|
||||
use git::status::FileStatus;
|
||||
use collections::{HashMap, HashSet};
|
||||
use gpui::{
|
||||
AbsoluteLength, Action, AnyElement, App, AvailableSpace, Bounds, ClickEvent, ClipboardItem,
|
||||
Context, DragMoveEvent, Element, Entity, Focusable, GlobalElementId, Hsla, InspectorElementId,
|
||||
IntoElement, LayoutId, Length, Modifiers, MouseButton, ParentElement, Pixels,
|
||||
StatefulInteractiveElement, Styled, TextStyleRefinement, Window, div, linear_color_stop,
|
||||
linear_gradient, point, px, size,
|
||||
AbsoluteLength, AnyElement, App, AvailableSpace, Bounds, Context, DragMoveEvent, Element,
|
||||
Entity, GlobalElementId, Hsla, InspectorElementId, IntoElement, LayoutId, Length,
|
||||
ParentElement, Pixels, StatefulInteractiveElement, Styled, TextStyleRefinement, Window, div,
|
||||
linear_color_stop, linear_gradient, point, px, size,
|
||||
};
|
||||
use multi_buffer::{Anchor, ExcerptId, ExcerptInfo};
|
||||
use project::Entry;
|
||||
use multi_buffer::{Anchor, ExcerptId};
|
||||
use settings::Settings;
|
||||
use text::BufferId;
|
||||
use theme::ActiveTheme;
|
||||
use ui::scrollbars::ShowScrollbar;
|
||||
use ui::{
|
||||
Button, ButtonLike, ButtonStyle, ContextMenu, Icon, IconName, Indicator, KeyBinding, Label,
|
||||
Tooltip, h_flex, prelude::*, right_click_menu, text_for_keystroke, v_flex,
|
||||
};
|
||||
use workspace::{ItemSettings, OpenInTerminal, OpenTerminal, RevealInProjectPanel};
|
||||
use ui::{h_flex, prelude::*, v_flex};
|
||||
|
||||
use gpui::ContentMask;
|
||||
|
||||
use crate::{
|
||||
DisplayRow, Editor, EditorSettings, EditorSnapshot, EditorStyle, FILE_HEADER_HEIGHT, JumpData,
|
||||
MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, OpenExcerpts, RowExt, StickyHeaderExcerpt, ToggleFold,
|
||||
ToggleFoldAll,
|
||||
DisplayRow, Editor, EditorSettings, EditorSnapshot, EditorStyle, FILE_HEADER_HEIGHT,
|
||||
MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, RowExt, StickyHeaderExcerpt,
|
||||
display_map::Block,
|
||||
element::{EditorElement, SplitSide},
|
||||
element::{EditorElement, SplitSide, header_jump_data, render_buffer_header},
|
||||
scroll::ScrollOffset,
|
||||
split::SplittableEditor,
|
||||
};
|
||||
@@ -319,7 +312,7 @@ impl Element for SplitBufferHeadersElement {
|
||||
&mut self,
|
||||
_id: Option<&GlobalElementId>,
|
||||
_inspector_id: Option<&InspectorElementId>,
|
||||
_bounds: Bounds<Pixels>,
|
||||
bounds: Bounds<Pixels>,
|
||||
_request_layout: &mut Self::RequestLayoutState,
|
||||
prepaint: &mut Self::PrepaintState,
|
||||
window: &mut Window,
|
||||
@@ -334,13 +327,15 @@ impl Element for SplitBufferHeadersElement {
|
||||
|
||||
window.with_rem_size(rem_size, |window| {
|
||||
window.with_text_style(Some(text_style), |window| {
|
||||
for header_layout in &mut prepaint.non_sticky_headers {
|
||||
header_layout.element.paint(window, cx);
|
||||
}
|
||||
window.with_content_mask(Some(ContentMask { bounds }), |window| {
|
||||
for header_layout in &mut prepaint.non_sticky_headers {
|
||||
header_layout.element.paint(window, cx);
|
||||
}
|
||||
|
||||
if let Some(mut sticky_header) = prepaint.sticky_header.take() {
|
||||
sticky_header.paint(window, cx);
|
||||
}
|
||||
if let Some(mut sticky_header) = prepaint.sticky_header.take() {
|
||||
sticky_header.paint(window, cx);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -532,8 +527,17 @@ impl SplitBufferHeadersElement {
|
||||
.top_0(),
|
||||
)
|
||||
.child(
|
||||
self.render_buffer_header(excerpt, false, selected, true, jump_data, window, cx)
|
||||
.into_any_element(),
|
||||
render_buffer_header(
|
||||
&self.editor,
|
||||
excerpt,
|
||||
false,
|
||||
selected,
|
||||
true,
|
||||
jump_data,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.into_any_element(),
|
||||
)
|
||||
.into_any_element();
|
||||
|
||||
@@ -606,9 +610,17 @@ impl SplitBufferHeadersElement {
|
||||
latest_selection_anchors,
|
||||
);
|
||||
|
||||
let mut header = self
|
||||
.render_buffer_header(excerpt, is_folded, selected, false, jump_data, window, cx)
|
||||
.into_any_element();
|
||||
let mut header = render_buffer_header(
|
||||
&self.editor,
|
||||
excerpt,
|
||||
is_folded,
|
||||
selected,
|
||||
false,
|
||||
jump_data,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.into_any_element();
|
||||
|
||||
let y_offset = (block_row.0 as f64 - scroll_position.y) * f64::from(line_height);
|
||||
let origin = point(bounds.origin.x, bounds.origin.y + Pixels::from(y_offset));
|
||||
@@ -625,462 +637,4 @@ impl SplitBufferHeadersElement {
|
||||
|
||||
headers
|
||||
}
|
||||
|
||||
fn render_buffer_header(
|
||||
&self,
|
||||
for_excerpt: &ExcerptInfo,
|
||||
is_folded: bool,
|
||||
is_selected: bool,
|
||||
is_sticky: bool,
|
||||
jump_data: JumpData,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> impl IntoElement {
|
||||
let editor = self.editor.read(cx);
|
||||
let multi_buffer = editor.buffer.read(cx);
|
||||
let is_read_only = self.editor.read(cx).read_only(cx);
|
||||
|
||||
let file_status = multi_buffer
|
||||
.all_diff_hunks_expanded()
|
||||
.then(|| editor.status_for_buffer_id(for_excerpt.buffer_id, cx))
|
||||
.flatten();
|
||||
let indicator = multi_buffer
|
||||
.buffer(for_excerpt.buffer_id)
|
||||
.and_then(|buffer| {
|
||||
let buffer = buffer.read(cx);
|
||||
let indicator_color = match (buffer.has_conflict(), buffer.is_dirty()) {
|
||||
(true, _) => Some(Color::Warning),
|
||||
(_, true) => Some(Color::Accent),
|
||||
(false, false) => None,
|
||||
};
|
||||
indicator_color.map(|indicator_color| Indicator::dot().color(indicator_color))
|
||||
});
|
||||
|
||||
let include_root = editor
|
||||
.project
|
||||
.as_ref()
|
||||
.map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
|
||||
.unwrap_or_default();
|
||||
let file = for_excerpt.buffer.file();
|
||||
let can_open_excerpts = file.is_none_or(|file| file.can_open());
|
||||
let path_style = file.map(|file| file.path_style(cx));
|
||||
let relative_path = for_excerpt.buffer.resolve_file_path(include_root, cx);
|
||||
let (parent_path, filename) = if let Some(path) = &relative_path {
|
||||
if let Some(path_style) = path_style {
|
||||
let (dir, file_name) = path_style.split(path);
|
||||
(dir.map(|dir| dir.to_owned()), Some(file_name.to_owned()))
|
||||
} else {
|
||||
(None, Some(path.clone()))
|
||||
}
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
let focus_handle = self.editor.read(cx).focus_handle(cx);
|
||||
let colors = cx.theme().colors();
|
||||
|
||||
let header = div()
|
||||
.p_1()
|
||||
.w_full()
|
||||
.h(FILE_HEADER_HEIGHT as f32 * window.line_height())
|
||||
.child(
|
||||
h_flex()
|
||||
.size_full()
|
||||
.flex_basis(Length::Definite(DefiniteLength::Fraction(0.667)))
|
||||
.pl_1()
|
||||
.pr_2()
|
||||
.rounded_sm()
|
||||
.gap_1p5()
|
||||
.when(is_sticky, |el| el.shadow_md())
|
||||
.border_1()
|
||||
.map(|border| {
|
||||
let border_color = if !is_sticky
|
||||
&& is_selected
|
||||
&& is_folded
|
||||
&& focus_handle.contains_focused(window, cx)
|
||||
{
|
||||
colors.border_focused
|
||||
} else {
|
||||
colors.border
|
||||
};
|
||||
border.border_color(border_color)
|
||||
})
|
||||
.bg(colors.editor_subheader_background)
|
||||
.hover(|style| style.bg(colors.element_hover))
|
||||
.map(|header| {
|
||||
let editor = self.editor.clone();
|
||||
let buffer_id = for_excerpt.buffer_id;
|
||||
let toggle_chevron_icon =
|
||||
FileIcons::get_chevron_icon(!is_folded, cx).map(Icon::from_path);
|
||||
let button_size = rems_from_px(28.);
|
||||
|
||||
header.child(
|
||||
div()
|
||||
.hover(|style| style.bg(colors.element_selected))
|
||||
.rounded_xs()
|
||||
.child(
|
||||
ButtonLike::new("toggle-buffer-fold")
|
||||
.style(ButtonStyle::Transparent)
|
||||
.height(button_size.into())
|
||||
.width(button_size)
|
||||
.children(toggle_chevron_icon)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
let is_folded_for_tooltip = is_folded;
|
||||
move |_window, cx| {
|
||||
Tooltip::with_meta_in(
|
||||
if is_folded_for_tooltip {
|
||||
"Unfold Excerpt"
|
||||
} else {
|
||||
"Fold Excerpt"
|
||||
},
|
||||
Some(&ToggleFold),
|
||||
format!(
|
||||
"{} to toggle all",
|
||||
text_for_keystroke(
|
||||
&Modifiers::alt(),
|
||||
"click",
|
||||
cx
|
||||
)
|
||||
),
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click(move |event, window, cx| {
|
||||
if event.modifiers().alt {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.toggle_fold_all(
|
||||
&ToggleFoldAll,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
} else {
|
||||
if is_folded {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.unfold_buffer(buffer_id, cx);
|
||||
});
|
||||
} else {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.fold_buffer(buffer_id, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
})
|
||||
.children(
|
||||
editor
|
||||
.addons
|
||||
.values()
|
||||
.filter_map(|addon| {
|
||||
addon.render_buffer_header_controls(for_excerpt, window, cx)
|
||||
})
|
||||
.take(1),
|
||||
)
|
||||
.when(!is_read_only, |this| {
|
||||
this.child(
|
||||
h_flex()
|
||||
.size_3()
|
||||
.justify_center()
|
||||
.flex_shrink_0()
|
||||
.children(indicator),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.cursor_pointer()
|
||||
.id("path_header_block")
|
||||
.min_w_0()
|
||||
.size_full()
|
||||
.justify_between()
|
||||
.overflow_hidden()
|
||||
.child(h_flex().min_w_0().flex_1().gap_0p5().map(|path_header| {
|
||||
let filename = filename
|
||||
.map(SharedString::from)
|
||||
.unwrap_or_else(|| "untitled".into());
|
||||
|
||||
path_header
|
||||
.when(ItemSettings::get_global(cx).file_icons, |el| {
|
||||
let path = path::Path::new(filename.as_str());
|
||||
let icon =
|
||||
FileIcons::get_icon(path, cx).unwrap_or_default();
|
||||
|
||||
el.child(Icon::from_path(icon).color(Color::Muted))
|
||||
})
|
||||
.child(
|
||||
ButtonLike::new("filename-button")
|
||||
.child(
|
||||
Label::new(filename)
|
||||
.single_line()
|
||||
.color(file_status_label_color(file_status))
|
||||
.when(
|
||||
file_status.is_some_and(|s| s.is_deleted()),
|
||||
|label| label.strikethrough(),
|
||||
),
|
||||
)
|
||||
.on_click(window.listener_for(&self.editor, {
|
||||
let jump_data = jump_data.clone();
|
||||
move |editor, e: &ClickEvent, window, cx| {
|
||||
editor.open_excerpts_common(
|
||||
Some(jump_data.clone()),
|
||||
e.modifiers().secondary(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
})),
|
||||
)
|
||||
.when(!for_excerpt.buffer.capability.editable(), |el| {
|
||||
el.child(Icon::new(IconName::FileLock).color(Color::Muted))
|
||||
})
|
||||
.when_some(parent_path, |then, path| {
|
||||
then.child(Label::new(path).truncate().color(
|
||||
if file_status.is_some_and(FileStatus::is_deleted) {
|
||||
Color::Custom(colors.text_disabled)
|
||||
} else {
|
||||
Color::Custom(colors.text_muted)
|
||||
},
|
||||
))
|
||||
})
|
||||
}))
|
||||
.when(
|
||||
can_open_excerpts && is_selected && relative_path.is_some(),
|
||||
|el| {
|
||||
el.child(
|
||||
Button::new("open-file-button", "Open File")
|
||||
.style(ButtonStyle::OutlinedGhost)
|
||||
.key_binding(KeyBinding::for_action_in(
|
||||
&OpenExcerpts,
|
||||
&focus_handle,
|
||||
cx,
|
||||
))
|
||||
.on_click(window.listener_for(&self.editor, {
|
||||
let jump_data = jump_data.clone();
|
||||
move |editor, e: &ClickEvent, window, cx| {
|
||||
editor.open_excerpts_common(
|
||||
Some(jump_data.clone()),
|
||||
e.modifiers().secondary(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
})),
|
||||
)
|
||||
},
|
||||
)
|
||||
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
|
||||
.on_click(window.listener_for(&self.editor, {
|
||||
let buffer_id = for_excerpt.buffer_id;
|
||||
move |editor, e: &ClickEvent, window, cx| {
|
||||
if e.modifiers().alt {
|
||||
editor.open_excerpts_common(
|
||||
Some(jump_data.clone()),
|
||||
e.modifiers().secondary(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if is_folded {
|
||||
editor.unfold_buffer(buffer_id, cx);
|
||||
} else {
|
||||
editor.fold_buffer(buffer_id, cx);
|
||||
}
|
||||
}
|
||||
})),
|
||||
),
|
||||
);
|
||||
|
||||
let file = for_excerpt.buffer.file().cloned();
|
||||
let editor = self.editor.clone();
|
||||
|
||||
right_click_menu("buffer-header-context-menu")
|
||||
.trigger(move |_, _, _| header)
|
||||
.menu(move |window, cx| {
|
||||
let menu_context = focus_handle.clone();
|
||||
let editor = editor.clone();
|
||||
let file = file.clone();
|
||||
ContextMenu::build(window, cx, move |mut menu, window, cx| {
|
||||
if let Some(file) = file
|
||||
&& let Some(project) = editor.read(cx).project()
|
||||
&& let Some(worktree) =
|
||||
project.read(cx).worktree_for_id(file.worktree_id(cx), cx)
|
||||
{
|
||||
let path_style = file.path_style(cx);
|
||||
let worktree = worktree.read(cx);
|
||||
let relative_path = file.path();
|
||||
let entry_for_path = worktree.entry_for_path(relative_path);
|
||||
let abs_path = entry_for_path.map(|e| {
|
||||
e.canonical_path.as_deref().map_or_else(
|
||||
|| worktree.absolutize(relative_path),
|
||||
Path::to_path_buf,
|
||||
)
|
||||
});
|
||||
let has_relative_path = worktree.root_entry().is_some_and(Entry::is_dir);
|
||||
|
||||
let parent_abs_path = abs_path
|
||||
.as_ref()
|
||||
.and_then(|abs_path| Some(abs_path.parent()?.to_path_buf()));
|
||||
let relative_path = has_relative_path
|
||||
.then_some(relative_path)
|
||||
.map(ToOwned::to_owned);
|
||||
|
||||
let visible_in_project_panel =
|
||||
relative_path.is_some() && worktree.is_visible();
|
||||
let reveal_in_project_panel = entry_for_path
|
||||
.filter(|_| visible_in_project_panel)
|
||||
.map(|entry| entry.id);
|
||||
menu = menu
|
||||
.when_some(abs_path, |menu, abs_path| {
|
||||
menu.entry(
|
||||
"Copy Path",
|
||||
Some(Box::new(zed_actions::workspace::CopyPath)),
|
||||
window.handler_for(&editor, move |_, _, cx| {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(
|
||||
abs_path.to_string_lossy().into_owned(),
|
||||
));
|
||||
}),
|
||||
)
|
||||
})
|
||||
.when_some(relative_path, |menu, relative_path| {
|
||||
menu.entry(
|
||||
"Copy Relative Path",
|
||||
Some(Box::new(zed_actions::workspace::CopyRelativePath)),
|
||||
window.handler_for(&editor, move |_, _, cx| {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(
|
||||
relative_path.display(path_style).to_string(),
|
||||
));
|
||||
}),
|
||||
)
|
||||
})
|
||||
.when(
|
||||
reveal_in_project_panel.is_some() || parent_abs_path.is_some(),
|
||||
|menu| menu.separator(),
|
||||
)
|
||||
.when_some(reveal_in_project_panel, |menu, entry_id| {
|
||||
menu.entry(
|
||||
"Reveal In Project Panel",
|
||||
Some(Box::new(RevealInProjectPanel::default())),
|
||||
window.handler_for(&editor, move |editor, _, cx| {
|
||||
if let Some(project) = &mut editor.project {
|
||||
project.update(cx, |_, cx| {
|
||||
cx.emit(project::Event::RevealInProjectPanel(
|
||||
entry_id,
|
||||
))
|
||||
});
|
||||
}
|
||||
}),
|
||||
)
|
||||
})
|
||||
.when_some(parent_abs_path, |menu, parent_abs_path| {
|
||||
menu.entry(
|
||||
"Open in Terminal",
|
||||
Some(Box::new(OpenInTerminal)),
|
||||
window.handler_for(&editor, move |_, window, cx| {
|
||||
window.dispatch_action(
|
||||
OpenTerminal {
|
||||
working_directory: parent_abs_path.clone(),
|
||||
local: false,
|
||||
}
|
||||
.boxed_clone(),
|
||||
cx,
|
||||
);
|
||||
}),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
menu.context(menu_context)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn header_jump_data(
|
||||
editor_snapshot: &EditorSnapshot,
|
||||
block_row_start: DisplayRow,
|
||||
height: u32,
|
||||
first_excerpt: &ExcerptInfo,
|
||||
latest_selection_anchors: &HashMap<BufferId, Anchor>,
|
||||
) -> JumpData {
|
||||
let jump_target = if let Some(anchor) = latest_selection_anchors.get(&first_excerpt.buffer_id)
|
||||
&& let Some(range) = editor_snapshot.context_range_for_excerpt(anchor.excerpt_id)
|
||||
&& let Some(buffer) = editor_snapshot
|
||||
.buffer_snapshot()
|
||||
.buffer_for_excerpt(anchor.excerpt_id)
|
||||
{
|
||||
JumpTargetInExcerptInput {
|
||||
id: anchor.excerpt_id,
|
||||
buffer,
|
||||
excerpt_start_anchor: range.start,
|
||||
jump_anchor: anchor.text_anchor,
|
||||
}
|
||||
} else {
|
||||
JumpTargetInExcerptInput {
|
||||
id: first_excerpt.id,
|
||||
buffer: &first_excerpt.buffer,
|
||||
excerpt_start_anchor: first_excerpt.range.context.start,
|
||||
jump_anchor: first_excerpt.range.primary.start,
|
||||
}
|
||||
};
|
||||
header_jump_data_inner(editor_snapshot, block_row_start, height, &jump_target)
|
||||
}
|
||||
|
||||
struct JumpTargetInExcerptInput<'a> {
|
||||
id: ExcerptId,
|
||||
buffer: &'a language::BufferSnapshot,
|
||||
excerpt_start_anchor: text::Anchor,
|
||||
jump_anchor: text::Anchor,
|
||||
}
|
||||
|
||||
fn header_jump_data_inner(
|
||||
snapshot: &EditorSnapshot,
|
||||
block_row_start: DisplayRow,
|
||||
height: u32,
|
||||
for_excerpt: &JumpTargetInExcerptInput,
|
||||
) -> JumpData {
|
||||
let buffer = &for_excerpt.buffer;
|
||||
let jump_position = language::ToPoint::to_point(&for_excerpt.jump_anchor, buffer);
|
||||
let excerpt_start = for_excerpt.excerpt_start_anchor;
|
||||
let rows_from_excerpt_start = if for_excerpt.jump_anchor == excerpt_start {
|
||||
0
|
||||
} else {
|
||||
let excerpt_start_point = language::ToPoint::to_point(&excerpt_start, buffer);
|
||||
jump_position.row.saturating_sub(excerpt_start_point.row)
|
||||
};
|
||||
|
||||
let line_offset_from_top = (block_row_start.0 + height + rows_from_excerpt_start)
|
||||
.saturating_sub(
|
||||
snapshot
|
||||
.scroll_anchor
|
||||
.scroll_position(&snapshot.display_snapshot)
|
||||
.y as u32,
|
||||
);
|
||||
|
||||
JumpData::MultiBufferPoint {
|
||||
excerpt_id: for_excerpt.id,
|
||||
anchor: for_excerpt.jump_anchor,
|
||||
position: jump_position,
|
||||
line_offset_from_top,
|
||||
}
|
||||
}
|
||||
|
||||
fn file_status_label_color(file_status: Option<FileStatus>) -> Color {
|
||||
file_status.map_or(Color::Default, |status| {
|
||||
if status.is_conflicted() {
|
||||
Color::Conflict
|
||||
} else if status.is_modified() {
|
||||
Color::Modified
|
||||
} else if status.is_deleted() {
|
||||
Color::Disabled
|
||||
} else if status.is_created() {
|
||||
Color::Created
|
||||
} else {
|
||||
Color::Default
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user