@@ -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<IndexedCell>,
+ mode: TermMode,
+ display_offset: usize,
+ selection_text: Option<String>,
+ selection: Option<SelectionRange>,
+ 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<FairMutex<Term<ZedListener>>>,
events: VecDeque<InternalEvent>,
default_title: String,
title: String,
- cur_size: TerminalSize,
- last_mode: TermMode,
- last_offset: usize,
last_mouse: Option<(Point, Direction)>,
pub matches: Vec<RangeInclusive<Point>>,
- pub selection_text: Option<String>,
+ cur_size: TerminalSize,
+ last_content: TerminalContent,
+ last_synced: Instant,
+ sync_task: Option<Task<()>>,
}
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<F, T>(&mut self, cx: &mut ModelContext<Self>, f: F) -> T
- where
- F: FnOnce(RenderableContent, char) -> T,
- {
+ pub fn try_sync(&mut self, cx: &mut ModelContext<Self>) {
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<ZedListener>) -> 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::<Vec<IndexedCell>>(),
+ 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);
-
- if let Some(scrolls) = scroll_report(point, scroll_lines as i32, e, self.last_mode) {
+ 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_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<Self>,
) -> Task<Vec<RangeInclusive<Point>>> {
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()
})
}
}
@@ -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<TerminalView>),
Error(ViewHandle<ErrorView>),
}
-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<PathBuf>,
}
@@ -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::<TerminalError>().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::<Settings>();
@@ -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<Self>) {
- 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<Self::Match>, cx: &mut ViewContext<Self>) {
- 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<Self>) -> 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<Self>,
) -> Task<Vec<Self::Match>> {
- 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 {
@@ -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<Range<Point>> -> Clip out the parts of the ranges
fn layout_grid(
- grid: Vec<IndexedCell>,
+ grid: &Vec<IndexedCell>,
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,
},
)
}