terminal_panel.rs

   1use std::{cmp, ops::ControlFlow, path::PathBuf, sync::Arc, time::Duration};
   2
   3use crate::{
   4    default_working_directory,
   5    persistence::{
   6        deserialize_terminal_panel, serialize_pane_group, SerializedItems, SerializedTerminalPanel,
   7    },
   8    TerminalView,
   9};
  10use breadcrumbs::Breadcrumbs;
  11use collections::HashMap;
  12use db::kvp::KEY_VALUE_STORE;
  13use futures::future::join_all;
  14use gpui::{
  15    actions, Action, AnyView, App, AsyncApp, AsyncWindowContext, Context, Corner, Entity,
  16    EventEmitter, ExternalPaths, FocusHandle, Focusable, IntoElement, ParentElement, Pixels,
  17    Render, Styled, Task, WeakEntity, Window,
  18};
  19use itertools::Itertools;
  20use project::{terminals::TerminalKind, Fs, Project, ProjectEntryId};
  21use search::{buffer_search::DivRegistrar, BufferSearchBar};
  22use settings::Settings;
  23use task::{RevealStrategy, RevealTarget, ShellBuilder, SpawnInTerminal, TaskId};
  24use terminal::{
  25    terminal_settings::{TerminalDockPosition, TerminalSettings},
  26    Terminal,
  27};
  28use ui::{
  29    prelude::*, ButtonCommon, Clickable, ContextMenu, FluentBuilder, PopoverMenu, Toggleable,
  30    Tooltip,
  31};
  32use util::{ResultExt, TryFutureExt};
  33use workspace::{
  34    dock::{DockPosition, Panel, PanelEvent, PanelHandle},
  35    item::SerializableItem,
  36    move_active_item, move_item, pane,
  37    ui::IconName,
  38    ActivateNextPane, ActivatePane, ActivatePaneDown, ActivatePaneLeft, ActivatePaneRight,
  39    ActivatePaneUp, ActivatePreviousPane, DraggedSelection, DraggedTab, ItemId, MoveItemToPane,
  40    MoveItemToPaneInDirection, NewTerminal, Pane, PaneGroup, SplitDirection, SplitDown, SplitLeft,
  41    SplitRight, SplitUp, SwapPaneDown, SwapPaneLeft, SwapPaneRight, SwapPaneUp, ToggleZoom,
  42    Workspace,
  43};
  44
  45use anyhow::{anyhow, Context as _, Result};
  46use zed_actions::assistant::InlineAssist;
  47
  48const TERMINAL_PANEL_KEY: &str = "TerminalPanel";
  49
  50actions!(terminal_panel, [ToggleFocus]);
  51
  52pub fn init(cx: &mut App) {
  53    cx.observe_new(
  54        |workspace: &mut Workspace, _window, _: &mut Context<Workspace>| {
  55            workspace.register_action(TerminalPanel::new_terminal);
  56            workspace.register_action(TerminalPanel::open_terminal);
  57            workspace.register_action(|workspace, _: &ToggleFocus, window, cx| {
  58                if is_enabled_in_workspace(workspace, cx) {
  59                    workspace.toggle_panel_focus::<TerminalPanel>(window, cx);
  60                }
  61            });
  62        },
  63    )
  64    .detach();
  65}
  66
  67pub struct TerminalPanel {
  68    pub(crate) active_pane: Entity<Pane>,
  69    pub(crate) center: PaneGroup,
  70    fs: Arc<dyn Fs>,
  71    workspace: WeakEntity<Workspace>,
  72    pub(crate) width: Option<Pixels>,
  73    pub(crate) height: Option<Pixels>,
  74    pending_serialization: Task<Option<()>>,
  75    pending_terminals_to_add: usize,
  76    deferred_tasks: HashMap<TaskId, Task<()>>,
  77    assistant_enabled: bool,
  78    assistant_tab_bar_button: Option<AnyView>,
  79    active: bool,
  80}
  81
  82impl TerminalPanel {
  83    pub fn new(workspace: &Workspace, window: &mut Window, cx: &mut Context<Self>) -> Self {
  84        let project = workspace.project();
  85        let pane = new_terminal_pane(workspace.weak_handle(), project.clone(), false, window, cx);
  86        let center = PaneGroup::new(pane.clone());
  87        let terminal_panel = Self {
  88            center,
  89            active_pane: pane,
  90            fs: workspace.app_state().fs.clone(),
  91            workspace: workspace.weak_handle(),
  92            pending_serialization: Task::ready(None),
  93            width: None,
  94            height: None,
  95            pending_terminals_to_add: 0,
  96            deferred_tasks: HashMap::default(),
  97            assistant_enabled: false,
  98            assistant_tab_bar_button: None,
  99            active: false,
 100        };
 101        terminal_panel.apply_tab_bar_buttons(&terminal_panel.active_pane, cx);
 102        terminal_panel
 103    }
 104
 105    pub fn set_assistant_enabled(&mut self, enabled: bool, cx: &mut Context<Self>) {
 106        self.assistant_enabled = enabled;
 107        if enabled {
 108            let focus_handle = self
 109                .active_pane
 110                .read(cx)
 111                .active_item()
 112                .map(|item| item.item_focus_handle(cx))
 113                .unwrap_or(self.focus_handle(cx));
 114            self.assistant_tab_bar_button = Some(
 115                cx.new(move |_| InlineAssistTabBarButton { focus_handle })
 116                    .into(),
 117            );
 118        } else {
 119            self.assistant_tab_bar_button = None;
 120        }
 121        for pane in self.center.panes() {
 122            self.apply_tab_bar_buttons(pane, cx);
 123        }
 124    }
 125
 126    fn apply_tab_bar_buttons(&self, terminal_pane: &Entity<Pane>, cx: &mut Context<Self>) {
 127        let assistant_tab_bar_button = self.assistant_tab_bar_button.clone();
 128        terminal_pane.update(cx, |pane, cx| {
 129            pane.set_render_tab_bar_buttons(cx, move |pane, window, cx| {
 130                let split_context = pane
 131                    .active_item()
 132                    .and_then(|item| item.downcast::<TerminalView>())
 133                    .map(|terminal_view| terminal_view.read(cx).focus_handle.clone());
 134                if !pane.has_focus(window, cx) && !pane.context_menu_focused(window, cx) {
 135                    return (None, None);
 136                }
 137                let focus_handle = pane.focus_handle(cx);
 138                let right_children = h_flex()
 139                    .gap(DynamicSpacing::Base02.rems(cx))
 140                    .child(
 141                        PopoverMenu::new("terminal-tab-bar-popover-menu")
 142                            .trigger_with_tooltip(
 143                                IconButton::new("plus", IconName::Plus).icon_size(IconSize::Small),
 144                                Tooltip::text("New…"),
 145                            )
 146                            .anchor(Corner::TopRight)
 147                            .with_handle(pane.new_item_context_menu_handle.clone())
 148                            .menu(move |window, cx| {
 149                                let focus_handle = focus_handle.clone();
 150                                let menu = ContextMenu::build(window, cx, |menu, _, _| {
 151                                    menu.context(focus_handle.clone())
 152                                        .action(
 153                                            "New Terminal",
 154                                            workspace::NewTerminal.boxed_clone(),
 155                                        )
 156                                        // We want the focus to go back to terminal panel once task modal is dismissed,
 157                                        // hence we focus that first. Otherwise, we'd end up without a focused element, as
 158                                        // context menu will be gone the moment we spawn the modal.
 159                                        .action(
 160                                            "Spawn task",
 161                                            zed_actions::Spawn::modal().boxed_clone(),
 162                                        )
 163                                });
 164
 165                                Some(menu)
 166                            }),
 167                    )
 168                    .children(assistant_tab_bar_button.clone())
 169                    .child(
 170                        PopoverMenu::new("terminal-pane-tab-bar-split")
 171                            .trigger_with_tooltip(
 172                                IconButton::new("terminal-pane-split", IconName::Split)
 173                                    .icon_size(IconSize::Small),
 174                                Tooltip::text("Split Pane"),
 175                            )
 176                            .anchor(Corner::TopRight)
 177                            .with_handle(pane.split_item_context_menu_handle.clone())
 178                            .menu({
 179                                let split_context = split_context.clone();
 180                                move |window, cx| {
 181                                    ContextMenu::build(window, cx, |menu, _, _| {
 182                                        menu.when_some(
 183                                            split_context.clone(),
 184                                            |menu, split_context| menu.context(split_context),
 185                                        )
 186                                        .action("Split Right", SplitRight.boxed_clone())
 187                                        .action("Split Left", SplitLeft.boxed_clone())
 188                                        .action("Split Up", SplitUp.boxed_clone())
 189                                        .action("Split Down", SplitDown.boxed_clone())
 190                                    })
 191                                    .into()
 192                                }
 193                            }),
 194                    )
 195                    .child({
 196                        let zoomed = pane.is_zoomed();
 197                        IconButton::new("toggle_zoom", IconName::Maximize)
 198                            .icon_size(IconSize::Small)
 199                            .toggle_state(zoomed)
 200                            .selected_icon(IconName::Minimize)
 201                            .on_click(cx.listener(|pane, _, window, cx| {
 202                                pane.toggle_zoom(&workspace::ToggleZoom, window, cx);
 203                            }))
 204                            .tooltip(move |window, cx| {
 205                                Tooltip::for_action(
 206                                    if zoomed { "Zoom Out" } else { "Zoom In" },
 207                                    &ToggleZoom,
 208                                    window,
 209                                    cx,
 210                                )
 211                            })
 212                    })
 213                    .into_any_element()
 214                    .into();
 215                (None, right_children)
 216            });
 217        });
 218    }
 219
 220    pub async fn load(
 221        workspace: WeakEntity<Workspace>,
 222        mut cx: AsyncWindowContext,
 223    ) -> Result<Entity<Self>> {
 224        let serialized_panel = cx
 225            .background_executor()
 226            .spawn(async move { KEY_VALUE_STORE.read_kvp(TERMINAL_PANEL_KEY) })
 227            .await
 228            .log_err()
 229            .flatten()
 230            .map(|panel| serde_json::from_str::<SerializedTerminalPanel>(&panel))
 231            .transpose()
 232            .log_err()
 233            .flatten();
 234
 235        let terminal_panel = workspace
 236            .update_in(&mut cx, |workspace, window, cx| {
 237                match serialized_panel.zip(workspace.database_id()) {
 238                    Some((serialized_panel, database_id)) => deserialize_terminal_panel(
 239                        workspace.weak_handle(),
 240                        workspace.project().clone(),
 241                        database_id,
 242                        serialized_panel,
 243                        window,
 244                        cx,
 245                    ),
 246                    None => Task::ready(Ok(cx.new(|cx| TerminalPanel::new(workspace, window, cx)))),
 247                }
 248            })?
 249            .await?;
 250
 251        if let Some(workspace) = workspace.upgrade() {
 252            terminal_panel
 253                .update_in(&mut cx, |_, window, cx| {
 254                    cx.subscribe_in(&workspace, window, |terminal_panel, _, e, window, cx| {
 255                        if let workspace::Event::SpawnTask {
 256                            action: spawn_in_terminal,
 257                        } = e
 258                        {
 259                            terminal_panel.spawn_task(spawn_in_terminal, window, cx);
 260                        };
 261                    })
 262                    .detach();
 263                })
 264                .ok();
 265        }
 266
 267        // Since panels/docks are loaded outside from the workspace, we cleanup here, instead of through the workspace.
 268        if let Some(workspace) = workspace.upgrade() {
 269            let cleanup_task = workspace.update_in(&mut cx, |workspace, window, cx| {
 270                let alive_item_ids = terminal_panel
 271                    .read(cx)
 272                    .center
 273                    .panes()
 274                    .into_iter()
 275                    .flat_map(|pane| pane.read(cx).items())
 276                    .map(|item| item.item_id().as_u64() as ItemId)
 277                    .collect();
 278                workspace.database_id().map(|workspace_id| {
 279                    TerminalView::cleanup(workspace_id, alive_item_ids, window, cx)
 280                })
 281            })?;
 282            if let Some(task) = cleanup_task {
 283                task.await.log_err();
 284            }
 285        }
 286
 287        if let Some(workspace) = workspace.upgrade() {
 288            let should_focus = workspace
 289                .update_in(&mut cx, |workspace, window, cx| {
 290                    workspace.active_item(cx).is_none()
 291                        && workspace
 292                            .is_dock_at_position_open(terminal_panel.position(window, cx), cx)
 293                })
 294                .unwrap_or(false);
 295
 296            if should_focus {
 297                terminal_panel
 298                    .update_in(&mut cx, |panel, window, cx| {
 299                        panel.active_pane.update(cx, |pane, cx| {
 300                            pane.focus_active_item(window, cx);
 301                        });
 302                    })
 303                    .ok();
 304            }
 305        }
 306
 307        Ok(terminal_panel)
 308    }
 309
 310    fn handle_pane_event(
 311        &mut self,
 312        pane: &Entity<Pane>,
 313        event: &pane::Event,
 314        window: &mut Window,
 315        cx: &mut Context<Self>,
 316    ) {
 317        match event {
 318            pane::Event::ActivateItem { .. } => self.serialize(cx),
 319            pane::Event::RemovedItem { .. } => self.serialize(cx),
 320            pane::Event::Remove { focus_on_pane } => {
 321                let pane_count_before_removal = self.center.panes().len();
 322                let _removal_result = self.center.remove(&pane);
 323                if pane_count_before_removal == 1 {
 324                    self.center.first_pane().update(cx, |pane, cx| {
 325                        pane.set_zoomed(false, cx);
 326                    });
 327                    cx.emit(PanelEvent::Close);
 328                } else {
 329                    if let Some(focus_on_pane) =
 330                        focus_on_pane.as_ref().or_else(|| self.center.panes().pop())
 331                    {
 332                        focus_on_pane.focus_handle(cx).focus(window);
 333                    }
 334                }
 335            }
 336            pane::Event::ZoomIn => {
 337                for pane in self.center.panes() {
 338                    pane.update(cx, |pane, cx| {
 339                        pane.set_zoomed(true, cx);
 340                    })
 341                }
 342                cx.emit(PanelEvent::ZoomIn);
 343                cx.notify();
 344            }
 345            pane::Event::ZoomOut => {
 346                for pane in self.center.panes() {
 347                    pane.update(cx, |pane, cx| {
 348                        pane.set_zoomed(false, cx);
 349                    })
 350                }
 351                cx.emit(PanelEvent::ZoomOut);
 352                cx.notify();
 353            }
 354            pane::Event::AddItem { item } => {
 355                if let Some(workspace) = self.workspace.upgrade() {
 356                    workspace.update(cx, |workspace, cx| {
 357                        item.added_to_pane(workspace, pane.clone(), window, cx)
 358                    })
 359                }
 360                self.serialize(cx);
 361            }
 362            pane::Event::Split(direction) => {
 363                let Some(new_pane) = self.new_pane_with_cloned_active_terminal(window, cx) else {
 364                    return;
 365                };
 366                let pane = pane.clone();
 367                let direction = *direction;
 368                self.center.split(&pane, &new_pane, direction).log_err();
 369                window.focus(&new_pane.focus_handle(cx));
 370            }
 371            pane::Event::Focus => {
 372                self.active_pane = pane.clone();
 373            }
 374
 375            _ => {}
 376        }
 377    }
 378
 379    fn new_pane_with_cloned_active_terminal(
 380        &mut self,
 381        window: &mut Window,
 382        cx: &mut Context<Self>,
 383    ) -> Option<Entity<Pane>> {
 384        let workspace = self.workspace.upgrade()?;
 385        let workspace = workspace.read(cx);
 386        let database_id = workspace.database_id();
 387        let weak_workspace = self.workspace.clone();
 388        let project = workspace.project().clone();
 389        let (working_directory, python_venv_directory) = self
 390            .active_pane
 391            .read(cx)
 392            .active_item()
 393            .and_then(|item| item.downcast::<TerminalView>())
 394            .map(|terminal_view| {
 395                let terminal = terminal_view.read(cx).terminal().read(cx);
 396                (
 397                    terminal
 398                        .working_directory()
 399                        .or_else(|| default_working_directory(workspace, cx)),
 400                    terminal.python_venv_directory.clone(),
 401                )
 402            })
 403            .unwrap_or((None, None));
 404        let kind = TerminalKind::Shell(working_directory);
 405        let window_handle = window.window_handle();
 406        let terminal = project
 407            .update(cx, |project, cx| {
 408                project.create_terminal_with_venv(kind, python_venv_directory, window_handle, cx)
 409            })
 410            .ok()?;
 411
 412        let terminal_view = Box::new(cx.new(|cx| {
 413            TerminalView::new(
 414                terminal.clone(),
 415                weak_workspace.clone(),
 416                database_id,
 417                project.downgrade(),
 418                window,
 419                cx,
 420            )
 421        }));
 422        let pane = new_terminal_pane(
 423            weak_workspace,
 424            project,
 425            self.active_pane.read(cx).is_zoomed(),
 426            window,
 427            cx,
 428        );
 429        self.apply_tab_bar_buttons(&pane, cx);
 430        pane.update(cx, |pane, cx| {
 431            pane.add_item(terminal_view, true, true, None, window, cx);
 432        });
 433
 434        Some(pane)
 435    }
 436
 437    pub fn open_terminal(
 438        workspace: &mut Workspace,
 439        action: &workspace::OpenTerminal,
 440        window: &mut Window,
 441        cx: &mut Context<Workspace>,
 442    ) {
 443        let Some(terminal_panel) = workspace.panel::<Self>(cx) else {
 444            return;
 445        };
 446
 447        terminal_panel
 448            .update(cx, |panel, cx| {
 449                panel.add_terminal(
 450                    TerminalKind::Shell(Some(action.working_directory.clone())),
 451                    RevealStrategy::Always,
 452                    window,
 453                    cx,
 454                )
 455            })
 456            .detach_and_log_err(cx);
 457    }
 458
 459    fn spawn_task(&mut self, task: &SpawnInTerminal, window: &mut Window, cx: &mut Context<Self>) {
 460        let Ok(is_local) = self
 461            .workspace
 462            .update(cx, |workspace, cx| workspace.project().read(cx).is_local())
 463        else {
 464            return;
 465        };
 466
 467        let builder = ShellBuilder::new(is_local, &task.shell);
 468        let command_label = builder.command_label(&task.command_label);
 469        let (command, args) = builder.build(task.command.clone(), &task.args);
 470
 471        let task = SpawnInTerminal {
 472            command_label,
 473            command,
 474            args,
 475            ..task.clone()
 476        };
 477
 478        if task.allow_concurrent_runs && task.use_new_terminal {
 479            self.spawn_in_new_terminal(task, window, cx)
 480                .detach_and_log_err(cx);
 481            return;
 482        }
 483
 484        let mut terminals_for_task = self.terminals_for_task(&task.full_label, cx);
 485        let Some(existing) = terminals_for_task.pop() else {
 486            self.spawn_in_new_terminal(task, window, cx)
 487                .detach_and_log_err(cx);
 488            return;
 489        };
 490
 491        let (existing_item_index, task_pane, existing_terminal) = existing;
 492        if task.allow_concurrent_runs {
 493            self.replace_terminal(
 494                task,
 495                task_pane,
 496                existing_item_index,
 497                existing_terminal,
 498                window,
 499                cx,
 500            )
 501            .detach();
 502            return;
 503        }
 504
 505        self.deferred_tasks.insert(
 506            task.id.clone(),
 507            cx.spawn_in(window, |terminal_panel, mut cx| async move {
 508                wait_for_terminals_tasks(terminals_for_task, &mut cx).await;
 509                let task = terminal_panel.update_in(&mut cx, |terminal_panel, window, cx| {
 510                    if task.use_new_terminal {
 511                        terminal_panel
 512                            .spawn_in_new_terminal(task, window, cx)
 513                            .detach_and_log_err(cx);
 514                        None
 515                    } else {
 516                        Some(terminal_panel.replace_terminal(
 517                            task,
 518                            task_pane,
 519                            existing_item_index,
 520                            existing_terminal,
 521                            window,
 522                            cx,
 523                        ))
 524                    }
 525                });
 526                if let Ok(Some(task)) = task {
 527                    task.await;
 528                }
 529            }),
 530        );
 531    }
 532
 533    pub fn spawn_in_new_terminal(
 534        &mut self,
 535        spawn_task: SpawnInTerminal,
 536        window: &mut Window,
 537        cx: &mut Context<Self>,
 538    ) -> Task<Result<Entity<Terminal>>> {
 539        let reveal = spawn_task.reveal;
 540        let reveal_target = spawn_task.reveal_target;
 541        let kind = TerminalKind::Task(spawn_task);
 542        match reveal_target {
 543            RevealTarget::Center => self
 544                .workspace
 545                .update(cx, |workspace, cx| {
 546                    Self::add_center_terminal(workspace, kind, window, cx)
 547                })
 548                .unwrap_or_else(|e| Task::ready(Err(e))),
 549            RevealTarget::Dock => self.add_terminal(kind, reveal, window, cx),
 550        }
 551    }
 552
 553    /// Create a new Terminal in the current working directory or the user's home directory
 554    fn new_terminal(
 555        workspace: &mut Workspace,
 556        _: &workspace::NewTerminal,
 557        window: &mut Window,
 558        cx: &mut Context<Workspace>,
 559    ) {
 560        let Some(terminal_panel) = workspace.panel::<Self>(cx) else {
 561            return;
 562        };
 563
 564        let kind = TerminalKind::Shell(default_working_directory(workspace, cx));
 565
 566        terminal_panel
 567            .update(cx, |this, cx| {
 568                this.add_terminal(kind, RevealStrategy::Always, window, cx)
 569            })
 570            .detach_and_log_err(cx);
 571    }
 572
 573    fn terminals_for_task(
 574        &self,
 575        label: &str,
 576        cx: &mut App,
 577    ) -> Vec<(usize, Entity<Pane>, Entity<TerminalView>)> {
 578        let Some(workspace) = self.workspace.upgrade() else {
 579            return Vec::new();
 580        };
 581
 582        let pane_terminal_views = |pane: Entity<Pane>| {
 583            pane.read(cx)
 584                .items()
 585                .enumerate()
 586                .filter_map(|(index, item)| Some((index, item.act_as::<TerminalView>(cx)?)))
 587                .filter_map(|(index, terminal_view)| {
 588                    let task_state = terminal_view.read(cx).terminal().read(cx).task()?;
 589                    if &task_state.full_label == label {
 590                        Some((index, terminal_view))
 591                    } else {
 592                        None
 593                    }
 594                })
 595                .map(move |(index, terminal_view)| (index, pane.clone(), terminal_view))
 596        };
 597
 598        self.center
 599            .panes()
 600            .into_iter()
 601            .cloned()
 602            .flat_map(pane_terminal_views)
 603            .chain(
 604                workspace
 605                    .read(cx)
 606                    .panes()
 607                    .into_iter()
 608                    .cloned()
 609                    .flat_map(pane_terminal_views),
 610            )
 611            .sorted_by_key(|(_, _, terminal_view)| terminal_view.entity_id())
 612            .collect()
 613    }
 614
 615    fn activate_terminal_view(
 616        &self,
 617        pane: &Entity<Pane>,
 618        item_index: usize,
 619        focus: bool,
 620        window: &mut Window,
 621        cx: &mut App,
 622    ) {
 623        pane.update(cx, |pane, cx| {
 624            pane.activate_item(item_index, true, focus, window, cx)
 625        })
 626    }
 627
 628    pub fn add_center_terminal(
 629        workspace: &mut Workspace,
 630        kind: TerminalKind,
 631        window: &mut Window,
 632        cx: &mut Context<Workspace>,
 633    ) -> Task<Result<Entity<Terminal>>> {
 634        if !is_enabled_in_workspace(workspace, cx) {
 635            return Task::ready(Err(anyhow!(
 636                "terminal not yet supported for remote projects"
 637            )));
 638        }
 639        let window_handle = window.window_handle();
 640        let project = workspace.project().downgrade();
 641        cx.spawn_in(window, move |workspace, mut cx| async move {
 642            let terminal = project
 643                .update(&mut cx, |project, cx| {
 644                    project.create_terminal(kind, window_handle, cx)
 645                })?
 646                .await?;
 647
 648            workspace.update_in(&mut cx, |workspace, window, cx| {
 649                let terminal_view = cx.new(|cx| {
 650                    TerminalView::new(
 651                        terminal.clone(),
 652                        workspace.weak_handle(),
 653                        workspace.database_id(),
 654                        workspace.project().downgrade(),
 655                        window,
 656                        cx,
 657                    )
 658                });
 659                workspace.add_item_to_active_pane(Box::new(terminal_view), None, true, window, cx);
 660            })?;
 661            Ok(terminal)
 662        })
 663    }
 664
 665    fn add_terminal(
 666        &mut self,
 667        kind: TerminalKind,
 668        reveal_strategy: RevealStrategy,
 669        window: &mut Window,
 670        cx: &mut Context<Self>,
 671    ) -> Task<Result<Entity<Terminal>>> {
 672        let workspace = self.workspace.clone();
 673        cx.spawn_in(window, |terminal_panel, mut cx| async move {
 674            if workspace.update(&mut cx, |workspace, cx| {
 675                !is_enabled_in_workspace(workspace, cx)
 676            })? {
 677                anyhow::bail!("terminal not yet supported for remote projects");
 678            }
 679            let pane = terminal_panel.update(&mut cx, |terminal_panel, _| {
 680                terminal_panel.pending_terminals_to_add += 1;
 681                terminal_panel.active_pane.clone()
 682            })?;
 683            let project = workspace.update(&mut cx, |workspace, _| workspace.project().clone())?;
 684            let window_handle = cx.window_handle();
 685            let terminal = project
 686                .update(&mut cx, |project, cx| {
 687                    project.create_terminal(kind, window_handle, cx)
 688                })?
 689                .await?;
 690            let result = workspace.update_in(&mut cx, |workspace, window, cx| {
 691                let terminal_view = Box::new(cx.new(|cx| {
 692                    TerminalView::new(
 693                        terminal.clone(),
 694                        workspace.weak_handle(),
 695                        workspace.database_id(),
 696                        workspace.project().downgrade(),
 697                        window,
 698                        cx,
 699                    )
 700                }));
 701
 702                match reveal_strategy {
 703                    RevealStrategy::Always => {
 704                        workspace.focus_panel::<Self>(window, cx);
 705                    }
 706                    RevealStrategy::NoFocus => {
 707                        workspace.open_panel::<Self>(window, cx);
 708                    }
 709                    RevealStrategy::Never => {}
 710                }
 711
 712                pane.update(cx, |pane, cx| {
 713                    let focus = pane.has_focus(window, cx)
 714                        || matches!(reveal_strategy, RevealStrategy::Always);
 715                    pane.add_item(terminal_view, true, focus, None, window, cx);
 716                });
 717
 718                Ok(terminal)
 719            })?;
 720            terminal_panel.update(&mut cx, |this, cx| {
 721                this.pending_terminals_to_add = this.pending_terminals_to_add.saturating_sub(1);
 722                this.serialize(cx)
 723            })?;
 724            result
 725        })
 726    }
 727
 728    fn serialize(&mut self, cx: &mut Context<Self>) {
 729        let height = self.height;
 730        let width = self.width;
 731        self.pending_serialization = cx.spawn(|terminal_panel, mut cx| async move {
 732            cx.background_executor()
 733                .timer(Duration::from_millis(50))
 734                .await;
 735            let terminal_panel = terminal_panel.upgrade()?;
 736            let items = terminal_panel
 737                .update(&mut cx, |terminal_panel, cx| {
 738                    SerializedItems::WithSplits(serialize_pane_group(
 739                        &terminal_panel.center,
 740                        &terminal_panel.active_pane,
 741                        cx,
 742                    ))
 743                })
 744                .ok()?;
 745            cx.background_executor()
 746                .spawn(
 747                    async move {
 748                        KEY_VALUE_STORE
 749                            .write_kvp(
 750                                TERMINAL_PANEL_KEY.into(),
 751                                serde_json::to_string(&SerializedTerminalPanel {
 752                                    items,
 753                                    active_item_id: None,
 754                                    height,
 755                                    width,
 756                                })?,
 757                            )
 758                            .await?;
 759                        anyhow::Ok(())
 760                    }
 761                    .log_err(),
 762                )
 763                .await;
 764            Some(())
 765        });
 766    }
 767
 768    fn replace_terminal(
 769        &self,
 770        spawn_task: SpawnInTerminal,
 771        task_pane: Entity<Pane>,
 772        terminal_item_index: usize,
 773        terminal_to_replace: Entity<TerminalView>,
 774        window: &mut Window,
 775        cx: &mut Context<Self>,
 776    ) -> Task<Option<()>> {
 777        let reveal = spawn_task.reveal;
 778        let reveal_target = spawn_task.reveal_target;
 779        let window_handle = window.window_handle();
 780        let task_workspace = self.workspace.clone();
 781        cx.spawn_in(window, move |terminal_panel, mut cx| async move {
 782            let project = terminal_panel
 783                .update(&mut cx, |this, cx| {
 784                    this.workspace
 785                        .update(cx, |workspace, _| workspace.project().clone())
 786                        .ok()
 787                })
 788                .ok()
 789                .flatten()?;
 790            let new_terminal = project
 791                .update(&mut cx, |project, cx| {
 792                    project.create_terminal(TerminalKind::Task(spawn_task), window_handle, cx)
 793                })
 794                .ok()?
 795                .await
 796                .log_err()?;
 797            terminal_to_replace
 798                .update_in(&mut cx, |terminal_to_replace, window, cx| {
 799                    terminal_to_replace.set_terminal(new_terminal, window, cx);
 800                })
 801                .ok()?;
 802
 803            match reveal {
 804                RevealStrategy::Always => match reveal_target {
 805                    RevealTarget::Center => {
 806                        task_workspace
 807                            .update_in(&mut cx, |workspace, window, cx| {
 808                                workspace
 809                                    .active_item(cx)
 810                                    .context("retrieving active terminal item in the workspace")
 811                                    .log_err()?
 812                                    .item_focus_handle(cx)
 813                                    .focus(window);
 814                                Some(())
 815                            })
 816                            .ok()??;
 817                    }
 818                    RevealTarget::Dock => {
 819                        terminal_panel
 820                            .update_in(&mut cx, |terminal_panel, window, cx| {
 821                                terminal_panel.activate_terminal_view(
 822                                    &task_pane,
 823                                    terminal_item_index,
 824                                    true,
 825                                    window,
 826                                    cx,
 827                                )
 828                            })
 829                            .ok()?;
 830
 831                        cx.spawn(|mut cx| async move {
 832                            task_workspace
 833                                .update_in(&mut cx, |workspace, window, cx| {
 834                                    workspace.focus_panel::<Self>(window, cx)
 835                                })
 836                                .ok()
 837                        })
 838                        .detach();
 839                    }
 840                },
 841                RevealStrategy::NoFocus => match reveal_target {
 842                    RevealTarget::Center => {
 843                        task_workspace
 844                            .update_in(&mut cx, |workspace, window, cx| {
 845                                workspace.active_pane().focus_handle(cx).focus(window);
 846                            })
 847                            .ok()?;
 848                    }
 849                    RevealTarget::Dock => {
 850                        terminal_panel
 851                            .update_in(&mut cx, |terminal_panel, window, cx| {
 852                                terminal_panel.activate_terminal_view(
 853                                    &task_pane,
 854                                    terminal_item_index,
 855                                    false,
 856                                    window,
 857                                    cx,
 858                                )
 859                            })
 860                            .ok()?;
 861
 862                        cx.spawn(|mut cx| async move {
 863                            task_workspace
 864                                .update_in(&mut cx, |workspace, window, cx| {
 865                                    workspace.open_panel::<Self>(window, cx)
 866                                })
 867                                .ok()
 868                        })
 869                        .detach();
 870                    }
 871                },
 872                RevealStrategy::Never => {}
 873            }
 874
 875            Some(())
 876        })
 877    }
 878
 879    fn has_no_terminals(&self, cx: &App) -> bool {
 880        self.active_pane.read(cx).items_len() == 0 && self.pending_terminals_to_add == 0
 881    }
 882
 883    pub fn assistant_enabled(&self) -> bool {
 884        self.assistant_enabled
 885    }
 886
 887    fn is_enabled(&self, cx: &App) -> bool {
 888        self.workspace.upgrade().map_or(false, |workspace| {
 889            is_enabled_in_workspace(workspace.read(cx), cx)
 890        })
 891    }
 892
 893    fn activate_pane_in_direction(
 894        &mut self,
 895        direction: SplitDirection,
 896        window: &mut Window,
 897        cx: &mut Context<Self>,
 898    ) {
 899        if let Some(pane) = self
 900            .center
 901            .find_pane_in_direction(&self.active_pane, direction, cx)
 902        {
 903            window.focus(&pane.focus_handle(cx));
 904        } else {
 905            self.workspace
 906                .update(cx, |workspace, cx| {
 907                    workspace.activate_pane_in_direction(direction, window, cx)
 908                })
 909                .ok();
 910        }
 911    }
 912
 913    fn swap_pane_in_direction(&mut self, direction: SplitDirection, cx: &mut Context<Self>) {
 914        if let Some(to) = self
 915            .center
 916            .find_pane_in_direction(&self.active_pane, direction, cx)
 917            .cloned()
 918        {
 919            self.center.swap(&self.active_pane, &to);
 920            cx.notify();
 921        }
 922    }
 923}
 924
 925fn is_enabled_in_workspace(workspace: &Workspace, cx: &App) -> bool {
 926    workspace.project().read(cx).supports_terminal(cx)
 927}
 928
 929pub fn new_terminal_pane(
 930    workspace: WeakEntity<Workspace>,
 931    project: Entity<Project>,
 932    zoomed: bool,
 933    window: &mut Window,
 934    cx: &mut Context<TerminalPanel>,
 935) -> Entity<Pane> {
 936    let is_local = project.read(cx).is_local();
 937    let terminal_panel = cx.entity().clone();
 938    let pane = cx.new(|cx| {
 939        let mut pane = Pane::new(
 940            workspace.clone(),
 941            project.clone(),
 942            Default::default(),
 943            None,
 944            NewTerminal.boxed_clone(),
 945            window,
 946            cx,
 947        );
 948        pane.set_zoomed(zoomed, cx);
 949        pane.set_can_navigate(false, cx);
 950        pane.display_nav_history_buttons(None);
 951        pane.set_should_display_tab_bar(|_, _| true);
 952        pane.set_zoom_out_on_close(false);
 953
 954        let split_closure_terminal_panel = terminal_panel.downgrade();
 955        pane.set_can_split(Some(Arc::new(move |pane, dragged_item, _window, cx| {
 956            if let Some(tab) = dragged_item.downcast_ref::<DraggedTab>() {
 957                let is_current_pane = tab.pane == cx.entity();
 958                let Some(can_drag_away) = split_closure_terminal_panel
 959                    .update(cx, |terminal_panel, _| {
 960                        let current_panes = terminal_panel.center.panes();
 961                        !current_panes.contains(&&tab.pane)
 962                            || current_panes.len() > 1
 963                            || (!is_current_pane || pane.items_len() > 1)
 964                    })
 965                    .ok()
 966                else {
 967                    return false;
 968                };
 969                if can_drag_away {
 970                    let item = if is_current_pane {
 971                        pane.item_for_index(tab.ix)
 972                    } else {
 973                        tab.pane.read(cx).item_for_index(tab.ix)
 974                    };
 975                    if let Some(item) = item {
 976                        return item.downcast::<TerminalView>().is_some();
 977                    }
 978                }
 979            }
 980            false
 981        })));
 982
 983        let buffer_search_bar = cx.new(|cx| search::BufferSearchBar::new(window, cx));
 984        let breadcrumbs = cx.new(|_| Breadcrumbs::new());
 985        pane.toolbar().update(cx, |toolbar, cx| {
 986            toolbar.add_item(buffer_search_bar, window, cx);
 987            toolbar.add_item(breadcrumbs, window, cx);
 988        });
 989
 990        let drop_closure_project = project.downgrade();
 991        let drop_closure_terminal_panel = terminal_panel.downgrade();
 992        pane.set_custom_drop_handle(cx, move |pane, dropped_item, window, cx| {
 993            let Some(project) = drop_closure_project.upgrade() else {
 994                return ControlFlow::Break(());
 995            };
 996            if let Some(tab) = dropped_item.downcast_ref::<DraggedTab>() {
 997                let this_pane = cx.entity().clone();
 998                let item = if tab.pane == this_pane {
 999                    pane.item_for_index(tab.ix)
1000                } else {
1001                    tab.pane.read(cx).item_for_index(tab.ix)
1002                };
1003                if let Some(item) = item {
1004                    if item.downcast::<TerminalView>().is_some() {
1005                        let source = tab.pane.clone();
1006                        let item_id_to_move = item.item_id();
1007
1008                        let Ok(new_split_pane) = pane
1009                            .drag_split_direction()
1010                            .map(|split_direction| {
1011                                drop_closure_terminal_panel.update(cx, |terminal_panel, cx| {
1012                                    let is_zoomed = if terminal_panel.active_pane == this_pane {
1013                                        pane.is_zoomed()
1014                                    } else {
1015                                        terminal_panel.active_pane.read(cx).is_zoomed()
1016                                    };
1017                                    let new_pane = new_terminal_pane(
1018                                        workspace.clone(),
1019                                        project.clone(),
1020                                        is_zoomed,
1021                                        window,
1022                                        cx,
1023                                    );
1024                                    terminal_panel.apply_tab_bar_buttons(&new_pane, cx);
1025                                    terminal_panel.center.split(
1026                                        &this_pane,
1027                                        &new_pane,
1028                                        split_direction,
1029                                    )?;
1030                                    anyhow::Ok(new_pane)
1031                                })
1032                            })
1033                            .transpose()
1034                        else {
1035                            return ControlFlow::Break(());
1036                        };
1037
1038                        match new_split_pane.transpose() {
1039                            // Source pane may be the one currently updated, so defer the move.
1040                            Ok(Some(new_pane)) => cx
1041                                .spawn_in(window, |_, mut cx| async move {
1042                                    cx.update(|window, cx| {
1043                                        move_item(
1044                                            &source,
1045                                            &new_pane,
1046                                            item_id_to_move,
1047                                            new_pane.read(cx).active_item_index(),
1048                                            window,
1049                                            cx,
1050                                        );
1051                                    })
1052                                    .ok();
1053                                })
1054                                .detach(),
1055                            // If we drop into existing pane or current pane,
1056                            // regular pane drop handler will take care of it,
1057                            // using the right tab index for the operation.
1058                            Ok(None) => return ControlFlow::Continue(()),
1059                            err @ Err(_) => {
1060                                err.log_err();
1061                                return ControlFlow::Break(());
1062                            }
1063                        };
1064                    } else if let Some(project_path) = item.project_path(cx) {
1065                        if let Some(entry_path) = project.read(cx).absolute_path(&project_path, cx)
1066                        {
1067                            add_paths_to_terminal(pane, &[entry_path], window, cx);
1068                        }
1069                    }
1070                }
1071            } else if let Some(selection) = dropped_item.downcast_ref::<DraggedSelection>() {
1072                let project = project.read(cx);
1073                let paths_to_add = selection
1074                    .items()
1075                    .map(|selected_entry| selected_entry.entry_id)
1076                    .filter_map(|entry_id| project.path_for_entry(entry_id, cx))
1077                    .filter_map(|project_path| project.absolute_path(&project_path, cx))
1078                    .collect::<Vec<_>>();
1079                if !paths_to_add.is_empty() {
1080                    add_paths_to_terminal(pane, &paths_to_add, window, cx);
1081                }
1082            } else if let Some(&entry_id) = dropped_item.downcast_ref::<ProjectEntryId>() {
1083                if let Some(entry_path) = project
1084                    .read(cx)
1085                    .path_for_entry(entry_id, cx)
1086                    .and_then(|project_path| project.read(cx).absolute_path(&project_path, cx))
1087                {
1088                    add_paths_to_terminal(pane, &[entry_path], window, cx);
1089                }
1090            } else if is_local {
1091                if let Some(paths) = dropped_item.downcast_ref::<ExternalPaths>() {
1092                    add_paths_to_terminal(pane, paths.paths(), window, cx);
1093                }
1094            }
1095
1096            ControlFlow::Break(())
1097        });
1098
1099        pane
1100    });
1101
1102    cx.subscribe_in(&pane, window, TerminalPanel::handle_pane_event)
1103        .detach();
1104    cx.observe(&pane, |_, _, cx| cx.notify()).detach();
1105
1106    pane
1107}
1108
1109async fn wait_for_terminals_tasks(
1110    terminals_for_task: Vec<(usize, Entity<Pane>, Entity<TerminalView>)>,
1111    cx: &mut AsyncApp,
1112) {
1113    let pending_tasks = terminals_for_task.iter().filter_map(|(_, _, terminal)| {
1114        terminal
1115            .update(cx, |terminal_view, cx| {
1116                terminal_view
1117                    .terminal()
1118                    .update(cx, |terminal, cx| terminal.wait_for_completed_task(cx))
1119            })
1120            .ok()
1121    });
1122    let _: Vec<()> = join_all(pending_tasks).await;
1123}
1124
1125fn add_paths_to_terminal(
1126    pane: &mut Pane,
1127    paths: &[PathBuf],
1128    window: &mut Window,
1129    cx: &mut Context<Pane>,
1130) {
1131    if let Some(terminal_view) = pane
1132        .active_item()
1133        .and_then(|item| item.downcast::<TerminalView>())
1134    {
1135        window.focus(&terminal_view.focus_handle(cx));
1136        let mut new_text = paths.iter().map(|path| format!(" {path:?}")).join("");
1137        new_text.push(' ');
1138        terminal_view.update(cx, |terminal_view, cx| {
1139            terminal_view.terminal().update(cx, |terminal, _| {
1140                terminal.paste(&new_text);
1141            });
1142        });
1143    }
1144}
1145
1146impl EventEmitter<PanelEvent> for TerminalPanel {}
1147
1148impl Render for TerminalPanel {
1149    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1150        let mut registrar = DivRegistrar::new(
1151            |panel, _, cx| {
1152                panel
1153                    .active_pane
1154                    .read(cx)
1155                    .toolbar()
1156                    .read(cx)
1157                    .item_of_type::<BufferSearchBar>()
1158            },
1159            cx,
1160        );
1161        BufferSearchBar::register(&mut registrar);
1162        let registrar = registrar.into_div();
1163        self.workspace
1164            .update(cx, |workspace, cx| {
1165                registrar.size_full().child(self.center.render(
1166                    workspace.project(),
1167                    &HashMap::default(),
1168                    None,
1169                    &self.active_pane,
1170                    workspace.zoomed_item(),
1171                    workspace.app_state(),
1172                    window,
1173                    cx,
1174                ))
1175            })
1176            .ok()
1177            .map(|div| {
1178                div.on_action({
1179                    cx.listener(|terminal_panel, _: &ActivatePaneLeft, window, cx| {
1180                        terminal_panel.activate_pane_in_direction(SplitDirection::Left, window, cx);
1181                    })
1182                })
1183                .on_action({
1184                    cx.listener(|terminal_panel, _: &ActivatePaneRight, window, cx| {
1185                        terminal_panel.activate_pane_in_direction(
1186                            SplitDirection::Right,
1187                            window,
1188                            cx,
1189                        );
1190                    })
1191                })
1192                .on_action({
1193                    cx.listener(|terminal_panel, _: &ActivatePaneUp, window, cx| {
1194                        terminal_panel.activate_pane_in_direction(SplitDirection::Up, window, cx);
1195                    })
1196                })
1197                .on_action({
1198                    cx.listener(|terminal_panel, _: &ActivatePaneDown, window, cx| {
1199                        terminal_panel.activate_pane_in_direction(SplitDirection::Down, window, cx);
1200                    })
1201                })
1202                .on_action(
1203                    cx.listener(|terminal_panel, _action: &ActivateNextPane, window, cx| {
1204                        let panes = terminal_panel.center.panes();
1205                        if let Some(ix) = panes
1206                            .iter()
1207                            .position(|pane| **pane == terminal_panel.active_pane)
1208                        {
1209                            let next_ix = (ix + 1) % panes.len();
1210                            window.focus(&panes[next_ix].focus_handle(cx));
1211                        }
1212                    }),
1213                )
1214                .on_action(cx.listener(
1215                    |terminal_panel, _action: &ActivatePreviousPane, window, cx| {
1216                        let panes = terminal_panel.center.panes();
1217                        if let Some(ix) = panes
1218                            .iter()
1219                            .position(|pane| **pane == terminal_panel.active_pane)
1220                        {
1221                            let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
1222                            window.focus(&panes[prev_ix].focus_handle(cx));
1223                        }
1224                    },
1225                ))
1226                .on_action(
1227                    cx.listener(|terminal_panel, action: &ActivatePane, window, cx| {
1228                        let panes = terminal_panel.center.panes();
1229                        if let Some(&pane) = panes.get(action.0) {
1230                            window.focus(&pane.read(cx).focus_handle(cx));
1231                        } else {
1232                            if let Some(new_pane) =
1233                                terminal_panel.new_pane_with_cloned_active_terminal(window, cx)
1234                            {
1235                                terminal_panel
1236                                    .center
1237                                    .split(
1238                                        &terminal_panel.active_pane,
1239                                        &new_pane,
1240                                        SplitDirection::Right,
1241                                    )
1242                                    .log_err();
1243                                window.focus(&new_pane.focus_handle(cx));
1244                            }
1245                        }
1246                    }),
1247                )
1248                .on_action(cx.listener(|terminal_panel, _: &SwapPaneLeft, _, cx| {
1249                    terminal_panel.swap_pane_in_direction(SplitDirection::Left, cx);
1250                }))
1251                .on_action(cx.listener(|terminal_panel, _: &SwapPaneRight, _, cx| {
1252                    terminal_panel.swap_pane_in_direction(SplitDirection::Right, cx);
1253                }))
1254                .on_action(cx.listener(|terminal_panel, _: &SwapPaneUp, _, cx| {
1255                    terminal_panel.swap_pane_in_direction(SplitDirection::Up, cx);
1256                }))
1257                .on_action(cx.listener(|terminal_panel, _: &SwapPaneDown, _, cx| {
1258                    terminal_panel.swap_pane_in_direction(SplitDirection::Down, cx);
1259                }))
1260                .on_action(
1261                    cx.listener(|terminal_panel, action: &MoveItemToPane, window, cx| {
1262                        let Some(&target_pane) =
1263                            terminal_panel.center.panes().get(action.destination)
1264                        else {
1265                            return;
1266                        };
1267                        move_active_item(
1268                            &terminal_panel.active_pane,
1269                            target_pane,
1270                            action.focus,
1271                            true,
1272                            window,
1273                            cx,
1274                        );
1275                    }),
1276                )
1277                .on_action(cx.listener(
1278                    |terminal_panel, action: &MoveItemToPaneInDirection, window, cx| {
1279                        let source_pane = &terminal_panel.active_pane;
1280                        if let Some(destination_pane) = terminal_panel
1281                            .center
1282                            .find_pane_in_direction(source_pane, action.direction, cx)
1283                        {
1284                            move_active_item(
1285                                source_pane,
1286                                destination_pane,
1287                                action.focus,
1288                                true,
1289                                window,
1290                                cx,
1291                            );
1292                        };
1293                    },
1294                ))
1295            })
1296            .unwrap_or_else(|| div())
1297    }
1298}
1299
1300impl Focusable for TerminalPanel {
1301    fn focus_handle(&self, cx: &App) -> FocusHandle {
1302        self.active_pane.focus_handle(cx)
1303    }
1304}
1305
1306impl Panel for TerminalPanel {
1307    fn position(&self, _window: &Window, cx: &App) -> DockPosition {
1308        match TerminalSettings::get_global(cx).dock {
1309            TerminalDockPosition::Left => DockPosition::Left,
1310            TerminalDockPosition::Bottom => DockPosition::Bottom,
1311            TerminalDockPosition::Right => DockPosition::Right,
1312        }
1313    }
1314
1315    fn position_is_valid(&self, _: DockPosition) -> bool {
1316        true
1317    }
1318
1319    fn set_position(
1320        &mut self,
1321        position: DockPosition,
1322        _window: &mut Window,
1323        cx: &mut Context<Self>,
1324    ) {
1325        settings::update_settings_file::<TerminalSettings>(
1326            self.fs.clone(),
1327            cx,
1328            move |settings, _| {
1329                let dock = match position {
1330                    DockPosition::Left => TerminalDockPosition::Left,
1331                    DockPosition::Bottom => TerminalDockPosition::Bottom,
1332                    DockPosition::Right => TerminalDockPosition::Right,
1333                };
1334                settings.dock = Some(dock);
1335            },
1336        );
1337    }
1338
1339    fn size(&self, window: &Window, cx: &App) -> Pixels {
1340        let settings = TerminalSettings::get_global(cx);
1341        match self.position(window, cx) {
1342            DockPosition::Left | DockPosition::Right => {
1343                self.width.unwrap_or(settings.default_width)
1344            }
1345            DockPosition::Bottom => self.height.unwrap_or(settings.default_height),
1346        }
1347    }
1348
1349    fn set_size(&mut self, size: Option<Pixels>, window: &mut Window, cx: &mut Context<Self>) {
1350        match self.position(window, cx) {
1351            DockPosition::Left | DockPosition::Right => self.width = size,
1352            DockPosition::Bottom => self.height = size,
1353        }
1354        self.serialize(cx);
1355        cx.notify();
1356    }
1357
1358    fn is_zoomed(&self, _window: &Window, cx: &App) -> bool {
1359        self.active_pane.read(cx).is_zoomed()
1360    }
1361
1362    fn set_zoomed(&mut self, zoomed: bool, _: &mut Window, cx: &mut Context<Self>) {
1363        for pane in self.center.panes() {
1364            pane.update(cx, |pane, cx| {
1365                pane.set_zoomed(zoomed, cx);
1366            })
1367        }
1368        cx.notify();
1369    }
1370
1371    fn set_active(&mut self, active: bool, window: &mut Window, cx: &mut Context<Self>) {
1372        let old_active = self.active;
1373        self.active = active;
1374        if !active || old_active == active || !self.has_no_terminals(cx) {
1375            return;
1376        }
1377        cx.defer_in(window, |this, window, cx| {
1378            let Ok(kind) = this.workspace.update(cx, |workspace, cx| {
1379                TerminalKind::Shell(default_working_directory(workspace, cx))
1380            }) else {
1381                return;
1382            };
1383
1384            this.add_terminal(kind, RevealStrategy::Always, window, cx)
1385                .detach_and_log_err(cx)
1386        })
1387    }
1388
1389    fn icon_label(&self, _window: &Window, cx: &App) -> Option<String> {
1390        let count = self
1391            .center
1392            .panes()
1393            .into_iter()
1394            .map(|pane| pane.read(cx).items_len())
1395            .sum::<usize>();
1396        if count == 0 {
1397            None
1398        } else {
1399            Some(count.to_string())
1400        }
1401    }
1402
1403    fn persistent_name() -> &'static str {
1404        "TerminalPanel"
1405    }
1406
1407    fn icon(&self, _window: &Window, cx: &App) -> Option<IconName> {
1408        if (self.is_enabled(cx) || !self.has_no_terminals(cx))
1409            && TerminalSettings::get_global(cx).button
1410        {
1411            Some(IconName::Terminal)
1412        } else {
1413            None
1414        }
1415    }
1416
1417    fn icon_tooltip(&self, _window: &Window, _cx: &App) -> Option<&'static str> {
1418        Some("Terminal Panel")
1419    }
1420
1421    fn toggle_action(&self) -> Box<dyn gpui::Action> {
1422        Box::new(ToggleFocus)
1423    }
1424
1425    fn pane(&self) -> Option<Entity<Pane>> {
1426        Some(self.active_pane.clone())
1427    }
1428
1429    fn activation_priority(&self) -> u32 {
1430        1
1431    }
1432}
1433
1434struct InlineAssistTabBarButton {
1435    focus_handle: FocusHandle,
1436}
1437
1438impl Render for InlineAssistTabBarButton {
1439    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1440        let focus_handle = self.focus_handle.clone();
1441        IconButton::new("terminal_inline_assistant", IconName::ZedAssistant)
1442            .icon_size(IconSize::Small)
1443            .on_click(cx.listener(|_, _, window, cx| {
1444                window.dispatch_action(InlineAssist::default().boxed_clone(), cx);
1445            }))
1446            .tooltip(move |window, cx| {
1447                Tooltip::for_action_in(
1448                    "Inline Assist",
1449                    &InlineAssist::default(),
1450                    &focus_handle,
1451                    window,
1452                    cx,
1453                )
1454            })
1455    }
1456}