pub mod mappings; pub use alacritty_terminal; mod pty_info;pub mod terminal_settings; use alacritty_terminal::{ event::{Event as AlacTermEvent, EventListener, Notify, WindowSize}, event_loop::{EventLoop, Msg, Notifier}, grid::{Dimensions, Scroll as AlacScroll}, index::{Boundary, Column, Direction as AlacDirection, Line, Point as AlacPoint}, selection::{Selection, SelectionRange, SelectionType}, sync::FairMutex, term::{ cell::Cell, search::{Match, RegexIter, RegexSearch}, Config, RenderableCursor, TermMode, }, tty::{self}, vi_mode::{ViModeCursor, ViMotion}, vte::ansi::{ ClearMode, CursorStyle as AlacCursorStyle, Handler, NamedPrivateMode, PrivateMode, }, Term,};use anyhow::{bail, Result}; use futures::{ channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}, FutureExt,}; use mappings::mouse::{ alt_scroll, grid_point, grid_point_and_side, mouse_button_report, mouse_moved_report, scroll_report,}; use collections::{HashMap, VecDeque};use futures::StreamExt;use pty_info::PtyProcessInfo;use serde::{Deserialize, Serialize};use settings::Settings;use smol::channel::{Receiver, Sender};use task::{HideStrategy, Shell, TaskId};use terminal_settings::{AlternateScroll, CursorShape, TerminalSettings};use theme::{ActiveTheme, Theme};use util::{paths::home_dir, truncate_and_trailoff}; use std::{ cmp::{self, min}, fmt::Display, ops::{Deref, Index, RangeInclusive}, path::PathBuf, sync::Arc, time::Duration,};use thiserror::Error; use gpui::{ actions, black, px, AnyWindowHandle, App, AppContext as _, Bounds, ClipboardItem, Context, EventEmitter, Hsla, Keystroke, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, Rgba, ScrollWheelEvent, SharedString, Size, Task, TouchPhase, Window,}; use crate::mappings::{colors::to_alac_rgb, keys::to_esc_str}; actions!( terminal, [ Clear, Copy, Paste, ShowCharacterPalette, SearchTest, ScrollLineUp, ScrollLineDown, ScrollPageUp, ScrollPageDown, ScrollToTop, ScrollToBottom, ToggleViMode, ]); ///Scrolling is unbearably sluggish by default. Alacritty supports a configurable///Scroll multiplier that is set to 3 by default. This will be removed when I///Implement scroll bars.#[cfg(target_os = "macos")]const SCROLL_MULTIPLIER: f32 = 4.;#[cfg(not(target_os = "macos"))]const SCROLL_MULTIPLIER: f32 = 1.;const MAX_SEARCH_LINES: usize = 100;const DEBUG_TERMINAL_WIDTH: Pixels = px(500.);const DEBUG_TERMINAL_HEIGHT: Pixels = px(30.);const DEBUG_CELL_WIDTH: Pixels = px(5.);const DEBUG_LINE_HEIGHT: Pixels = px(5.); ///Upward flowing events, for changing the title and such#[derive(Clone, Debug)]pub enum Event { TitleChanged, BreadcrumbsChanged, CloseTerminal, Bell, Wakeup, BlinkChanged(bool), SelectionsChanged, NewNavigationTarget(Option<MaybeNavigationTarget>), Open(MaybeNavigationTarget),} #[derive(Clone, Debug)]pub struct PathLikeTarget { /// File system path, absolute or relative, existing or not. /// Might have line and column number(s) attached as `file.rs:1:23` pub maybe_path: String, /// Current working directory of the terminal pub terminal_dir: Option<PathBuf>,} /// A string inside terminal, potentially useful as a URI that can be opened.#[derive(Clone, Debug)]pub enum MaybeNavigationTarget { /// HTTP, git, etc. string determined by the `URL_REGEX` regex. Url(String), /// File system path, absolute or relative, existing or not. /// Might have line and column number(s) attached as `file.rs:1:23` PathLike(PathLikeTarget),} #[derive(Clone)]enum InternalEvent { Resize(TerminalBounds), Clear, // FocusNextMatch, Scroll(AlacScroll), ScrollToAlacPoint(AlacPoint), SetSelection(Option<(Selection, AlacPoint)>), UpdateSelection(Point<Pixels>), // Adjusted mouse position, should open FindHyperlink(Point<Pixels>, bool), Copy, // Vi mode events ToggleViMode, ViMotion(ViMotion),} ///A translation struct for Alacritty to communicate with us from their event loop#[derive(Clone)]pub struct ZedListener(pub UnboundedSender<AlacTermEvent>); impl EventListener for ZedListener { fn send_event(&self, event: AlacTermEvent) { self.0.unbounded_send(event).ok(); }} pub fn init(cx: &mut App) { TerminalSettings::register(cx);} #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]pub struct TerminalBounds { pub cell_width: Pixels, pub line_height: Pixels, pub bounds: Bounds<Pixels>,} impl TerminalBounds { pub fn new(line_height: Pixels, cell_width: Pixels, bounds: Bounds<Pixels>) -> Self { TerminalBounds { cell_width, line_height, bounds, } } pub fn num_lines(&self) -> usize { (self.bounds.size.height / self.line_height).floor() as usize } pub fn num_columns(&self) -> usize { (self.bounds.size.width / self.cell_width).floor() as usize } pub fn height(&self) -> Pixels { self.bounds.size.height } pub fn width(&self) -> Pixels { self.bounds.size.width } pub fn cell_width(&self) -> Pixels { self.cell_width } pub fn line_height(&self) -> Pixels { self.line_height }} impl Default for TerminalBounds { fn default() -> Self { TerminalBounds::new( DEBUG_LINE_HEIGHT, DEBUG_CELL_WIDTH, Bounds { origin: Point::default(), size: Size { width: DEBUG_TERMINAL_WIDTH, height: DEBUG_TERMINAL_HEIGHT, }, }, ) }} impl From<TerminalBounds> for WindowSize { fn from(val: TerminalBounds) -> Self { WindowSize { num_lines: val.num_lines() as u16, num_cols: val.num_columns() as u16, cell_width: f32::from(val.cell_width()) as u16, cell_height: f32::from(val.line_height()) as u16, } }} impl Dimensions for TerminalBounds { /// Note: this is supposed to be for the back buffer's length, /// but we exclusively use it to resize the terminal, which does not /// use this method. We still have to implement it for the trait though, /// hence, this comment. fn total_lines(&self) -> usize { self.screen_lines() } fn screen_lines(&self) -> usize { self.num_lines() } fn columns(&self) -> usize { self.num_columns() }} #[derive(Error, Debug)]pub struct TerminalError { pub directory: Option<PathBuf>, pub shell: Shell, pub source: std::io::Error,} impl TerminalError { pub fn fmt_directory(&self) -> String { self.directory .clone() .map(|path| { match path .into_os_string() .into_string() .map_err(|os_str| format!("<non-utf8 path> {}", os_str.to_string_lossy())) { Ok(s) => s, Err(s) => s, } }) .unwrap_or_else(|| { let default_dir = dirs::home_dir().map(|buf| buf.into_os_string().to_string_lossy().to_string()); match default_dir { Some(dir) => format!("<none specified, using home directory> {}", dir), None => "<none specified, could not find home directory>".to_string(), } }) } pub fn fmt_shell(&self) -> String { match &self.shell { Shell::System => "<system defined shell>".to_string(), Shell::Program(s) => s.to_string(), Shell::WithArguments { program, args, title_override, } => { if let Some(title_override) = title_override { format!("{} {} ({})", program, args.join(" "), title_override) } else { format!("{} {}", program, args.join(" ")) } } } }} impl Display for TerminalError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let dir_string: String = self.fmt_directory(); let shell = self.fmt_shell(); write!( f, "Working directory: {} Shell command: `{}`, IOError: {}", dir_string, shell, self.source ) }} // https://github.com/alacritty/alacritty/blob/cb3a79dbf6472740daca8440d5166c1d4af5029e/extra/man/alacritty.5.scd?plain=1#L207-L213const DEFAULT_SCROLL_HISTORY_LINES: usize = 10_000;const MAX_SCROLL_HISTORY_LINES: usize = 100_000;const URL_REGEX: &str = r#"(ipfs:|ipns:|magnet:|mailto:|gemini://|gopher://|https://|http://|news:|file://|git://|ssh:|ftp://)[^\u{0000}-\u{001F}\u{007F}-\u{009F}<>"\s{-}\^⟨⟩`]+"#;// Optional suffix matches MSBuild diagnostic suffixes for path parsing in PathLikeWithPosition// https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-diagnostic-format-for-tasksconst WORD_REGEX: &str = r#"[\$\+\w.\[\]:/\\@\-~()]+(?:\((?:\d+|\d+,\d+)\))|[\$\+\w.\[\]:/\\@\-~()]+"#; pub struct TerminalBuilder { terminal: Terminal, events_rx: UnboundedReceiver<AlacTermEvent>,} impl TerminalBuilder { #[allow(clippy::too_many_arguments)] pub fn new( working_directory: Option<PathBuf>, python_venv_directory: Option<PathBuf>, task: Option<TaskState>, shell: Shell, mut env: HashMap<String, String>, cursor_shape: CursorShape, alternate_scroll: AlternateScroll, max_scroll_history_lines: Option<usize>, is_ssh_terminal: bool, window: AnyWindowHandle, completion_tx: Sender<()>, cx: &App, ) -> Result<TerminalBuilder> { // If the parent environment doesn't have a locale set // (As is the case when launched from a .app on MacOS), // and the Project doesn't have a locale set, then // set a fallback for our child environment to use. if std::env::var("LANG").is_err() { env.entry("LANG".to_string()) .or_insert_with(|| "en_US.UTF-8".to_string()); } env.insert("ZED_TERM".to_string(), "true".to_string()); env.insert("TERM_PROGRAM".to_string(), "zed".to_string()); env.insert("TERM".to_string(), "xterm-256color".to_string()); env.insert( "TERM_PROGRAM_VERSION".to_string(), release_channel::AppVersion::global(cx).to_string(), ); let mut terminal_title_override = None; let pty_options = { let alac_shell = match shell.clone() { Shell::System => None, Shell::Program(program) => { Some(alacritty_terminal::tty::Shell::new(program, Vec::new())) } Shell::WithArguments { program, args, title_override, } => { terminal_title_override = title_override; Some(alacritty_terminal::tty::Shell::new(program, args)) } }; alacritty_terminal::tty::Options { shell: alac_shell, working_directory: working_directory .clone() .or_else(|| Some(home_dir().to_path_buf())), drain_on_exit: true, env: env.into_iter().collect(), } }; // Setup Alacritty's env, which modifies the current process's environment alacritty_terminal::tty::setup_env(); let default_cursor_style = AlacCursorStyle::from(cursor_shape); let scrolling_history = if task.is_some() { // Tasks like `cargo build --all` may produce a lot of output, ergo allow maximum scrolling. // After the task finishes, we do not allow appending to that terminal, so small tasks output should not // cause excessive memory usage over time. MAX_SCROLL_HISTORY_LINES } else { max_scroll_history_lines .unwrap_or(DEFAULT_SCROLL_HISTORY_LINES) .min(MAX_SCROLL_HISTORY_LINES) }; let config = Config { scrolling_history, default_cursor_style, ..Config::default() }; //Spawn a task so the Alacritty EventLoop can communicate with us //TODO: Remove with a bounded sender which can be dispatched on &self let (events_tx, events_rx) = unbounded(); //Set up the terminal... let mut term = Term::new( config.clone(), &TerminalBounds::default(), ZedListener(events_tx.clone()), ); //Alacritty defaults to alternate scrolling being on, so we just need to turn it off. if let AlternateScroll::Off = alternate_scroll { term.unset_private_mode(PrivateMode::Named(NamedPrivateMode::AlternateScroll)); } let term = Arc::new(FairMutex::new(term)); //Setup the pty... let pty = match tty::new( &pty_options, TerminalBounds::default().into(), window.window_id().as_u64(), ) { Ok(pty) => pty, Err(error) => { bail!(TerminalError { directory: working_directory, shell, source: error, }); } }; let pty_info = PtyProcessInfo::new(&pty); //And connect them together let event_loop = EventLoop::new( term.clone(), ZedListener(events_tx.clone()), pty, pty_options.drain_on_exit, false, )?; //Kick things off let pty_tx = event_loop.channel(); let _io_thread = event_loop.spawn(); // DANGER let terminal = Terminal { task, pty_tx: Notifier(pty_tx), completion_tx, term, term_config: config, title_override: terminal_title_override, events: VecDeque::with_capacity(10), //Should never get this high. last_content: Default::default(), last_mouse: None, matches: Vec::new(), selection_head: None, pty_info, breadcrumb_text: String::new(), scroll_px: px(0.), next_link_id: 0, selection_phase: SelectionPhase::Ended, // hovered_word: false, url_regex: RegexSearch::new(URL_REGEX).unwrap(), word_regex: RegexSearch::new(WORD_REGEX).unwrap(), vi_mode_enabled: false, is_ssh_terminal, python_venv_directory, }; Ok(TerminalBuilder { terminal, events_rx, }) } pub fn subscribe(mut self, cx: &Context<Terminal>) -> Terminal { //Event loop cx.spawn(|terminal, mut cx| async move { while let Some(event) = self.events_rx.next().await { terminal.update(&mut cx, |terminal, cx| { //Process the first event immediately for lowered latency terminal.process_event(&event, cx); })?; 'outer: loop { let mut events = Vec::new(); let mut timer = cx .background_executor() .timer(Duration::from_millis(4))Lines 1–500 of 2202. Load more
pub mod mappings; pub use alacritty_terminal; mod pty_info; pub mod terminal_settings; use alacritty_terminal::{ event::{Event as AlacTermEvent, EventListener, Notify, WindowSize}, event_loop::{EventLoop, Msg, Notifier}, grid::{Dimensions, Scroll as AlacScroll}, index::{Boundary, Column, Direction as AlacDirection, Line, Point as AlacPoint}, selection::{Selection, SelectionRange, SelectionType}, sync::FairMutex, term::{ cell::Cell, search::{Match, RegexIter, RegexSearch}, Config, RenderableCursor, TermMode, }, tty::{self}, vi_mode::{ViModeCursor, ViMotion}, vte::ansi::{ ClearMode, CursorStyle as AlacCursorStyle, Handler, NamedPrivateMode, PrivateMode, }, Term, }; use anyhow::{bail, Result}; use futures::{ channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}, FutureExt, }; use mappings::mouse::{ alt_scroll, grid_point, grid_point_and_side, mouse_button_report, mouse_moved_report, scroll_report, }; use collections::{HashMap, VecDeque}; use futures::StreamExt; use pty_info::PtyProcessInfo; use serde::{Deserialize, Serialize}; use settings::Settings; use smol::channel::{Receiver, Sender}; use task::{HideStrategy, Shell, TaskId}; use terminal_settings::{AlternateScroll, CursorShape, TerminalSettings}; use theme::{ActiveTheme, Theme}; use util::{paths::home_dir, truncate_and_trailoff}; use std::{ cmp::{self, min}, fmt::Display, ops::{Deref, Index, RangeInclusive}, path::PathBuf, sync::Arc, time::Duration, }; use thiserror::Error; use gpui::{ actions, black, px, AnyWindowHandle, App, AppContext as _, Bounds, ClipboardItem, Context, EventEmitter, Hsla, Keystroke, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, Rgba, ScrollWheelEvent, SharedString, Size, Task, TouchPhase, Window, }; use crate::mappings::{colors::to_alac_rgb, keys::to_esc_str}; actions!( terminal, [ Clear, Copy, Paste, ShowCharacterPalette, SearchTest, ScrollLineUp, ScrollLineDown, ScrollPageUp, ScrollPageDown, ScrollToTop, ScrollToBottom, ToggleViMode, ] ); ///Scrolling is unbearably sluggish by default. Alacritty supports a configurable ///Scroll multiplier that is set to 3 by default. This will be removed when I ///Implement scroll bars. #[cfg(target_os = "macos")] const SCROLL_MULTIPLIER: f32 = 4.; #[cfg(not(target_os = "macos"))] const SCROLL_MULTIPLIER: f32 = 1.; const MAX_SEARCH_LINES: usize = 100; const DEBUG_TERMINAL_WIDTH: Pixels = px(500.); const DEBUG_TERMINAL_HEIGHT: Pixels = px(30.); const DEBUG_CELL_WIDTH: Pixels = px(5.); const DEBUG_LINE_HEIGHT: Pixels = px(5.); ///Upward flowing events, for changing the title and such #[derive(Clone, Debug)] pub enum Event { TitleChanged, BreadcrumbsChanged, CloseTerminal, Bell, Wakeup, BlinkChanged(bool), SelectionsChanged, NewNavigationTarget(Option<MaybeNavigationTarget>), Open(MaybeNavigationTarget), } #[derive(Clone, Debug)] pub struct PathLikeTarget { /// File system path, absolute or relative, existing or not. /// Might have line and column number(s) attached as `file.rs:1:23` pub maybe_path: String, /// Current working directory of the terminal pub terminal_dir: Option<PathBuf>, } /// A string inside terminal, potentially useful as a URI that can be opened. #[derive(Clone, Debug)] pub enum MaybeNavigationTarget { /// HTTP, git, etc. string determined by the `URL_REGEX` regex. Url(String), /// File system path, absolute or relative, existing or not. /// Might have line and column number(s) attached as `file.rs:1:23` PathLike(PathLikeTarget), } #[derive(Clone)] enum InternalEvent { Resize(TerminalBounds), Clear, // FocusNextMatch, Scroll(AlacScroll), ScrollToAlacPoint(AlacPoint), SetSelection(Option<(Selection, AlacPoint)>), UpdateSelection(Point<Pixels>), // Adjusted mouse position, should open FindHyperlink(Point<Pixels>, bool), Copy, // Vi mode events ToggleViMode, ViMotion(ViMotion), } ///A translation struct for Alacritty to communicate with us from their event loop #[derive(Clone)] pub struct ZedListener(pub UnboundedSender<AlacTermEvent>); impl EventListener for ZedListener { fn send_event(&self, event: AlacTermEvent) { self.0.unbounded_send(event).ok(); } } pub fn init(cx: &mut App) { TerminalSettings::register(cx); } #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct TerminalBounds { pub cell_width: Pixels, pub line_height: Pixels, pub bounds: Bounds<Pixels>, } impl TerminalBounds { pub fn new(line_height: Pixels, cell_width: Pixels, bounds: Bounds<Pixels>) -> Self { TerminalBounds { cell_width, line_height, bounds, } } pub fn num_lines(&self) -> usize { (self.bounds.size.height / self.line_height).floor() as usize } pub fn num_columns(&self) -> usize { (self.bounds.size.width / self.cell_width).floor() as usize } pub fn height(&self) -> Pixels { self.bounds.size.height } pub fn width(&self) -> Pixels { self.bounds.size.width } pub fn cell_width(&self) -> Pixels { self.cell_width } pub fn line_height(&self) -> Pixels { self.line_height } } impl Default for TerminalBounds { fn default() -> Self { TerminalBounds::new( DEBUG_LINE_HEIGHT, DEBUG_CELL_WIDTH, Bounds { origin: Point::default(), size: Size { width: DEBUG_TERMINAL_WIDTH, height: DEBUG_TERMINAL_HEIGHT, }, }, ) } } impl From<TerminalBounds> for WindowSize { fn from(val: TerminalBounds) -> Self { WindowSize { num_lines: val.num_lines() as u16, num_cols: val.num_columns() as u16, cell_width: f32::from(val.cell_width()) as u16, cell_height: f32::from(val.line_height()) as u16, } } } impl Dimensions for TerminalBounds { /// Note: this is supposed to be for the back buffer's length, /// but we exclusively use it to resize the terminal, which does not /// use this method. We still have to implement it for the trait though, /// hence, this comment. fn total_lines(&self) -> usize { self.screen_lines() } fn screen_lines(&self) -> usize { self.num_lines() } fn columns(&self) -> usize { self.num_columns() } } #[derive(Error, Debug)] pub struct TerminalError { pub directory: Option<PathBuf>, pub shell: Shell, pub source: std::io::Error, } impl TerminalError { pub fn fmt_directory(&self) -> String { self.directory .clone() .map(|path| { match path .into_os_string() .into_string() .map_err(|os_str| format!("<non-utf8 path> {}", os_str.to_string_lossy())) { Ok(s) => s, Err(s) => s, } }) .unwrap_or_else(|| { let default_dir = dirs::home_dir().map(|buf| buf.into_os_string().to_string_lossy().to_string()); match default_dir { Some(dir) => format!("<none specified, using home directory> {}", dir), None => "<none specified, could not find home directory>".to_string(), } }) } pub fn fmt_shell(&self) -> String { match &self.shell { Shell::System => "<system defined shell>".to_string(), Shell::Program(s) => s.to_string(), Shell::WithArguments { program, args, title_override, } => { if let Some(title_override) = title_override { format!("{} {} ({})", program, args.join(" "), title_override) } else { format!("{} {}", program, args.join(" ")) } } } } } impl Display for TerminalError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let dir_string: String = self.fmt_directory(); let shell = self.fmt_shell(); write!( f, "Working directory: {} Shell command: `{}`, IOError: {}", dir_string, shell, self.source ) } } // https://github.com/alacritty/alacritty/blob/cb3a79dbf6472740daca8440d5166c1d4af5029e/extra/man/alacritty.5.scd?plain=1#L207-L213 const DEFAULT_SCROLL_HISTORY_LINES: usize = 10_000; const MAX_SCROLL_HISTORY_LINES: usize = 100_000; const URL_REGEX: &str = r#"(ipfs:|ipns:|magnet:|mailto:|gemini://|gopher://|https://|http://|news:|file://|git://|ssh:|ftp://)[^\u{0000}-\u{001F}\u{007F}-\u{009F}<>"\s{-}\^⟨⟩`]+"#; // Optional suffix matches MSBuild diagnostic suffixes for path parsing in PathLikeWithPosition // https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-diagnostic-format-for-tasks const WORD_REGEX: &str = r#"[\$\+\w.\[\]:/\\@\-~()]+(?:\((?:\d+|\d+,\d+)\))|[\$\+\w.\[\]:/\\@\-~()]+"#; pub struct TerminalBuilder { terminal: Terminal, events_rx: UnboundedReceiver<AlacTermEvent>, } impl TerminalBuilder { #[allow(clippy::too_many_arguments)] pub fn new( working_directory: Option<PathBuf>, python_venv_directory: Option<PathBuf>, task: Option<TaskState>, shell: Shell, mut env: HashMap<String, String>, cursor_shape: CursorShape, alternate_scroll: AlternateScroll, max_scroll_history_lines: Option<usize>, is_ssh_terminal: bool, window: AnyWindowHandle, completion_tx: Sender<()>, cx: &App, ) -> Result<TerminalBuilder> { // If the parent environment doesn't have a locale set // (As is the case when launched from a .app on MacOS), // and the Project doesn't have a locale set, then // set a fallback for our child environment to use. if std::env::var("LANG").is_err() { env.entry("LANG".to_string()) .or_insert_with(|| "en_US.UTF-8".to_string()); } env.insert("ZED_TERM".to_string(), "true".to_string()); env.insert("TERM_PROGRAM".to_string(), "zed".to_string()); env.insert("TERM".to_string(), "xterm-256color".to_string()); env.insert( "TERM_PROGRAM_VERSION".to_string(), release_channel::AppVersion::global(cx).to_string(), ); let mut terminal_title_override = None; let pty_options = { let alac_shell = match shell.clone() { Shell::System => None, Shell::Program(program) => { Some(alacritty_terminal::tty::Shell::new(program, Vec::new())) } Shell::WithArguments { program, args, title_override, } => { terminal_title_override = title_override; Some(alacritty_terminal::tty::Shell::new(program, args)) } }; alacritty_terminal::tty::Options { shell: alac_shell, working_directory: working_directory .clone() .or_else(|| Some(home_dir().to_path_buf())), drain_on_exit: true, env: env.into_iter().collect(), } }; // Setup Alacritty's env, which modifies the current process's environment alacritty_terminal::tty::setup_env(); let default_cursor_style = AlacCursorStyle::from(cursor_shape); let scrolling_history = if task.is_some() { // Tasks like `cargo build --all` may produce a lot of output, ergo allow maximum scrolling. // After the task finishes, we do not allow appending to that terminal, so small tasks output should not // cause excessive memory usage over time. MAX_SCROLL_HISTORY_LINES } else { max_scroll_history_lines .unwrap_or(DEFAULT_SCROLL_HISTORY_LINES) .min(MAX_SCROLL_HISTORY_LINES) }; let config = Config { scrolling_history, default_cursor_style, ..Config::default() }; //Spawn a task so the Alacritty EventLoop can communicate with us //TODO: Remove with a bounded sender which can be dispatched on &self let (events_tx, events_rx) = unbounded(); //Set up the terminal... let mut term = Term::new( config.clone(), &TerminalBounds::default(), ZedListener(events_tx.clone()), ); //Alacritty defaults to alternate scrolling being on, so we just need to turn it off. if let AlternateScroll::Off = alternate_scroll { term.unset_private_mode(PrivateMode::Named(NamedPrivateMode::AlternateScroll)); } let term = Arc::new(FairMutex::new(term)); //Setup the pty... let pty = match tty::new( &pty_options, TerminalBounds::default().into(), window.window_id().as_u64(), ) { Ok(pty) => pty, Err(error) => { bail!(TerminalError { directory: working_directory, shell, source: error, }); } }; let pty_info = PtyProcessInfo::new(&pty); //And connect them together let event_loop = EventLoop::new( term.clone(), ZedListener(events_tx.clone()), pty, pty_options.drain_on_exit, false, )?; //Kick things off let pty_tx = event_loop.channel(); let _io_thread = event_loop.spawn(); // DANGER let terminal = Terminal { task, pty_tx: Notifier(pty_tx), completion_tx, term, term_config: config, title_override: terminal_title_override, events: VecDeque::with_capacity(10), //Should never get this high. last_content: Default::default(), last_mouse: None, matches: Vec::new(), selection_head: None, pty_info, breadcrumb_text: String::new(), scroll_px: px(0.), next_link_id: 0, selection_phase: SelectionPhase::Ended, // hovered_word: false, url_regex: RegexSearch::new(URL_REGEX).unwrap(), word_regex: RegexSearch::new(WORD_REGEX).unwrap(), vi_mode_enabled: false, is_ssh_terminal, python_venv_directory, }; Ok(TerminalBuilder { terminal, events_rx, }) } pub fn subscribe(mut self, cx: &Context<Terminal>) -> Terminal { //Event loop cx.spawn(|terminal, mut cx| async move { while let Some(event) = self.events_rx.next().await { terminal.update(&mut cx, |terminal, cx| { //Process the first event immediately for lowered latency terminal.process_event(&event, cx); })?; 'outer: loop { let mut events = Vec::new(); let mut timer = cx .background_executor() .timer(Duration::from_millis(4))