1// mod dragged_item_receiver;
2
3use crate::{
4 item::{Item, ItemHandle, WeakItemHandle},
5 toolbar::Toolbar,
6 SplitDirection, Workspace,
7};
8use anyhow::Result;
9use collections::{HashMap, VecDeque};
10use gpui2::{
11 AppContext, EventEmitter, Model, Task, View, ViewContext, VisualContext, WeakView,
12 WindowContext,
13};
14use parking_lot::Mutex;
15use project2::{Project, ProjectEntryId, ProjectPath};
16use serde::Deserialize;
17use std::{
18 any::Any,
19 cmp, fmt, mem,
20 path::PathBuf,
21 sync::{
22 atomic::{AtomicUsize, Ordering},
23 Arc,
24 },
25};
26
27#[derive(PartialEq, Clone, Copy, Deserialize, Debug)]
28#[serde(rename_all = "camelCase")]
29pub enum SaveIntent {
30 /// write all files (even if unchanged)
31 /// prompt before overwriting on-disk changes
32 Save,
33 /// write any files that have local changes
34 /// prompt before overwriting on-disk changes
35 SaveAll,
36 /// always prompt for a new path
37 SaveAs,
38 /// prompt "you have unsaved changes" before writing
39 Close,
40 /// write all dirty files, don't prompt on conflict
41 Overwrite,
42 /// skip all save-related behavior
43 Skip,
44}
45
46// #[derive(Clone, Deserialize, PartialEq)]
47// pub struct ActivateItem(pub usize);
48
49// #[derive(Clone, PartialEq)]
50// pub struct CloseItemById {
51// pub item_id: usize,
52// pub pane: WeakView<Pane>,
53// }
54
55// #[derive(Clone, PartialEq)]
56// pub struct CloseItemsToTheLeftById {
57// pub item_id: usize,
58// pub pane: WeakView<Pane>,
59// }
60
61// #[derive(Clone, PartialEq)]
62// pub struct CloseItemsToTheRightById {
63// pub item_id: usize,
64// pub pane: WeakView<Pane>,
65// }
66
67// #[derive(Clone, PartialEq, Debug, Deserialize, Default)]
68// #[serde(rename_all = "camelCase")]
69// pub struct CloseActiveItem {
70// pub save_intent: Option<SaveIntent>,
71// }
72
73// #[derive(Clone, PartialEq, Debug, Deserialize)]
74// #[serde(rename_all = "camelCase")]
75// pub struct CloseAllItems {
76// pub save_intent: Option<SaveIntent>,
77// }
78
79// todo!()
80// actions!(
81// pane,
82// [
83// ActivatePrevItem,
84// ActivateNextItem,
85// ActivateLastItem,
86// CloseInactiveItems,
87// CloseCleanItems,
88// CloseItemsToTheLeft,
89// CloseItemsToTheRight,
90// GoBack,
91// GoForward,
92// ReopenClosedItem,
93// SplitLeft,
94// SplitUp,
95// SplitRight,
96// SplitDown,
97// ]
98// );
99
100// impl_actions!(pane, [ActivateItem, CloseActiveItem, CloseAllItems]);
101
102const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
103
104// todo!()
105// pub fn init(cx: &mut AppContext) {
106// cx.add_action(Pane::toggle_zoom);
107// cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| {
108// pane.activate_item(action.0, true, true, cx);
109// });
110// cx.add_action(|pane: &mut Pane, _: &ActivateLastItem, cx| {
111// pane.activate_item(pane.items.len() - 1, true, true, cx);
112// });
113// cx.add_action(|pane: &mut Pane, _: &ActivatePrevItem, cx| {
114// pane.activate_prev_item(true, cx);
115// });
116// cx.add_action(|pane: &mut Pane, _: &ActivateNextItem, cx| {
117// pane.activate_next_item(true, cx);
118// });
119// cx.add_async_action(Pane::close_active_item);
120// cx.add_async_action(Pane::close_inactive_items);
121// cx.add_async_action(Pane::close_clean_items);
122// cx.add_async_action(Pane::close_items_to_the_left);
123// cx.add_async_action(Pane::close_items_to_the_right);
124// cx.add_async_action(Pane::close_all_items);
125// cx.add_action(|pane: &mut Pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx));
126// cx.add_action(|pane: &mut Pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx));
127// cx.add_action(|pane: &mut Pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx));
128// cx.add_action(|pane: &mut Pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx));
129// }
130
131pub enum Event {
132 AddItem { item: Box<dyn ItemHandle> },
133 ActivateItem { local: bool },
134 Remove,
135 RemoveItem { item_id: usize },
136 Split(SplitDirection),
137 ChangeItemTitle,
138 Focus,
139 ZoomIn,
140 ZoomOut,
141}
142
143impl fmt::Debug for Event {
144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145 match self {
146 Event::AddItem { item } => f.debug_struct("AddItem").field("item", &item.id()).finish(),
147 Event::ActivateItem { local } => f
148 .debug_struct("ActivateItem")
149 .field("local", local)
150 .finish(),
151 Event::Remove => f.write_str("Remove"),
152 Event::RemoveItem { item_id } => f
153 .debug_struct("RemoveItem")
154 .field("item_id", item_id)
155 .finish(),
156 Event::Split(direction) => f
157 .debug_struct("Split")
158 .field("direction", direction)
159 .finish(),
160 Event::ChangeItemTitle => f.write_str("ChangeItemTitle"),
161 Event::Focus => f.write_str("Focus"),
162 Event::ZoomIn => f.write_str("ZoomIn"),
163 Event::ZoomOut => f.write_str("ZoomOut"),
164 }
165 }
166}
167
168pub struct Pane {
169 items: Vec<Box<dyn ItemHandle>>,
170 activation_history: Vec<usize>,
171 // zoomed: bool,
172 active_item_index: usize,
173 // last_focused_view_by_item: HashMap<usize, AnyWeakViewHandle>,
174 autoscroll: bool,
175 nav_history: NavHistory,
176 toolbar: View<Toolbar>,
177 // tab_bar_context_menu: TabBarContextMenu,
178 // tab_context_menu: ViewHandle<ContextMenu>,
179 // workspace: WeakView<Workspace>,
180 project: Model<Project>,
181 has_focus: bool,
182 // can_drop: Rc<dyn Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool>,
183 // can_split: bool,
184 // render_tab_bar_buttons: Rc<dyn Fn(&mut Pane, &mut ViewContext<Pane>) -> AnyElement<Pane>>,
185}
186
187pub struct ItemNavHistory {
188 history: NavHistory,
189 item: Arc<dyn WeakItemHandle>,
190}
191
192#[derive(Clone)]
193pub struct NavHistory(Arc<Mutex<NavHistoryState>>);
194
195struct NavHistoryState {
196 mode: NavigationMode,
197 backward_stack: VecDeque<NavigationEntry>,
198 forward_stack: VecDeque<NavigationEntry>,
199 closed_stack: VecDeque<NavigationEntry>,
200 paths_by_item: HashMap<usize, (ProjectPath, Option<PathBuf>)>,
201 pane: WeakView<Pane>,
202 next_timestamp: Arc<AtomicUsize>,
203}
204
205#[derive(Copy, Clone)]
206pub enum NavigationMode {
207 Normal,
208 GoingBack,
209 GoingForward,
210 ClosingItem,
211 ReopeningClosedItem,
212 Disabled,
213}
214
215impl Default for NavigationMode {
216 fn default() -> Self {
217 Self::Normal
218 }
219}
220
221pub struct NavigationEntry {
222 pub item: Arc<dyn WeakItemHandle>,
223 pub data: Option<Box<dyn Any>>,
224 pub timestamp: usize,
225}
226
227// pub struct DraggedItem {
228// pub handle: Box<dyn ItemHandle>,
229// pub pane: WeakView<Pane>,
230// }
231
232// pub enum ReorderBehavior {
233// None,
234// MoveAfterActive,
235// MoveToIndex(usize),
236// }
237
238// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
239// enum TabBarContextMenuKind {
240// New,
241// Split,
242// }
243
244// struct TabBarContextMenu {
245// kind: TabBarContextMenuKind,
246// handle: ViewHandle<ContextMenu>,
247// }
248
249// impl TabBarContextMenu {
250// fn handle_if_kind(&self, kind: TabBarContextMenuKind) -> Option<ViewHandle<ContextMenu>> {
251// if self.kind == kind {
252// return Some(self.handle.clone());
253// }
254// None
255// }
256// }
257
258// #[allow(clippy::too_many_arguments)]
259// fn nav_button<A: Action, F: 'static + Fn(&mut Pane, &mut ViewContext<Pane>)>(
260// svg_path: &'static str,
261// style: theme2::Interactive<theme2::IconButton>,
262// nav_button_height: f32,
263// tooltip_style: TooltipStyle,
264// enabled: bool,
265// on_click: F,
266// tooltip_action: A,
267// action_name: &str,
268// cx: &mut ViewContext<Pane>,
269// ) -> AnyElement<Pane> {
270// MouseEventHandler::new::<A, _>(0, cx, |state, _| {
271// let style = if enabled {
272// style.style_for(state)
273// } else {
274// style.disabled_style()
275// };
276// Svg::new(svg_path)
277// .with_color(style.color)
278// .constrained()
279// .with_width(style.icon_width)
280// .aligned()
281// .contained()
282// .with_style(style.container)
283// .constrained()
284// .with_width(style.button_width)
285// .with_height(nav_button_height)
286// .aligned()
287// .top()
288// })
289// .with_cursor_style(if enabled {
290// CursorStyle::PointingHand
291// } else {
292// CursorStyle::default()
293// })
294// .on_click(MouseButton::Left, move |_, toolbar, cx| {
295// on_click(toolbar, cx)
296// })
297// .with_tooltip::<A>(
298// 0,
299// action_name.to_string(),
300// Some(Box::new(tooltip_action)),
301// tooltip_style,
302// cx,
303// )
304// .contained()
305// .into_any_named("nav button")
306// }
307
308impl EventEmitter for Pane {
309 type Event = Event;
310}
311
312impl Pane {
313 pub fn new(
314 workspace: WeakView<Workspace>,
315 project: Model<Project>,
316 next_timestamp: Arc<AtomicUsize>,
317 cx: &mut ViewContext<Self>,
318 ) -> Self {
319 // todo!("context menu")
320 // let pane_view_id = cx.view_id();
321 // let context_menu = cx.add_view(|cx| ContextMenu::new(pane_view_id, cx));
322 // context_menu.update(cx, |menu, _| {
323 // menu.set_position_mode(OverlayPositionMode::Local)
324 // });
325
326 let handle = cx.view();
327 Self {
328 items: Vec::new(),
329 activation_history: Vec::new(),
330 // zoomed: false,
331 active_item_index: 0,
332 // last_focused_view_by_item: Default::default(),
333 autoscroll: false,
334 nav_history: NavHistory(Arc::new(Mutex::new(NavHistoryState {
335 mode: NavigationMode::Normal,
336 backward_stack: Default::default(),
337 forward_stack: Default::default(),
338 closed_stack: Default::default(),
339 paths_by_item: Default::default(),
340 pane: handle.clone(),
341 next_timestamp,
342 }))),
343 toolbar: cx.build_view(|_| Toolbar::new()),
344 // tab_bar_context_menu: TabBarContextMenu {
345 // kind: TabBarContextMenuKind::New,
346 // handle: context_menu,
347 // },
348 // tab_context_menu: cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)),
349 // workspace,
350 project,
351 has_focus: false,
352 // can_drop: Rc::new(|_, _| true),
353 // can_split: true,
354 // render_tab_bar_buttons: Rc::new(move |pane, cx| {
355 // Flex::row()
356 // // New menu
357 // .with_child(Self::render_tab_bar_button(
358 // 0,
359 // "icons/plus.svg",
360 // false,
361 // Some(("New...".into(), None)),
362 // cx,
363 // |pane, cx| pane.deploy_new_menu(cx),
364 // |pane, cx| {
365 // pane.tab_bar_context_menu
366 // .handle
367 // .update(cx, |menu, _| menu.delay_cancel())
368 // },
369 // pane.tab_bar_context_menu
370 // .handle_if_kind(TabBarContextMenuKind::New),
371 // ))
372 // .with_child(Self::render_tab_bar_button(
373 // 1,
374 // "icons/split.svg",
375 // false,
376 // Some(("Split Pane".into(), None)),
377 // cx,
378 // |pane, cx| pane.deploy_split_menu(cx),
379 // |pane, cx| {
380 // pane.tab_bar_context_menu
381 // .handle
382 // .update(cx, |menu, _| menu.delay_cancel())
383 // },
384 // pane.tab_bar_context_menu
385 // .handle_if_kind(TabBarContextMenuKind::Split),
386 // ))
387 // .with_child({
388 // let icon_path;
389 // let tooltip_label;
390 // if pane.is_zoomed() {
391 // icon_path = "icons/minimize.svg";
392 // tooltip_label = "Zoom In";
393 // } else {
394 // icon_path = "icons/maximize.svg";
395 // tooltip_label = "Zoom In";
396 // }
397
398 // Pane::render_tab_bar_button(
399 // 2,
400 // icon_path,
401 // pane.is_zoomed(),
402 // Some((tooltip_label, Some(Box::new(ToggleZoom)))),
403 // cx,
404 // move |pane, cx| pane.toggle_zoom(&Default::default(), cx),
405 // move |_, _| {},
406 // None,
407 // )
408 // })
409 // .into_any()
410 // }),
411 }
412 }
413
414 // pub(crate) fn workspace(&self) -> &WeakView<Workspace> {
415 // &self.workspace
416 // }
417
418 pub fn has_focus(&self) -> bool {
419 self.has_focus
420 }
421
422 // pub fn active_item_index(&self) -> usize {
423 // self.active_item_index
424 // }
425
426 // pub fn on_can_drop<F>(&mut self, can_drop: F)
427 // where
428 // F: 'static + Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool,
429 // {
430 // self.can_drop = Rc::new(can_drop);
431 // }
432
433 // pub fn set_can_split(&mut self, can_split: bool, cx: &mut ViewContext<Self>) {
434 // self.can_split = can_split;
435 // cx.notify();
436 // }
437
438 // pub fn set_can_navigate(&mut self, can_navigate: bool, cx: &mut ViewContext<Self>) {
439 // self.toolbar.update(cx, |toolbar, cx| {
440 // toolbar.set_can_navigate(can_navigate, cx);
441 // });
442 // cx.notify();
443 // }
444
445 // pub fn set_render_tab_bar_buttons<F>(&mut self, cx: &mut ViewContext<Self>, render: F)
446 // where
447 // F: 'static + Fn(&mut Pane, &mut ViewContext<Pane>) -> AnyElement<Pane>,
448 // {
449 // self.render_tab_bar_buttons = Rc::new(render);
450 // cx.notify();
451 // }
452
453 pub fn nav_history_for_item<T: Item>(&self, item: &View<T>) -> ItemNavHistory {
454 ItemNavHistory {
455 history: self.nav_history.clone(),
456 item: Arc::new(item.downgrade()),
457 }
458 }
459
460 pub fn nav_history(&self) -> &NavHistory {
461 &self.nav_history
462 }
463
464 pub fn nav_history_mut(&mut self) -> &mut NavHistory {
465 &mut self.nav_history
466 }
467
468 pub fn disable_history(&mut self) {
469 self.nav_history.disable();
470 }
471
472 pub fn enable_history(&mut self) {
473 self.nav_history.enable();
474 }
475
476 pub fn can_navigate_backward(&self) -> bool {
477 !self.nav_history.0.lock().backward_stack.is_empty()
478 }
479
480 pub fn can_navigate_forward(&self) -> bool {
481 !self.nav_history.0.lock().forward_stack.is_empty()
482 }
483
484 fn history_updated(&mut self, cx: &mut ViewContext<Self>) {
485 self.toolbar.update(cx, |_, cx| cx.notify());
486 }
487
488 pub(crate) fn open_item(
489 &mut self,
490 project_entry_id: ProjectEntryId,
491 focus_item: bool,
492 cx: &mut ViewContext<Self>,
493 build_item: impl FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
494 ) -> Box<dyn ItemHandle> {
495 let mut existing_item = None;
496 for (index, item) in self.items.iter().enumerate() {
497 if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [project_entry_id]
498 {
499 let item = item.boxed_clone();
500 existing_item = Some((index, item));
501 break;
502 }
503 }
504
505 if let Some((index, existing_item)) = existing_item {
506 self.activate_item(index, focus_item, focus_item, cx);
507 existing_item
508 } else {
509 let new_item = build_item(cx);
510 self.add_item(new_item.clone(), true, focus_item, None, cx);
511 new_item
512 }
513 }
514
515 pub fn add_item(
516 &mut self,
517 item: Box<dyn ItemHandle>,
518 activate_pane: bool,
519 focus_item: bool,
520 destination_index: Option<usize>,
521 cx: &mut ViewContext<Self>,
522 ) {
523 if item.is_singleton(cx) {
524 if let Some(&entry_id) = item.project_entry_ids(cx).get(0) {
525 let project = self.project.read(cx);
526 if let Some(project_path) = project.path_for_entry(entry_id, cx) {
527 let abs_path = project.absolute_path(&project_path, cx);
528 self.nav_history
529 .0
530 .lock()
531 .paths_by_item
532 .insert(item.id(), (project_path, abs_path));
533 }
534 }
535 }
536 // If no destination index is specified, add or move the item after the active item.
537 let mut insertion_index = {
538 cmp::min(
539 if let Some(destination_index) = destination_index {
540 destination_index
541 } else {
542 self.active_item_index + 1
543 },
544 self.items.len(),
545 )
546 };
547
548 // Does the item already exist?
549 let project_entry_id = if item.is_singleton(cx) {
550 item.project_entry_ids(cx).get(0).copied()
551 } else {
552 None
553 };
554
555 let existing_item_index = self.items.iter().position(|existing_item| {
556 if existing_item.id() == item.id() {
557 true
558 } else if existing_item.is_singleton(cx) {
559 existing_item
560 .project_entry_ids(cx)
561 .get(0)
562 .map_or(false, |existing_entry_id| {
563 Some(existing_entry_id) == project_entry_id.as_ref()
564 })
565 } else {
566 false
567 }
568 });
569
570 if let Some(existing_item_index) = existing_item_index {
571 // If the item already exists, move it to the desired destination and activate it
572
573 if existing_item_index != insertion_index {
574 let existing_item_is_active = existing_item_index == self.active_item_index;
575
576 // If the caller didn't specify a destination and the added item is already
577 // the active one, don't move it
578 if existing_item_is_active && destination_index.is_none() {
579 insertion_index = existing_item_index;
580 } else {
581 self.items.remove(existing_item_index);
582 if existing_item_index < self.active_item_index {
583 self.active_item_index -= 1;
584 }
585 insertion_index = insertion_index.min(self.items.len());
586
587 self.items.insert(insertion_index, item.clone());
588
589 if existing_item_is_active {
590 self.active_item_index = insertion_index;
591 } else if insertion_index <= self.active_item_index {
592 self.active_item_index += 1;
593 }
594 }
595
596 cx.notify();
597 }
598
599 self.activate_item(insertion_index, activate_pane, focus_item, cx);
600 } else {
601 self.items.insert(insertion_index, item.clone());
602 if insertion_index <= self.active_item_index {
603 self.active_item_index += 1;
604 }
605
606 self.activate_item(insertion_index, activate_pane, focus_item, cx);
607 cx.notify();
608 }
609
610 cx.emit(Event::AddItem { item });
611 }
612
613 pub fn items_len(&self) -> usize {
614 self.items.len()
615 }
616
617 pub fn items(&self) -> impl Iterator<Item = &Box<dyn ItemHandle>> + DoubleEndedIterator {
618 self.items.iter()
619 }
620
621 // pub fn items_of_type<T: View>(&self) -> impl '_ + Iterator<Item = ViewHandle<T>> {
622 // self.items
623 // .iter()
624 // .filter_map(|item| item.as_any().clone().downcast())
625 // }
626
627 pub fn active_item(&self) -> Option<Box<dyn ItemHandle>> {
628 self.items.get(self.active_item_index).cloned()
629 }
630
631 // pub fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Vector2F> {
632 // self.items
633 // .get(self.active_item_index)?
634 // .pixel_position_of_cursor(cx)
635 // }
636
637 // pub fn item_for_entry(
638 // &self,
639 // entry_id: ProjectEntryId,
640 // cx: &AppContext,
641 // ) -> Option<Box<dyn ItemHandle>> {
642 // self.items.iter().find_map(|item| {
643 // if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] {
644 // Some(item.boxed_clone())
645 // } else {
646 // None
647 // }
648 // })
649 // }
650
651 // pub fn index_for_item(&self, item: &dyn ItemHandle) -> Option<usize> {
652 // self.items.iter().position(|i| i.id() == item.id())
653 // }
654
655 // pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) {
656 // // Potentially warn the user of the new keybinding
657 // let workspace_handle = self.workspace().clone();
658 // cx.spawn(|_, mut cx| async move { notify_of_new_dock(&workspace_handle, &mut cx) })
659 // .detach();
660
661 // if self.zoomed {
662 // cx.emit(Event::ZoomOut);
663 // } else if !self.items.is_empty() {
664 // if !self.has_focus {
665 // cx.focus_self();
666 // }
667 // cx.emit(Event::ZoomIn);
668 // }
669 // }
670
671 pub fn activate_item(
672 &mut self,
673 index: usize,
674 activate_pane: bool,
675 focus_item: bool,
676 cx: &mut ViewContext<Self>,
677 ) {
678 use NavigationMode::{GoingBack, GoingForward};
679
680 if index < self.items.len() {
681 let prev_active_item_ix = mem::replace(&mut self.active_item_index, index);
682 if prev_active_item_ix != self.active_item_index
683 || matches!(self.nav_history.mode(), GoingBack | GoingForward)
684 {
685 if let Some(prev_item) = self.items.get(prev_active_item_ix) {
686 prev_item.deactivated(cx);
687 }
688
689 cx.emit(Event::ActivateItem {
690 local: activate_pane,
691 });
692 }
693
694 if let Some(newly_active_item) = self.items.get(index) {
695 self.activation_history
696 .retain(|&previously_active_item_id| {
697 previously_active_item_id != newly_active_item.id()
698 });
699 self.activation_history.push(newly_active_item.id());
700 }
701
702 self.update_toolbar(cx);
703
704 if focus_item {
705 self.focus_active_item(cx);
706 }
707
708 self.autoscroll = true;
709 cx.notify();
710 }
711 }
712
713 // pub fn activate_prev_item(&mut self, activate_pane: bool, cx: &mut ViewContext<Self>) {
714 // let mut index = self.active_item_index;
715 // if index > 0 {
716 // index -= 1;
717 // } else if !self.items.is_empty() {
718 // index = self.items.len() - 1;
719 // }
720 // self.activate_item(index, activate_pane, activate_pane, cx);
721 // }
722
723 // pub fn activate_next_item(&mut self, activate_pane: bool, cx: &mut ViewContext<Self>) {
724 // let mut index = self.active_item_index;
725 // if index + 1 < self.items.len() {
726 // index += 1;
727 // } else {
728 // index = 0;
729 // }
730 // self.activate_item(index, activate_pane, activate_pane, cx);
731 // }
732
733 // pub fn close_active_item(
734 // &mut self,
735 // action: &CloseActiveItem,
736 // cx: &mut ViewContext<Self>,
737 // ) -> Option<Task<Result<()>>> {
738 // if self.items.is_empty() {
739 // return None;
740 // }
741 // let active_item_id = self.items[self.active_item_index].id();
742 // Some(self.close_item_by_id(
743 // active_item_id,
744 // action.save_intent.unwrap_or(SaveIntent::Close),
745 // cx,
746 // ))
747 // }
748
749 pub fn close_item_by_id(
750 &mut self,
751 item_id_to_close: usize,
752 save_intent: SaveIntent,
753 cx: &mut ViewContext<Self>,
754 ) -> Task<Result<()>> {
755 // self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close)
756 todo!()
757 }
758
759 // pub fn close_inactive_items(
760 // &mut self,
761 // _: &CloseInactiveItems,
762 // cx: &mut ViewContext<Self>,
763 // ) -> Option<Task<Result<()>>> {
764 // if self.items.is_empty() {
765 // return None;
766 // }
767
768 // let active_item_id = self.items[self.active_item_index].id();
769 // Some(self.close_items(cx, SaveIntent::Close, move |item_id| {
770 // item_id != active_item_id
771 // }))
772 // }
773
774 // pub fn close_clean_items(
775 // &mut self,
776 // _: &CloseCleanItems,
777 // cx: &mut ViewContext<Self>,
778 // ) -> Option<Task<Result<()>>> {
779 // let item_ids: Vec<_> = self
780 // .items()
781 // .filter(|item| !item.is_dirty(cx))
782 // .map(|item| item.id())
783 // .collect();
784 // Some(self.close_items(cx, SaveIntent::Close, move |item_id| {
785 // item_ids.contains(&item_id)
786 // }))
787 // }
788
789 // pub fn close_items_to_the_left(
790 // &mut self,
791 // _: &CloseItemsToTheLeft,
792 // cx: &mut ViewContext<Self>,
793 // ) -> Option<Task<Result<()>>> {
794 // if self.items.is_empty() {
795 // return None;
796 // }
797 // let active_item_id = self.items[self.active_item_index].id();
798 // Some(self.close_items_to_the_left_by_id(active_item_id, cx))
799 // }
800
801 // pub fn close_items_to_the_left_by_id(
802 // &mut self,
803 // item_id: usize,
804 // cx: &mut ViewContext<Self>,
805 // ) -> Task<Result<()>> {
806 // let item_ids: Vec<_> = self
807 // .items()
808 // .take_while(|item| item.id() != item_id)
809 // .map(|item| item.id())
810 // .collect();
811 // self.close_items(cx, SaveIntent::Close, move |item_id| {
812 // item_ids.contains(&item_id)
813 // })
814 // }
815
816 // pub fn close_items_to_the_right(
817 // &mut self,
818 // _: &CloseItemsToTheRight,
819 // cx: &mut ViewContext<Self>,
820 // ) -> Option<Task<Result<()>>> {
821 // if self.items.is_empty() {
822 // return None;
823 // }
824 // let active_item_id = self.items[self.active_item_index].id();
825 // Some(self.close_items_to_the_right_by_id(active_item_id, cx))
826 // }
827
828 // pub fn close_items_to_the_right_by_id(
829 // &mut self,
830 // item_id: usize,
831 // cx: &mut ViewContext<Self>,
832 // ) -> Task<Result<()>> {
833 // let item_ids: Vec<_> = self
834 // .items()
835 // .rev()
836 // .take_while(|item| item.id() != item_id)
837 // .map(|item| item.id())
838 // .collect();
839 // self.close_items(cx, SaveIntent::Close, move |item_id| {
840 // item_ids.contains(&item_id)
841 // })
842 // }
843
844 // pub fn close_all_items(
845 // &mut self,
846 // action: &CloseAllItems,
847 // cx: &mut ViewContext<Self>,
848 // ) -> Option<Task<Result<()>>> {
849 // if self.items.is_empty() {
850 // return None;
851 // }
852
853 // Some(
854 // self.close_items(cx, action.save_intent.unwrap_or(SaveIntent::Close), |_| {
855 // true
856 // }),
857 // )
858 // }
859
860 // pub(super) fn file_names_for_prompt(
861 // items: &mut dyn Iterator<Item = &Box<dyn ItemHandle>>,
862 // all_dirty_items: usize,
863 // cx: &AppContext,
864 // ) -> String {
865 // /// Quantity of item paths displayed in prompt prior to cutoff..
866 // const FILE_NAMES_CUTOFF_POINT: usize = 10;
867 // let mut file_names: Vec<_> = items
868 // .filter_map(|item| {
869 // item.project_path(cx).and_then(|project_path| {
870 // project_path
871 // .path
872 // .file_name()
873 // .and_then(|name| name.to_str().map(ToOwned::to_owned))
874 // })
875 // })
876 // .take(FILE_NAMES_CUTOFF_POINT)
877 // .collect();
878 // let should_display_followup_text =
879 // all_dirty_items > FILE_NAMES_CUTOFF_POINT || file_names.len() != all_dirty_items;
880 // if should_display_followup_text {
881 // let not_shown_files = all_dirty_items - file_names.len();
882 // if not_shown_files == 1 {
883 // file_names.push(".. 1 file not shown".into());
884 // } else {
885 // file_names.push(format!(".. {} files not shown", not_shown_files).into());
886 // }
887 // }
888 // let file_names = file_names.join("\n");
889 // format!(
890 // "Do you want to save changes to the following {} files?\n{file_names}",
891 // all_dirty_items
892 // )
893 // }
894
895 // pub fn close_items(
896 // &mut self,
897 // cx: &mut ViewContext<Pane>,
898 // mut save_intent: SaveIntent,
899 // should_close: impl 'static + Fn(usize) -> bool,
900 // ) -> Task<Result<()>> {
901 // // Find the items to close.
902 // let mut items_to_close = Vec::new();
903 // let mut dirty_items = Vec::new();
904 // for item in &self.items {
905 // if should_close(item.id()) {
906 // items_to_close.push(item.boxed_clone());
907 // if item.is_dirty(cx) {
908 // dirty_items.push(item.boxed_clone());
909 // }
910 // }
911 // }
912
913 // // If a buffer is open both in a singleton editor and in a multibuffer, make sure
914 // // to focus the singleton buffer when prompting to save that buffer, as opposed
915 // // to focusing the multibuffer, because this gives the user a more clear idea
916 // // of what content they would be saving.
917 // items_to_close.sort_by_key(|item| !item.is_singleton(cx));
918
919 // let workspace = self.workspace.clone();
920 // cx.spawn(|pane, mut cx| async move {
921 // if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
922 // let mut answer = pane.update(&mut cx, |_, cx| {
923 // let prompt =
924 // Self::file_names_for_prompt(&mut dirty_items.iter(), dirty_items.len(), cx);
925 // cx.prompt(
926 // PromptLevel::Warning,
927 // &prompt,
928 // &["Save all", "Discard all", "Cancel"],
929 // )
930 // })?;
931 // match answer.next().await {
932 // Some(0) => save_intent = SaveIntent::SaveAll,
933 // Some(1) => save_intent = SaveIntent::Skip,
934 // _ => {}
935 // }
936 // }
937 // let mut saved_project_items_ids = HashSet::default();
938 // for item in items_to_close.clone() {
939 // // Find the item's current index and its set of project item models. Avoid
940 // // storing these in advance, in case they have changed since this task
941 // // was started.
942 // let (item_ix, mut project_item_ids) = pane.read_with(&cx, |pane, cx| {
943 // (pane.index_for_item(&*item), item.project_item_model_ids(cx))
944 // })?;
945 // let item_ix = if let Some(ix) = item_ix {
946 // ix
947 // } else {
948 // continue;
949 // };
950
951 // // Check if this view has any project items that are not open anywhere else
952 // // in the workspace, AND that the user has not already been prompted to save.
953 // // If there are any such project entries, prompt the user to save this item.
954 // let project = workspace.read_with(&cx, |workspace, cx| {
955 // for item in workspace.items(cx) {
956 // if !items_to_close
957 // .iter()
958 // .any(|item_to_close| item_to_close.id() == item.id())
959 // {
960 // let other_project_item_ids = item.project_item_model_ids(cx);
961 // project_item_ids.retain(|id| !other_project_item_ids.contains(id));
962 // }
963 // }
964 // workspace.project().clone()
965 // })?;
966 // let should_save = project_item_ids
967 // .iter()
968 // .any(|id| saved_project_items_ids.insert(*id));
969
970 // if should_save
971 // && !Self::save_item(
972 // project.clone(),
973 // &pane,
974 // item_ix,
975 // &*item,
976 // save_intent,
977 // &mut cx,
978 // )
979 // .await?
980 // {
981 // break;
982 // }
983
984 // // Remove the item from the pane.
985 // pane.update(&mut cx, |pane, cx| {
986 // if let Some(item_ix) = pane.items.iter().position(|i| i.id() == item.id()) {
987 // pane.remove_item(item_ix, false, cx);
988 // }
989 // })?;
990 // }
991
992 // pane.update(&mut cx, |_, cx| cx.notify())?;
993 // Ok(())
994 // })
995 // }
996
997 // pub fn remove_item(
998 // &mut self,
999 // item_index: usize,
1000 // activate_pane: bool,
1001 // cx: &mut ViewContext<Self>,
1002 // ) {
1003 // self.activation_history
1004 // .retain(|&history_entry| history_entry != self.items[item_index].id());
1005
1006 // if item_index == self.active_item_index {
1007 // let index_to_activate = self
1008 // .activation_history
1009 // .pop()
1010 // .and_then(|last_activated_item| {
1011 // self.items.iter().enumerate().find_map(|(index, item)| {
1012 // (item.id() == last_activated_item).then_some(index)
1013 // })
1014 // })
1015 // // We didn't have a valid activation history entry, so fallback
1016 // // to activating the item to the left
1017 // .unwrap_or_else(|| item_index.min(self.items.len()).saturating_sub(1));
1018
1019 // let should_activate = activate_pane || self.has_focus;
1020 // self.activate_item(index_to_activate, should_activate, should_activate, cx);
1021 // }
1022
1023 // let item = self.items.remove(item_index);
1024
1025 // cx.emit(Event::RemoveItem { item_id: item.id() });
1026 // if self.items.is_empty() {
1027 // item.deactivated(cx);
1028 // self.update_toolbar(cx);
1029 // cx.emit(Event::Remove);
1030 // }
1031
1032 // if item_index < self.active_item_index {
1033 // self.active_item_index -= 1;
1034 // }
1035
1036 // self.nav_history.set_mode(NavigationMode::ClosingItem);
1037 // item.deactivated(cx);
1038 // self.nav_history.set_mode(NavigationMode::Normal);
1039
1040 // if let Some(path) = item.project_path(cx) {
1041 // let abs_path = self
1042 // .nav_history
1043 // .0
1044 // .borrow()
1045 // .paths_by_item
1046 // .get(&item.id())
1047 // .and_then(|(_, abs_path)| abs_path.clone());
1048
1049 // self.nav_history
1050 // .0
1051 // .borrow_mut()
1052 // .paths_by_item
1053 // .insert(item.id(), (path, abs_path));
1054 // } else {
1055 // self.nav_history
1056 // .0
1057 // .borrow_mut()
1058 // .paths_by_item
1059 // .remove(&item.id());
1060 // }
1061
1062 // if self.items.is_empty() && self.zoomed {
1063 // cx.emit(Event::ZoomOut);
1064 // }
1065
1066 // cx.notify();
1067 // }
1068
1069 // pub async fn save_item(
1070 // project: Model<Project>,
1071 // pane: &WeakView<Pane>,
1072 // item_ix: usize,
1073 // item: &dyn ItemHandle,
1074 // save_intent: SaveIntent,
1075 // cx: &mut AsyncAppContext,
1076 // ) -> Result<bool> {
1077 // const CONFLICT_MESSAGE: &str =
1078 // "This file has changed on disk since you started editing it. Do you want to overwrite it?";
1079
1080 // if save_intent == SaveIntent::Skip {
1081 // return Ok(true);
1082 // }
1083
1084 // let (mut has_conflict, mut is_dirty, mut can_save, can_save_as) = cx.read(|cx| {
1085 // (
1086 // item.has_conflict(cx),
1087 // item.is_dirty(cx),
1088 // item.can_save(cx),
1089 // item.is_singleton(cx),
1090 // )
1091 // });
1092
1093 // // when saving a single buffer, we ignore whether or not it's dirty.
1094 // if save_intent == SaveIntent::Save {
1095 // is_dirty = true;
1096 // }
1097
1098 // if save_intent == SaveIntent::SaveAs {
1099 // is_dirty = true;
1100 // has_conflict = false;
1101 // can_save = false;
1102 // }
1103
1104 // if save_intent == SaveIntent::Overwrite {
1105 // has_conflict = false;
1106 // }
1107
1108 // if has_conflict && can_save {
1109 // let mut answer = pane.update(cx, |pane, cx| {
1110 // pane.activate_item(item_ix, true, true, cx);
1111 // cx.prompt(
1112 // PromptLevel::Warning,
1113 // CONFLICT_MESSAGE,
1114 // &["Overwrite", "Discard", "Cancel"],
1115 // )
1116 // })?;
1117 // match answer.next().await {
1118 // Some(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?,
1119 // Some(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?,
1120 // _ => return Ok(false),
1121 // }
1122 // } else if is_dirty && (can_save || can_save_as) {
1123 // if save_intent == SaveIntent::Close {
1124 // let will_autosave = cx.read(|cx| {
1125 // matches!(
1126 // settings::get::<WorkspaceSettings>(cx).autosave,
1127 // AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange
1128 // ) && Self::can_autosave_item(&*item, cx)
1129 // });
1130 // if !will_autosave {
1131 // let mut answer = pane.update(cx, |pane, cx| {
1132 // pane.activate_item(item_ix, true, true, cx);
1133 // let prompt = dirty_message_for(item.project_path(cx));
1134 // cx.prompt(
1135 // PromptLevel::Warning,
1136 // &prompt,
1137 // &["Save", "Don't Save", "Cancel"],
1138 // )
1139 // })?;
1140 // match answer.next().await {
1141 // Some(0) => {}
1142 // Some(1) => return Ok(true), // Don't save his file
1143 // _ => return Ok(false), // Cancel
1144 // }
1145 // }
1146 // }
1147
1148 // if can_save {
1149 // pane.update(cx, |_, cx| item.save(project, cx))?.await?;
1150 // } else if can_save_as {
1151 // let start_abs_path = project
1152 // .read_with(cx, |project, cx| {
1153 // let worktree = project.visible_worktrees(cx).next()?;
1154 // Some(worktree.read(cx).as_local()?.abs_path().to_path_buf())
1155 // })
1156 // .unwrap_or_else(|| Path::new("").into());
1157
1158 // let mut abs_path = cx.update(|cx| cx.prompt_for_new_path(&start_abs_path));
1159 // if let Some(abs_path) = abs_path.next().await.flatten() {
1160 // pane.update(cx, |_, cx| item.save_as(project, abs_path, cx))?
1161 // .await?;
1162 // } else {
1163 // return Ok(false);
1164 // }
1165 // }
1166 // }
1167 // Ok(true)
1168 // }
1169
1170 fn can_autosave_item(item: &dyn ItemHandle, cx: &AppContext) -> bool {
1171 let is_deleted = item.project_entry_ids(cx).is_empty();
1172 item.is_dirty(cx) && !item.has_conflict(cx) && item.can_save(cx) && !is_deleted
1173 }
1174
1175 pub fn autosave_item(
1176 item: &dyn ItemHandle,
1177 project: Model<Project>,
1178 cx: &mut WindowContext,
1179 ) -> Task<Result<()>> {
1180 if Self::can_autosave_item(item, cx) {
1181 item.save(project, cx)
1182 } else {
1183 Task::ready(Ok(()))
1184 }
1185 }
1186
1187 pub fn focus_active_item(&mut self, cx: &mut ViewContext<Self>) {
1188 todo!();
1189 // if let Some(active_item) = self.active_item() {
1190 // cx.focus(active_item.as_any());
1191 // }
1192 }
1193
1194 // pub fn split(&mut self, direction: SplitDirection, cx: &mut ViewContext<Self>) {
1195 // cx.emit(Event::Split(direction));
1196 // }
1197
1198 // fn deploy_split_menu(&mut self, cx: &mut ViewContext<Self>) {
1199 // self.tab_bar_context_menu.handle.update(cx, |menu, cx| {
1200 // menu.toggle(
1201 // Default::default(),
1202 // AnchorCorner::TopRight,
1203 // vec![
1204 // ContextMenuItem::action("Split Right", SplitRight),
1205 // ContextMenuItem::action("Split Left", SplitLeft),
1206 // ContextMenuItem::action("Split Up", SplitUp),
1207 // ContextMenuItem::action("Split Down", SplitDown),
1208 // ],
1209 // cx,
1210 // );
1211 // });
1212
1213 // self.tab_bar_context_menu.kind = TabBarContextMenuKind::Split;
1214 // }
1215
1216 // fn deploy_new_menu(&mut self, cx: &mut ViewContext<Self>) {
1217 // self.tab_bar_context_menu.handle.update(cx, |menu, cx| {
1218 // menu.toggle(
1219 // Default::default(),
1220 // AnchorCorner::TopRight,
1221 // vec![
1222 // ContextMenuItem::action("New File", NewFile),
1223 // ContextMenuItem::action("New Terminal", NewCenterTerminal),
1224 // ContextMenuItem::action("New Search", NewSearch),
1225 // ],
1226 // cx,
1227 // );
1228 // });
1229
1230 // self.tab_bar_context_menu.kind = TabBarContextMenuKind::New;
1231 // }
1232
1233 // fn deploy_tab_context_menu(
1234 // &mut self,
1235 // position: Vector2F,
1236 // target_item_id: usize,
1237 // cx: &mut ViewContext<Self>,
1238 // ) {
1239 // let active_item_id = self.items[self.active_item_index].id();
1240 // let is_active_item = target_item_id == active_item_id;
1241 // let target_pane = cx.weak_handle();
1242
1243 // // The `CloseInactiveItems` action should really be called "CloseOthers" and the behaviour should be dynamically based on the tab the action is ran on. Currently, this is a weird action because you can run it on a non-active tab and it will close everything by the actual active tab
1244
1245 // self.tab_context_menu.update(cx, |menu, cx| {
1246 // menu.show(
1247 // position,
1248 // AnchorCorner::TopLeft,
1249 // if is_active_item {
1250 // vec![
1251 // ContextMenuItem::action(
1252 // "Close Active Item",
1253 // CloseActiveItem { save_intent: None },
1254 // ),
1255 // ContextMenuItem::action("Close Inactive Items", CloseInactiveItems),
1256 // ContextMenuItem::action("Close Clean Items", CloseCleanItems),
1257 // ContextMenuItem::action("Close Items To The Left", CloseItemsToTheLeft),
1258 // ContextMenuItem::action("Close Items To The Right", CloseItemsToTheRight),
1259 // ContextMenuItem::action(
1260 // "Close All Items",
1261 // CloseAllItems { save_intent: None },
1262 // ),
1263 // ]
1264 // } else {
1265 // // In the case of the user right clicking on a non-active tab, for some item-closing commands, we need to provide the id of the tab, for the others, we can reuse the existing command.
1266 // vec![
1267 // ContextMenuItem::handler("Close Inactive Item", {
1268 // let pane = target_pane.clone();
1269 // move |cx| {
1270 // if let Some(pane) = pane.upgrade(cx) {
1271 // pane.update(cx, |pane, cx| {
1272 // pane.close_item_by_id(
1273 // target_item_id,
1274 // SaveIntent::Close,
1275 // cx,
1276 // )
1277 // .detach_and_log_err(cx);
1278 // })
1279 // }
1280 // }
1281 // }),
1282 // ContextMenuItem::action("Close Inactive Items", CloseInactiveItems),
1283 // ContextMenuItem::action("Close Clean Items", CloseCleanItems),
1284 // ContextMenuItem::handler("Close Items To The Left", {
1285 // let pane = target_pane.clone();
1286 // move |cx| {
1287 // if let Some(pane) = pane.upgrade(cx) {
1288 // pane.update(cx, |pane, cx| {
1289 // pane.close_items_to_the_left_by_id(target_item_id, cx)
1290 // .detach_and_log_err(cx);
1291 // })
1292 // }
1293 // }
1294 // }),
1295 // ContextMenuItem::handler("Close Items To The Right", {
1296 // let pane = target_pane.clone();
1297 // move |cx| {
1298 // if let Some(pane) = pane.upgrade(cx) {
1299 // pane.update(cx, |pane, cx| {
1300 // pane.close_items_to_the_right_by_id(target_item_id, cx)
1301 // .detach_and_log_err(cx);
1302 // })
1303 // }
1304 // }
1305 // }),
1306 // ContextMenuItem::action(
1307 // "Close All Items",
1308 // CloseAllItems { save_intent: None },
1309 // ),
1310 // ]
1311 // },
1312 // cx,
1313 // );
1314 // });
1315 // }
1316
1317 // pub fn toolbar(&self) -> &ViewHandle<Toolbar> {
1318 // &self.toolbar
1319 // }
1320
1321 // pub fn handle_deleted_project_item(
1322 // &mut self,
1323 // entry_id: ProjectEntryId,
1324 // cx: &mut ViewContext<Pane>,
1325 // ) -> Option<()> {
1326 // let (item_index_to_delete, item_id) = self.items().enumerate().find_map(|(i, item)| {
1327 // if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] {
1328 // Some((i, item.id()))
1329 // } else {
1330 // None
1331 // }
1332 // })?;
1333
1334 // self.remove_item(item_index_to_delete, false, cx);
1335 // self.nav_history.remove_item(item_id);
1336
1337 // Some(())
1338 // }
1339
1340 fn update_toolbar(&mut self, cx: &mut ViewContext<Self>) {
1341 let active_item = self
1342 .items
1343 .get(self.active_item_index)
1344 .map(|item| item.as_ref());
1345 self.toolbar.update(cx, |toolbar, cx| {
1346 toolbar.set_active_item(active_item, cx);
1347 });
1348 }
1349
1350 // fn render_tabs(&mut self, cx: &mut ViewContext<Self>) -> impl Element<Self> {
1351 // let theme = theme::current(cx).clone();
1352
1353 // let pane = cx.handle().downgrade();
1354 // let autoscroll = if mem::take(&mut self.autoscroll) {
1355 // Some(self.active_item_index)
1356 // } else {
1357 // None
1358 // };
1359
1360 // let pane_active = self.has_focus;
1361
1362 // enum Tabs {}
1363 // let mut row = Flex::row().scrollable::<Tabs>(1, autoscroll, cx);
1364 // for (ix, (item, detail)) in self
1365 // .items
1366 // .iter()
1367 // .cloned()
1368 // .zip(self.tab_details(cx))
1369 // .enumerate()
1370 // {
1371 // let git_status = item
1372 // .project_path(cx)
1373 // .and_then(|path| self.project.read(cx).entry_for_path(&path, cx))
1374 // .and_then(|entry| entry.git_status());
1375
1376 // let detail = if detail == 0 { None } else { Some(detail) };
1377 // let tab_active = ix == self.active_item_index;
1378
1379 // row.add_child({
1380 // enum TabDragReceiver {}
1381 // let mut receiver =
1382 // dragged_item_receiver::<TabDragReceiver, _, _>(self, ix, ix, true, None, cx, {
1383 // let item = item.clone();
1384 // let pane = pane.clone();
1385 // let detail = detail.clone();
1386
1387 // let theme = theme::current(cx).clone();
1388 // let mut tooltip_theme = theme.tooltip.clone();
1389 // tooltip_theme.max_text_width = None;
1390 // let tab_tooltip_text =
1391 // item.tab_tooltip_text(cx).map(|text| text.into_owned());
1392
1393 // let mut tab_style = theme
1394 // .workspace
1395 // .tab_bar
1396 // .tab_style(pane_active, tab_active)
1397 // .clone();
1398 // let should_show_status = settings::get::<ItemSettings>(cx).git_status;
1399 // if should_show_status && git_status != None {
1400 // tab_style.label.text.color = match git_status.unwrap() {
1401 // GitFileStatus::Added => tab_style.git.inserted,
1402 // GitFileStatus::Modified => tab_style.git.modified,
1403 // GitFileStatus::Conflict => tab_style.git.conflict,
1404 // };
1405 // }
1406
1407 // move |mouse_state, cx| {
1408 // let hovered = mouse_state.hovered();
1409
1410 // enum Tab {}
1411 // let mouse_event_handler =
1412 // MouseEventHandler::new::<Tab, _>(ix, cx, |_, cx| {
1413 // Self::render_tab(
1414 // &item,
1415 // pane.clone(),
1416 // ix == 0,
1417 // detail,
1418 // hovered,
1419 // &tab_style,
1420 // cx,
1421 // )
1422 // })
1423 // .on_down(MouseButton::Left, move |_, this, cx| {
1424 // this.activate_item(ix, true, true, cx);
1425 // })
1426 // .on_click(MouseButton::Middle, {
1427 // let item_id = item.id();
1428 // move |_, pane, cx| {
1429 // pane.close_item_by_id(item_id, SaveIntent::Close, cx)
1430 // .detach_and_log_err(cx);
1431 // }
1432 // })
1433 // .on_down(
1434 // MouseButton::Right,
1435 // move |event, pane, cx| {
1436 // pane.deploy_tab_context_menu(event.position, item.id(), cx);
1437 // },
1438 // );
1439
1440 // if let Some(tab_tooltip_text) = tab_tooltip_text {
1441 // mouse_event_handler
1442 // .with_tooltip::<Self>(
1443 // ix,
1444 // tab_tooltip_text,
1445 // None,
1446 // tooltip_theme,
1447 // cx,
1448 // )
1449 // .into_any()
1450 // } else {
1451 // mouse_event_handler.into_any()
1452 // }
1453 // }
1454 // });
1455
1456 // if !pane_active || !tab_active {
1457 // receiver = receiver.with_cursor_style(CursorStyle::PointingHand);
1458 // }
1459
1460 // receiver.as_draggable(
1461 // DraggedItem {
1462 // handle: item,
1463 // pane: pane.clone(),
1464 // },
1465 // {
1466 // let theme = theme::current(cx).clone();
1467
1468 // let detail = detail.clone();
1469 // move |_, dragged_item: &DraggedItem, cx: &mut ViewContext<Workspace>| {
1470 // let tab_style = &theme.workspace.tab_bar.dragged_tab;
1471 // Self::render_dragged_tab(
1472 // &dragged_item.handle,
1473 // dragged_item.pane.clone(),
1474 // false,
1475 // detail,
1476 // false,
1477 // &tab_style,
1478 // cx,
1479 // )
1480 // }
1481 // },
1482 // )
1483 // })
1484 // }
1485
1486 // // Use the inactive tab style along with the current pane's active status to decide how to render
1487 // // the filler
1488 // let filler_index = self.items.len();
1489 // let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false);
1490 // enum Filler {}
1491 // row.add_child(
1492 // dragged_item_receiver::<Filler, _, _>(self, 0, filler_index, true, None, cx, |_, _| {
1493 // Empty::new()
1494 // .contained()
1495 // .with_style(filler_style.container)
1496 // .with_border(filler_style.container.border)
1497 // })
1498 // .flex(1., true)
1499 // .into_any_named("filler"),
1500 // );
1501
1502 // row
1503 // }
1504
1505 // fn tab_details(&self, cx: &AppContext) -> Vec<usize> {
1506 // let mut tab_details = (0..self.items.len()).map(|_| 0).collect::<Vec<_>>();
1507
1508 // let mut tab_descriptions = HashMap::default();
1509 // let mut done = false;
1510 // while !done {
1511 // done = true;
1512
1513 // // Store item indices by their tab description.
1514 // for (ix, (item, detail)) in self.items.iter().zip(&tab_details).enumerate() {
1515 // if let Some(description) = item.tab_description(*detail, cx) {
1516 // if *detail == 0
1517 // || Some(&description) != item.tab_description(detail - 1, cx).as_ref()
1518 // {
1519 // tab_descriptions
1520 // .entry(description)
1521 // .or_insert(Vec::new())
1522 // .push(ix);
1523 // }
1524 // }
1525 // }
1526
1527 // // If two or more items have the same tab description, increase their level
1528 // // of detail and try again.
1529 // for (_, item_ixs) in tab_descriptions.drain() {
1530 // if item_ixs.len() > 1 {
1531 // done = false;
1532 // for ix in item_ixs {
1533 // tab_details[ix] += 1;
1534 // }
1535 // }
1536 // }
1537 // }
1538
1539 // tab_details
1540 // }
1541
1542 // fn render_tab(
1543 // item: &Box<dyn ItemHandle>,
1544 // pane: WeakView<Pane>,
1545 // first: bool,
1546 // detail: Option<usize>,
1547 // hovered: bool,
1548 // tab_style: &theme::Tab,
1549 // cx: &mut ViewContext<Self>,
1550 // ) -> AnyElement<Self> {
1551 // let title = item.tab_content(detail, &tab_style, cx);
1552 // Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx)
1553 // }
1554
1555 // fn render_dragged_tab(
1556 // item: &Box<dyn ItemHandle>,
1557 // pane: WeakView<Pane>,
1558 // first: bool,
1559 // detail: Option<usize>,
1560 // hovered: bool,
1561 // tab_style: &theme::Tab,
1562 // cx: &mut ViewContext<Workspace>,
1563 // ) -> AnyElement<Workspace> {
1564 // let title = item.dragged_tab_content(detail, &tab_style, cx);
1565 // Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx)
1566 // }
1567
1568 // fn render_tab_with_title<T: View>(
1569 // title: AnyElement<T>,
1570 // item: &Box<dyn ItemHandle>,
1571 // pane: WeakView<Pane>,
1572 // first: bool,
1573 // hovered: bool,
1574 // tab_style: &theme::Tab,
1575 // cx: &mut ViewContext<T>,
1576 // ) -> AnyElement<T> {
1577 // let mut container = tab_style.container.clone();
1578 // if first {
1579 // container.border.left = false;
1580 // }
1581
1582 // let buffer_jewel_element = {
1583 // let diameter = 7.0;
1584 // let icon_color = if item.has_conflict(cx) {
1585 // Some(tab_style.icon_conflict)
1586 // } else if item.is_dirty(cx) {
1587 // Some(tab_style.icon_dirty)
1588 // } else {
1589 // None
1590 // };
1591
1592 // Canvas::new(move |bounds, _, _, cx| {
1593 // if let Some(color) = icon_color {
1594 // let square = RectF::new(bounds.origin(), vec2f(diameter, diameter));
1595 // cx.scene().push_quad(Quad {
1596 // bounds: square,
1597 // background: Some(color),
1598 // border: Default::default(),
1599 // corner_radii: (diameter / 2.).into(),
1600 // });
1601 // }
1602 // })
1603 // .constrained()
1604 // .with_width(diameter)
1605 // .with_height(diameter)
1606 // .aligned()
1607 // };
1608
1609 // let title_element = title.aligned().contained().with_style(ContainerStyle {
1610 // margin: Margin {
1611 // left: tab_style.spacing,
1612 // right: tab_style.spacing,
1613 // ..Default::default()
1614 // },
1615 // ..Default::default()
1616 // });
1617
1618 // let close_element = if hovered {
1619 // let item_id = item.id();
1620 // enum TabCloseButton {}
1621 // let icon = Svg::new("icons/x.svg");
1622 // MouseEventHandler::new::<TabCloseButton, _>(item_id, cx, |mouse_state, _| {
1623 // if mouse_state.hovered() {
1624 // icon.with_color(tab_style.icon_close_active)
1625 // } else {
1626 // icon.with_color(tab_style.icon_close)
1627 // }
1628 // })
1629 // .with_padding(Padding::uniform(4.))
1630 // .with_cursor_style(CursorStyle::PointingHand)
1631 // .on_click(MouseButton::Left, {
1632 // let pane = pane.clone();
1633 // move |_, _, cx| {
1634 // let pane = pane.clone();
1635 // cx.window_context().defer(move |cx| {
1636 // if let Some(pane) = pane.upgrade(cx) {
1637 // pane.update(cx, |pane, cx| {
1638 // pane.close_item_by_id(item_id, SaveIntent::Close, cx)
1639 // .detach_and_log_err(cx);
1640 // });
1641 // }
1642 // });
1643 // }
1644 // })
1645 // .into_any_named("close-tab-icon")
1646 // .constrained()
1647 // } else {
1648 // Empty::new().constrained()
1649 // }
1650 // .with_width(tab_style.close_icon_width)
1651 // .aligned();
1652
1653 // let close_right = settings::get::<ItemSettings>(cx).close_position.right();
1654
1655 // if close_right {
1656 // Flex::row()
1657 // .with_child(buffer_jewel_element)
1658 // .with_child(title_element)
1659 // .with_child(close_element)
1660 // } else {
1661 // Flex::row()
1662 // .with_child(close_element)
1663 // .with_child(title_element)
1664 // .with_child(buffer_jewel_element)
1665 // }
1666 // .contained()
1667 // .with_style(container)
1668 // .constrained()
1669 // .with_height(tab_style.height)
1670 // .into_any()
1671 // }
1672
1673 // pub fn render_tab_bar_button<
1674 // F1: 'static + Fn(&mut Pane, &mut EventContext<Pane>),
1675 // F2: 'static + Fn(&mut Pane, &mut EventContext<Pane>),
1676 // >(
1677 // index: usize,
1678 // icon: &'static str,
1679 // is_active: bool,
1680 // tooltip: Option<(&'static str, Option<Box<dyn Action>>)>,
1681 // cx: &mut ViewContext<Pane>,
1682 // on_click: F1,
1683 // on_down: F2,
1684 // context_menu: Option<ViewHandle<ContextMenu>>,
1685 // ) -> AnyElement<Pane> {
1686 // enum TabBarButton {}
1687
1688 // let mut button = MouseEventHandler::new::<TabBarButton, _>(index, cx, |mouse_state, cx| {
1689 // let theme = &settings2::get::<ThemeSettings>(cx).theme.workspace.tab_bar;
1690 // let style = theme.pane_button.in_state(is_active).style_for(mouse_state);
1691 // Svg::new(icon)
1692 // .with_color(style.color)
1693 // .constrained()
1694 // .with_width(style.icon_width)
1695 // .aligned()
1696 // .constrained()
1697 // .with_width(style.button_width)
1698 // .with_height(style.button_width)
1699 // })
1700 // .with_cursor_style(CursorStyle::PointingHand)
1701 // .on_down(MouseButton::Left, move |_, pane, cx| on_down(pane, cx))
1702 // .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx))
1703 // .into_any();
1704 // if let Some((tooltip, action)) = tooltip {
1705 // let tooltip_style = settings::get::<ThemeSettings>(cx).theme.tooltip.clone();
1706 // button = button
1707 // .with_tooltip::<TabBarButton>(index, tooltip, action, tooltip_style, cx)
1708 // .into_any();
1709 // }
1710
1711 // Stack::new()
1712 // .with_child(button)
1713 // .with_children(
1714 // context_menu.map(|menu| ChildView::new(&menu, cx).aligned().bottom().right()),
1715 // )
1716 // .flex(1., false)
1717 // .into_any_named("tab bar button")
1718 // }
1719
1720 // fn render_blank_pane(&self, theme: &Theme, _cx: &mut ViewContext<Self>) -> AnyElement<Self> {
1721 // let background = theme.workspace.background;
1722 // Empty::new()
1723 // .contained()
1724 // .with_background_color(background)
1725 // .into_any()
1726 // }
1727
1728 // pub fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext<Self>) {
1729 // self.zoomed = zoomed;
1730 // cx.notify();
1731 // }
1732
1733 // pub fn is_zoomed(&self) -> bool {
1734 // self.zoomed
1735 // }
1736}
1737
1738// impl Entity for Pane {
1739// type Event = Event;
1740// }
1741
1742// impl View for Pane {
1743// fn ui_name() -> &'static str {
1744// "Pane"
1745// }
1746
1747// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
1748// enum MouseNavigationHandler {}
1749
1750// MouseEventHandler::new::<MouseNavigationHandler, _>(0, cx, |_, cx| {
1751// let active_item_index = self.active_item_index;
1752
1753// if let Some(active_item) = self.active_item() {
1754// Flex::column()
1755// .with_child({
1756// let theme = theme::current(cx).clone();
1757
1758// let mut stack = Stack::new();
1759
1760// enum TabBarEventHandler {}
1761// stack.add_child(
1762// MouseEventHandler::new::<TabBarEventHandler, _>(0, cx, |_, _| {
1763// Empty::new()
1764// .contained()
1765// .with_style(theme.workspace.tab_bar.container)
1766// })
1767// .on_down(
1768// MouseButton::Left,
1769// move |_, this, cx| {
1770// this.activate_item(active_item_index, true, true, cx);
1771// },
1772// ),
1773// );
1774// let tooltip_style = theme.tooltip.clone();
1775// let tab_bar_theme = theme.workspace.tab_bar.clone();
1776
1777// let nav_button_height = tab_bar_theme.height;
1778// let button_style = tab_bar_theme.nav_button;
1779// let border_for_nav_buttons = tab_bar_theme
1780// .tab_style(false, false)
1781// .container
1782// .border
1783// .clone();
1784
1785// let mut tab_row = Flex::row()
1786// .with_child(nav_button(
1787// "icons/arrow_left.svg",
1788// button_style.clone(),
1789// nav_button_height,
1790// tooltip_style.clone(),
1791// self.can_navigate_backward(),
1792// {
1793// move |pane, cx| {
1794// if let Some(workspace) = pane.workspace.upgrade(cx) {
1795// let pane = cx.weak_handle();
1796// cx.window_context().defer(move |cx| {
1797// workspace.update(cx, |workspace, cx| {
1798// workspace
1799// .go_back(pane, cx)
1800// .detach_and_log_err(cx)
1801// })
1802// })
1803// }
1804// }
1805// },
1806// super::GoBack,
1807// "Go Back",
1808// cx,
1809// ))
1810// .with_child(
1811// nav_button(
1812// "icons/arrow_right.svg",
1813// button_style.clone(),
1814// nav_button_height,
1815// tooltip_style,
1816// self.can_navigate_forward(),
1817// {
1818// move |pane, cx| {
1819// if let Some(workspace) = pane.workspace.upgrade(cx) {
1820// let pane = cx.weak_handle();
1821// cx.window_context().defer(move |cx| {
1822// workspace.update(cx, |workspace, cx| {
1823// workspace
1824// .go_forward(pane, cx)
1825// .detach_and_log_err(cx)
1826// })
1827// })
1828// }
1829// }
1830// },
1831// super::GoForward,
1832// "Go Forward",
1833// cx,
1834// )
1835// .contained()
1836// .with_border(border_for_nav_buttons),
1837// )
1838// .with_child(self.render_tabs(cx).flex(1., true).into_any_named("tabs"));
1839
1840// if self.has_focus {
1841// let render_tab_bar_buttons = self.render_tab_bar_buttons.clone();
1842// tab_row.add_child(
1843// (render_tab_bar_buttons)(self, cx)
1844// .contained()
1845// .with_style(theme.workspace.tab_bar.pane_button_container)
1846// .flex(1., false)
1847// .into_any(),
1848// )
1849// }
1850
1851// stack.add_child(tab_row);
1852// stack
1853// .constrained()
1854// .with_height(theme.workspace.tab_bar.height)
1855// .flex(1., false)
1856// .into_any_named("tab bar")
1857// })
1858// .with_child({
1859// enum PaneContentTabDropTarget {}
1860// dragged_item_receiver::<PaneContentTabDropTarget, _, _>(
1861// self,
1862// 0,
1863// self.active_item_index + 1,
1864// !self.can_split,
1865// if self.can_split { Some(100.) } else { None },
1866// cx,
1867// {
1868// let toolbar = self.toolbar.clone();
1869// let toolbar_hidden = toolbar.read(cx).hidden();
1870// move |_, cx| {
1871// Flex::column()
1872// .with_children(
1873// (!toolbar_hidden)
1874// .then(|| ChildView::new(&toolbar, cx).expanded()),
1875// )
1876// .with_child(
1877// ChildView::new(active_item.as_any(), cx).flex(1., true),
1878// )
1879// }
1880// },
1881// )
1882// .flex(1., true)
1883// })
1884// .with_child(ChildView::new(&self.tab_context_menu, cx))
1885// .into_any()
1886// } else {
1887// enum EmptyPane {}
1888// let theme = theme::current(cx).clone();
1889
1890// dragged_item_receiver::<EmptyPane, _, _>(self, 0, 0, false, None, cx, |_, cx| {
1891// self.render_blank_pane(&theme, cx)
1892// })
1893// .on_down(MouseButton::Left, |_, _, cx| {
1894// cx.focus_parent();
1895// })
1896// .into_any()
1897// }
1898// })
1899// .on_down(
1900// MouseButton::Navigate(NavigationDirection::Back),
1901// move |_, pane, cx| {
1902// if let Some(workspace) = pane.workspace.upgrade(cx) {
1903// let pane = cx.weak_handle();
1904// cx.window_context().defer(move |cx| {
1905// workspace.update(cx, |workspace, cx| {
1906// workspace.go_back(pane, cx).detach_and_log_err(cx)
1907// })
1908// })
1909// }
1910// },
1911// )
1912// .on_down(MouseButton::Navigate(NavigationDirection::Forward), {
1913// move |_, pane, cx| {
1914// if let Some(workspace) = pane.workspace.upgrade(cx) {
1915// let pane = cx.weak_handle();
1916// cx.window_context().defer(move |cx| {
1917// workspace.update(cx, |workspace, cx| {
1918// workspace.go_forward(pane, cx).detach_and_log_err(cx)
1919// })
1920// })
1921// }
1922// }
1923// })
1924// .into_any_named("pane")
1925// }
1926
1927// fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext<Self>) {
1928// if !self.has_focus {
1929// self.has_focus = true;
1930// cx.emit(Event::Focus);
1931// cx.notify();
1932// }
1933
1934// self.toolbar.update(cx, |toolbar, cx| {
1935// toolbar.focus_changed(true, cx);
1936// });
1937
1938// if let Some(active_item) = self.active_item() {
1939// if cx.is_self_focused() {
1940// // Pane was focused directly. We need to either focus a view inside the active item,
1941// // or focus the active item itself
1942// if let Some(weak_last_focused_view) =
1943// self.last_focused_view_by_item.get(&active_item.id())
1944// {
1945// if let Some(last_focused_view) = weak_last_focused_view.upgrade(cx) {
1946// cx.focus(&last_focused_view);
1947// return;
1948// } else {
1949// self.last_focused_view_by_item.remove(&active_item.id());
1950// }
1951// }
1952
1953// cx.focus(active_item.as_any());
1954// } else if focused != self.tab_bar_context_menu.handle {
1955// self.last_focused_view_by_item
1956// .insert(active_item.id(), focused.downgrade());
1957// }
1958// }
1959// }
1960
1961// fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
1962// self.has_focus = false;
1963// self.toolbar.update(cx, |toolbar, cx| {
1964// toolbar.focus_changed(false, cx);
1965// });
1966// cx.notify();
1967// }
1968
1969// fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
1970// Self::reset_to_default_keymap_context(keymap);
1971// }
1972// }
1973
1974impl ItemNavHistory {
1975 pub fn push<D: 'static + Any>(&mut self, data: Option<D>, cx: &mut WindowContext) {
1976 self.history.push(data, self.item.clone(), cx);
1977 }
1978
1979 pub fn pop_backward(&mut self, cx: &mut WindowContext) -> Option<NavigationEntry> {
1980 self.history.pop(NavigationMode::GoingBack, cx)
1981 }
1982
1983 pub fn pop_forward(&mut self, cx: &mut WindowContext) -> Option<NavigationEntry> {
1984 self.history.pop(NavigationMode::GoingForward, cx)
1985 }
1986}
1987
1988impl NavHistory {
1989 pub fn for_each_entry(
1990 &self,
1991 cx: &AppContext,
1992 mut f: impl FnMut(&NavigationEntry, (ProjectPath, Option<PathBuf>)),
1993 ) {
1994 let borrowed_history = self.0.lock();
1995 borrowed_history
1996 .forward_stack
1997 .iter()
1998 .chain(borrowed_history.backward_stack.iter())
1999 .chain(borrowed_history.closed_stack.iter())
2000 .for_each(|entry| {
2001 if let Some(project_and_abs_path) =
2002 borrowed_history.paths_by_item.get(&entry.item.id())
2003 {
2004 f(entry, project_and_abs_path.clone());
2005 } else if let Some(item) = entry.item.upgrade() {
2006 if let Some(path) = item.project_path(cx) {
2007 f(entry, (path, None));
2008 }
2009 }
2010 })
2011 }
2012
2013 pub fn set_mode(&mut self, mode: NavigationMode) {
2014 self.0.lock().mode = mode;
2015 }
2016
2017 pub fn mode(&self) -> NavigationMode {
2018 self.0.lock().mode
2019 }
2020
2021 pub fn disable(&mut self) {
2022 self.0.lock().mode = NavigationMode::Disabled;
2023 }
2024
2025 pub fn enable(&mut self) {
2026 self.0.lock().mode = NavigationMode::Normal;
2027 }
2028
2029 pub fn pop(&mut self, mode: NavigationMode, cx: &mut WindowContext) -> Option<NavigationEntry> {
2030 let mut state = self.0.lock();
2031 let entry = match mode {
2032 NavigationMode::Normal | NavigationMode::Disabled | NavigationMode::ClosingItem => {
2033 return None
2034 }
2035 NavigationMode::GoingBack => &mut state.backward_stack,
2036 NavigationMode::GoingForward => &mut state.forward_stack,
2037 NavigationMode::ReopeningClosedItem => &mut state.closed_stack,
2038 }
2039 .pop_back();
2040 if entry.is_some() {
2041 state.did_update(cx);
2042 }
2043 entry
2044 }
2045
2046 pub fn push<D: 'static + Any>(
2047 &mut self,
2048 data: Option<D>,
2049 item: Arc<dyn WeakItemHandle>,
2050 cx: &mut WindowContext,
2051 ) {
2052 let state = &mut *self.0.lock();
2053 match state.mode {
2054 NavigationMode::Disabled => {}
2055 NavigationMode::Normal | NavigationMode::ReopeningClosedItem => {
2056 if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
2057 state.backward_stack.pop_front();
2058 }
2059 state.backward_stack.push_back(NavigationEntry {
2060 item,
2061 data: data.map(|data| Box::new(data) as Box<dyn Any>),
2062 timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
2063 });
2064 state.forward_stack.clear();
2065 }
2066 NavigationMode::GoingBack => {
2067 if state.forward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
2068 state.forward_stack.pop_front();
2069 }
2070 state.forward_stack.push_back(NavigationEntry {
2071 item,
2072 data: data.map(|data| Box::new(data) as Box<dyn Any>),
2073 timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
2074 });
2075 }
2076 NavigationMode::GoingForward => {
2077 if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
2078 state.backward_stack.pop_front();
2079 }
2080 state.backward_stack.push_back(NavigationEntry {
2081 item,
2082 data: data.map(|data| Box::new(data) as Box<dyn Any>),
2083 timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
2084 });
2085 }
2086 NavigationMode::ClosingItem => {
2087 if state.closed_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
2088 state.closed_stack.pop_front();
2089 }
2090 state.closed_stack.push_back(NavigationEntry {
2091 item,
2092 data: data.map(|data| Box::new(data) as Box<dyn Any>),
2093 timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
2094 });
2095 }
2096 }
2097 state.did_update(cx);
2098 }
2099
2100 pub fn remove_item(&mut self, item_id: usize) {
2101 let mut state = self.0.lock();
2102 state.paths_by_item.remove(&item_id);
2103 state
2104 .backward_stack
2105 .retain(|entry| entry.item.id() != item_id);
2106 state
2107 .forward_stack
2108 .retain(|entry| entry.item.id() != item_id);
2109 state
2110 .closed_stack
2111 .retain(|entry| entry.item.id() != item_id);
2112 }
2113
2114 pub fn path_for_item(&self, item_id: usize) -> Option<(ProjectPath, Option<PathBuf>)> {
2115 self.0.lock().paths_by_item.get(&item_id).cloned()
2116 }
2117}
2118
2119impl NavHistoryState {
2120 pub fn did_update(&self, cx: &mut WindowContext) {
2121 if let Some(pane) = self.pane.upgrade() {
2122 cx.defer(move |cx| {
2123 pane.update(cx, |pane, cx| pane.history_updated(cx));
2124 });
2125 }
2126 }
2127}
2128
2129// pub struct PaneBackdrop<V> {
2130// child_view: usize,
2131// child: AnyElement<V>,
2132// }
2133
2134// impl<V> PaneBackdrop<V> {
2135// pub fn new(pane_item_view: usize, child: AnyElement<V>) -> Self {
2136// PaneBackdrop {
2137// child,
2138// child_view: pane_item_view,
2139// }
2140// }
2141// }
2142
2143// impl<V: 'static> Element<V> for PaneBackdrop<V> {
2144// type LayoutState = ();
2145
2146// type PaintState = ();
2147
2148// fn layout(
2149// &mut self,
2150// constraint: gpui::SizeConstraint,
2151// view: &mut V,
2152// cx: &mut ViewContext<V>,
2153// ) -> (Vector2F, Self::LayoutState) {
2154// let size = self.child.layout(constraint, view, cx);
2155// (size, ())
2156// }
2157
2158// fn paint(
2159// &mut self,
2160// bounds: RectF,
2161// visible_bounds: RectF,
2162// _: &mut Self::LayoutState,
2163// view: &mut V,
2164// cx: &mut ViewContext<V>,
2165// ) -> Self::PaintState {
2166// let background = theme::current(cx).editor.background;
2167
2168// let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
2169
2170// cx.scene().push_quad(gpui::Quad {
2171// bounds: RectF::new(bounds.origin(), bounds.size()),
2172// background: Some(background),
2173// ..Default::default()
2174// });
2175
2176// let child_view_id = self.child_view;
2177// cx.scene().push_mouse_region(
2178// MouseRegion::new::<Self>(child_view_id, 0, visible_bounds).on_down(
2179// gpui::platform::MouseButton::Left,
2180// move |_, _: &mut V, cx| {
2181// let window = cx.window();
2182// cx.app_context().focus(window, Some(child_view_id))
2183// },
2184// ),
2185// );
2186
2187// cx.scene().push_layer(Some(bounds));
2188// self.child.paint(bounds.origin(), visible_bounds, view, cx);
2189// cx.scene().pop_layer();
2190// }
2191
2192// fn rect_for_text_range(
2193// &self,
2194// range_utf16: std::ops::Range<usize>,
2195// _bounds: RectF,
2196// _visible_bounds: RectF,
2197// _layout: &Self::LayoutState,
2198// _paint: &Self::PaintState,
2199// view: &V,
2200// cx: &gpui::ViewContext<V>,
2201// ) -> Option<RectF> {
2202// self.child.rect_for_text_range(range_utf16, view, cx)
2203// }
2204
2205// fn debug(
2206// &self,
2207// _bounds: RectF,
2208// _layout: &Self::LayoutState,
2209// _paint: &Self::PaintState,
2210// view: &V,
2211// cx: &gpui::ViewContext<V>,
2212// ) -> serde_json::Value {
2213// gpui::json::json!({
2214// "type": "Pane Back Drop",
2215// "view": self.child_view,
2216// "child": self.child.debug(view, cx),
2217// })
2218// }
2219// }
2220
2221// fn dirty_message_for(buffer_path: Option<ProjectPath>) -> String {
2222// let path = buffer_path
2223// .as_ref()
2224// .and_then(|p| p.path.to_str())
2225// .unwrap_or(&"This buffer");
2226// let path = truncate_and_remove_front(path, 80);
2227// format!("{path} contains unsaved edits. Do you want to save it?")
2228// }
2229
2230// todo!("uncomment tests")
2231// #[cfg(test)]
2232// mod tests {
2233// use super::*;
2234// use crate::item::test::{TestItem, TestProjectItem};
2235// use gpui::TestAppContext;
2236// use project::FakeFs;
2237// use settings::SettingsStore;
2238
2239// #[gpui::test]
2240// async fn test_remove_active_empty(cx: &mut TestAppContext) {
2241// init_test(cx);
2242// let fs = FakeFs::new(cx.background());
2243
2244// let project = Project::test(fs, None, cx).await;
2245// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2246// let workspace = window.root(cx);
2247// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2248
2249// pane.update(cx, |pane, cx| {
2250// assert!(pane
2251// .close_active_item(&CloseActiveItem { save_intent: None }, cx)
2252// .is_none())
2253// });
2254// }
2255
2256// #[gpui::test]
2257// async fn test_add_item_with_new_item(cx: &mut TestAppContext) {
2258// cx.foreground().forbid_parking();
2259// init_test(cx);
2260// let fs = FakeFs::new(cx.background());
2261
2262// let project = Project::test(fs, None, cx).await;
2263// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2264// let workspace = window.root(cx);
2265// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2266
2267// // 1. Add with a destination index
2268// // a. Add before the active item
2269// set_labeled_items(&pane, ["A", "B*", "C"], cx);
2270// pane.update(cx, |pane, cx| {
2271// pane.add_item(
2272// Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
2273// false,
2274// false,
2275// Some(0),
2276// cx,
2277// );
2278// });
2279// assert_item_labels(&pane, ["D*", "A", "B", "C"], cx);
2280
2281// // b. Add after the active item
2282// set_labeled_items(&pane, ["A", "B*", "C"], cx);
2283// pane.update(cx, |pane, cx| {
2284// pane.add_item(
2285// Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
2286// false,
2287// false,
2288// Some(2),
2289// cx,
2290// );
2291// });
2292// assert_item_labels(&pane, ["A", "B", "D*", "C"], cx);
2293
2294// // c. Add at the end of the item list (including off the length)
2295// set_labeled_items(&pane, ["A", "B*", "C"], cx);
2296// pane.update(cx, |pane, cx| {
2297// pane.add_item(
2298// Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
2299// false,
2300// false,
2301// Some(5),
2302// cx,
2303// );
2304// });
2305// assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
2306
2307// // 2. Add without a destination index
2308// // a. Add with active item at the start of the item list
2309// set_labeled_items(&pane, ["A*", "B", "C"], cx);
2310// pane.update(cx, |pane, cx| {
2311// pane.add_item(
2312// Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
2313// false,
2314// false,
2315// None,
2316// cx,
2317// );
2318// });
2319// set_labeled_items(&pane, ["A", "D*", "B", "C"], cx);
2320
2321// // b. Add with active item at the end of the item list
2322// set_labeled_items(&pane, ["A", "B", "C*"], cx);
2323// pane.update(cx, |pane, cx| {
2324// pane.add_item(
2325// Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
2326// false,
2327// false,
2328// None,
2329// cx,
2330// );
2331// });
2332// assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
2333// }
2334
2335// #[gpui::test]
2336// async fn test_add_item_with_existing_item(cx: &mut TestAppContext) {
2337// cx.foreground().forbid_parking();
2338// init_test(cx);
2339// let fs = FakeFs::new(cx.background());
2340
2341// let project = Project::test(fs, None, cx).await;
2342// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2343// let workspace = window.root(cx);
2344// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2345
2346// // 1. Add with a destination index
2347// // 1a. Add before the active item
2348// let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx);
2349// pane.update(cx, |pane, cx| {
2350// pane.add_item(d, false, false, Some(0), cx);
2351// });
2352// assert_item_labels(&pane, ["D*", "A", "B", "C"], cx);
2353
2354// // 1b. Add after the active item
2355// let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx);
2356// pane.update(cx, |pane, cx| {
2357// pane.add_item(d, false, false, Some(2), cx);
2358// });
2359// assert_item_labels(&pane, ["A", "B", "D*", "C"], cx);
2360
2361// // 1c. Add at the end of the item list (including off the length)
2362// let [a, _, _, _] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx);
2363// pane.update(cx, |pane, cx| {
2364// pane.add_item(a, false, false, Some(5), cx);
2365// });
2366// assert_item_labels(&pane, ["B", "C", "D", "A*"], cx);
2367
2368// // 1d. Add same item to active index
2369// let [_, b, _] = set_labeled_items(&pane, ["A", "B*", "C"], cx);
2370// pane.update(cx, |pane, cx| {
2371// pane.add_item(b, false, false, Some(1), cx);
2372// });
2373// assert_item_labels(&pane, ["A", "B*", "C"], cx);
2374
2375// // 1e. Add item to index after same item in last position
2376// let [_, _, c] = set_labeled_items(&pane, ["A", "B*", "C"], cx);
2377// pane.update(cx, |pane, cx| {
2378// pane.add_item(c, false, false, Some(2), cx);
2379// });
2380// assert_item_labels(&pane, ["A", "B", "C*"], cx);
2381
2382// // 2. Add without a destination index
2383// // 2a. Add with active item at the start of the item list
2384// let [_, _, _, d] = set_labeled_items(&pane, ["A*", "B", "C", "D"], cx);
2385// pane.update(cx, |pane, cx| {
2386// pane.add_item(d, false, false, None, cx);
2387// });
2388// assert_item_labels(&pane, ["A", "D*", "B", "C"], cx);
2389
2390// // 2b. Add with active item at the end of the item list
2391// let [a, _, _, _] = set_labeled_items(&pane, ["A", "B", "C", "D*"], cx);
2392// pane.update(cx, |pane, cx| {
2393// pane.add_item(a, false, false, None, cx);
2394// });
2395// assert_item_labels(&pane, ["B", "C", "D", "A*"], cx);
2396
2397// // 2c. Add active item to active item at end of list
2398// let [_, _, c] = set_labeled_items(&pane, ["A", "B", "C*"], cx);
2399// pane.update(cx, |pane, cx| {
2400// pane.add_item(c, false, false, None, cx);
2401// });
2402// assert_item_labels(&pane, ["A", "B", "C*"], cx);
2403
2404// // 2d. Add active item to active item at start of list
2405// let [a, _, _] = set_labeled_items(&pane, ["A*", "B", "C"], cx);
2406// pane.update(cx, |pane, cx| {
2407// pane.add_item(a, false, false, None, cx);
2408// });
2409// assert_item_labels(&pane, ["A*", "B", "C"], cx);
2410// }
2411
2412// #[gpui::test]
2413// async fn test_add_item_with_same_project_entries(cx: &mut TestAppContext) {
2414// cx.foreground().forbid_parking();
2415// init_test(cx);
2416// let fs = FakeFs::new(cx.background());
2417
2418// let project = Project::test(fs, None, cx).await;
2419// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2420// let workspace = window.root(cx);
2421// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2422
2423// // singleton view
2424// pane.update(cx, |pane, cx| {
2425// let item = TestItem::new()
2426// .with_singleton(true)
2427// .with_label("buffer 1")
2428// .with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]);
2429
2430// pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
2431// });
2432// assert_item_labels(&pane, ["buffer 1*"], cx);
2433
2434// // new singleton view with the same project entry
2435// pane.update(cx, |pane, cx| {
2436// let item = TestItem::new()
2437// .with_singleton(true)
2438// .with_label("buffer 1")
2439// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
2440
2441// pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
2442// });
2443// assert_item_labels(&pane, ["buffer 1*"], cx);
2444
2445// // new singleton view with different project entry
2446// pane.update(cx, |pane, cx| {
2447// let item = TestItem::new()
2448// .with_singleton(true)
2449// .with_label("buffer 2")
2450// .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]);
2451// pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
2452// });
2453// assert_item_labels(&pane, ["buffer 1", "buffer 2*"], cx);
2454
2455// // new multibuffer view with the same project entry
2456// pane.update(cx, |pane, cx| {
2457// let item = TestItem::new()
2458// .with_singleton(false)
2459// .with_label("multibuffer 1")
2460// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
2461
2462// pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
2463// });
2464// assert_item_labels(&pane, ["buffer 1", "buffer 2", "multibuffer 1*"], cx);
2465
2466// // another multibuffer view with the same project entry
2467// pane.update(cx, |pane, cx| {
2468// let item = TestItem::new()
2469// .with_singleton(false)
2470// .with_label("multibuffer 1b")
2471// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
2472
2473// pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
2474// });
2475// assert_item_labels(
2476// &pane,
2477// ["buffer 1", "buffer 2", "multibuffer 1", "multibuffer 1b*"],
2478// cx,
2479// );
2480// }
2481
2482// #[gpui::test]
2483// async fn test_remove_item_ordering(cx: &mut TestAppContext) {
2484// init_test(cx);
2485// let fs = FakeFs::new(cx.background());
2486
2487// let project = Project::test(fs, None, cx).await;
2488// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2489// let workspace = window.root(cx);
2490// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2491
2492// add_labeled_item(&pane, "A", false, cx);
2493// add_labeled_item(&pane, "B", false, cx);
2494// add_labeled_item(&pane, "C", false, cx);
2495// add_labeled_item(&pane, "D", false, cx);
2496// assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
2497
2498// pane.update(cx, |pane, cx| pane.activate_item(1, false, false, cx));
2499// add_labeled_item(&pane, "1", false, cx);
2500// assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx);
2501
2502// pane.update(cx, |pane, cx| {
2503// pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
2504// })
2505// .unwrap()
2506// .await
2507// .unwrap();
2508// assert_item_labels(&pane, ["A", "B*", "C", "D"], cx);
2509
2510// pane.update(cx, |pane, cx| pane.activate_item(3, false, false, cx));
2511// assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
2512
2513// pane.update(cx, |pane, cx| {
2514// pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
2515// })
2516// .unwrap()
2517// .await
2518// .unwrap();
2519// assert_item_labels(&pane, ["A", "B*", "C"], cx);
2520
2521// pane.update(cx, |pane, cx| {
2522// pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
2523// })
2524// .unwrap()
2525// .await
2526// .unwrap();
2527// assert_item_labels(&pane, ["A", "C*"], cx);
2528
2529// pane.update(cx, |pane, cx| {
2530// pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
2531// })
2532// .unwrap()
2533// .await
2534// .unwrap();
2535// assert_item_labels(&pane, ["A*"], cx);
2536// }
2537
2538// #[gpui::test]
2539// async fn test_close_inactive_items(cx: &mut TestAppContext) {
2540// init_test(cx);
2541// let fs = FakeFs::new(cx.background());
2542
2543// let project = Project::test(fs, None, cx).await;
2544// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2545// let workspace = window.root(cx);
2546// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2547
2548// set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
2549
2550// pane.update(cx, |pane, cx| {
2551// pane.close_inactive_items(&CloseInactiveItems, cx)
2552// })
2553// .unwrap()
2554// .await
2555// .unwrap();
2556// assert_item_labels(&pane, ["C*"], cx);
2557// }
2558
2559// #[gpui::test]
2560// async fn test_close_clean_items(cx: &mut TestAppContext) {
2561// init_test(cx);
2562// let fs = FakeFs::new(cx.background());
2563
2564// let project = Project::test(fs, None, cx).await;
2565// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2566// let workspace = window.root(cx);
2567// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2568
2569// add_labeled_item(&pane, "A", true, cx);
2570// add_labeled_item(&pane, "B", false, cx);
2571// add_labeled_item(&pane, "C", true, cx);
2572// add_labeled_item(&pane, "D", false, cx);
2573// add_labeled_item(&pane, "E", false, cx);
2574// assert_item_labels(&pane, ["A^", "B", "C^", "D", "E*"], cx);
2575
2576// pane.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx))
2577// .unwrap()
2578// .await
2579// .unwrap();
2580// assert_item_labels(&pane, ["A^", "C*^"], cx);
2581// }
2582
2583// #[gpui::test]
2584// async fn test_close_items_to_the_left(cx: &mut TestAppContext) {
2585// init_test(cx);
2586// let fs = FakeFs::new(cx.background());
2587
2588// let project = Project::test(fs, None, cx).await;
2589// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2590// let workspace = window.root(cx);
2591// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2592
2593// set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
2594
2595// pane.update(cx, |pane, cx| {
2596// pane.close_items_to_the_left(&CloseItemsToTheLeft, cx)
2597// })
2598// .unwrap()
2599// .await
2600// .unwrap();
2601// assert_item_labels(&pane, ["C*", "D", "E"], cx);
2602// }
2603
2604// #[gpui::test]
2605// async fn test_close_items_to_the_right(cx: &mut TestAppContext) {
2606// init_test(cx);
2607// let fs = FakeFs::new(cx.background());
2608
2609// let project = Project::test(fs, None, cx).await;
2610// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2611// let workspace = window.root(cx);
2612// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2613
2614// set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
2615
2616// pane.update(cx, |pane, cx| {
2617// pane.close_items_to_the_right(&CloseItemsToTheRight, cx)
2618// })
2619// .unwrap()
2620// .await
2621// .unwrap();
2622// assert_item_labels(&pane, ["A", "B", "C*"], cx);
2623// }
2624
2625// #[gpui::test]
2626// async fn test_close_all_items(cx: &mut TestAppContext) {
2627// init_test(cx);
2628// let fs = FakeFs::new(cx.background());
2629
2630// let project = Project::test(fs, None, cx).await;
2631// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2632// let workspace = window.root(cx);
2633// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2634
2635// add_labeled_item(&pane, "A", false, cx);
2636// add_labeled_item(&pane, "B", false, cx);
2637// add_labeled_item(&pane, "C", false, cx);
2638// assert_item_labels(&pane, ["A", "B", "C*"], cx);
2639
2640// pane.update(cx, |pane, cx| {
2641// pane.close_all_items(&CloseAllItems { save_intent: None }, cx)
2642// })
2643// .unwrap()
2644// .await
2645// .unwrap();
2646// assert_item_labels(&pane, [], cx);
2647
2648// add_labeled_item(&pane, "A", true, cx);
2649// add_labeled_item(&pane, "B", true, cx);
2650// add_labeled_item(&pane, "C", true, cx);
2651// assert_item_labels(&pane, ["A^", "B^", "C*^"], cx);
2652
2653// let save = pane
2654// .update(cx, |pane, cx| {
2655// pane.close_all_items(&CloseAllItems { save_intent: None }, cx)
2656// })
2657// .unwrap();
2658
2659// cx.foreground().run_until_parked();
2660// window.simulate_prompt_answer(2, cx);
2661// save.await.unwrap();
2662// assert_item_labels(&pane, [], cx);
2663// }
2664
2665// fn init_test(cx: &mut TestAppContext) {
2666// cx.update(|cx| {
2667// cx.set_global(SettingsStore::test(cx));
2668// theme::init((), cx);
2669// crate::init_settings(cx);
2670// Project::init_settings(cx);
2671// });
2672// }
2673
2674// fn add_labeled_item(
2675// pane: &ViewHandle<Pane>,
2676// label: &str,
2677// is_dirty: bool,
2678// cx: &mut TestAppContext,
2679// ) -> Box<ViewHandle<TestItem>> {
2680// pane.update(cx, |pane, cx| {
2681// let labeled_item =
2682// Box::new(cx.add_view(|_| TestItem::new().with_label(label).with_dirty(is_dirty)));
2683// pane.add_item(labeled_item.clone(), false, false, None, cx);
2684// labeled_item
2685// })
2686// }
2687
2688// fn set_labeled_items<const COUNT: usize>(
2689// pane: &ViewHandle<Pane>,
2690// labels: [&str; COUNT],
2691// cx: &mut TestAppContext,
2692// ) -> [Box<ViewHandle<TestItem>>; COUNT] {
2693// pane.update(cx, |pane, cx| {
2694// pane.items.clear();
2695// let mut active_item_index = 0;
2696
2697// let mut index = 0;
2698// let items = labels.map(|mut label| {
2699// if label.ends_with("*") {
2700// label = label.trim_end_matches("*");
2701// active_item_index = index;
2702// }
2703
2704// let labeled_item = Box::new(cx.add_view(|_| TestItem::new().with_label(label)));
2705// pane.add_item(labeled_item.clone(), false, false, None, cx);
2706// index += 1;
2707// labeled_item
2708// });
2709
2710// pane.activate_item(active_item_index, false, false, cx);
2711
2712// items
2713// })
2714// }
2715
2716// // Assert the item label, with the active item label suffixed with a '*'
2717// fn assert_item_labels<const COUNT: usize>(
2718// pane: &ViewHandle<Pane>,
2719// expected_states: [&str; COUNT],
2720// cx: &mut TestAppContext,
2721// ) {
2722// pane.read_with(cx, |pane, cx| {
2723// let actual_states = pane
2724// .items
2725// .iter()
2726// .enumerate()
2727// .map(|(ix, item)| {
2728// let mut state = item
2729// .as_any()
2730// .downcast_ref::<TestItem>()
2731// .unwrap()
2732// .read(cx)
2733// .label
2734// .clone();
2735// if ix == pane.active_item_index {
2736// state.push('*');
2737// }
2738// if item.is_dirty(cx) {
2739// state.push('^');
2740// }
2741// state
2742// })
2743// .collect::<Vec<_>>();
2744
2745// assert_eq!(
2746// actual_states, expected_states,
2747// "pane items do not match expectation"
2748// );
2749// })
2750// }
2751// }