mod persistence;pub mod terminal_element;pub mod terminal_panel;pub mod terminal_scrollbar;pub mod terminal_tab_tooltip; use collections::HashSet;use editor::{actions::SelectAll, scroll::ScrollbarAutoHide, Editor, EditorSettings};use futures::{stream::FuturesUnordered, StreamExt};use gpui::{ anchored, deferred, div, impl_actions, AnyElement, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, KeyContext, KeyDownEvent, Keystroke, MouseButton, MouseDownEvent, Pixels, Render, ScrollWheelEvent, Stateful, Styled, Subscription, Task, WeakEntity,};use persistence::TERMINAL_DB;use project::{search::SearchQuery, terminals::TerminalKind, Fs, Metadata, Project};use schemars::JsonSchema;use terminal::{ alacritty_terminal::{ index::Point, term::{search::RegexSearch, TermMode}, }, terminal_settings::{self, CursorShape, TerminalBlink, TerminalSettings, WorkingDirectory}, Clear, Copy, Event, MaybeNavigationTarget, Paste, ScrollLineDown, ScrollLineUp, ScrollPageDown, ScrollPageUp, ScrollToBottom, ScrollToTop, ShowCharacterPalette, TaskStatus, Terminal, TerminalBounds, ToggleViMode,};use terminal_element::{is_blank, TerminalElement};use terminal_panel::TerminalPanel;use terminal_scrollbar::TerminalScrollHandle;use terminal_tab_tooltip::TerminalTooltip;use ui::{ h_flex, prelude::*, ContextMenu, Icon, IconName, Label, Scrollbar, ScrollbarState, Tooltip,};use util::{ paths::{PathWithPosition, SanitizedPath}, ResultExt,};use workspace::{ item::{ BreadcrumbText, Item, ItemEvent, SerializableItem, TabContentParams, TabTooltipContent, }, register_serializable_item, searchable::{SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle}, CloseActiveItem, NewCenterTerminal, NewTerminal, OpenVisible, ToolbarItemLocation, Workspace, WorkspaceId,}; use anyhow::Context as _;use serde::Deserialize;use settings::{Settings, SettingsStore};use smol::Timer;use zed_actions::assistant::InlineAssist; use std::{ cmp, ops::RangeInclusive, path::{Path, PathBuf}, rc::Rc, sync::Arc, time::Duration,}; const REGEX_SPECIAL_CHARS: &[char] = &[ '\\', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '^', '$',]; const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); const GIT_DIFF_PATH_PREFIXES: &[char] = &['a', 'b']; /// Event to transmit the scroll from the element to the view#[derive(Clone, Debug, PartialEq)]pub struct ScrollTerminal(pub i32); #[derive(Clone, Debug, Default, Deserialize, JsonSchema, PartialEq)]pub struct SendText(String); #[derive(Clone, Debug, Default, Deserialize, JsonSchema, PartialEq)]pub struct SendKeystroke(String); impl_actions!(terminal, [SendText, SendKeystroke]); pub fn init(cx: &mut App) { terminal_panel::init(cx); terminal::init(cx); register_serializable_item::<TerminalView>(cx); cx.observe_new(|workspace: &mut Workspace, _window, _cx| { workspace.register_action(TerminalView::deploy); }) .detach();} pub struct BlockProperties { pub height: u8, pub render: Box<dyn Send + Fn(&mut BlockContext) -> AnyElement>,} pub struct BlockContext<'a, 'b> { pub window: &'a mut Window, pub context: &'b mut App, pub dimensions: TerminalBounds,} ///A terminal view, maintains the PTY's file handles and communicates with the terminalpub struct TerminalView { terminal: Entity<Terminal>, workspace: WeakEntity<Workspace>, project: WeakEntity<Project>, focus_handle: FocusHandle, //Currently using iTerm bell, show bell emoji in tab until input is received has_bell: bool, context_menu: Option<(Entity<ContextMenu>, gpui::Point<Pixels>, Subscription)>, cursor_shape: CursorShape, blink_state: bool, blinking_terminal_enabled: bool, blinking_paused: bool, blink_epoch: usize, can_navigate_to_selected_word: bool, workspace_id: Option<WorkspaceId>, show_breadcrumbs: bool, block_below_cursor: Option<Rc<BlockProperties>>, scroll_top: Pixels, scrollbar_state: ScrollbarState, scroll_handle: TerminalScrollHandle, show_scrollbar: bool, hide_scrollbar_task: Option<Task<()>>, _subscriptions: Vec<Subscription>, _terminal_subscriptions: Vec<Subscription>,} impl EventEmitter<Event> for TerminalView {}impl EventEmitter<ItemEvent> for TerminalView {}impl EventEmitter<SearchEvent> for TerminalView {} impl Focusable for TerminalView { fn focus_handle(&self, _cx: &App) -> FocusHandle { self.focus_handle.clone() }} impl TerminalView { ///Create a new Terminal in the current working directory or the user's home directory pub fn deploy( workspace: &mut Workspace, _: &NewCenterTerminal, window: &mut Window, cx: &mut Context<Workspace>, ) { let working_directory = default_working_directory(workspace, cx); TerminalPanel::add_center_terminal( workspace, TerminalKind::Shell(working_directory), window, cx, ) .detach_and_log_err(cx); } pub fn new( terminal: Entity<Terminal>, workspace: WeakEntity<Workspace>, workspace_id: Option<WorkspaceId>, project: WeakEntity<Project>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let workspace_handle = workspace.clone(); let terminal_subscriptions = subscribe_for_terminal_events(&terminal, workspace, window, cx); let focus_handle = cx.focus_handle(); let focus_in = cx.on_focus_in(&focus_handle, window, |terminal_view, window, cx| { terminal_view.focus_in(window, cx); }); let focus_out = cx.on_focus_out( &focus_handle, window, |terminal_view, _event, window, cx| { terminal_view.focus_out(window, cx); }, ); let cursor_shape = TerminalSettings::get_global(cx) .cursor_shape .unwrap_or_default(); let scroll_handle = TerminalScrollHandle::new(terminal.read(cx)); Self { terminal, workspace: workspace_handle, project, has_bell: false, focus_handle, context_menu: None, cursor_shape, blink_state: true, blinking_terminal_enabled: false, blinking_paused: false, blink_epoch: 0, can_navigate_to_selected_word: false, workspace_id, show_breadcrumbs: TerminalSettings::get_global(cx).toolbar.breadcrumbs, block_below_cursor: None, scroll_top: Pixels::ZERO, scrollbar_state: ScrollbarState::new(scroll_handle.clone()), scroll_handle, show_scrollbar: !Self::should_autohide_scrollbar(cx), hide_scrollbar_task: None, _subscriptions: vec![ focus_in, focus_out, cx.observe_global::<SettingsStore>(Self::settings_changed), ], _terminal_subscriptions: terminal_subscriptions, } } pub fn entity(&self) -> &Entity<Terminal> { &self.terminal } pub fn has_bell(&self) -> bool { self.has_bell } pub fn clear_bell(&mut self, cx: &mut Context<TerminalView>) { self.has_bell = false; cx.emit(Event::Wakeup); } pub fn deploy_context_menu( &mut self, position: gpui::Point<Pixels>, window: &mut Window, cx: &mut Context<Self>, ) { let assistant_enabled = self .workspace .upgrade() .and_then(|workspace| workspace.read(cx).panel::<TerminalPanel>(cx)) .map_or(false, |terminal_panel| { terminal_panel.read(cx).assistant_enabled() }); let context_menu = ContextMenu::build(window, cx, |menu, _, _| { menu.context(self.focus_handle.clone()) .action("New Terminal", Box::new(NewTerminal)) .separator() .action("Copy", Box::new(Copy)) .action("Paste", Box::new(Paste)) .action("Select All", Box::new(SelectAll)) .action("Clear", Box::new(Clear)) .when(assistant_enabled, |menu| { menu.separator() .action("Inline Assist", Box::new(InlineAssist::default())) }) .separator() .action( "Close Terminal Tab", Box::new(CloseActiveItem { save_intent: None, close_pinned: true, }), ) }); window.focus(&context_menu.focus_handle(cx)); let subscription = cx.subscribe_in( &context_menu, window, |this, _, _: &DismissEvent, window, cx| { if this.context_menu.as_ref().is_some_and(|context_menu| { context_menu.0.focus_handle(cx).contains_focused(window, cx) }) { cx.focus_self(window); } this.context_menu.take(); cx.notify(); }, ); self.context_menu = Some((context_menu, position, subscription)); } fn settings_changed(&mut self, cx: &mut Context<Self>) { let settings = TerminalSettings::get_global(cx); self.show_breadcrumbs = settings.toolbar.breadcrumbs; let new_cursor_shape = settings.cursor_shape.unwrap_or_default(); let old_cursor_shape = self.cursor_shape; if old_cursor_shape != new_cursor_shape { self.cursor_shape = new_cursor_shape; self.terminal.update(cx, |term, _| { term.set_cursor_shape(self.cursor_shape); }); } cx.notify(); } fn show_character_palette( &mut self, _: &ShowCharacterPalette, window: &mut Window, cx: &mut Context<Self>, ) { if self .terminal .read(cx) .last_content .mode .contains(TermMode::ALT_SCREEN) { self.terminal.update(cx, |term, cx| { term.try_keystroke( &Keystroke::parse("ctrl-cmd-space").unwrap(), TerminalSettings::get_global(cx).option_as_meta, ) }); } else { window.show_character_palette(); } } fn select_all(&mut self, _: &SelectAll, _: &mut Window, cx: &mut Context<Self>) { self.terminal.update(cx, |term, _| term.select_all()); cx.notify(); } fn clear(&mut self, _: &Clear, _: &mut Window, cx: &mut Context<Self>) { self.scroll_top = px(0.); self.terminal.update(cx, |term, _| term.clear()); cx.notify(); } fn max_scroll_top(&self, cx: &App) -> Pixels { let terminal = self.terminal.read(cx); let Some(block) = self.block_below_cursor.as_ref() else { return Pixels::ZERO; }; let line_height = terminal.last_content().terminal_bounds.line_height; let mut terminal_lines = terminal.total_lines(); let viewport_lines = terminal.viewport_lines(); if terminal.total_lines() == terminal.viewport_lines() { let mut last_line = None; for cell in terminal.last_content.cells.iter().rev() { if !is_blank(cell) { break; } let last_line = last_line.get_or_insert(cell.point.line); if *last_line != cell.point.line { terminal_lines -= 1; } *last_line = cell.point.line; } } let max_scroll_top_in_lines = (block.height as usize).saturating_sub(viewport_lines.saturating_sub(terminal_lines)); max_scroll_top_in_lines as f32 * line_height } fn scroll_wheel(&mut self, event: &ScrollWheelEvent, cx: &mut Context<Self>) { let terminal_content = self.terminal.read(cx).last_content(); if self.block_below_cursor.is_some() && terminal_content.display_offset == 0 { let line_height = terminal_content.terminal_bounds.line_height; let y_delta = event.delta.pixel_delta(line_height).y; if y_delta < Pixels::ZERO || self.scroll_top > Pixels::ZERO { self.scroll_top = cmp::max( Pixels::ZERO, cmp::min(self.scroll_top - y_delta, self.max_scroll_top(cx)), ); cx.notify(); return; } } self.terminal.update(cx, |term, _| term.scroll_wheel(event)); } fn scroll_line_up(&mut self, _: &ScrollLineUp, _: &mut Window, cx: &mut Context<Self>) { let terminal_content = self.terminal.read(cx).last_content(); if self.block_below_cursor.is_some() && terminal_content.display_offset == 0 && self.scroll_top > Pixels::ZERO { let line_height = terminal_content.terminal_bounds.line_height; self.scroll_top = cmp::max(self.scroll_top - line_height, Pixels::ZERO); return; } self.terminal.update(cx, |term, _| term.scroll_line_up()); cx.notify(); } fn scroll_line_down(&mut self, _: &ScrollLineDown, _: &mut Window, cx: &mut Context<Self>) { let terminal_content = self.terminal.read(cx).last_content(); if self.block_below_cursor.is_some() && terminal_content.display_offset == 0 { let max_scroll_top = self.max_scroll_top(cx); if self.scroll_top < max_scroll_top { let line_height = terminal_content.terminal_bounds.line_height; self.scroll_top = cmp::min(self.scroll_top + line_height, max_scroll_top); } return; } self.terminal.update(cx, |term, _| term.scroll_line_down()); cx.notify(); } fn scroll_page_up(&mut self, _: &ScrollPageUp, _: &mut Window, cx: &mut Context<Self>) { if self.scroll_top == Pixels::ZERO { self.terminal.update(cx, |term, _| term.scroll_page_up()); } else { let line_height = self .terminal .read(cx) .last_content .terminal_bounds .line_height(); let visible_block_lines = (self.scroll_top / line_height) as usize; let viewport_lines = self.terminal.read(cx).viewport_lines(); let visible_content_lines = viewport_lines - visible_block_lines; if visible_block_lines >= viewport_lines { self.scroll_top = ((visible_block_lines - viewport_lines) as f32) * line_height; } else { self.scroll_top = px(0.); self.terminal .update(cx, |term, _| term.scroll_up_by(visible_content_lines)); } } cx.notify(); } fn scroll_page_down(&mut self, _: &ScrollPageDown, _: &mut Window, cx: &mut Context<Self>) { self.terminal.update(cx, |term, _| term.scroll_page_down()); let terminal = self.terminal.read(cx); if terminal.last_content().display_offset < terminal.viewport_lines() { self.scroll_top = self.max_scroll_top(cx); } cx.notify(); } fn scroll_to_top(&mut self, _: &ScrollToTop, _: &mut Window, cx: &mut Context<Self>) { self.terminal.update(cx, |term, _| term.scroll_to_top()); cx.notify(); } fn scroll_to_bottom(&mut self, _: &ScrollToBottom, _: &mut Window, cx: &mut Context<Self>) { self.terminal.update(cx, |term, _| term.scroll_to_bottom()); if self.block_below_cursor.is_some() { self.scroll_top = self.max_scroll_top(cx); } cx.notify(); } fn toggle_vi_mode(&mut self, _: &ToggleViMode, _: &mut Window, cx: &mut Context<Self>) { self.terminal.update(cx, |term, _| term.toggle_vi_mode()); cx.notify(); } pub fn should_show_cursor(&self, focused: bool, cx: &mut Context<Self>) -> bool { //Don't blink the cursor when not focused, blinking is disabled, or paused if !focused || self.blinking_paused || self .terminal .read(cx) .last_content .mode .contains(TermMode::ALT_SCREEN) { return true; } match TerminalSettings::get_global(cx).blinking { //If the user requested to never blink, don't blink it. TerminalBlink::Off => true, //If the terminal is controlling it, check terminal mode TerminalBlink::TerminalControlled => { !self.blinking_terminal_enabled || self.blink_state } TerminalBlink::On => self.blink_state, } } fn blink_cursors(&mut self, epoch: usize, window: &mut Window, cx: &mut Context<Self>) { if epoch == self.blink_epoch && !self.blinking_paused { self.blink_state = !self.blink_state; cx.notify(); let epoch = self.next_blink_epoch();Lines 1–500 of 1858. Load more
mod persistence; pub mod terminal_element; pub mod terminal_panel; pub mod terminal_scrollbar; pub mod terminal_tab_tooltip; use collections::HashSet; use editor::{actions::SelectAll, scroll::ScrollbarAutoHide, Editor, EditorSettings}; use futures::{stream::FuturesUnordered, StreamExt}; use gpui::{ anchored, deferred, div, impl_actions, AnyElement, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, KeyContext, KeyDownEvent, Keystroke, MouseButton, MouseDownEvent, Pixels, Render, ScrollWheelEvent, Stateful, Styled, Subscription, Task, WeakEntity, }; use persistence::TERMINAL_DB; use project::{search::SearchQuery, terminals::TerminalKind, Fs, Metadata, Project}; use schemars::JsonSchema; use terminal::{ alacritty_terminal::{ index::Point, term::{search::RegexSearch, TermMode}, }, terminal_settings::{self, CursorShape, TerminalBlink, TerminalSettings, WorkingDirectory}, Clear, Copy, Event, MaybeNavigationTarget, Paste, ScrollLineDown, ScrollLineUp, ScrollPageDown, ScrollPageUp, ScrollToBottom, ScrollToTop, ShowCharacterPalette, TaskStatus, Terminal, TerminalBounds, ToggleViMode, }; use terminal_element::{is_blank, TerminalElement}; use terminal_panel::TerminalPanel; use terminal_scrollbar::TerminalScrollHandle; use terminal_tab_tooltip::TerminalTooltip; use ui::{ h_flex, prelude::*, ContextMenu, Icon, IconName, Label, Scrollbar, ScrollbarState, Tooltip, }; use util::{ paths::{PathWithPosition, SanitizedPath}, ResultExt, }; use workspace::{ item::{ BreadcrumbText, Item, ItemEvent, SerializableItem, TabContentParams, TabTooltipContent, }, register_serializable_item, searchable::{SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle}, CloseActiveItem, NewCenterTerminal, NewTerminal, OpenVisible, ToolbarItemLocation, Workspace, WorkspaceId, }; use anyhow::Context as _; use serde::Deserialize; use settings::{Settings, SettingsStore}; use smol::Timer; use zed_actions::assistant::InlineAssist; use std::{ cmp, ops::RangeInclusive, path::{Path, PathBuf}, rc::Rc, sync::Arc, time::Duration, }; const REGEX_SPECIAL_CHARS: &[char] = &[ '\\', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '^', '$', ]; const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); const GIT_DIFF_PATH_PREFIXES: &[char] = &['a', 'b']; /// Event to transmit the scroll from the element to the view #[derive(Clone, Debug, PartialEq)] pub struct ScrollTerminal(pub i32); #[derive(Clone, Debug, Default, Deserialize, JsonSchema, PartialEq)] pub struct SendText(String); #[derive(Clone, Debug, Default, Deserialize, JsonSchema, PartialEq)] pub struct SendKeystroke(String); impl_actions!(terminal, [SendText, SendKeystroke]); pub fn init(cx: &mut App) { terminal_panel::init(cx); terminal::init(cx); register_serializable_item::<TerminalView>(cx); cx.observe_new(|workspace: &mut Workspace, _window, _cx| { workspace.register_action(TerminalView::deploy); }) .detach(); } pub struct BlockProperties { pub height: u8, pub render: Box<dyn Send + Fn(&mut BlockContext) -> AnyElement>, } pub struct BlockContext<'a, 'b> { pub window: &'a mut Window, pub context: &'b mut App, pub dimensions: TerminalBounds, } ///A terminal view, maintains the PTY's file handles and communicates with the terminal pub struct TerminalView { terminal: Entity<Terminal>, workspace: WeakEntity<Workspace>, project: WeakEntity<Project>, focus_handle: FocusHandle, //Currently using iTerm bell, show bell emoji in tab until input is received has_bell: bool, context_menu: Option<(Entity<ContextMenu>, gpui::Point<Pixels>, Subscription)>, cursor_shape: CursorShape, blink_state: bool, blinking_terminal_enabled: bool, blinking_paused: bool, blink_epoch: usize, can_navigate_to_selected_word: bool, workspace_id: Option<WorkspaceId>, show_breadcrumbs: bool, block_below_cursor: Option<Rc<BlockProperties>>, scroll_top: Pixels, scrollbar_state: ScrollbarState, scroll_handle: TerminalScrollHandle, show_scrollbar: bool, hide_scrollbar_task: Option<Task<()>>, _subscriptions: Vec<Subscription>, _terminal_subscriptions: Vec<Subscription>, } impl EventEmitter<Event> for TerminalView {} impl EventEmitter<ItemEvent> for TerminalView {} impl EventEmitter<SearchEvent> for TerminalView {} impl Focusable for TerminalView { fn focus_handle(&self, _cx: &App) -> FocusHandle { self.focus_handle.clone() } } impl TerminalView { ///Create a new Terminal in the current working directory or the user's home directory pub fn deploy( workspace: &mut Workspace, _: &NewCenterTerminal, window: &mut Window, cx: &mut Context<Workspace>, ) { let working_directory = default_working_directory(workspace, cx); TerminalPanel::add_center_terminal( workspace, TerminalKind::Shell(working_directory), window, cx, ) .detach_and_log_err(cx); } pub fn new( terminal: Entity<Terminal>, workspace: WeakEntity<Workspace>, workspace_id: Option<WorkspaceId>, project: WeakEntity<Project>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let workspace_handle = workspace.clone(); let terminal_subscriptions = subscribe_for_terminal_events(&terminal, workspace, window, cx); let focus_handle = cx.focus_handle(); let focus_in = cx.on_focus_in(&focus_handle, window, |terminal_view, window, cx| { terminal_view.focus_in(window, cx); }); let focus_out = cx.on_focus_out( &focus_handle, window, |terminal_view, _event, window, cx| { terminal_view.focus_out(window, cx); }, ); let cursor_shape = TerminalSettings::get_global(cx) .cursor_shape .unwrap_or_default(); let scroll_handle = TerminalScrollHandle::new(terminal.read(cx)); Self { terminal, workspace: workspace_handle, project, has_bell: false, focus_handle, context_menu: None, cursor_shape, blink_state: true, blinking_terminal_enabled: false, blinking_paused: false, blink_epoch: 0, can_navigate_to_selected_word: false, workspace_id, show_breadcrumbs: TerminalSettings::get_global(cx).toolbar.breadcrumbs, block_below_cursor: None, scroll_top: Pixels::ZERO, scrollbar_state: ScrollbarState::new(scroll_handle.clone()), scroll_handle, show_scrollbar: !Self::should_autohide_scrollbar(cx), hide_scrollbar_task: None, _subscriptions: vec![ focus_in, focus_out, cx.observe_global::<SettingsStore>(Self::settings_changed), ], _terminal_subscriptions: terminal_subscriptions, } } pub fn entity(&self) -> &Entity<Terminal> { &self.terminal } pub fn has_bell(&self) -> bool { self.has_bell } pub fn clear_bell(&mut self, cx: &mut Context<TerminalView>) { self.has_bell = false; cx.emit(Event::Wakeup); } pub fn deploy_context_menu( &mut self, position: gpui::Point<Pixels>, window: &mut Window, cx: &mut Context<Self>, ) { let assistant_enabled = self .workspace .upgrade() .and_then(|workspace| workspace.read(cx).panel::<TerminalPanel>(cx)) .map_or(false, |terminal_panel| { terminal_panel.read(cx).assistant_enabled() }); let context_menu = ContextMenu::build(window, cx, |menu, _, _| { menu.context(self.focus_handle.clone()) .action("New Terminal", Box::new(NewTerminal)) .separator() .action("Copy", Box::new(Copy)) .action("Paste", Box::new(Paste)) .action("Select All", Box::new(SelectAll)) .action("Clear", Box::new(Clear)) .when(assistant_enabled, |menu| { menu.separator() .action("Inline Assist", Box::new(InlineAssist::default())) }) .separator() .action( "Close Terminal Tab", Box::new(CloseActiveItem { save_intent: None, close_pinned: true, }), ) }); window.focus(&context_menu.focus_handle(cx)); let subscription = cx.subscribe_in( &context_menu, window, |this, _, _: &DismissEvent, window, cx| { if this.context_menu.as_ref().is_some_and(|context_menu| { context_menu.0.focus_handle(cx).contains_focused(window, cx) }) { cx.focus_self(window); } this.context_menu.take(); cx.notify(); }, ); self.context_menu = Some((context_menu, position, subscription)); } fn settings_changed(&mut self, cx: &mut Context<Self>) { let settings = TerminalSettings::get_global(cx); self.show_breadcrumbs = settings.toolbar.breadcrumbs; let new_cursor_shape = settings.cursor_shape.unwrap_or_default(); let old_cursor_shape = self.cursor_shape; if old_cursor_shape != new_cursor_shape { self.cursor_shape = new_cursor_shape; self.terminal.update(cx, |term, _| { term.set_cursor_shape(self.cursor_shape); }); } cx.notify(); } fn show_character_palette( &mut self, _: &ShowCharacterPalette, window: &mut Window, cx: &mut Context<Self>, ) { if self .terminal .read(cx) .last_content .mode .contains(TermMode::ALT_SCREEN) { self.terminal.update(cx, |term, cx| { term.try_keystroke( &Keystroke::parse("ctrl-cmd-space").unwrap(), TerminalSettings::get_global(cx).option_as_meta, ) }); } else { window.show_character_palette(); } } fn select_all(&mut self, _: &SelectAll, _: &mut Window, cx: &mut Context<Self>) { self.terminal.update(cx, |term, _| term.select_all()); cx.notify(); } fn clear(&mut self, _: &Clear, _: &mut Window, cx: &mut Context<Self>) { self.scroll_top = px(0.); self.terminal.update(cx, |term, _| term.clear()); cx.notify(); } fn max_scroll_top(&self, cx: &App) -> Pixels { let terminal = self.terminal.read(cx); let Some(block) = self.block_below_cursor.as_ref() else { return Pixels::ZERO; }; let line_height = terminal.last_content().terminal_bounds.line_height; let mut terminal_lines = terminal.total_lines(); let viewport_lines = terminal.viewport_lines(); if terminal.total_lines() == terminal.viewport_lines() { let mut last_line = None; for cell in terminal.last_content.cells.iter().rev() { if !is_blank(cell) { break; } let last_line = last_line.get_or_insert(cell.point.line); if *last_line != cell.point.line { terminal_lines -= 1; } *last_line = cell.point.line; } } let max_scroll_top_in_lines = (block.height as usize).saturating_sub(viewport_lines.saturating_sub(terminal_lines)); max_scroll_top_in_lines as f32 * line_height } fn scroll_wheel(&mut self, event: &ScrollWheelEvent, cx: &mut Context<Self>) { let terminal_content = self.terminal.read(cx).last_content(); if self.block_below_cursor.is_some() && terminal_content.display_offset == 0 { let line_height = terminal_content.terminal_bounds.line_height; let y_delta = event.delta.pixel_delta(line_height).y; if y_delta < Pixels::ZERO || self.scroll_top > Pixels::ZERO { self.scroll_top = cmp::max( Pixels::ZERO, cmp::min(self.scroll_top - y_delta, self.max_scroll_top(cx)), ); cx.notify(); return; } } self.terminal.update(cx, |term, _| term.scroll_wheel(event)); } fn scroll_line_up(&mut self, _: &ScrollLineUp, _: &mut Window, cx: &mut Context<Self>) { let terminal_content = self.terminal.read(cx).last_content(); if self.block_below_cursor.is_some() && terminal_content.display_offset == 0 && self.scroll_top > Pixels::ZERO { let line_height = terminal_content.terminal_bounds.line_height; self.scroll_top = cmp::max(self.scroll_top - line_height, Pixels::ZERO); return; } self.terminal.update(cx, |term, _| term.scroll_line_up()); cx.notify(); } fn scroll_line_down(&mut self, _: &ScrollLineDown, _: &mut Window, cx: &mut Context<Self>) { let terminal_content = self.terminal.read(cx).last_content(); if self.block_below_cursor.is_some() && terminal_content.display_offset == 0 { let max_scroll_top = self.max_scroll_top(cx); if self.scroll_top < max_scroll_top { let line_height = terminal_content.terminal_bounds.line_height; self.scroll_top = cmp::min(self.scroll_top + line_height, max_scroll_top); } return; } self.terminal.update(cx, |term, _| term.scroll_line_down()); cx.notify(); } fn scroll_page_up(&mut self, _: &ScrollPageUp, _: &mut Window, cx: &mut Context<Self>) { if self.scroll_top == Pixels::ZERO { self.terminal.update(cx, |term, _| term.scroll_page_up()); } else { let line_height = self .terminal .read(cx) .last_content .terminal_bounds .line_height(); let visible_block_lines = (self.scroll_top / line_height) as usize; let viewport_lines = self.terminal.read(cx).viewport_lines(); let visible_content_lines = viewport_lines - visible_block_lines; if visible_block_lines >= viewport_lines { self.scroll_top = ((visible_block_lines - viewport_lines) as f32) * line_height; } else { self.scroll_top = px(0.); self.terminal .update(cx, |term, _| term.scroll_up_by(visible_content_lines)); } } cx.notify(); } fn scroll_page_down(&mut self, _: &ScrollPageDown, _: &mut Window, cx: &mut Context<Self>) { self.terminal.update(cx, |term, _| term.scroll_page_down()); let terminal = self.terminal.read(cx); if terminal.last_content().display_offset < terminal.viewport_lines() { self.scroll_top = self.max_scroll_top(cx); } cx.notify(); } fn scroll_to_top(&mut self, _: &ScrollToTop, _: &mut Window, cx: &mut Context<Self>) { self.terminal.update(cx, |term, _| term.scroll_to_top()); cx.notify(); } fn scroll_to_bottom(&mut self, _: &ScrollToBottom, _: &mut Window, cx: &mut Context<Self>) { self.terminal.update(cx, |term, _| term.scroll_to_bottom()); if self.block_below_cursor.is_some() { self.scroll_top = self.max_scroll_top(cx); } cx.notify(); } fn toggle_vi_mode(&mut self, _: &ToggleViMode, _: &mut Window, cx: &mut Context<Self>) { self.terminal.update(cx, |term, _| term.toggle_vi_mode()); cx.notify(); } pub fn should_show_cursor(&self, focused: bool, cx: &mut Context<Self>) -> bool { //Don't blink the cursor when not focused, blinking is disabled, or paused if !focused || self.blinking_paused || self .terminal .read(cx) .last_content .mode .contains(TermMode::ALT_SCREEN) { return true; } match TerminalSettings::get_global(cx).blinking { //If the user requested to never blink, don't blink it. TerminalBlink::Off => true, //If the terminal is controlling it, check terminal mode TerminalBlink::TerminalControlled => { !self.blinking_terminal_enabled || self.blink_state } TerminalBlink::On => self.blink_state, } } fn blink_cursors(&mut self, epoch: usize, window: &mut Window, cx: &mut Context<Self>) { if epoch == self.blink_epoch && !self.blinking_paused { self.blink_state = !self.blink_state; cx.notify(); let epoch = self.next_blink_epoch();