Zed2 breadcrumbs & tab bar context menus & pane focus events (#3475)
See title Release Notes: - N/A
This commit is contained in:
Generated
+18
@@ -1095,6 +1095,23 @@ dependencies = [
|
||||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "breadcrumbs2"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"collections",
|
||||
"editor2",
|
||||
"gpui2",
|
||||
"itertools 0.10.5",
|
||||
"language2",
|
||||
"project2",
|
||||
"search2",
|
||||
"settings2",
|
||||
"theme2",
|
||||
"ui2",
|
||||
"workspace2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bromberg_sl2"
|
||||
version = "0.6.0"
|
||||
@@ -11726,6 +11743,7 @@ dependencies = [
|
||||
"audio2",
|
||||
"auto_update2",
|
||||
"backtrace",
|
||||
"breadcrumbs2",
|
||||
"call2",
|
||||
"channel2",
|
||||
"chrono",
|
||||
|
||||
@@ -9,6 +9,7 @@ members = [
|
||||
"crates/auto_update",
|
||||
"crates/auto_update2",
|
||||
"crates/breadcrumbs",
|
||||
"crates/breadcrumbs2",
|
||||
"crates/call",
|
||||
"crates/call2",
|
||||
"crates/channel",
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
[package]
|
||||
name = "breadcrumbs2"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
path = "src/breadcrumbs.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
collections = { path = "../collections" }
|
||||
editor = { package = "editor2", path = "../editor2" }
|
||||
gpui = { package = "gpui2", path = "../gpui2" }
|
||||
ui = { package = "ui2", path = "../ui2" }
|
||||
language = { package = "language2", path = "../language2" }
|
||||
project = { package = "project2", path = "../project2" }
|
||||
search = { package = "search2", path = "../search2" }
|
||||
settings = { package = "settings2", path = "../settings2" }
|
||||
theme = { package = "theme2", path = "../theme2" }
|
||||
workspace = { package = "workspace2", path = "../workspace2" }
|
||||
# outline = { path = "../outline" }
|
||||
itertools = "0.10"
|
||||
|
||||
[dev-dependencies]
|
||||
editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
|
||||
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
||||
workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
|
||||
@@ -0,0 +1,204 @@
|
||||
use gpui::{
|
||||
Component, Element, EventEmitter, IntoElement, ParentElement, Render, StyledText, Subscription,
|
||||
ViewContext, WeakView,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use theme::ActiveTheme;
|
||||
use ui::{ButtonCommon, ButtonLike, ButtonStyle, Clickable, Disableable, Label};
|
||||
use workspace::{
|
||||
item::{ItemEvent, ItemHandle},
|
||||
ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
|
||||
};
|
||||
|
||||
pub enum Event {
|
||||
UpdateLocation,
|
||||
}
|
||||
|
||||
pub struct Breadcrumbs {
|
||||
pane_focused: bool,
|
||||
active_item: Option<Box<dyn ItemHandle>>,
|
||||
subscription: Option<Subscription>,
|
||||
_workspace: WeakView<Workspace>,
|
||||
}
|
||||
|
||||
impl Breadcrumbs {
|
||||
pub fn new(workspace: &Workspace) -> Self {
|
||||
Self {
|
||||
pane_focused: false,
|
||||
active_item: Default::default(),
|
||||
subscription: Default::default(),
|
||||
_workspace: workspace.weak_handle(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<Event> for Breadcrumbs {}
|
||||
impl EventEmitter<ToolbarItemEvent> for Breadcrumbs {}
|
||||
|
||||
impl Render for Breadcrumbs {
|
||||
type Element = Component<ButtonLike>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
let button = ButtonLike::new("breadcrumbs")
|
||||
.style(ButtonStyle::Transparent)
|
||||
.disabled(true);
|
||||
|
||||
let active_item = match &self.active_item {
|
||||
Some(active_item) => active_item,
|
||||
None => return button.into_element(),
|
||||
};
|
||||
let not_editor = active_item.downcast::<editor::Editor>().is_none();
|
||||
|
||||
let breadcrumbs = match active_item.breadcrumbs(cx.theme(), cx) {
|
||||
Some(breadcrumbs) => breadcrumbs,
|
||||
None => return button.into_element(),
|
||||
}
|
||||
.into_iter()
|
||||
.map(|breadcrumb| {
|
||||
StyledText::new(breadcrumb.text)
|
||||
.with_highlights(&cx.text_style(), breadcrumb.highlights.unwrap_or_default())
|
||||
.into_any()
|
||||
});
|
||||
|
||||
let button = button.children(Itertools::intersperse_with(breadcrumbs, || {
|
||||
Label::new(" › ").into_any_element()
|
||||
}));
|
||||
|
||||
if not_editor || !self.pane_focused {
|
||||
return button.into_element();
|
||||
}
|
||||
|
||||
// let this = cx.view().downgrade();
|
||||
button
|
||||
.style(ButtonStyle::Filled)
|
||||
.disabled(false)
|
||||
.on_click(move |_, _cx| {
|
||||
todo!("outline::toggle");
|
||||
// this.update(cx, |this, cx| {
|
||||
// if let Some(workspace) = this.workspace.upgrade() {
|
||||
// workspace.update(cx, |_workspace, _cx| {
|
||||
// outline::toggle(workspace, &Default::default(), cx)
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
// .ok();
|
||||
})
|
||||
.into_element()
|
||||
}
|
||||
}
|
||||
|
||||
// impl View for Breadcrumbs {
|
||||
// fn ui_name() -> &'static str {
|
||||
// "Breadcrumbs"
|
||||
// }
|
||||
|
||||
// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
// let active_item = match &self.active_item {
|
||||
// Some(active_item) => active_item,
|
||||
// None => return Empty::new().into_any(),
|
||||
// };
|
||||
// let not_editor = active_item.downcast::<editor::Editor>().is_none();
|
||||
|
||||
// let theme = theme::current(cx).clone();
|
||||
// let style = &theme.workspace.toolbar.breadcrumbs;
|
||||
|
||||
// let breadcrumbs = match active_item.breadcrumbs(&theme, cx) {
|
||||
// Some(breadcrumbs) => breadcrumbs,
|
||||
// None => return Empty::new().into_any(),
|
||||
// }
|
||||
// .into_iter()
|
||||
// .map(|breadcrumb| {
|
||||
// Text::new(
|
||||
// breadcrumb.text,
|
||||
// theme.workspace.toolbar.breadcrumbs.default.text.clone(),
|
||||
// )
|
||||
// .with_highlights(breadcrumb.highlights.unwrap_or_default())
|
||||
// .into_any()
|
||||
// });
|
||||
|
||||
// let crumbs = Flex::row()
|
||||
// .with_children(Itertools::intersperse_with(breadcrumbs, || {
|
||||
// Label::new(" › ", style.default.text.clone()).into_any()
|
||||
// }))
|
||||
// .constrained()
|
||||
// .with_height(theme.workspace.toolbar.breadcrumb_height)
|
||||
// .contained();
|
||||
|
||||
// if not_editor || !self.pane_focused {
|
||||
// return crumbs
|
||||
// .with_style(style.default.container)
|
||||
// .aligned()
|
||||
// .left()
|
||||
// .into_any();
|
||||
// }
|
||||
|
||||
// MouseEventHandler::new::<Breadcrumbs, _>(0, cx, |state, _| {
|
||||
// let style = style.style_for(state);
|
||||
// crumbs.with_style(style.container)
|
||||
// })
|
||||
// .on_click(MouseButton::Left, |_, this, cx| {
|
||||
// if let Some(workspace) = this.workspace.upgrade(cx) {
|
||||
// workspace.update(cx, |workspace, cx| {
|
||||
// outline::toggle(workspace, &Default::default(), cx)
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
// .with_tooltip::<Breadcrumbs>(
|
||||
// 0,
|
||||
// "Show symbol outline".to_owned(),
|
||||
// Some(Box::new(outline::Toggle)),
|
||||
// theme.tooltip.clone(),
|
||||
// cx,
|
||||
// )
|
||||
// .aligned()
|
||||
// .left()
|
||||
// .into_any()
|
||||
// }
|
||||
// }
|
||||
|
||||
impl ToolbarItemView for Breadcrumbs {
|
||||
fn set_active_pane_item(
|
||||
&mut self,
|
||||
active_pane_item: Option<&dyn ItemHandle>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> ToolbarItemLocation {
|
||||
cx.notify();
|
||||
self.active_item = None;
|
||||
if let Some(item) = active_pane_item {
|
||||
let this = cx.view().downgrade();
|
||||
self.subscription = Some(item.subscribe_to_item_events(
|
||||
cx,
|
||||
Box::new(move |event, cx| {
|
||||
if let ItemEvent::UpdateBreadcrumbs = event {
|
||||
this.update(cx, |_, cx| {
|
||||
cx.emit(Event::UpdateLocation);
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}),
|
||||
));
|
||||
self.active_item = Some(item.boxed_clone());
|
||||
item.breadcrumb_location(cx)
|
||||
} else {
|
||||
ToolbarItemLocation::Hidden
|
||||
}
|
||||
}
|
||||
|
||||
// fn location_for_event(
|
||||
// &self,
|
||||
// _: &Event,
|
||||
// current_location: ToolbarItemLocation,
|
||||
// cx: &AppContext,
|
||||
// ) -> ToolbarItemLocation {
|
||||
// if let Some(active_item) = self.active_item.as_ref() {
|
||||
// active_item.breadcrumb_location(cx)
|
||||
// } else {
|
||||
// current_location
|
||||
// }
|
||||
// }
|
||||
|
||||
fn pane_focus_update(&mut self, pane_focused: bool, _: &mut ViewContext<Self>) {
|
||||
self.pane_focused = pane_focused;
|
||||
}
|
||||
}
|
||||
@@ -346,7 +346,7 @@ impl<T: Entity> Drop for PendingEntitySubscription<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct TelemetrySettings {
|
||||
pub diagnostics: bool,
|
||||
pub metrics: bool,
|
||||
|
||||
@@ -350,6 +350,7 @@ impl Telemetry {
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
||||
};
|
||||
|
||||
dbg!(telemetry_settings);
|
||||
self.report_clickhouse_event(event, telemetry_settings, true)
|
||||
}
|
||||
|
||||
|
||||
@@ -154,7 +154,6 @@ pub fn render_parsed_markdown(
|
||||
}
|
||||
}),
|
||||
);
|
||||
let runs = text_runs_for_highlights(&parsed.text, &editor_style.text, highlights);
|
||||
|
||||
let mut links = Vec::new();
|
||||
let mut link_ranges = Vec::new();
|
||||
@@ -167,7 +166,7 @@ pub fn render_parsed_markdown(
|
||||
|
||||
InteractiveText::new(
|
||||
element_id,
|
||||
StyledText::new(parsed.text.clone()).with_runs(runs),
|
||||
StyledText::new(parsed.text.clone()).with_highlights(&editor_style.text, highlights),
|
||||
)
|
||||
.on_click(link_ranges, move |clicked_range_ix, cx| {
|
||||
match &links[clicked_range_ix] {
|
||||
@@ -1199,11 +1198,7 @@ impl CompletionsMenu {
|
||||
),
|
||||
);
|
||||
let completion_label = StyledText::new(completion.label.text.clone())
|
||||
.with_runs(text_runs_for_highlights(
|
||||
&completion.label.text,
|
||||
&style.text,
|
||||
highlights,
|
||||
));
|
||||
.with_highlights(&style.text, highlights);
|
||||
let documentation_label =
|
||||
if let Some(Documentation::SingleLine(text)) = documentation {
|
||||
Some(SharedString::from(text.clone()))
|
||||
@@ -9755,31 +9750,6 @@ pub fn diagnostic_style(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text_runs_for_highlights(
|
||||
text: &str,
|
||||
default_style: &TextStyle,
|
||||
highlights: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>,
|
||||
) -> Vec<TextRun> {
|
||||
let mut runs = Vec::new();
|
||||
let mut ix = 0;
|
||||
for (range, highlight) in highlights {
|
||||
if ix < range.start {
|
||||
runs.push(default_style.clone().to_run(range.start - ix));
|
||||
}
|
||||
runs.push(
|
||||
default_style
|
||||
.clone()
|
||||
.highlight(highlight)
|
||||
.to_run(range.len()),
|
||||
);
|
||||
ix = range.end;
|
||||
}
|
||||
if ix < text.len() {
|
||||
runs.push(default_style.to_run(text.len() - ix));
|
||||
}
|
||||
runs
|
||||
}
|
||||
|
||||
pub fn styled_runs_for_code_label<'a>(
|
||||
label: &'a CodeLabel,
|
||||
syntax_theme: &'a theme::SyntaxTheme,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::{
|
||||
Bounds, DispatchPhase, Element, ElementId, IntoElement, LayoutId, MouseDownEvent, MouseUpEvent,
|
||||
Pixels, Point, SharedString, Size, TextRun, WhiteSpace, WindowContext, WrappedLine,
|
||||
Bounds, DispatchPhase, Element, ElementId, HighlightStyle, IntoElement, LayoutId,
|
||||
MouseDownEvent, MouseUpEvent, Pixels, Point, SharedString, Size, TextRun, TextStyle,
|
||||
WhiteSpace, WindowContext, WrappedLine,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use parking_lot::{Mutex, MutexGuard};
|
||||
@@ -87,7 +88,28 @@ impl StyledText {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_runs(mut self, runs: Vec<TextRun>) -> Self {
|
||||
pub fn with_highlights(
|
||||
mut self,
|
||||
default_style: &TextStyle,
|
||||
highlights: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>,
|
||||
) -> Self {
|
||||
let mut runs = Vec::new();
|
||||
let mut ix = 0;
|
||||
for (range, highlight) in highlights {
|
||||
if ix < range.start {
|
||||
runs.push(default_style.clone().to_run(range.start - ix));
|
||||
}
|
||||
runs.push(
|
||||
default_style
|
||||
.clone()
|
||||
.highlight(highlight)
|
||||
.to_run(range.len()),
|
||||
);
|
||||
ix = range.end;
|
||||
}
|
||||
if ix < self.text.len() {
|
||||
runs.push(default_style.to_run(self.text.len() - ix));
|
||||
}
|
||||
self.runs = Some(runs);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use gpui::{
|
||||
blue, div, green, red, white, Div, InteractiveText, ParentElement, Render, Styled, StyledText,
|
||||
TextRun, View, VisualContext, WindowContext,
|
||||
blue, div, green, red, white, Div, HighlightStyle, InteractiveText, ParentElement, Render,
|
||||
Styled, StyledText, View, VisualContext, WindowContext,
|
||||
};
|
||||
use ui::v_stack;
|
||||
|
||||
@@ -59,13 +59,11 @@ impl Render for TextStory {
|
||||
))).child(
|
||||
InteractiveText::new(
|
||||
"interactive",
|
||||
StyledText::new("Hello world, how is it going?").with_runs(vec![
|
||||
cx.text_style().to_run(6),
|
||||
TextRun {
|
||||
StyledText::new("Hello world, how is it going?").with_highlights(&cx.text_style(), [
|
||||
(6..11, HighlightStyle {
|
||||
background_color: Some(green()),
|
||||
..cx.text_style().to_run(5)
|
||||
},
|
||||
cx.text_style().to_run(18),
|
||||
..Default::default()
|
||||
}),
|
||||
]),
|
||||
)
|
||||
.on_click(vec![2..4, 1..3, 7..9], |range_ix, _cx| {
|
||||
|
||||
@@ -317,7 +317,7 @@ impl RenderOnce for ButtonLike {
|
||||
.id(self.id.clone())
|
||||
.h(self.size.height())
|
||||
.rounded_md()
|
||||
.cursor_pointer()
|
||||
.when(!self.disabled, |el| el.cursor_pointer())
|
||||
.gap_1()
|
||||
.px_1()
|
||||
.bg(self.style.enabled(cx).background)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::styled_ext::StyledExt;
|
||||
use gpui::{relative, Div, IntoElement, StyledText, TextRun, WindowContext};
|
||||
use gpui::{relative, Div, HighlightStyle, IntoElement, StyledText, WindowContext};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
|
||||
pub enum LabelSize {
|
||||
@@ -99,38 +101,32 @@ impl RenderOnce for HighlightedLabel {
|
||||
|
||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
||||
let highlight_color = cx.theme().colors().text_accent;
|
||||
let mut text_style = cx.text_style().clone();
|
||||
|
||||
let mut highlight_indices = self.highlight_indices.iter().copied().peekable();
|
||||
let mut highlights: Vec<(Range<usize>, HighlightStyle)> = Vec::new();
|
||||
|
||||
let mut runs: Vec<TextRun> = Vec::new();
|
||||
while let Some(start_ix) = highlight_indices.next() {
|
||||
let mut end_ix = start_ix;
|
||||
|
||||
for (char_ix, char) in self.label.char_indices() {
|
||||
let mut color = self.color.color(cx);
|
||||
|
||||
if let Some(highlight_ix) = highlight_indices.peek() {
|
||||
if char_ix == *highlight_ix {
|
||||
color = highlight_color;
|
||||
highlight_indices.next();
|
||||
loop {
|
||||
end_ix = end_ix + self.label[end_ix..].chars().next().unwrap().len_utf8();
|
||||
if let Some(&next_ix) = highlight_indices.peek() {
|
||||
if next_ix == end_ix {
|
||||
end_ix = next_ix;
|
||||
highlight_indices.next();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
let last_run = runs.last_mut();
|
||||
let start_new_run = if let Some(last_run) = last_run {
|
||||
if color == last_run.color {
|
||||
last_run.len += char.len_utf8();
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
if start_new_run {
|
||||
text_style.color = color;
|
||||
runs.push(text_style.to_run(char.len_utf8()))
|
||||
}
|
||||
highlights.push((
|
||||
start_ix..end_ix,
|
||||
HighlightStyle {
|
||||
color: Some(highlight_color),
|
||||
..Default::default()
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
div()
|
||||
@@ -150,7 +146,7 @@ impl RenderOnce for HighlightedLabel {
|
||||
LabelSize::Default => this.text_ui(),
|
||||
LabelSize::Small => this.text_ui_sm(),
|
||||
})
|
||||
.child(StyledText::new(self.label).with_runs(runs))
|
||||
.child(StyledText::new(self.label).with_highlights(&cx.text_style(), highlights))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+230
-48
@@ -2,14 +2,15 @@ use crate::{
|
||||
item::{Item, ItemHandle, ItemSettings, WeakItemHandle},
|
||||
toolbar::Toolbar,
|
||||
workspace_settings::{AutosaveSetting, WorkspaceSettings},
|
||||
SplitDirection, Workspace,
|
||||
NewCenterTerminal, NewFile, NewSearch, SplitDirection, ToggleZoom, Workspace,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use collections::{HashMap, HashSet, VecDeque};
|
||||
use gpui::{
|
||||
actions, prelude::*, Action, AppContext, AsyncWindowContext, Div, EntityId, EventEmitter,
|
||||
FocusHandle, Focusable, FocusableView, Model, Pixels, Point, PromptLevel, Render, Task, View,
|
||||
ViewContext, VisualContext, WeakView, WindowContext,
|
||||
actions, overlay, prelude::*, Action, AnchorCorner, AnyWeakView, AppContext,
|
||||
AsyncWindowContext, DismissEvent, Div, EntityId, EventEmitter, FocusHandle, Focusable,
|
||||
FocusableView, Model, Pixels, Point, PromptLevel, Render, Task, View, ViewContext,
|
||||
VisualContext, WeakView, WindowContext,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use project::{Project, ProjectEntryId, ProjectPath};
|
||||
@@ -25,8 +26,8 @@ use std::{
|
||||
},
|
||||
};
|
||||
|
||||
use ui::v_stack;
|
||||
use ui::{prelude::*, Color, Icon, IconButton, IconElement, Tooltip};
|
||||
use ui::{prelude::*, right_click_menu, Color, Icon, IconButton, IconElement, Tooltip};
|
||||
use ui::{v_stack, ContextMenu};
|
||||
use util::truncate_and_remove_front;
|
||||
|
||||
#[derive(PartialEq, Clone, Copy, Deserialize, Debug)]
|
||||
@@ -50,7 +51,7 @@ pub enum SaveIntent {
|
||||
|
||||
//todo!("Do we need the default bound on actions? Decide soon")
|
||||
// #[register_action]
|
||||
#[derive(Clone, Deserialize, PartialEq, Debug)]
|
||||
#[derive(Action, Clone, Deserialize, PartialEq, Debug)]
|
||||
pub struct ActivateItem(pub usize);
|
||||
|
||||
// #[derive(Clone, PartialEq)]
|
||||
@@ -143,17 +144,24 @@ impl fmt::Debug for Event {
|
||||
}
|
||||
}
|
||||
|
||||
struct FocusedView {
|
||||
view: AnyWeakView,
|
||||
focus_handle: FocusHandle,
|
||||
}
|
||||
|
||||
pub struct Pane {
|
||||
focus_handle: FocusHandle,
|
||||
items: Vec<Box<dyn ItemHandle>>,
|
||||
activation_history: Vec<EntityId>,
|
||||
zoomed: bool,
|
||||
active_item_index: usize,
|
||||
// last_focused_view_by_item: HashMap<usize, AnyWeakViewHandle>,
|
||||
last_focused_view_by_item: HashMap<EntityId, FocusHandle>,
|
||||
autoscroll: bool,
|
||||
nav_history: NavHistory,
|
||||
toolbar: View<Toolbar>,
|
||||
// tab_bar_context_menu: TabBarContextMenu,
|
||||
tab_bar_focus_handle: FocusHandle,
|
||||
new_item_menu: Option<View<ContextMenu>>,
|
||||
split_item_menu: Option<View<ContextMenu>>,
|
||||
// tab_context_menu: ViewHandle<ContextMenu>,
|
||||
workspace: WeakView<Workspace>,
|
||||
project: Model<Project>,
|
||||
@@ -306,7 +314,7 @@ impl Pane {
|
||||
activation_history: Vec::new(),
|
||||
zoomed: false,
|
||||
active_item_index: 0,
|
||||
// last_focused_view_by_item: Default::default(),
|
||||
last_focused_view_by_item: Default::default(),
|
||||
autoscroll: false,
|
||||
nav_history: NavHistory(Arc::new(Mutex::new(NavHistoryState {
|
||||
mode: NavigationMode::Normal,
|
||||
@@ -318,6 +326,9 @@ impl Pane {
|
||||
next_timestamp,
|
||||
}))),
|
||||
toolbar: cx.build_view(|_| Toolbar::new()),
|
||||
tab_bar_focus_handle: cx.focus_handle(),
|
||||
new_item_menu: None,
|
||||
split_item_menu: None,
|
||||
// tab_bar_context_menu: TabBarContextMenu {
|
||||
// kind: TabBarContextMenuKind::New,
|
||||
// handle: context_menu,
|
||||
@@ -392,9 +403,48 @@ impl Pane {
|
||||
}
|
||||
|
||||
pub fn has_focus(&self, cx: &WindowContext) -> bool {
|
||||
// todo!(); // inline this manually
|
||||
self.focus_handle.contains_focused(cx)
|
||||
}
|
||||
|
||||
fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if !self.has_focus(cx) {
|
||||
cx.emit(Event::Focus);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
self.toolbar.update(cx, |toolbar, cx| {
|
||||
toolbar.focus_changed(true, cx);
|
||||
});
|
||||
|
||||
if let Some(active_item) = self.active_item() {
|
||||
if self.focus_handle.is_focused(cx) {
|
||||
// Pane was focused directly. We need to either focus a view inside the active item,
|
||||
// or focus the active item itself
|
||||
if let Some(weak_last_focused_view) =
|
||||
self.last_focused_view_by_item.get(&active_item.item_id())
|
||||
{
|
||||
weak_last_focused_view.focus(cx);
|
||||
return;
|
||||
}
|
||||
|
||||
active_item.focus_handle(cx).focus(cx);
|
||||
} else if !self.tab_bar_focus_handle.contains_focused(cx) {
|
||||
if let Some(focused) = cx.focused() {
|
||||
self.last_focused_view_by_item
|
||||
.insert(active_item.item_id(), focused);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.toolbar.update(cx, |toolbar, cx| {
|
||||
toolbar.focus_changed(false, cx);
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn active_item_index(&self) -> usize {
|
||||
self.active_item_index
|
||||
}
|
||||
@@ -652,21 +702,16 @@ impl Pane {
|
||||
.position(|i| i.item_id() == item.item_id())
|
||||
}
|
||||
|
||||
// pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) {
|
||||
// // Potentially warn the user of the new keybinding
|
||||
// let workspace_handle = self.workspace().clone();
|
||||
// cx.spawn(|_, mut cx| async move { notify_of_new_dock(&workspace_handle, &mut cx) })
|
||||
// .detach();
|
||||
|
||||
// if self.zoomed {
|
||||
// cx.emit(Event::ZoomOut);
|
||||
// } else if !self.items.is_empty() {
|
||||
// if !self.has_focus {
|
||||
// cx.focus_self();
|
||||
// }
|
||||
// cx.emit(Event::ZoomIn);
|
||||
// }
|
||||
// }
|
||||
pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) {
|
||||
if self.zoomed {
|
||||
cx.emit(Event::ZoomOut);
|
||||
} else if !self.items.is_empty() {
|
||||
if !self.focus_handle.contains_focused(cx) {
|
||||
cx.focus_self();
|
||||
}
|
||||
cx.emit(Event::ZoomIn);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn activate_item(
|
||||
&mut self,
|
||||
@@ -1403,7 +1448,7 @@ impl Pane {
|
||||
let close_right = ItemSettings::get_global(cx).close_position.right();
|
||||
let is_active = ix == self.active_item_index;
|
||||
|
||||
div()
|
||||
let tab = div()
|
||||
.group("")
|
||||
.id(ix)
|
||||
.cursor_pointer()
|
||||
@@ -1477,13 +1522,41 @@ impl Pane {
|
||||
.children((!close_right).then(|| close_icon()))
|
||||
.child(label)
|
||||
.children(close_right.then(|| close_icon())),
|
||||
)
|
||||
);
|
||||
|
||||
right_click_menu(ix).trigger(tab).menu(|cx| {
|
||||
ContextMenu::build(cx, |menu, cx| {
|
||||
menu.action(
|
||||
"Close Active Item",
|
||||
CloseActiveItem { save_intent: None }.boxed_clone(),
|
||||
cx,
|
||||
)
|
||||
.action("Close Inactive Items", CloseInactiveItems.boxed_clone(), cx)
|
||||
.action("Close Clean Items", CloseCleanItems.boxed_clone(), cx)
|
||||
.action(
|
||||
"Close Items To The Left",
|
||||
CloseItemsToTheLeft.boxed_clone(),
|
||||
cx,
|
||||
)
|
||||
.action(
|
||||
"Close Items To The Right",
|
||||
CloseItemsToTheRight.boxed_clone(),
|
||||
cx,
|
||||
)
|
||||
.action(
|
||||
"Close All Items",
|
||||
CloseAllItems { save_intent: None }.boxed_clone(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn render_tab_bar(&mut self, cx: &mut ViewContext<'_, Pane>) -> impl IntoElement {
|
||||
div()
|
||||
.group("tab_bar")
|
||||
.id("tab_bar")
|
||||
.track_focus(&self.tab_bar_focus_handle)
|
||||
.w_full()
|
||||
.flex()
|
||||
.bg(cx.theme().colors().tab_bar_background)
|
||||
@@ -1550,20 +1623,87 @@ impl Pane {
|
||||
.gap_px()
|
||||
.child(
|
||||
div()
|
||||
.bg(gpui::blue())
|
||||
.border()
|
||||
.border_color(gpui::red())
|
||||
.child(IconButton::new("plus", Icon::Plus)),
|
||||
.child(IconButton::new("plus", Icon::Plus).on_click(
|
||||
cx.listener(|this, _, cx| {
|
||||
let menu = ContextMenu::build(cx, |menu, cx| {
|
||||
menu.action("New File", NewFile.boxed_clone(), cx)
|
||||
.action(
|
||||
"New Terminal",
|
||||
NewCenterTerminal.boxed_clone(),
|
||||
cx,
|
||||
)
|
||||
.action(
|
||||
"New Search",
|
||||
NewSearch.boxed_clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
cx.subscribe(
|
||||
&menu,
|
||||
|this, _, event: &DismissEvent, cx| {
|
||||
this.focus(cx);
|
||||
this.new_item_menu = None;
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
this.new_item_menu = Some(menu);
|
||||
}),
|
||||
))
|
||||
.when_some(self.new_item_menu.as_ref(), |el, new_item_menu| {
|
||||
el.child(Self::render_menu_overlay(new_item_menu))
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.border()
|
||||
.border_color(gpui::red())
|
||||
.child(IconButton::new("split", Icon::Split)),
|
||||
.child(IconButton::new("split", Icon::Split).on_click(
|
||||
cx.listener(|this, _, cx| {
|
||||
let menu = ContextMenu::build(cx, |menu, cx| {
|
||||
menu.action(
|
||||
"Split Right",
|
||||
SplitRight.boxed_clone(),
|
||||
cx,
|
||||
)
|
||||
.action("Split Left", SplitLeft.boxed_clone(), cx)
|
||||
.action("Split Up", SplitUp.boxed_clone(), cx)
|
||||
.action("Split Down", SplitDown.boxed_clone(), cx)
|
||||
});
|
||||
cx.subscribe(
|
||||
&menu,
|
||||
|this, _, event: &DismissEvent, cx| {
|
||||
this.focus(cx);
|
||||
this.split_item_menu = None;
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
this.split_item_menu = Some(menu);
|
||||
}),
|
||||
))
|
||||
.when_some(
|
||||
self.split_item_menu.as_ref(),
|
||||
|el, split_item_menu| {
|
||||
el.child(Self::render_menu_overlay(split_item_menu))
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_menu_overlay(menu: &View<ContextMenu>) -> Div {
|
||||
div()
|
||||
.absolute()
|
||||
.z_index(1)
|
||||
.bottom_0()
|
||||
.right_0()
|
||||
.size_0()
|
||||
.child(overlay().anchor(AnchorCorner::TopRight).child(menu.clone()))
|
||||
}
|
||||
|
||||
// fn render_tabs(&mut self, cx: &mut ViewContext<Self>) -> impl Element<Self> {
|
||||
// let theme = theme::current(cx).clone();
|
||||
|
||||
@@ -1962,9 +2102,23 @@ impl Render for Pane {
|
||||
type Element = Focusable<Div>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
let this = cx.view().downgrade();
|
||||
|
||||
v_stack()
|
||||
.key_context("Pane")
|
||||
.track_focus(&self.focus_handle)
|
||||
.on_focus_in({
|
||||
let this = this.clone();
|
||||
move |event, cx| {
|
||||
this.update(cx, |this, cx| this.focus_in(cx)).ok();
|
||||
}
|
||||
})
|
||||
.on_focus_out({
|
||||
let this = this.clone();
|
||||
move |event, cx| {
|
||||
this.update(cx, |this, cx| this.focus_out(cx)).ok();
|
||||
}
|
||||
})
|
||||
.on_action(cx.listener(|pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx)))
|
||||
.on_action(cx.listener(|pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx)))
|
||||
.on_action(
|
||||
@@ -1973,25 +2127,53 @@ impl Render for Pane {
|
||||
.on_action(cx.listener(|pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx)))
|
||||
.on_action(cx.listener(|pane, _: &GoBack, cx| pane.navigate_backward(cx)))
|
||||
.on_action(cx.listener(|pane, _: &GoForward, cx| pane.navigate_forward(cx)))
|
||||
// cx.add_action(Pane::toggle_zoom);
|
||||
// cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| {
|
||||
// pane.activate_item(action.0, true, true, cx);
|
||||
// });
|
||||
// cx.add_action(|pane: &mut Pane, _: &ActivateLastItem, cx| {
|
||||
// pane.activate_item(pane.items.len() - 1, true, true, cx);
|
||||
// });
|
||||
// cx.add_action(|pane: &mut Pane, _: &ActivatePrevItem, cx| {
|
||||
// pane.activate_prev_item(true, cx);
|
||||
// });
|
||||
// cx.add_action(|pane: &mut Pane, _: &ActivateNextItem, cx| {
|
||||
// pane.activate_next_item(true, cx);
|
||||
// });
|
||||
// cx.add_async_action(Pane::close_active_item);
|
||||
// cx.add_async_action(Pane::close_inactive_items);
|
||||
// cx.add_async_action(Pane::close_clean_items);
|
||||
// cx.add_async_action(Pane::close_items_to_the_left);
|
||||
// cx.add_async_action(Pane::close_items_to_the_right);
|
||||
// cx.add_async_action(Pane::close_all_items);
|
||||
.on_action(cx.listener(Pane::toggle_zoom))
|
||||
.on_action(cx.listener(|pane: &mut Pane, action: &ActivateItem, cx| {
|
||||
pane.activate_item(action.0, true, true, cx);
|
||||
}))
|
||||
.on_action(cx.listener(|pane: &mut Pane, _: &ActivateLastItem, cx| {
|
||||
pane.activate_item(pane.items.len() - 1, true, true, cx);
|
||||
}))
|
||||
.on_action(cx.listener(|pane: &mut Pane, _: &ActivatePrevItem, cx| {
|
||||
pane.activate_prev_item(true, cx);
|
||||
}))
|
||||
.on_action(cx.listener(|pane: &mut Pane, _: &ActivateNextItem, cx| {
|
||||
pane.activate_next_item(true, cx);
|
||||
}))
|
||||
.on_action(
|
||||
cx.listener(|pane: &mut Self, action: &CloseActiveItem, cx| {
|
||||
pane.close_active_item(action, cx)
|
||||
.map(|task| task.detach_and_log_err(cx));
|
||||
}),
|
||||
)
|
||||
.on_action(
|
||||
cx.listener(|pane: &mut Self, action: &CloseInactiveItems, cx| {
|
||||
pane.close_inactive_items(action, cx)
|
||||
.map(|task| task.detach_and_log_err(cx));
|
||||
}),
|
||||
)
|
||||
.on_action(
|
||||
cx.listener(|pane: &mut Self, action: &CloseCleanItems, cx| {
|
||||
pane.close_clean_items(action, cx)
|
||||
.map(|task| task.detach_and_log_err(cx));
|
||||
}),
|
||||
)
|
||||
.on_action(
|
||||
cx.listener(|pane: &mut Self, action: &CloseItemsToTheLeft, cx| {
|
||||
pane.close_items_to_the_left(action, cx)
|
||||
.map(|task| task.detach_and_log_err(cx));
|
||||
}),
|
||||
)
|
||||
.on_action(
|
||||
cx.listener(|pane: &mut Self, action: &CloseItemsToTheRight, cx| {
|
||||
pane.close_items_to_the_right(action, cx)
|
||||
.map(|task| task.detach_and_log_err(cx));
|
||||
}),
|
||||
)
|
||||
.on_action(cx.listener(|pane: &mut Self, action: &CloseAllItems, cx| {
|
||||
pane.close_all_items(action, cx)
|
||||
.map(|task| task.detach_and_log_err(cx));
|
||||
}))
|
||||
.size_full()
|
||||
.on_action(
|
||||
cx.listener(|pane: &mut Self, action: &CloseActiveItem, cx| {
|
||||
|
||||
@@ -4,7 +4,7 @@ use gpui::{
|
||||
ViewContext, WindowContext,
|
||||
};
|
||||
use ui::prelude::*;
|
||||
use ui::{h_stack, v_stack, ButtonLike, Color, Icon, IconButton, Label};
|
||||
use ui::{h_stack, v_stack, Icon, IconButton};
|
||||
|
||||
pub enum ToolbarItemEvent {
|
||||
ChangeLocation(ToolbarItemLocation),
|
||||
@@ -87,17 +87,8 @@ impl Render for Toolbar {
|
||||
.child(
|
||||
h_stack()
|
||||
.justify_between()
|
||||
.child(
|
||||
// Toolbar left side
|
||||
h_stack().border().border_color(gpui::red()).p_1().child(
|
||||
ButtonLike::new("breadcrumb")
|
||||
.child(Label::new("crates/workspace2/src/toolbar.rs"))
|
||||
.child(Label::new("›").color(Color::Muted))
|
||||
.child(Label::new("impl Render for Toolbar"))
|
||||
.child(Label::new("›").color(Color::Muted))
|
||||
.child(Label::new("fn render")),
|
||||
),
|
||||
)
|
||||
// Toolbar left side
|
||||
.children(self.items.iter().map(|(child, _)| child.to_any()))
|
||||
// Toolbar right side
|
||||
.child(
|
||||
h_stack()
|
||||
@@ -116,7 +107,6 @@ impl Render for Toolbar {
|
||||
),
|
||||
),
|
||||
)
|
||||
.children(self.items.iter().map(|(child, _)| child.to_any()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3585,87 +3585,6 @@ fn open_items(
|
||||
})
|
||||
}
|
||||
|
||||
// todo!()
|
||||
// fn notify_of_new_dock(workspace: &WeakView<Workspace>, cx: &mut AsyncAppContext) {
|
||||
// const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system";
|
||||
// const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key";
|
||||
// const MESSAGE_ID: usize = 2;
|
||||
|
||||
// if workspace
|
||||
// .read_with(cx, |workspace, cx| {
|
||||
// workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
|
||||
// })
|
||||
// .unwrap_or(false)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// if db::kvp::KEY_VALUE_STORE
|
||||
// .read_kvp(NEW_DOCK_HINT_KEY)
|
||||
// .ok()
|
||||
// .flatten()
|
||||
// .is_some()
|
||||
// {
|
||||
// if !workspace
|
||||
// .read_with(cx, |workspace, cx| {
|
||||
// workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
|
||||
// })
|
||||
// .unwrap_or(false)
|
||||
// {
|
||||
// cx.update(|cx| {
|
||||
// cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
|
||||
// let entry = tracker
|
||||
// .entry(TypeId::of::<MessageNotification>())
|
||||
// .or_default();
|
||||
// if !entry.contains(&MESSAGE_ID) {
|
||||
// entry.push(MESSAGE_ID);
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
// return;
|
||||
// }
|
||||
|
||||
// cx.spawn(|_| async move {
|
||||
// db::kvp::KEY_VALUE_STORE
|
||||
// .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string())
|
||||
// .await
|
||||
// .ok();
|
||||
// })
|
||||
// .detach();
|
||||
|
||||
// workspace
|
||||
// .update(cx, |workspace, cx| {
|
||||
// workspace.show_notification_once(2, cx, |cx| {
|
||||
// cx.build_view(|_| {
|
||||
// MessageNotification::new_element(|text, _| {
|
||||
// Text::new(
|
||||
// "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
|
||||
// text,
|
||||
// )
|
||||
// .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| {
|
||||
// let code_span_background_color = settings::get::<ThemeSettings>(cx)
|
||||
// .theme
|
||||
// .editor
|
||||
// .document_highlight_read_background;
|
||||
|
||||
// cx.scene().push_quad(gpui::Quad {
|
||||
// bounds,
|
||||
// background: Some(code_span_background_color),
|
||||
// border: Default::default(),
|
||||
// corner_radii: (2.0).into(),
|
||||
// })
|
||||
// })
|
||||
// .into_any()
|
||||
// })
|
||||
// .with_click_message("Read more about the new panel system")
|
||||
// .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST))
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
// .ok();
|
||||
|
||||
fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
|
||||
const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ ai = { package = "ai2", path = "../ai2"}
|
||||
audio = { package = "audio2", path = "../audio2" }
|
||||
activity_indicator = { package = "activity_indicator2", path = "../activity_indicator2"}
|
||||
auto_update = { package = "auto_update2", path = "../auto_update2" }
|
||||
# breadcrumbs = { path = "../breadcrumbs" }
|
||||
breadcrumbs = { package = "breadcrumbs2", path = "../breadcrumbs2" }
|
||||
call = { package = "call2", path = "../call2" }
|
||||
channel = { package = "channel2", path = "../channel2" }
|
||||
cli = { path = "../cli" }
|
||||
|
||||
@@ -7,6 +7,7 @@ mod only_instance;
|
||||
mod open_listener;
|
||||
|
||||
pub use assets::*;
|
||||
use breadcrumbs::Breadcrumbs;
|
||||
use collections::VecDeque;
|
||||
use editor::{Editor, MultiBuffer};
|
||||
use gpui::{
|
||||
@@ -95,11 +96,11 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||
if let workspace::Event::PaneAdded(pane) = event {
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.toolbar().update(cx, |toolbar, cx| {
|
||||
// todo!()
|
||||
// let breadcrumbs = cx.add_view(|_| Breadcrumbs::new(workspace));
|
||||
// toolbar.add_item(breadcrumbs, cx);
|
||||
let breadcrumbs = cx.build_view(|_| Breadcrumbs::new(workspace));
|
||||
toolbar.add_item(breadcrumbs, cx);
|
||||
let buffer_search_bar = cx.build_view(search::BufferSearchBar::new);
|
||||
toolbar.add_item(buffer_search_bar.clone(), cx);
|
||||
// todo!()
|
||||
// let quick_action_bar = cx.add_view(|_| {
|
||||
// QuickActionBar::new(buffer_search_bar, workspace)
|
||||
// });
|
||||
|
||||
Reference in New Issue
Block a user