Merge pull request #1613 from zed-industries/breadcrumbs-refactoring
Terminal Breadcrumbs
This commit is contained in:
commit
6555d6f1c9
12 changed files with 270 additions and 203 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -613,6 +613,7 @@ dependencies = [
|
||||||
"collections",
|
"collections",
|
||||||
"editor",
|
"editor",
|
||||||
"gpui",
|
"gpui",
|
||||||
|
"itertools",
|
||||||
"language",
|
"language",
|
||||||
"project",
|
"project",
|
||||||
"search",
|
"search",
|
||||||
|
|
|
@ -17,6 +17,7 @@ search = { path = "../search" }
|
||||||
settings = { path = "../settings" }
|
settings = { path = "../settings" }
|
||||||
theme = { path = "../theme" }
|
theme = { path = "../theme" }
|
||||||
workspace = { path = "../workspace" }
|
workspace = { path = "../workspace" }
|
||||||
|
itertools = "0.10"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
editor = { path = "../editor", features = ["test-support"] }
|
editor = { path = "../editor", features = ["test-support"] }
|
||||||
|
|
|
@ -1,48 +1,29 @@
|
||||||
use editor::{Anchor, Editor};
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
elements::*, AppContext, Entity, ModelHandle, RenderContext, Subscription, View, ViewContext,
|
elements::*, AppContext, Entity, RenderContext, Subscription, View, ViewContext, ViewHandle,
|
||||||
ViewHandle,
|
|
||||||
};
|
};
|
||||||
use language::{Buffer, OutlineItem};
|
use itertools::Itertools;
|
||||||
use project::Project;
|
|
||||||
use search::ProjectSearchView;
|
use search::ProjectSearchView;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use theme::SyntaxTheme;
|
use workspace::{ItemEvent, ItemHandle, ToolbarItemLocation, ToolbarItemView};
|
||||||
use workspace::{ItemHandle, ToolbarItemLocation, ToolbarItemView};
|
|
||||||
|
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
UpdateLocation,
|
UpdateLocation,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Breadcrumbs {
|
pub struct Breadcrumbs {
|
||||||
project: ModelHandle<Project>,
|
active_item: Option<Box<dyn ItemHandle>>,
|
||||||
editor: Option<ViewHandle<Editor>>,
|
|
||||||
project_search: Option<ViewHandle<ProjectSearchView>>,
|
project_search: Option<ViewHandle<ProjectSearchView>>,
|
||||||
subscriptions: Vec<Subscription>,
|
subscription: Option<Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Breadcrumbs {
|
impl Breadcrumbs {
|
||||||
pub fn new(project: ModelHandle<Project>) -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
project,
|
active_item: Default::default(),
|
||||||
editor: Default::default(),
|
subscription: Default::default(),
|
||||||
subscriptions: Default::default(),
|
|
||||||
project_search: Default::default(),
|
project_search: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn active_symbols(
|
|
||||||
&self,
|
|
||||||
theme: &SyntaxTheme,
|
|
||||||
cx: &AppContext,
|
|
||||||
) -> Option<(ModelHandle<Buffer>, Vec<OutlineItem<Anchor>>)> {
|
|
||||||
let editor = self.editor.as_ref()?.read(cx);
|
|
||||||
let cursor = editor.selections.newest_anchor().head();
|
|
||||||
let multibuffer = &editor.buffer().read(cx);
|
|
||||||
let (buffer_id, symbols) = multibuffer.symbols_containing(cursor, Some(theme), cx)?;
|
|
||||||
let buffer = multibuffer.buffer(buffer_id)?;
|
|
||||||
Some((buffer, symbols))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for Breadcrumbs {
|
impl Entity for Breadcrumbs {
|
||||||
|
@ -56,40 +37,23 @@ impl View for Breadcrumbs {
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||||
let theme = cx.global::<Settings>().theme.clone();
|
let theme = cx.global::<Settings>().theme.clone();
|
||||||
let (buffer, symbols) =
|
if let Some(breadcrumbs) = self
|
||||||
if let Some((buffer, symbols)) = self.active_symbols(&theme.editor.syntax, cx) {
|
.active_item
|
||||||
(buffer, symbols)
|
.as_ref()
|
||||||
} else {
|
.and_then(|item| item.breadcrumbs(&theme, cx))
|
||||||
return Empty::new().boxed();
|
{
|
||||||
};
|
Flex::row()
|
||||||
let buffer = buffer.read(cx);
|
.with_children(Itertools::intersperse_with(breadcrumbs.into_iter(), || {
|
||||||
let filename = if let Some(file) = buffer.file() {
|
Label::new(" 〉 ".to_string(), theme.breadcrumbs.text.clone()).boxed()
|
||||||
if file.path().file_name().is_none()
|
}))
|
||||||
|| self.project.read(cx).visible_worktrees(cx).count() > 1
|
.contained()
|
||||||
{
|
.with_style(theme.breadcrumbs.container)
|
||||||
file.full_path(cx).to_string_lossy().to_string()
|
.aligned()
|
||||||
} else {
|
.left()
|
||||||
file.path().to_string_lossy().to_string()
|
.boxed()
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
"untitled".to_string()
|
Empty::new().boxed()
|
||||||
};
|
}
|
||||||
|
|
||||||
Flex::row()
|
|
||||||
.with_child(Label::new(filename, theme.breadcrumbs.text.clone()).boxed())
|
|
||||||
.with_children(symbols.into_iter().flat_map(|symbol| {
|
|
||||||
[
|
|
||||||
Label::new(" 〉 ".to_string(), theme.breadcrumbs.text.clone()).boxed(),
|
|
||||||
Text::new(symbol.text, theme.breadcrumbs.text.clone())
|
|
||||||
.with_highlights(symbol.highlight_ranges)
|
|
||||||
.boxed(),
|
|
||||||
]
|
|
||||||
}))
|
|
||||||
.contained()
|
|
||||||
.with_style(theme.breadcrumbs.container)
|
|
||||||
.aligned()
|
|
||||||
.left()
|
|
||||||
.boxed()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,39 +64,25 @@ impl ToolbarItemView for Breadcrumbs {
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> ToolbarItemLocation {
|
) -> ToolbarItemLocation {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
self.subscriptions.clear();
|
self.active_item = None;
|
||||||
self.editor = None;
|
|
||||||
self.project_search = None;
|
self.project_search = None;
|
||||||
if let Some(item) = active_pane_item {
|
if let Some(item) = active_pane_item {
|
||||||
if let Some(editor) = item.act_as::<Editor>(cx) {
|
let this = cx.weak_handle();
|
||||||
self.subscriptions
|
self.subscription = Some(item.subscribe_to_item_events(
|
||||||
.push(cx.subscribe(&editor, |_, _, event, cx| match event {
|
cx,
|
||||||
editor::Event::BufferEdited
|
Box::new(move |event, cx| {
|
||||||
| editor::Event::TitleChanged
|
if let Some(this) = this.upgrade(cx) {
|
||||||
| editor::Event::Saved
|
if let ItemEvent::UpdateBreadcrumbs = event {
|
||||||
| editor::Event::Reparsed => cx.notify(),
|
this.update(cx, |_, cx| {
|
||||||
editor::Event::SelectionsChanged { local } if *local => cx.notify(),
|
cx.emit(Event::UpdateLocation);
|
||||||
_ => {}
|
cx.notify();
|
||||||
}));
|
});
|
||||||
self.editor = Some(editor);
|
}
|
||||||
if let Some(project_search) = item.downcast::<ProjectSearchView>() {
|
|
||||||
self.subscriptions
|
|
||||||
.push(cx.subscribe(&project_search, |_, _, _, cx| {
|
|
||||||
cx.emit(Event::UpdateLocation);
|
|
||||||
}));
|
|
||||||
self.project_search = Some(project_search.clone());
|
|
||||||
|
|
||||||
if project_search.read(cx).has_matches() {
|
|
||||||
ToolbarItemLocation::Secondary
|
|
||||||
} else {
|
|
||||||
ToolbarItemLocation::Hidden
|
|
||||||
}
|
}
|
||||||
} else {
|
}),
|
||||||
ToolbarItemLocation::PrimaryLeft { flex: None }
|
));
|
||||||
}
|
self.active_item = Some(item.boxed_clone());
|
||||||
} else {
|
item.breadcrumb_location(cx)
|
||||||
ToolbarItemLocation::Hidden
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
ToolbarItemLocation::Hidden
|
ToolbarItemLocation::Hidden
|
||||||
}
|
}
|
||||||
|
@ -144,12 +94,8 @@ impl ToolbarItemView for Breadcrumbs {
|
||||||
current_location: ToolbarItemLocation,
|
current_location: ToolbarItemLocation,
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
) -> ToolbarItemLocation {
|
) -> ToolbarItemLocation {
|
||||||
if let Some(project_search) = self.project_search.as_ref() {
|
if let Some(active_item) = self.active_item.as_ref() {
|
||||||
if project_search.read(cx).has_matches() {
|
active_item.breadcrumb_location(cx)
|
||||||
ToolbarItemLocation::Secondary
|
|
||||||
} else {
|
|
||||||
ToolbarItemLocation::Hidden
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
current_location
|
current_location
|
||||||
}
|
}
|
||||||
|
|
|
@ -566,12 +566,8 @@ impl workspace::Item for ProjectDiagnosticsEditor {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_update_tab_on_event(event: &Event) -> bool {
|
fn to_item_events(event: &Self::Event) -> Vec<workspace::ItemEvent> {
|
||||||
Editor::should_update_tab_on_event(event)
|
Editor::to_item_events(event)
|
||||||
}
|
|
||||||
|
|
||||||
fn is_edit_event(event: &Self::Event) -> bool {
|
|
||||||
Editor::is_edit_event(event)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_nav_history(&mut self, nav_history: ItemNavHistory, cx: &mut ViewContext<Self>) {
|
fn set_nav_history(&mut self, nav_history: ItemNavHistory, cx: &mut ViewContext<Self>) {
|
||||||
|
|
|
@ -26,7 +26,8 @@ use text::{Point, Selection};
|
||||||
use util::TryFutureExt;
|
use util::TryFutureExt;
|
||||||
use workspace::{
|
use workspace::{
|
||||||
searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
|
searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
|
||||||
FollowableItem, Item, ItemHandle, ItemNavHistory, ProjectItem, StatusItemView,
|
FollowableItem, Item, ItemEvent, ItemHandle, ItemNavHistory, ProjectItem, StatusItemView,
|
||||||
|
ToolbarItemLocation,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);
|
pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);
|
||||||
|
@ -475,24 +476,72 @@ impl Item for Editor {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_close_item_on_event(event: &Event) -> bool {
|
fn to_item_events(event: &Self::Event) -> Vec<workspace::ItemEvent> {
|
||||||
matches!(event, Event::Closed)
|
let mut result = Vec::new();
|
||||||
}
|
match event {
|
||||||
|
Event::Closed => result.push(ItemEvent::CloseItem),
|
||||||
fn should_update_tab_on_event(event: &Event) -> bool {
|
Event::Saved | Event::TitleChanged => {
|
||||||
matches!(
|
result.push(ItemEvent::UpdateTab);
|
||||||
event,
|
result.push(ItemEvent::UpdateBreadcrumbs);
|
||||||
Event::Saved | Event::DirtyChanged | Event::TitleChanged
|
}
|
||||||
)
|
Event::Reparsed => {
|
||||||
}
|
result.push(ItemEvent::UpdateBreadcrumbs);
|
||||||
|
}
|
||||||
fn is_edit_event(event: &Self::Event) -> bool {
|
Event::SelectionsChanged { local } if *local => {
|
||||||
matches!(event, Event::BufferEdited)
|
result.push(ItemEvent::UpdateBreadcrumbs);
|
||||||
|
}
|
||||||
|
Event::DirtyChanged => {
|
||||||
|
result.push(ItemEvent::UpdateTab);
|
||||||
|
}
|
||||||
|
Event::BufferEdited => {
|
||||||
|
result.push(ItemEvent::Edit);
|
||||||
|
result.push(ItemEvent::UpdateBreadcrumbs);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_searchable(&self, handle: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
|
fn as_searchable(&self, handle: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
|
||||||
Some(Box::new(handle.clone()))
|
Some(Box::new(handle.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn breadcrumb_location(&self) -> ToolbarItemLocation {
|
||||||
|
ToolbarItemLocation::PrimaryLeft { flex: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<ElementBox>> {
|
||||||
|
let cursor = self.selections.newest_anchor().head();
|
||||||
|
let multibuffer = &self.buffer().read(cx);
|
||||||
|
let (buffer_id, symbols) =
|
||||||
|
multibuffer.symbols_containing(cursor, Some(&theme.editor.syntax), cx)?;
|
||||||
|
let buffer = multibuffer.buffer(buffer_id)?;
|
||||||
|
|
||||||
|
let buffer = buffer.read(cx);
|
||||||
|
let filename = if let Some(file) = buffer.file() {
|
||||||
|
if file.path().file_name().is_none()
|
||||||
|
|| self
|
||||||
|
.project
|
||||||
|
.as_ref()
|
||||||
|
.map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
file.full_path(cx).to_string_lossy().to_string()
|
||||||
|
} else {
|
||||||
|
file.path().to_string_lossy().to_string()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
"untitled".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut breadcrumbs = vec![Label::new(filename, theme.breadcrumbs.text.clone()).boxed()];
|
||||||
|
breadcrumbs.extend(symbols.into_iter().map(|symbol| {
|
||||||
|
Text::new(symbol.text, theme.breadcrumbs.text.clone())
|
||||||
|
.with_highlights(symbol.highlight_ranges)
|
||||||
|
.boxed()
|
||||||
|
}));
|
||||||
|
Some(breadcrumbs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProjectItem for Editor {
|
impl ProjectItem for Editor {
|
||||||
|
|
|
@ -189,18 +189,21 @@ impl ToolbarItemView for BufferSearchBar {
|
||||||
self.active_searchable_item.take();
|
self.active_searchable_item.take();
|
||||||
self.pending_search.take();
|
self.pending_search.take();
|
||||||
|
|
||||||
if let Some(searchable_item_handle) = item.and_then(|item| item.as_searchable(cx)) {
|
if let Some(searchable_item_handle) =
|
||||||
|
item.and_then(|item| item.to_searchable_item_handle(cx))
|
||||||
|
{
|
||||||
let handle = cx.weak_handle();
|
let handle = cx.weak_handle();
|
||||||
self.active_searchable_item_subscription = Some(searchable_item_handle.subscribe(
|
self.active_searchable_item_subscription =
|
||||||
cx,
|
Some(searchable_item_handle.subscribe_to_search_events(
|
||||||
Box::new(move |search_event, cx| {
|
cx,
|
||||||
if let Some(this) = handle.upgrade(cx) {
|
Box::new(move |search_event, cx| {
|
||||||
this.update(cx, |this, cx| {
|
if let Some(this) = handle.upgrade(cx) {
|
||||||
this.on_active_searchable_item_event(search_event, cx)
|
this.update(cx, |this, cx| {
|
||||||
});
|
this.on_active_searchable_item_event(search_event, cx)
|
||||||
}
|
});
|
||||||
}),
|
}
|
||||||
));
|
}),
|
||||||
|
));
|
||||||
|
|
||||||
self.active_searchable_item = Some(searchable_item_handle);
|
self.active_searchable_item = Some(searchable_item_handle);
|
||||||
self.update_matches(false, cx);
|
self.update_matches(false, cx);
|
||||||
|
|
|
@ -24,7 +24,8 @@ use std::{
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
use workspace::{
|
use workspace::{
|
||||||
searchable::{Direction, SearchableItem, SearchableItemHandle},
|
searchable::{Direction, SearchableItem, SearchableItemHandle},
|
||||||
Item, ItemHandle, ItemNavHistory, Pane, ToolbarItemLocation, ToolbarItemView, Workspace,
|
Item, ItemEvent, ItemHandle, ItemNavHistory, Pane, ToolbarItemLocation, ToolbarItemView,
|
||||||
|
Workspace,
|
||||||
};
|
};
|
||||||
|
|
||||||
actions!(project_search, [SearchInNew, ToggleFocus]);
|
actions!(project_search, [SearchInNew, ToggleFocus]);
|
||||||
|
@ -326,17 +327,25 @@ impl Item for ProjectSearchView {
|
||||||
.update(cx, |editor, cx| editor.navigate(data, cx))
|
.update(cx, |editor, cx| editor.navigate(data, cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_update_tab_on_event(event: &ViewEvent) -> bool {
|
fn to_item_events(event: &Self::Event) -> Vec<ItemEvent> {
|
||||||
matches!(event, ViewEvent::UpdateTab)
|
match event {
|
||||||
|
ViewEvent::UpdateTab => vec![ItemEvent::UpdateBreadcrumbs, ItemEvent::UpdateTab],
|
||||||
|
ViewEvent::EditorEvent(editor_event) => Editor::to_item_events(editor_event),
|
||||||
|
_ => Vec::new(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_edit_event(event: &Self::Event) -> bool {
|
fn breadcrumb_location(&self) -> ToolbarItemLocation {
|
||||||
if let ViewEvent::EditorEvent(editor_event) = event {
|
if self.has_matches() {
|
||||||
Editor::is_edit_event(editor_event)
|
ToolbarItemLocation::Secondary
|
||||||
} else {
|
} else {
|
||||||
false
|
ToolbarItemLocation::Hidden
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<ElementBox>> {
|
||||||
|
self.results_editor.breadcrumbs(theme, cx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProjectSearchView {
|
impl ProjectSearchView {
|
||||||
|
|
|
@ -83,6 +83,7 @@ const DEBUG_LINE_HEIGHT: f32 = 5.;
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
TitleChanged,
|
TitleChanged,
|
||||||
|
BreadcrumbsChanged,
|
||||||
CloseTerminal,
|
CloseTerminal,
|
||||||
Bell,
|
Bell,
|
||||||
Wakeup,
|
Wakeup,
|
||||||
|
@ -494,9 +495,11 @@ impl Terminal {
|
||||||
match event {
|
match event {
|
||||||
AlacTermEvent::Title(title) => {
|
AlacTermEvent::Title(title) => {
|
||||||
self.breadcrumb_text = title.to_string();
|
self.breadcrumb_text = title.to_string();
|
||||||
|
cx.emit(Event::BreadcrumbsChanged);
|
||||||
}
|
}
|
||||||
AlacTermEvent::ResetTitle => {
|
AlacTermEvent::ResetTitle => {
|
||||||
self.breadcrumb_text = String::new();
|
self.breadcrumb_text = String::new();
|
||||||
|
cx.emit(Event::BreadcrumbsChanged);
|
||||||
}
|
}
|
||||||
AlacTermEvent::ClipboardStore(_, data) => {
|
AlacTermEvent::ClipboardStore(_, data) => {
|
||||||
cx.write_to_clipboard(ClipboardItem::new(data.to_string()))
|
cx.write_to_clipboard(ClipboardItem::new(data.to_string()))
|
||||||
|
|
|
@ -9,7 +9,7 @@ use gpui::{
|
||||||
};
|
};
|
||||||
use util::truncate_and_trailoff;
|
use util::truncate_and_trailoff;
|
||||||
use workspace::searchable::{SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle};
|
use workspace::searchable::{SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle};
|
||||||
use workspace::{Item, Workspace};
|
use workspace::{Item, ItemEvent, ToolbarItemLocation, Workspace};
|
||||||
|
|
||||||
use crate::TerminalSize;
|
use crate::TerminalSize;
|
||||||
use project::{LocalWorktree, Project, ProjectPath};
|
use project::{LocalWorktree, Project, ProjectPath};
|
||||||
|
@ -359,17 +359,41 @@ impl Item for TerminalContainer {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_update_tab_on_event(event: &Self::Event) -> bool {
|
|
||||||
matches!(event, &Event::TitleChanged | &Event::Wakeup)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn should_close_item_on_event(event: &Self::Event) -> bool {
|
|
||||||
matches!(event, &Event::CloseTerminal)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_searchable(&self, handle: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
|
fn as_searchable(&self, handle: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
|
||||||
Some(Box::new(handle.clone()))
|
Some(Box::new(handle.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn to_item_events(event: &Self::Event) -> Vec<ItemEvent> {
|
||||||
|
match event {
|
||||||
|
Event::BreadcrumbsChanged => vec![ItemEvent::UpdateBreadcrumbs],
|
||||||
|
Event::TitleChanged | Event::Wakeup => vec![ItemEvent::UpdateTab],
|
||||||
|
Event::CloseTerminal => vec![ItemEvent::CloseItem],
|
||||||
|
_ => vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn breadcrumb_location(&self) -> ToolbarItemLocation {
|
||||||
|
if self.connected().is_some() {
|
||||||
|
ToolbarItemLocation::PrimaryLeft { flex: None }
|
||||||
|
} else {
|
||||||
|
ToolbarItemLocation::Hidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<ElementBox>> {
|
||||||
|
let connected = self.connected()?;
|
||||||
|
|
||||||
|
Some(vec![Text::new(
|
||||||
|
connected
|
||||||
|
.read(cx)
|
||||||
|
.terminal()
|
||||||
|
.read(cx)
|
||||||
|
.breadcrumb_text
|
||||||
|
.to_string(),
|
||||||
|
theme.breadcrumbs.text.clone(),
|
||||||
|
)
|
||||||
|
.boxed()])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SearchableItem for TerminalContainer {
|
impl SearchableItem for TerminalContainer {
|
||||||
|
|
|
@ -88,7 +88,7 @@ pub trait SearchableItemHandle: ItemHandle {
|
||||||
fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle>;
|
fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle>;
|
||||||
fn boxed_clone(&self) -> Box<dyn SearchableItemHandle>;
|
fn boxed_clone(&self) -> Box<dyn SearchableItemHandle>;
|
||||||
fn supported_options(&self) -> SearchOptions;
|
fn supported_options(&self) -> SearchOptions;
|
||||||
fn subscribe(
|
fn subscribe_to_search_events(
|
||||||
&self,
|
&self,
|
||||||
cx: &mut MutableAppContext,
|
cx: &mut MutableAppContext,
|
||||||
handler: Box<dyn Fn(SearchEvent, &mut MutableAppContext)>,
|
handler: Box<dyn Fn(SearchEvent, &mut MutableAppContext)>,
|
||||||
|
@ -134,7 +134,7 @@ impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
|
||||||
T::supported_options()
|
T::supported_options()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subscribe(
|
fn subscribe_to_search_events(
|
||||||
&self,
|
&self,
|
||||||
cx: &mut MutableAppContext,
|
cx: &mut MutableAppContext,
|
||||||
handler: Box<dyn Fn(SearchEvent, &mut MutableAppContext)>,
|
handler: Box<dyn Fn(SearchEvent, &mut MutableAppContext)>,
|
||||||
|
|
|
@ -267,6 +267,14 @@ pub struct AppState {
|
||||||
pub initialize_workspace: fn(&mut Workspace, &Arc<AppState>, &mut ViewContext<Workspace>),
|
pub initialize_workspace: fn(&mut Workspace, &Arc<AppState>, &mut ViewContext<Workspace>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Eq, PartialEq, Hash)]
|
||||||
|
pub enum ItemEvent {
|
||||||
|
CloseItem,
|
||||||
|
UpdateTab,
|
||||||
|
UpdateBreadcrumbs,
|
||||||
|
Edit,
|
||||||
|
}
|
||||||
|
|
||||||
pub trait Item: View {
|
pub trait Item: View {
|
||||||
fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
|
fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
|
||||||
fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
|
fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
|
||||||
|
@ -311,15 +319,7 @@ pub trait Item: View {
|
||||||
project: ModelHandle<Project>,
|
project: ModelHandle<Project>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Task<Result<()>>;
|
) -> Task<Result<()>>;
|
||||||
fn should_close_item_on_event(_: &Self::Event) -> bool {
|
fn to_item_events(event: &Self::Event) -> Vec<ItemEvent>;
|
||||||
false
|
|
||||||
}
|
|
||||||
fn should_update_tab_on_event(_: &Self::Event) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
fn is_edit_event(_: &Self::Event) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
fn act_as_type(
|
fn act_as_type(
|
||||||
&self,
|
&self,
|
||||||
type_id: TypeId,
|
type_id: TypeId,
|
||||||
|
@ -335,6 +335,13 @@ pub trait Item: View {
|
||||||
fn as_searchable(&self, _: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
|
fn as_searchable(&self, _: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn breadcrumb_location(&self) -> ToolbarItemLocation {
|
||||||
|
ToolbarItemLocation::Hidden
|
||||||
|
}
|
||||||
|
fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option<Vec<ElementBox>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ProjectItem: Item {
|
pub trait ProjectItem: Item {
|
||||||
|
@ -430,6 +437,11 @@ impl<T: FollowableItem> FollowableItemHandle for ViewHandle<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ItemHandle: 'static + fmt::Debug {
|
pub trait ItemHandle: 'static + fmt::Debug {
|
||||||
|
fn subscribe_to_item_events(
|
||||||
|
&self,
|
||||||
|
cx: &mut MutableAppContext,
|
||||||
|
handler: Box<dyn Fn(ItemEvent, &mut MutableAppContext)>,
|
||||||
|
) -> gpui::Subscription;
|
||||||
fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>>;
|
fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>>;
|
||||||
fn tab_content(&self, detail: Option<usize>, style: &theme::Tab, cx: &AppContext)
|
fn tab_content(&self, detail: Option<usize>, style: &theme::Tab, cx: &AppContext)
|
||||||
-> ElementBox;
|
-> ElementBox;
|
||||||
|
@ -469,7 +481,9 @@ pub trait ItemHandle: 'static + fmt::Debug {
|
||||||
cx: &mut MutableAppContext,
|
cx: &mut MutableAppContext,
|
||||||
callback: Box<dyn FnOnce(&mut MutableAppContext)>,
|
callback: Box<dyn FnOnce(&mut MutableAppContext)>,
|
||||||
) -> gpui::Subscription;
|
) -> gpui::Subscription;
|
||||||
fn as_searchable(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
|
fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
|
||||||
|
fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation;
|
||||||
|
fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<ElementBox>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait WeakItemHandle {
|
pub trait WeakItemHandle {
|
||||||
|
@ -490,6 +504,18 @@ impl dyn ItemHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Item> ItemHandle for ViewHandle<T> {
|
impl<T: Item> ItemHandle for ViewHandle<T> {
|
||||||
|
fn subscribe_to_item_events(
|
||||||
|
&self,
|
||||||
|
cx: &mut MutableAppContext,
|
||||||
|
handler: Box<dyn Fn(ItemEvent, &mut MutableAppContext)>,
|
||||||
|
) -> gpui::Subscription {
|
||||||
|
cx.subscribe(self, move |_, event, cx| {
|
||||||
|
for item_event in T::to_item_events(event) {
|
||||||
|
handler(item_event, cx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>> {
|
fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>> {
|
||||||
self.read(cx).tab_description(detail, cx)
|
self.read(cx).tab_description(detail, cx)
|
||||||
}
|
}
|
||||||
|
@ -605,47 +631,53 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if T::should_close_item_on_event(event) {
|
for item_event in T::to_item_events(event).into_iter() {
|
||||||
Pane::close_item(workspace, pane, item.id(), cx).detach_and_log_err(cx);
|
match item_event {
|
||||||
return;
|
ItemEvent::CloseItem => {
|
||||||
}
|
Pane::close_item(workspace, pane, item.id(), cx)
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ItemEvent::UpdateTab => {
|
||||||
|
pane.update(cx, |_, cx| {
|
||||||
|
cx.emit(pane::Event::ChangeItemTitle);
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ItemEvent::Edit => {
|
||||||
|
if let Autosave::AfterDelay { milliseconds } =
|
||||||
|
cx.global::<Settings>().autosave
|
||||||
|
{
|
||||||
|
let prev_autosave = pending_autosave
|
||||||
|
.take()
|
||||||
|
.unwrap_or_else(|| Task::ready(Some(())));
|
||||||
|
let (cancel_tx, mut cancel_rx) = oneshot::channel::<()>();
|
||||||
|
let prev_cancel_tx =
|
||||||
|
mem::replace(&mut cancel_pending_autosave, cancel_tx);
|
||||||
|
let project = workspace.project.downgrade();
|
||||||
|
let _ = prev_cancel_tx.send(());
|
||||||
|
let item = item.clone();
|
||||||
|
pending_autosave =
|
||||||
|
Some(cx.spawn_weak(|_, mut cx| async move {
|
||||||
|
let mut timer = cx
|
||||||
|
.background()
|
||||||
|
.timer(Duration::from_millis(milliseconds))
|
||||||
|
.fuse();
|
||||||
|
prev_autosave.await;
|
||||||
|
futures::select_biased! {
|
||||||
|
_ = cancel_rx => return None,
|
||||||
|
_ = timer => {}
|
||||||
|
}
|
||||||
|
|
||||||
if T::should_update_tab_on_event(event) {
|
let project = project.upgrade(&cx)?;
|
||||||
pane.update(cx, |_, cx| {
|
cx.update(|cx| Pane::autosave_item(&item, project, cx))
|
||||||
cx.emit(pane::Event::ChangeItemTitle);
|
.await
|
||||||
cx.notify();
|
.log_err();
|
||||||
});
|
None
|
||||||
}
|
}));
|
||||||
|
|
||||||
if T::is_edit_event(event) {
|
|
||||||
if let Autosave::AfterDelay { milliseconds } =
|
|
||||||
cx.global::<Settings>().autosave
|
|
||||||
{
|
|
||||||
let prev_autosave = pending_autosave
|
|
||||||
.take()
|
|
||||||
.unwrap_or_else(|| Task::ready(Some(())));
|
|
||||||
let (cancel_tx, mut cancel_rx) = oneshot::channel::<()>();
|
|
||||||
let prev_cancel_tx =
|
|
||||||
mem::replace(&mut cancel_pending_autosave, cancel_tx);
|
|
||||||
let project = workspace.project.downgrade();
|
|
||||||
let _ = prev_cancel_tx.send(());
|
|
||||||
pending_autosave = Some(cx.spawn_weak(|_, mut cx| async move {
|
|
||||||
let mut timer = cx
|
|
||||||
.background()
|
|
||||||
.timer(Duration::from_millis(milliseconds))
|
|
||||||
.fuse();
|
|
||||||
prev_autosave.await;
|
|
||||||
futures::select_biased! {
|
|
||||||
_ = cancel_rx => return None,
|
|
||||||
_ = timer => {}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
let project = project.upgrade(&cx)?;
|
_ => {}
|
||||||
cx.update(|cx| Pane::autosave_item(&item, project, cx))
|
|
||||||
.await
|
|
||||||
.log_err();
|
|
||||||
None
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
@ -746,9 +778,17 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
|
||||||
cx.observe_release(self, move |_, cx| callback(cx))
|
cx.observe_release(self, move |_, cx| callback(cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_searchable(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
|
fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
|
||||||
self.read(cx).as_searchable(self)
|
self.read(cx).as_searchable(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation {
|
||||||
|
self.read(cx).breadcrumb_location()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<ElementBox>> {
|
||||||
|
self.read(cx).breadcrumbs(theme, cx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Box<dyn ItemHandle>> for AnyViewHandle {
|
impl From<Box<dyn ItemHandle>> for AnyViewHandle {
|
||||||
|
@ -3590,12 +3630,8 @@ mod tests {
|
||||||
Task::ready(Ok(()))
|
Task::ready(Ok(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_update_tab_on_event(_: &Self::Event) -> bool {
|
fn to_item_events(_: &Self::Event) -> Vec<ItemEvent> {
|
||||||
true
|
vec![ItemEvent::UpdateTab, ItemEvent::Edit]
|
||||||
}
|
|
||||||
|
|
||||||
fn is_edit_event(event: &Self::Event) -> bool {
|
|
||||||
matches!(event, TestItemEvent::Edit)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -225,12 +225,11 @@ pub fn initialize_workspace(
|
||||||
cx: &mut ViewContext<Workspace>,
|
cx: &mut ViewContext<Workspace>,
|
||||||
) {
|
) {
|
||||||
cx.subscribe(&cx.handle(), {
|
cx.subscribe(&cx.handle(), {
|
||||||
let project = workspace.project().clone();
|
|
||||||
move |_, _, event, cx| {
|
move |_, _, event, cx| {
|
||||||
if let workspace::Event::PaneAdded(pane) = event {
|
if let workspace::Event::PaneAdded(pane) = event {
|
||||||
pane.update(cx, |pane, cx| {
|
pane.update(cx, |pane, cx| {
|
||||||
pane.toolbar().update(cx, |toolbar, cx| {
|
pane.toolbar().update(cx, |toolbar, cx| {
|
||||||
let breadcrumbs = cx.add_view(|_| Breadcrumbs::new(project.clone()));
|
let breadcrumbs = cx.add_view(|_| Breadcrumbs::new());
|
||||||
toolbar.add_item(breadcrumbs, cx);
|
toolbar.add_item(breadcrumbs, cx);
|
||||||
let buffer_search_bar = cx.add_view(BufferSearchBar::new);
|
let buffer_search_bar = cx.add_view(BufferSearchBar::new);
|
||||||
toolbar.add_item(buffer_search_bar, cx);
|
toolbar.add_item(buffer_search_bar, cx);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue