diff --git a/crates/terminal/src/modal.rs b/crates/terminal/src/modal.rs index 1aea96277a..504a4b84ab 100644 --- a/crates/terminal/src/modal.rs +++ b/crates/terminal/src/modal.rs @@ -4,7 +4,7 @@ use workspace::Workspace; use crate::{ terminal_container_view::{ - get_working_directory, DeployModal, TerminalContainer, TerminalContent, + get_working_directory, DeployModal, TerminalContainer, TerminalContainerContent, }, Event, Terminal, }; @@ -42,7 +42,7 @@ pub fn deploy_modal(workspace: &mut Workspace, _: &DeployModal, cx: &mut ViewCon let this = cx.add_view(|cx| TerminalContainer::new(working_directory, true, cx)); - if let TerminalContent::Connected(connected) = &this.read(cx).content { + if let TerminalContainerContent::Connected(connected) = &this.read(cx).content { let terminal_handle = connected.read(cx).handle(); cx.subscribe(&terminal_handle, on_event).detach(); // Set the global immediately if terminal construction was successful, @@ -55,7 +55,8 @@ pub fn deploy_modal(workspace: &mut Workspace, _: &DeployModal, cx: &mut ViewCon this }) { // Terminal modal was dismissed. Store terminal if the terminal view is connected - if let TerminalContent::Connected(connected) = &closed_terminal_handle.read(cx).content + if let TerminalContainerContent::Connected(connected) = + &closed_terminal_handle.read(cx).content { let terminal_handle = connected.read(cx).handle(); // Set the global immediately if terminal construction was successful, diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 4adaa2140f..c375babf84 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -11,17 +11,19 @@ use alacritty_terminal::{ event_loop::{EventLoop, Msg, Notifier}, grid::{Dimensions, Scroll as AlacScroll}, index::{Column, Direction, Line, Point}, - selection::{Selection, SelectionType}, + selection::{Selection, SelectionRange, SelectionType}, sync::FairMutex, term::{ + cell::Cell, color::Rgb, search::{Match, RegexIter, RegexSearch}, - RenderableContent, TermMode, + RenderableCursor, TermMode, }, tty::{self, setup_env}, Term, }; use anyhow::{bail, Result}; + use futures::{ channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}, FutureExt, @@ -36,10 +38,10 @@ use settings::{AlternateScroll, Settings, Shell, TerminalBlink}; use std::{ collections::{HashMap, VecDeque}, fmt::Display, - ops::{RangeInclusive, Sub}, + ops::{Deref, RangeInclusive, Sub}, path::PathBuf, sync::Arc, - time::Duration, + time::{Duration, Instant}, }; use thiserror::Error; @@ -376,12 +378,12 @@ impl TerminalBuilder { events: VecDeque::with_capacity(10), //Should never get this high. title: shell_txt.clone(), default_title: shell_txt, - last_mode: TermMode::NONE, + last_content: Default::default(), cur_size: initial_size, last_mouse: None, - last_offset: 0, matches: Vec::new(), - selection_text: None, + last_synced: Instant::now(), + sync_task: None, }; Ok(TerminalBuilder { @@ -443,18 +445,61 @@ impl TerminalBuilder { } } +#[derive(Debug, Clone)] +struct IndexedCell { + point: Point, + cell: Cell, +} + +impl Deref for IndexedCell { + type Target = Cell; + + #[inline] + fn deref(&self) -> &Cell { + &self.cell + } +} + +#[derive(Clone)] +pub struct TerminalContent { + cells: Vec, + mode: TermMode, + display_offset: usize, + selection_text: Option, + selection: Option, + cursor: RenderableCursor, + cursor_char: char, +} + +impl Default for TerminalContent { + fn default() -> Self { + TerminalContent { + cells: Default::default(), + mode: Default::default(), + display_offset: Default::default(), + selection_text: Default::default(), + selection: Default::default(), + cursor: RenderableCursor { + shape: alacritty_terminal::ansi::CursorShape::Block, + point: Point::new(Line(0), Column(0)), + }, + cursor_char: Default::default(), + } + } +} + pub struct Terminal { pty_tx: Notifier, term: Arc>>, events: VecDeque, default_title: String, title: String, - cur_size: TerminalSize, - last_mode: TermMode, - last_offset: usize, last_mouse: Option<(Point, Direction)>, pub matches: Vec>, - pub selection_text: Option, + cur_size: TerminalSize, + last_content: TerminalContent, + last_synced: Instant, + sync_task: Option>, } impl Terminal { @@ -576,6 +621,10 @@ impl Terminal { } } + pub fn last_content(&self) -> &TerminalContent { + &self.last_content + } + fn begin_select(&mut self, sel: Selection) { self.events .push_back(InternalEvent::SetSelection(Some(sel))); @@ -648,7 +697,7 @@ impl Terminal { } pub fn try_keystroke(&mut self, keystroke: &Keystroke) -> bool { - let esc = to_esc_str(keystroke, &self.last_mode); + let esc = to_esc_str(keystroke, &self.last_content.mode); if let Some(esc) = esc { self.input(esc); true @@ -659,7 +708,7 @@ impl Terminal { ///Paste text into the terminal pub fn paste(&mut self, text: &str) { - let paste_text = if self.last_mode.contains(TermMode::BRACKETED_PASTE) { + let paste_text = if self.last_content.mode.contains(TermMode::BRACKETED_PASTE) { format!("{}{}{}", "\x1b[200~", text.replace('\x1b', ""), "\x1b[201~") } else { text.replace("\r\n", "\r").replace('\n', "\r") @@ -667,38 +716,76 @@ impl Terminal { self.input(paste_text) } - pub fn render_lock(&mut self, cx: &mut ModelContext, f: F) -> T - where - F: FnOnce(RenderableContent, char) -> T, - { + pub fn try_sync(&mut self, cx: &mut ModelContext) { let term = self.term.clone(); - let mut term = term.lock(); + + let mut terminal = if let Some(term) = term.try_lock_unfair() { + term + } else if self.last_synced.elapsed().as_secs_f32() > 0.25 { + term.lock_unfair() + } else if let None = self.sync_task { + //Skip this frame + let delay = cx.background().timer(Duration::from_millis(16)); + self.sync_task = Some(cx.spawn_weak(|weak_handle, mut cx| async move { + delay.await; + cx.update(|cx| { + if let Some(handle) = weak_handle.upgrade(cx) { + handle.update(cx, |terminal, cx| { + terminal.sync_task.take(); + cx.notify(); + }); + } + }); + })); + return; + } else { + //No lock and delayed rendering already scheduled, nothing to do + return; + }; //Note that this ordering matters for event processing while let Some(e) = self.events.pop_front() { - self.process_terminal_event(&e, &mut term, cx) + self.process_terminal_event(&e, &mut terminal, cx) } - self.last_mode = *term.mode(); + self.last_content = Self::make_content(&terminal); + self.last_synced = Instant::now(); + } + fn make_content(term: &Term) -> TerminalContent { let content = term.renderable_content(); - - self.selection_text = term.selection_to_string(); - self.last_offset = content.display_offset; - - let cursor_text = term.grid()[content.cursor.point].c; - - f(content, cursor_text) + TerminalContent { + cells: content + .display_iter + //TODO: Add this once there's a way to retain empty lines + // .filter(|ic| { + // !ic.flags.contains(Flags::HIDDEN) + // && !(ic.bg == Named(NamedColor::Background) + // && ic.c == ' ' + // && !ic.flags.contains(Flags::INVERSE)) + // }) + .map(|ic| IndexedCell { + point: ic.point, + cell: ic.cell.clone(), + }) + .collect::>(), + mode: content.mode, + display_offset: content.display_offset, + selection_text: term.selection_to_string(), + selection: content.selection, + cursor: content.cursor, + cursor_char: term.grid()[content.cursor.point].c, + } } pub fn focus_in(&self) { - if self.last_mode.contains(TermMode::FOCUS_IN_OUT) { + if self.last_content.mode.contains(TermMode::FOCUS_IN_OUT) { self.write_to_pty("\x1b[I".to_string()); } } pub fn focus_out(&self) { - if self.last_mode.contains(TermMode::FOCUS_IN_OUT) { + if self.last_content.mode.contains(TermMode::FOCUS_IN_OUT) { self.write_to_pty("\x1b[O".to_string()); } } @@ -721,17 +808,17 @@ impl Terminal { } pub fn mouse_mode(&self, shift: bool) -> bool { - self.last_mode.intersects(TermMode::MOUSE_MODE) && !shift + self.last_content.mode.intersects(TermMode::MOUSE_MODE) && !shift } pub fn mouse_move(&mut self, e: &MouseMovedEvent, origin: Vector2F) { let position = e.position.sub(origin); - let point = mouse_point(position, self.cur_size, self.last_offset); + let point = mouse_point(position, self.cur_size, self.last_content.display_offset); let side = mouse_side(position, self.cur_size); if self.mouse_changed(point, side) && self.mouse_mode(e.shift) { - if let Some(bytes) = mouse_moved_report(point, e, self.last_mode) { + if let Some(bytes) = mouse_moved_report(point, e, self.last_content.mode) { self.pty_tx.notify(bytes); } } @@ -746,7 +833,7 @@ impl Terminal { self.continue_selection(position); // Doesn't make sense to scroll the alt screen - if !self.last_mode.contains(TermMode::ALT_SCREEN) { + if !self.last_content.mode.contains(TermMode::ALT_SCREEN) { let scroll_delta = match self.drag_line_delta(e) { Some(value) => value, None => return, @@ -775,11 +862,11 @@ impl Terminal { pub fn mouse_down(&mut self, e: &DownRegionEvent, origin: Vector2F) { let position = e.position.sub(origin); - let point = mouse_point(position, self.cur_size, self.last_offset); + let point = mouse_point(position, self.cur_size, self.last_content.display_offset); let side = mouse_side(position, self.cur_size); if self.mouse_mode(e.shift) { - if let Some(bytes) = mouse_button_report(point, e, true, self.last_mode) { + if let Some(bytes) = mouse_button_report(point, e, true, self.last_content.mode) { self.pty_tx.notify(bytes); } } else if e.button == MouseButton::Left { @@ -791,7 +878,7 @@ impl Terminal { let position = e.position.sub(origin); if !self.mouse_mode(e.shift) { - let point = mouse_point(position, self.cur_size, self.last_offset); + let point = mouse_point(position, self.cur_size, self.last_content.display_offset); let side = mouse_side(position, self.cur_size); let selection_type = match e.click_count { @@ -814,9 +901,9 @@ impl Terminal { pub fn mouse_up(&mut self, e: &UpRegionEvent, origin: Vector2F) { let position = e.position.sub(origin); if self.mouse_mode(e.shift) { - let point = mouse_point(position, self.cur_size, self.last_offset); + let point = mouse_point(position, self.cur_size, self.last_content.display_offset); - if let Some(bytes) = mouse_button_report(point, e, false, self.last_mode) { + if let Some(bytes) = mouse_button_report(point, e, false, self.last_content.mode) { self.pty_tx.notify(bytes); } } else if e.button == MouseButton::Left { @@ -835,15 +922,22 @@ impl Terminal { //The scroll enters 'TouchPhase::Started'. Do I need to replicate this? //This would be consistent with a scroll model based on 'distance from origin'... let scroll_lines = (e.delta.y() / self.cur_size.line_height) as i32; - let point = mouse_point(e.position.sub(origin), self.cur_size, self.last_offset); + let point = mouse_point( + e.position.sub(origin), + self.cur_size, + self.last_content.display_offset, + ); - if let Some(scrolls) = scroll_report(point, scroll_lines as i32, e, self.last_mode) { + if let Some(scrolls) = + scroll_report(point, scroll_lines as i32, e, self.last_content.mode) + { for scroll in scrolls { self.pty_tx.notify(scroll); } }; } else if self - .last_mode + .last_content + .mode .contains(TermMode::ALT_SCREEN | TermMode::ALTERNATE_SCROLL) && !e.shift { @@ -868,7 +962,6 @@ impl Terminal { cx: &mut ModelContext, ) -> Task>> { let term = self.term.clone(); - dbg!("Spawning find_matches"); cx.background().spawn(async move { let searcher = match query { project::search::SearchQuery::Text { query, .. } => { @@ -885,7 +978,8 @@ impl Terminal { let searcher = searcher.unwrap(); let term = term.lock(); - dbg!(make_search_matches(&term, &searcher).collect()) + + make_search_matches(&term, &searcher).collect() }) } } diff --git a/crates/terminal/src/terminal_container_view.rs b/crates/terminal/src/terminal_container_view.rs index d93dfe4a78..204ff24112 100644 --- a/crates/terminal/src/terminal_container_view.rs +++ b/crates/terminal/src/terminal_container_view.rs @@ -29,12 +29,12 @@ pub fn init(cx: &mut MutableAppContext) { //Take away all the result unwrapping in the current TerminalView by making it 'infallible' //Bubble up to deploy(_modal)() calls -pub enum TerminalContent { +pub enum TerminalContainerContent { Connected(ViewHandle), Error(ViewHandle), } -impl TerminalContent { +impl TerminalContainerContent { fn handle(&self) -> AnyViewHandle { match self { Self::Connected(handle) => handle.into(), @@ -45,7 +45,7 @@ impl TerminalContent { pub struct TerminalContainer { modal: bool, - pub content: TerminalContent, + pub content: TerminalContainerContent, associated_directory: Option, } @@ -119,13 +119,13 @@ impl TerminalContainer { let view = cx.add_view(|cx| TerminalView::from_terminal(terminal, modal, cx)); cx.subscribe(&view, |_this, _content, event, cx| cx.emit(*event)) .detach(); - TerminalContent::Connected(view) + TerminalContainerContent::Connected(view) } Err(error) => { let view = cx.add_view(|_| ErrorView { error: error.downcast::().unwrap(), }); - TerminalContent::Error(view) + TerminalContainerContent::Error(view) } }; cx.focus(content.handle()); @@ -145,7 +145,7 @@ impl TerminalContainer { let connected_view = cx.add_view(|cx| TerminalView::from_terminal(terminal, modal, cx)); TerminalContainer { modal, - content: TerminalContent::Connected(connected_view), + content: TerminalContainerContent::Connected(connected_view), associated_directory: None, } } @@ -158,8 +158,8 @@ impl View for TerminalContainer { fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox { let child_view = match &self.content { - TerminalContent::Connected(connected) => ChildView::new(connected), - TerminalContent::Error(error) => ChildView::new(error), + TerminalContainerContent::Connected(connected) => ChildView::new(connected), + TerminalContainerContent::Error(error) => ChildView::new(error), }; if self.modal { let settings = cx.global::(); @@ -238,10 +238,10 @@ impl Item for TerminalContainer { cx: &gpui::AppContext, ) -> ElementBox { let title = match &self.content { - TerminalContent::Connected(connected) => { + TerminalContainerContent::Connected(connected) => { connected.read(cx).handle().read(cx).title.to_string() } - TerminalContent::Error(_) => "Terminal".to_string(), + TerminalContainerContent::Error(_) => "Terminal".to_string(), }; Flex::row() @@ -309,7 +309,7 @@ impl Item for TerminalContainer { } fn is_dirty(&self, cx: &gpui::AppContext) -> bool { - if let TerminalContent::Connected(connected) = &self.content { + if let TerminalContainerContent::Connected(connected) = &self.content { connected.read(cx).has_new_content() } else { false @@ -317,7 +317,7 @@ impl Item for TerminalContainer { } fn has_conflict(&self, cx: &AppContext) -> bool { - if let TerminalContent::Connected(connected) = &self.content { + if let TerminalContainerContent::Connected(connected) = &self.content { connected.read(cx).has_bell() } else { false @@ -351,7 +351,7 @@ impl SearchableItem for TerminalContainer { /// Clear stored matches fn clear_matches(&mut self, cx: &mut ViewContext) { - if let TerminalContent::Connected(connected) = &self.content { + if let TerminalContainerContent::Connected(connected) = &self.content { let terminal = connected.read(cx).terminal().clone(); terminal.update(cx, |term, _| term.matches.clear()) } @@ -359,18 +359,22 @@ impl SearchableItem for TerminalContainer { /// Store matches returned from find_matches somewhere for rendering fn update_matches(&mut self, matches: Vec, cx: &mut ViewContext) { - if let TerminalContent::Connected(connected) = &self.content { + if let TerminalContainerContent::Connected(connected) = &self.content { let terminal = connected.read(cx).terminal().clone(); - dbg!(&matches); terminal.update(cx, |term, _| term.matches = matches) } } /// Return the selection content to pre-load into this search fn query_suggestion(&mut self, cx: &mut ViewContext) -> String { - if let TerminalContent::Connected(connected) = &self.content { + if let TerminalContainerContent::Connected(connected) = &self.content { let terminal = connected.read(cx).terminal().clone(); - terminal.read(cx).selection_text.clone().unwrap_or_default() + terminal + .read(cx) + .last_content + .selection_text + .clone() + .unwrap_or_default() } else { Default::default() } @@ -403,7 +407,7 @@ impl SearchableItem for TerminalContainer { query: project::search::SearchQuery, cx: &mut ViewContext, ) -> Task> { - if let TerminalContent::Connected(connected) = &self.content { + if let TerminalContainerContent::Connected(connected) = &self.content { let terminal = connected.read(cx).terminal().clone(); terminal.update(cx, |term, cx| term.find_matches(query, cx)) } else { diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index 828ae20ab6..2f3d06e2b7 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -2,10 +2,7 @@ use alacritty_terminal::{ ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape, NamedColor}, grid::Dimensions, index::Point, - term::{ - cell::{Cell, Flags}, - TermMode, - }, + term::{cell::Flags, TermMode}, }; use editor::{Cursor, CursorShape, HighlightedRange, HighlightedRangeLine}; use gpui::{ @@ -27,15 +24,12 @@ use theme::TerminalStyle; use util::ResultExt; use std::{fmt::Debug, ops::RangeInclusive}; -use std::{ - mem, - ops::{Deref, Range}, -}; +use std::{mem, ops::Range}; use crate::{ mappings::colors::convert_color, terminal_view::{DeployContextMenu, TerminalView}, - Terminal, TerminalSize, + IndexedCell, Terminal, TerminalContent, TerminalSize, }; ///The information generated during layout that is nescessary for painting @@ -50,21 +44,6 @@ pub struct LayoutState { display_offset: usize, } -#[derive(Debug)] -struct IndexedCell { - point: Point, - cell: Cell, -} - -impl Deref for IndexedCell { - type Target = Cell; - - #[inline] - fn deref(&self) -> &Cell { - &self.cell - } -} - ///Helper struct for converting data between alacritty's cursor points, and displayed cursor points struct DisplayCursor { line: i32, @@ -195,7 +174,7 @@ impl TerminalElement { //Vec> -> Clip out the parts of the ranges fn layout_grid( - grid: Vec, + grid: &Vec, text_style: &TextStyle, terminal_theme: &TerminalStyle, text_layout_cache: &TextLayoutCache, @@ -581,40 +560,22 @@ impl Element for TerminalElement { } else { terminal_theme.colors.background }; + let terminal_handle = self.terminal.upgrade(cx).unwrap(); - let (cells, selection, cursor, display_offset, cursor_text, mode) = self - .terminal - .upgrade(cx) - .unwrap() - .update(cx.app, |terminal, cx| { - terminal.set_size(dimensions); - terminal.render_lock(cx, |content, cursor_text| { - let mut cells = vec![]; - cells.extend( - content - .display_iter - //TODO: Add this once there's a way to retain empty lines - // .filter(|ic| { - // !ic.flags.contains(Flags::HIDDEN) - // && !(ic.bg == Named(NamedColor::Background) - // && ic.c == ' ' - // && !ic.flags.contains(Flags::INVERSE)) - // }) - .map(|ic| IndexedCell { - point: ic.point, - cell: ic.cell.clone(), - }), - ); - ( - cells, - content.selection, - content.cursor, - content.display_offset, - cursor_text, - content.mode, - ) - }) - }); + terminal_handle.update(cx.app, |terminal, cx| { + terminal.set_size(dimensions); + terminal.try_sync(cx) + }); + + let TerminalContent { + cells, + mode, + display_offset, + cursor_char, + selection, + cursor, + .. + } = &terminal_handle.read(cx).last_content; // searches, highlights to a single range representations let mut relative_highlighted_ranges = Vec::new(); @@ -641,9 +602,9 @@ impl Element for TerminalElement { let cursor = if let AlacCursorShape::Hidden = cursor.shape { None } else { - let cursor_point = DisplayCursor::from(cursor.point, display_offset); + let cursor_point = DisplayCursor::from(cursor.point, *display_offset); let cursor_text = { - let str_trxt = cursor_text.to_string(); + let str_trxt = cursor_char.to_string(); let color = if self.focused { terminal_theme.colors.background @@ -699,8 +660,8 @@ impl Element for TerminalElement { size: dimensions, rects, relative_highlighted_ranges, - mode, - display_offset, + mode: *mode, + display_offset: *display_offset, }, ) } diff --git a/crates/terminal/src/terminal_view.rs b/crates/terminal/src/terminal_view.rs index fee84dc859..1c49f8b3c2 100644 --- a/crates/terminal/src/terminal_view.rs +++ b/crates/terminal/src/terminal_view.rs @@ -149,7 +149,8 @@ impl TerminalView { if !self .terminal .read(cx) - .last_mode + .last_content + .mode .contains(TermMode::ALT_SCREEN) { cx.show_character_palette(); @@ -177,7 +178,8 @@ impl TerminalView { || self .terminal .read(cx) - .last_mode + .last_content + .mode .contains(TermMode::ALT_SCREEN) { return true; @@ -362,7 +364,8 @@ impl View for TerminalView { if self .terminal .read(cx) - .last_mode + .last_content + .mode .contains(TermMode::ALT_SCREEN) { None @@ -387,7 +390,7 @@ impl View for TerminalView { if self.modal { context.set.insert("ModalTerminal".into()); } - let mode = self.terminal.read(cx).last_mode; + let mode = self.terminal.read(cx).last_content.mode; context.map.insert( "screen".to_string(), (if mode.contains(TermMode::ALT_SCREEN) { diff --git a/styles/package-lock.json b/styles/package-lock.json index 582f1c8496..5499f1852c 100644 --- a/styles/package-lock.json +++ b/styles/package-lock.json @@ -5,7 +5,6 @@ "requires": true, "packages": { "": { - "name": "styles", "version": "1.0.0", "license": "ISC", "dependencies": {