use crate::TerminalView; use gpui::{ elements::*, AppContext, Entity, ModelHandle, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; use project::Project; use settings::{settings_file::SettingsFile, Settings, TerminalDockPosition, WorkingDirectory}; use util::ResultExt; use workspace::{ dock::{DockPosition, Panel}, pane, DraggedItem, Pane, Workspace, }; pub fn init(cx: &mut AppContext) { cx.add_action(TerminalPanel::add_terminal); } pub enum Event { Close, DockPositionChanged, } pub struct TerminalPanel { project: ModelHandle, pane: ViewHandle, workspace: WeakViewHandle, _subscriptions: Vec, } impl TerminalPanel { pub fn new(workspace: &Workspace, cx: &mut ViewContext) -> Self { let mut old_dock_position = cx.global::().terminal_overrides.dock; cx.observe_global::(move |_, cx| { let new_dock_position = cx.global::().terminal_overrides.dock; if new_dock_position != old_dock_position { old_dock_position = new_dock_position; cx.emit(Event::DockPositionChanged); } }) .detach(); let this = cx.weak_handle(); let pane = cx.add_view(|cx| { let window_id = cx.window_id(); let mut pane = Pane::new( workspace.weak_handle(), workspace.app_state().background_actions, cx, ); pane.set_can_split(false, cx); pane.on_can_drop(move |drag_and_drop, cx| { drag_and_drop .currently_dragged::(window_id) .map_or(false, |(_, item)| { item.handle.act_as::(cx).is_some() }) }); pane.set_render_tab_bar_buttons(cx, move |_, cx| { let this = this.clone(); Pane::render_tab_bar_button( 0, "icons/plus_12.svg", cx, move |_, cx| { let this = this.clone(); cx.window_context().defer(move |cx| { if let Some(this) = this.upgrade(cx) { this.update(cx, |this, cx| { this.add_terminal(&Default::default(), cx); }); } }) }, None, ) }); pane }); let subscriptions = vec![ cx.observe(&pane, |_, _, cx| cx.notify()), cx.subscribe(&pane, Self::handle_pane_event), ]; Self { project: workspace.project().clone(), pane, workspace: workspace.weak_handle(), _subscriptions: subscriptions, } } fn handle_pane_event( &mut self, _pane: ViewHandle, event: &pane::Event, cx: &mut ViewContext, ) { match event { pane::Event::Remove => cx.emit(Event::Close), _ => {} } } fn add_terminal(&mut self, _: &workspace::NewTerminal, cx: &mut ViewContext) { 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 Entity for TerminalPanel { type Event = 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 { self.add_terminal(&Default::default(), cx) } } } impl Panel for TerminalPanel { fn position(&self, cx: &WindowContext) -> DockPosition { let settings = cx.global::(); let dock = settings .terminal_overrides .dock .or(settings.terminal_defaults.dock) .unwrap() .into(); match dock { settings::TerminalDockPosition::Left => DockPosition::Left, settings::TerminalDockPosition::Bottom => DockPosition::Bottom, settings::TerminalDockPosition::Right => DockPosition::Right, } } fn position_is_valid(&self, _: DockPosition) -> bool { true } fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext) { SettingsFile::update(cx, move |settings| { let dock = match position { DockPosition::Left => TerminalDockPosition::Left, DockPosition::Bottom => TerminalDockPosition::Bottom, DockPosition::Right => TerminalDockPosition::Right, }; settings.terminal.dock = Some(dock); }); } fn default_size(&self, cx: &WindowContext) -> f32 { let settings = &cx.global::().terminal_overrides; match self.position(cx) { DockPosition::Left | DockPosition::Right => settings.default_width.unwrap_or(640.), DockPosition::Bottom => settings.default_height.unwrap_or(320.), } } fn can_zoom(&self, _: &WindowContext) -> bool { true } fn icon_path(&self) -> &'static str { "icons/terminal_12.svg" } fn icon_tooltip(&self) -> String { "Terminals".to_string() } fn icon_label(&self, cx: &AppContext) -> Option { let count = self.pane.read(cx).items_len(); if count == 0 { None } else { Some(count.to_string()) } } fn should_change_position_on_event(event: &Self::Event) -> bool { matches!(event, Event::DockPositionChanged) } fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { false } fn should_close_on_event(&self, event: &Event, _: &AppContext) -> bool { matches!(event, Event::Close) } }