From 6ddc610fa21905f366d5032a381e2f857a400754 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 8 May 2023 18:31:08 +0200 Subject: [PATCH] WIP: Start on `TerminalPanel` --- crates/terminal_view/src/terminal_panel.rs | 76 +++++++++++++++++++ crates/terminal_view/src/terminal_view.rs | 1 + crates/theme/src/theme.rs | 1 + crates/workspace/src/dock.rs | 4 +- crates/workspace/src/pane.rs | 9 +-- crates/workspace/src/workspace.rs | 87 +++++++++++++--------- crates/zed/src/zed.rs | 18 +++-- styles/src/styleTree/statusBar.ts | 1 + 8 files changed, 149 insertions(+), 48 deletions(-) create mode 100644 crates/terminal_view/src/terminal_panel.rs diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs new file mode 100644 index 0000000000..19b34209a3 --- /dev/null +++ b/crates/terminal_view/src/terminal_panel.rs @@ -0,0 +1,76 @@ +use gpui::{elements::*, Entity, ModelHandle, View, ViewContext, ViewHandle, WeakViewHandle}; +use project::Project; +use settings::{Settings, WorkingDirectory}; +use util::ResultExt; +use workspace::{dock::Panel, Pane, Workspace}; + +use crate::TerminalView; + +pub struct TerminalPanel { + project: ModelHandle, + pane: ViewHandle, + workspace: WeakViewHandle, +} + +impl TerminalPanel { + pub fn new(workspace: &Workspace, cx: &mut ViewContext) -> Self { + Self { + project: workspace.project().clone(), + pane: cx.add_view(|cx| { + Pane::new( + workspace.weak_handle(), + workspace.app_state().background_actions, + cx, + ) + }), + workspace: workspace.weak_handle(), + } + } +} + +impl Entity for TerminalPanel { + type Event = (); +} + +impl View for TerminalPanel { + fn ui_name() -> &'static str { + "TerminalPanel" + } + + fn render(&mut self, cx: &mut ViewContext) -> gpui::AnyElement { + ChildView::new(&self.pane, cx).into_any() + } + + fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { + if self.pane.read(cx).items_len() == 0 { + if let Some(workspace) = self.workspace.upgrade(cx) { + let working_directory_strategy = cx + .global::() + .terminal_overrides + .working_directory + .clone() + .unwrap_or(WorkingDirectory::CurrentProjectDirectory); + let working_directory = crate::get_working_directory( + workspace.read(cx), + cx, + working_directory_strategy, + ); + let window_id = cx.window_id(); + if let Some(terminal) = self.project.update(cx, |project, cx| { + project + .create_terminal(working_directory, window_id, cx) + .log_err() + }) { + workspace.update(cx, |workspace, cx| { + let terminal = Box::new(cx.add_view(|cx| { + TerminalView::new(terminal, workspace.database_id(), cx) + })); + Pane::add_item(workspace, &self.pane, terminal, true, true, None, cx); + }); + } + } + } + } +} + +impl Panel for TerminalPanel {} diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index dfb2334dc5..e3a5153ef3 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -1,6 +1,7 @@ mod persistence; pub mod terminal_button; pub mod terminal_element; +pub mod terminal_panel; use context_menu::{ContextMenu, ContextMenuItem}; use dirs::home_dir; diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index bc6d639447..67d07a3998 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -343,6 +343,7 @@ pub struct StatusBar { #[derive(Deserialize, Default)] pub struct StatusBarPanelButtons { pub group_left: ContainerStyle, + pub group_bottom: ContainerStyle, pub group_right: ContainerStyle, pub button: Interactive, pub badge: ContainerStyle, diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 432ca646c8..70708837e6 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -20,7 +20,6 @@ pub trait Panel: View { } pub trait PanelHandle { - fn id(&self) -> usize; fn should_show_badge(&self, cx: &WindowContext) -> bool; fn is_focused(&self, cx: &WindowContext) -> bool; @@ -64,6 +63,7 @@ pub struct Dock { #[derive(Clone, Copy, Debug, Deserialize, PartialEq)] pub enum DockPosition { Left, + Bottom, Right, } @@ -71,6 +71,7 @@ impl DockPosition { fn to_resizable_side(self) -> Side { match self { Self::Left => Side::Right, + Self::Bottom => Side::Bottom, Self::Right => Side::Left, } } @@ -243,6 +244,7 @@ impl View for PanelButtons { let dock_position = dock.position; let group_style = match dock_position { DockPosition::Left => theme.group_left, + DockPosition::Bottom => theme.group_bottom, DockPosition::Right => theme.group_right, }; diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 1aed1c011e..2597b4af81 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2,9 +2,7 @@ mod dragged_item_receiver; use super::{ItemHandle, SplitDirection}; use crate::{ - item::WeakItemHandle, - toolbar::Toolbar, - Item, NewFile, NewSearch, NewTerminal, Workspace, + item::WeakItemHandle, toolbar::Toolbar, Item, NewFile, NewSearch, NewTerminal, Workspace, }; use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; @@ -485,7 +483,7 @@ impl Pane { } } - pub(crate) fn add_item( + pub fn add_item( workspace: &mut Workspace, pane: &ViewHandle, item: Box, @@ -1594,7 +1592,8 @@ impl Pane { "icons/split_12.svg", cx, |pane, cx| pane.deploy_split_menu(cx), - self.tab_bar_context_menu.handle_if_kind(TabBarContextMenuKind::Split), + self.tab_bar_context_menu + .handle_if_kind(TabBarContextMenuKind::Split), )) .contained() .with_style(theme.workspace.tab_bar.pane_button_container) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index f1bcb3928f..ee02a97439 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1,3 +1,4 @@ +pub mod dock; /// NOTE: Focus only 'takes' after an update has flushed_effects. /// /// This may cause issues when you're trying to write tests that use workspace focus to add items at @@ -9,7 +10,6 @@ pub mod pane_group; mod persistence; pub mod searchable; pub mod shared_screen; -pub mod dock; mod status_bar; mod toolbar; @@ -60,6 +60,7 @@ use crate::{ notifications::simple_message_notification::MessageNotification, persistence::model::{SerializedPane, SerializedPaneGroup, SerializedWorkspace}, }; +use dock::{Dock, DockPosition, PanelButtons, TogglePanel}; use lazy_static::lazy_static; use notifications::{NotificationHandle, NotifyResultExt}; pub use pane::*; @@ -74,7 +75,6 @@ use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; use serde::Deserialize; use settings::{Autosave, Settings}; use shared_screen::SharedScreen; -use dock::{Dock, PanelButtons, DockPosition, TogglePanel}; use status_bar::StatusBar; pub use status_bar::StatusItemView; use theme::{Theme, ThemeRegistry}; @@ -443,6 +443,7 @@ pub struct Workspace { modal: Option, center: PaneGroup, left_dock: ViewHandle, + bottom_dock: ViewHandle, right_dock: ViewHandle, panes: Vec>, panes_by_item: HashMap>, @@ -526,8 +527,8 @@ impl Workspace { let weak_handle = cx.weak_handle(); - let center_pane = cx - .add_view(|cx| Pane::new(weak_handle.clone(), app_state.background_actions, cx)); + let center_pane = + cx.add_view(|cx| Pane::new(weak_handle.clone(), app_state.background_actions, cx)); cx.subscribe(¢er_pane, Self::handle_pane_event).detach(); cx.focus(¢er_pane); cx.emit(Event::PaneAdded(center_pane.clone())); @@ -563,14 +564,18 @@ impl Workspace { cx.emit_global(WorkspaceCreated(weak_handle.clone())); let left_dock = cx.add_view(|_| Dock::new(DockPosition::Left)); + let bottom_dock = cx.add_view(|_| Dock::new(DockPosition::Bottom)); let right_dock = cx.add_view(|_| Dock::new(DockPosition::Right)); let left_dock_buttons = cx.add_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx)); + let bottom_dock_buttons = + cx.add_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx)); let right_dock_buttons = cx.add_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx)); let status_bar = cx.add_view(|cx| { let mut status_bar = StatusBar::new(¢er_pane.clone(), cx); status_bar.add_left_item(left_dock_buttons, cx); + status_bar.add_right_item(bottom_dock_buttons, cx); status_bar.add_right_item(right_dock_buttons, cx); status_bar }); @@ -621,8 +626,9 @@ impl Workspace { titlebar_item: None, notifications: Default::default(), remote_entity_subscription: None, - left_dock: left_dock, - right_dock: right_dock, + left_dock, + bottom_dock, + right_dock, project: project.clone(), leader_state: Default::default(), follower_states_by_leader: Default::default(), @@ -817,6 +823,10 @@ impl Workspace { &self.left_dock } + pub fn bottom_dock(&self) -> &ViewHandle { + &self.bottom_dock + } + pub fn right_dock(&self) -> &ViewHandle { &self.right_dock } @@ -1309,6 +1319,7 @@ impl Workspace { pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext) { let dock = match dock_side { DockPosition::Left => &mut self.left_dock, + DockPosition::Bottom => &mut self.bottom_dock, DockPosition::Right => &mut self.right_dock, }; dock.update(cx, |dock, cx| { @@ -1325,6 +1336,7 @@ impl Workspace { pub fn toggle_panel(&mut self, action: &TogglePanel, cx: &mut ViewContext) { let dock = match action.dock_position { DockPosition::Left => &mut self.left_dock, + DockPosition::Bottom => &mut self.bottom_dock, DockPosition::Right => &mut self.right_dock, }; let active_item = dock.update(cx, move |dock, cx| { @@ -1361,6 +1373,7 @@ impl Workspace { ) { let dock = match dock_position { DockPosition::Left => &mut self.left_dock, + DockPosition::Bottom => &mut self.bottom_dock, DockPosition::Right => &mut self.right_dock, }; let active_item = dock.update(cx, |dock, cx| { @@ -1387,13 +1400,8 @@ impl Workspace { } fn add_pane(&mut self, cx: &mut ViewContext) -> ViewHandle { - let pane = cx.add_view(|cx| { - Pane::new( - self.weak_handle(), - self.app_state.background_actions, - cx, - ) - }); + let pane = + cx.add_view(|cx| Pane::new(self.weak_handle(), self.app_state.background_actions, cx)); cx.subscribe(&pane, Self::handle_pane_event).detach(); self.panes.push(pane.clone()); cx.focus(&pane); @@ -1560,7 +1568,7 @@ impl Workspace { status_bar.set_active_pane(&self.active_pane, cx); }); self.active_item_path_changed(cx); - self.last_active_center_pane = Some(pane.downgrade()); + self.last_active_center_pane = Some(pane.downgrade()); cx.notify(); } @@ -2470,13 +2478,12 @@ impl Workspace { cx: &mut AppContext, ) { cx.spawn(|mut cx| async move { - let (project, old_center_pane) = - workspace.read_with(&cx, |workspace, _| { - ( - workspace.project().clone(), - workspace.last_active_center_pane.clone(), - ) - })?; + let (project, old_center_pane) = workspace.read_with(&cx, |workspace, _| { + ( + workspace.project().clone(), + workspace.last_active_center_pane.clone(), + ) + })?; // Traverse the splits tree and add to things let center_group = serialized_workspace @@ -2615,22 +2622,28 @@ impl View for Workspace { }, ) .with_child( - FlexItem::new( - Flex::column() - .with_child( - FlexItem::new(self.center.render( - &project, - &theme, - &self.follower_states_by_leader, - self.active_call(), - self.active_pane(), - &self.app_state, - cx, - )) - .flex(1., true), - ) - ) - .flex(1., true), + Flex::column() + .with_child( + FlexItem::new(self.center.render( + &project, + &theme, + &self.follower_states_by_leader, + self.active_call(), + self.active_pane(), + &self.app_state, + cx, + )) + .flex(1., true), + ) + .with_children( + if self.bottom_dock.read(cx).active_item().is_some() + { + Some(ChildView::new(&self.bottom_dock, cx)) + } else { + None + }, + ) + .flex(1., true), ) .with_children( if self.right_dock.read(cx).active_item().is_some() { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index b3968e45fc..52b0ad0898 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -31,12 +31,12 @@ use serde::Deserialize; use serde_json::to_string_pretty; use settings::{Settings, DEFAULT_SETTINGS_ASSET_PATH}; use std::{borrow::Cow, str, sync::Arc}; -use terminal_view::terminal_button::TerminalButton; +use terminal_view::terminal_panel::TerminalPanel; use util::{channel::ReleaseChannel, paths, ResultExt}; use uuid::Uuid; pub use workspace; use workspace::{ - create_and_open_local_file, open_new, dock::DockPosition, AppState, NewFile, NewWindow, + create_and_open_local_file, dock::DockPosition, open_new, AppState, NewFile, NewWindow, Workspace, }; @@ -318,10 +318,19 @@ pub fn initialize_workspace( "Project Panel".to_string(), project_panel, cx, - ) + ); + }); + + let terminal_panel = cx.add_view(|cx| TerminalPanel::new(workspace, cx)); + workspace.bottom_dock().update(cx, |dock, cx| { + dock.add_item( + "icons/terminal_12.svg", + "Terminals".to_string(), + terminal_panel, + cx, + ); }); - let toggle_terminal = cx.add_view(|cx| TerminalButton::new(workspace_handle.clone(), cx)); let copilot = cx.add_view(|cx| copilot_button::CopilotButton::new(cx)); let diagnostic_summary = cx.add_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx)); @@ -335,7 +344,6 @@ pub fn initialize_workspace( workspace.status_bar().update(cx, |status_bar, cx| { status_bar.add_left_item(diagnostic_summary, cx); status_bar.add_left_item(activity_indicator, cx); - status_bar.add_right_item(toggle_terminal, cx); status_bar.add_right_item(feedback_button, cx); status_bar.add_right_item(copilot, cx); status_bar.add_right_item(active_buffer_language, cx); diff --git a/styles/src/styleTree/statusBar.ts b/styles/src/styleTree/statusBar.ts index 614b6ab34d..eca537c150 100644 --- a/styles/src/styleTree/statusBar.ts +++ b/styles/src/styleTree/statusBar.ts @@ -95,6 +95,7 @@ export default function statusBar(colorScheme: ColorScheme) { }, panelButtons: { groupLeft: {}, + groupBottom: {}, groupRight: {}, button: { ...statusContainer,