diff --git a/.swp b/.swp new file mode 100644 index 0000000000000000000000000000000000000000..5f9940d5044d36923e5abbe428dace63080fbef8 Binary files /dev/null and b/.swp differ diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 8da4b2a9cafe6acbfe99a51b4bce79346dc125cc..c4e98d90943cb3b7d5daf758ee3eda4bb394a881 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -356,16 +356,16 @@ { "context": "Terminal", "bindings": { - "ctrl-c": "terminal::SIGINT", - "escape": "terminal::ESCAPE", + "ctrl-c": "terminal::Sigint", + "escape": "terminal::Escape", "ctrl-d": "terminal::Quit", - "backspace": "terminal::DEL", - "enter": "terminal::RETURN", - "left": "terminal::LEFT", - "right": "terminal::RIGHT", - "up": "terminal::UP", - "down": "terminal::DOWN", - "tab": "terminal::TAB", + "backspace": "terminal::Del", + "enter": "terminal::Return", + "left": "terminal::Left", + "right": "terminal::Right", + "up": "terminal::Up", + "down": "terminal::Down", + "tab": "terminal::Tab", "cmd-v": "terminal::Paste" } } diff --git a/crates/terminal/Cargo.toml b/crates/terminal/Cargo.toml index 3cb4d631f3a4ef827e7157b51c6beece8e483093..175c741421351de0bf2557763e9c7ad7ca298eb8 100644 --- a/crates/terminal/Cargo.toml +++ b/crates/terminal/Cargo.toml @@ -20,3 +20,6 @@ smallvec = { version = "1.6", features = ["union"] } mio-extras = "2.0.6" futures = "0.3" ordered-float = "2.1.1" + +[dev-dependencies] +gpui = { path = "../gpui", features = ["test-support"] } diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 8fc01f2f9a135b50bc5f96ade3a57ff917b773d4..9adcb3122ef4d2a4fe5b1b8ba1166c35ae2f648b 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -22,7 +22,7 @@ use smallvec::SmallVec; use std::sync::Arc; use workspace::{Item, Workspace}; -use crate::element::TerminalEl; +use crate::terminal_element::TerminalEl; //ASCII Control characters on a keyboard //Consts -> Structs -> Impls -> Functions, Vaguely in order of importance @@ -37,16 +37,19 @@ const UP_SEQ: &str = "\x1b[A"; const DOWN_SEQ: &str = "\x1b[B"; const DEFAULT_TITLE: &str = "Terminal"; -pub mod element; +pub mod terminal_element; #[derive(Clone, Default, Debug, PartialEq, Eq)] pub struct Input(pub String); +#[derive(Clone, Debug, PartialEq)] +pub struct ScrollTerminal(pub i32); + actions!( terminal, - [SIGINT, ESCAPE, DEL, RETURN, LEFT, RIGHT, UP, DOWN, TAB, Clear, Paste, Deploy, Quit] + [Sigint, Escape, Del, Return, Left, Right, Up, Down, Tab, Clear, Paste, Deploy, Quit] ); -impl_internal_actions!(terminal, [Input]); +impl_internal_actions!(terminal, [Input, ScrollTerminal]); pub fn init(cx: &mut MutableAppContext) { cx.add_action(Terminal::deploy); @@ -62,6 +65,7 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(Terminal::down); cx.add_action(Terminal::tab); cx.add_action(Terminal::paste); + cx.add_action(Terminal::scroll_terminal); } #[derive(Clone)] @@ -74,15 +78,16 @@ impl EventListener for ZedListener { } ///A terminal renderer. -struct Terminal { +pub struct Terminal { pty_tx: Notifier, term: Arc>>, title: String, has_new_content: bool, has_bell: bool, //Currently using iTerm bell, show bell emoji in tab until input is received + cur_size: SizeInfo, } -enum Event { +pub enum Event { TitleChanged, CloseTerminal, Activate, @@ -124,7 +129,7 @@ impl Terminal { }; //TODO figure out how to derive this better - let size_info = SizeInfo::new(400., 100.0, 5., 5., 0., 0., false); + let size_info = SizeInfo::new(200., 100.0, 5., 5., 0., 0., false); //Set up the terminal... let term = Term::new(&config, size_info, ZedListener(events_tx.clone())); @@ -151,6 +156,7 @@ impl Terminal { pty_tx, has_new_content: false, has_bell: false, + cur_size: size_info, } } @@ -214,6 +220,18 @@ impl Terminal { } } + fn set_size(&mut self, new_size: SizeInfo) { + if new_size != self.cur_size { + self.pty_tx.0.send(Msg::Resize(new_size)).ok(); + self.term.lock().resize(new_size); + self.cur_size = new_size; + } + } + + fn scroll_terminal(&mut self, scroll: &ScrollTerminal, _: &mut ViewContext) { + self.term.lock().scroll_display(Scroll::Delta(scroll.0)); + } + ///Create a new Terminal fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext) { workspace.add_item(Box::new(cx.add_view(|cx| Terminal::new(cx))), cx); @@ -242,39 +260,39 @@ impl Terminal { self.pty_tx.notify(input.0.clone().into_bytes()); } - fn up(&mut self, _: &UP, cx: &mut ViewContext) { + fn up(&mut self, _: &Up, cx: &mut ViewContext) { self.write_to_pty(&Input(UP_SEQ.to_string()), cx); } - fn down(&mut self, _: &DOWN, cx: &mut ViewContext) { + fn down(&mut self, _: &Down, cx: &mut ViewContext) { self.write_to_pty(&Input(DOWN_SEQ.to_string()), cx); } - fn tab(&mut self, _: &TAB, cx: &mut ViewContext) { + fn tab(&mut self, _: &Tab, cx: &mut ViewContext) { self.write_to_pty(&Input(TAB_CHAR.to_string()), cx); } - fn send_sigint(&mut self, _: &SIGINT, cx: &mut ViewContext) { + fn send_sigint(&mut self, _: &Sigint, cx: &mut ViewContext) { self.write_to_pty(&Input(ETX_CHAR.to_string()), cx); } - fn escape(&mut self, _: &ESCAPE, cx: &mut ViewContext) { + fn escape(&mut self, _: &Escape, cx: &mut ViewContext) { self.write_to_pty(&Input(ESC_CHAR.to_string()), cx); } - fn del(&mut self, _: &DEL, cx: &mut ViewContext) { + fn del(&mut self, _: &Del, cx: &mut ViewContext) { self.write_to_pty(&Input(DEL_CHAR.to_string()), cx); } - fn carriage_return(&mut self, _: &RETURN, cx: &mut ViewContext) { + fn carriage_return(&mut self, _: &Return, cx: &mut ViewContext) { self.write_to_pty(&Input(CARRIAGE_RETURN_CHAR.to_string()), cx); } - fn left(&mut self, _: &LEFT, cx: &mut ViewContext) { + fn left(&mut self, _: &Left, cx: &mut ViewContext) { self.write_to_pty(&Input(LEFT_SEQ.to_string()), cx); } - fn right(&mut self, _: &RIGHT, cx: &mut ViewContext) { + fn right(&mut self, _: &Right, cx: &mut ViewContext) { self.write_to_pty(&Input(RIGHT_SEQ.to_string()), cx); } } @@ -291,20 +309,10 @@ impl View for Terminal { } fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox { - let _theme = cx.global::().theme.clone(); - - //TODO: derive this - let size_info = SizeInfo::new(400., 100.0, 5., 5., 0., 0., false); - - TerminalEl::new( - self.term.clone(), - self.pty_tx.0.clone(), - size_info, - cx.view_id(), - ) - .contained() - // .with_style(theme.terminal.container) - .boxed() + TerminalEl::new(cx.handle()) + .contained() + // .with_style(theme.terminal.container) + .boxed() } fn on_focus(&mut self, cx: &mut ViewContext) { @@ -404,3 +412,32 @@ impl Item for Terminal { matches!(event, &Event::Activate) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::terminal_element::build_chunks; + use gpui::TestAppContext; + + #[gpui::test] + async fn test_terminal(cx: &mut TestAppContext) { + let terminal = cx.add_view(Default::default(), |cx| Terminal::new(cx)); + + terminal.update(cx, |terminal, cx| { + terminal.write_to_pty(&Input(("expr 3 + 4".to_string()).to_string()), cx); + terminal.carriage_return(&Return, cx); + }); + + terminal + .condition(cx, |terminal, _cx| { + let term = terminal.term.clone(); + let (chunks, _) = build_chunks( + term.lock().renderable_content().display_iter, + &Default::default(), + ); + let content = chunks.iter().map(|e| e.0.trim()).collect::(); + content.contains("7") + }) + .await; + } +} diff --git a/crates/terminal/src/element.rs b/crates/terminal/src/terminal_element.rs similarity index 67% rename from crates/terminal/src/element.rs rename to crates/terminal/src/terminal_element.rs index 08d49ec6d8689e89252beb0c2e80581cea2bbfd9..e5149d6cb56845d0350fda940d68ed9824bf51eb 100644 --- a/crates/terminal/src/element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -1,14 +1,11 @@ use alacritty_terminal::{ ansi::Color as AnsiColor, - event_loop::Msg, - grid::{Indexed, Scroll}, + grid::{GridIterator, Indexed}, index::Point, - sync::FairMutex, term::{ cell::{Cell, Flags}, SizeInfo, }, - Term, }; use gpui::{ color::Color, @@ -17,38 +14,28 @@ use gpui::{ geometry::{rect::RectF, vector::vec2f}, json::json, text_layout::Line, - Event, MouseRegion, Quad, + Event, MouseRegion, PaintContext, Quad, WeakViewHandle, }; -use mio_extras::channel::Sender; use ordered_float::OrderedFloat; use settings::Settings; -use std::{rc::Rc, sync::Arc}; +use std::rc::Rc; use theme::TerminalStyle; -use crate::{Input, ZedListener}; +use crate::{Input, ScrollTerminal, Terminal}; const ALACRITTY_SCROLL_MULTIPLIER: f32 = 3.; +const MAGIC_VISUAL_WIDTH_MULTIPLIER: f32 = 1.28; //1/8th + .003 so we bias long instead of short + +#[cfg(debug_assertions)] +const DEBUG_GRID: bool = false; pub struct TerminalEl { - term: Arc>>, - pty_tx: Sender, - size: SizeInfo, - view_id: usize, + view: WeakViewHandle, } impl TerminalEl { - pub fn new( - term: Arc>>, - pty_tx: Sender, - size: SizeInfo, - view_id: usize, - ) -> TerminalEl { - TerminalEl { - term, - pty_tx, - size, - view_id, - } + pub fn new(view: WeakViewHandle) -> TerminalEl { + TerminalEl { view } } } @@ -57,6 +44,7 @@ pub struct LayoutState { line_height: f32, em_width: f32, cursor: Option, + cur_size: SizeInfo, } impl Element for TerminalEl { @@ -68,88 +56,56 @@ impl Element for TerminalEl { constraint: gpui::SizeConstraint, cx: &mut gpui::LayoutContext, ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) { + let view = self.view.upgrade(cx).unwrap(); let size = constraint.max; let settings = cx.global::(); let editor_theme = &settings.theme.editor; - let terminal_theme = &settings.theme.terminal; - //Get terminal - let mut term = self.term.lock(); //Set up text rendering - let font_cache = cx.font_cache(); - - let font_family_id = settings.buffer_font_family; - let font_family_name = cx.font_cache().family_name(font_family_id).unwrap(); - let font_properties = Default::default(); - let font_id = font_cache - .select_font(font_family_id, &font_properties) - .unwrap(); - let font_size = settings.buffer_font_size; - let text_style = TextStyle { color: editor_theme.text_color, font_family_id: settings.buffer_font_family, - font_family_name, - font_id, - font_size, + font_family_name: cx + .font_cache() + .family_name(settings.buffer_font_family) + .unwrap(), + font_id: cx + .font_cache() + .select_font(settings.buffer_font_family, &Default::default()) + .unwrap(), + font_size: settings.buffer_font_size, font_properties: Default::default(), underline: Default::default(), }; let line_height = cx.font_cache.line_height(text_style.font_size); - let em_width = cx + let cell_width = cx .font_cache() .em_width(text_style.font_id, text_style.font_size) - + 2.; - - //Resize terminal - let new_size = SizeInfo::new(size.x(), size.y(), em_width, line_height, 0., 0., false); - if !new_size.eq(&self.size) { - self.pty_tx.send(Msg::Resize(new_size)).ok(); - term.resize(new_size); - self.size = new_size; - } - - //Start rendering - let content = term.renderable_content(); - - let mut lines: Vec<(String, Option)> = vec![]; - let mut last_line = 0; - let mut line_count = 1; - let mut cur_chunk = String::new(); - - let mut cur_highlight = HighlightStyle { - color: Some(Color::white()), - ..Default::default() - }; - - for cell in content.display_iter { - let Indexed { - point: Point { line, .. }, - cell: Cell { - c, fg, flags, .. // TODO: Add bg and flags - }, //TODO: Learn what 'CellExtra does' - } = cell; - - let new_highlight = make_style_from_cell(fg, flags, &terminal_theme); + * MAGIC_VISUAL_WIDTH_MULTIPLIER; + + let new_size = SizeInfo::new( + size.x() - cell_width, //Padding. Really should make this more explicit + size.y(), + cell_width, + line_height, + 0., + 0., + false, + ); + view.update(cx.app, |view, _cx| { + view.set_size(new_size); + }); - if line != last_line { - line_count += 1; - cur_chunk.push('\n'); - last_line = line.0; - } + let settings = cx.global::(); + let terminal_theme = &settings.theme.terminal; + let term = view.read(cx).term.lock(); - if new_highlight != cur_highlight { - lines.push((cur_chunk.clone(), Some(cur_highlight.clone()))); - cur_chunk.clear(); - cur_highlight = new_highlight; - } - cur_chunk.push(*c) - } - lines.push((cur_chunk, Some(cur_highlight))); + let content = term.renderable_content(); + let (chunks, line_count) = build_chunks(content.display_iter, &terminal_theme); let shaped_lines = layout_highlighted_chunks( - lines.iter().map(|(text, style)| (text.as_str(), *style)), + chunks.iter().map(|(text, style)| (text.as_str(), *style)), &text_style, cx.text_layout_cache, &cx.font_cache, @@ -167,7 +123,7 @@ impl Element for TerminalEl { let cursor_x = layout_line.x_for_index(content.cursor.point.column.0); cursor = Some(RectF::new( vec2f(cursor_x, cursor_line as f32 * line_height), - vec2f(em_width, line_height), + vec2f(cell_width, line_height), )); } @@ -176,8 +132,9 @@ impl Element for TerminalEl { LayoutState { lines: shaped_lines, line_height, - em_width, + em_width: cell_width, cursor, + cur_size: new_size, }, ) } @@ -192,7 +149,7 @@ impl Element for TerminalEl { cx.scene.push_layer(Some(visible_bounds)); cx.scene.push_mouse_region(MouseRegion { - view_id: self.view_id, + view_id: self.view.id(), discriminant: None, bounds: visible_bounds, hover: None, @@ -205,7 +162,7 @@ impl Element for TerminalEl { right_mouse_down_out: None, }); - let origin = bounds.origin() + vec2f(layout.em_width, 0.); + let origin = bounds.origin() + vec2f(layout.em_width, 0.); //Padding let mut line_origin = origin; for line in &layout.lines { @@ -229,6 +186,11 @@ impl Element for TerminalEl { }); } + #[cfg(debug_assertions)] + if DEBUG_GRID { + draw_debug_grid(bounds, layout, cx); + } + cx.scene.pop_layer(); } @@ -248,8 +210,7 @@ impl Element for TerminalEl { if visible_bounds.contains_point(*position) { let vertical_scroll = (delta.y() / layout.line_height) * ALACRITTY_SCROLL_MULTIPLIER; - let scroll = Scroll::Delta(vertical_scroll.round() as i32); - self.term.lock().scroll_display(scroll); + cx.dispatch_action(ScrollTerminal(vertical_scroll.round() as i32)); true } else { false @@ -282,6 +243,47 @@ impl Element for TerminalEl { } } +pub(crate) fn build_chunks( + grid_iterator: GridIterator, + theme: &TerminalStyle, +) -> (Vec<(String, Option)>, usize) { + let mut lines: Vec<(String, Option)> = vec![]; + let mut last_line = 0; + let mut line_count = 1; + let mut cur_chunk = String::new(); + + let mut cur_highlight = HighlightStyle { + color: Some(Color::white()), + ..Default::default() + }; + + for cell in grid_iterator { + let Indexed { + point: Point { line, .. }, + cell: Cell { + c, fg, flags, .. // TODO: Add bg and flags + }, //TODO: Learn what 'CellExtra does' + } = cell; + + let new_highlight = make_style_from_cell(fg, flags, theme); + + if line != last_line { + line_count += 1; + cur_chunk.push('\n'); + last_line = line.0; + } + + if new_highlight != cur_highlight { + lines.push((cur_chunk.clone(), Some(cur_highlight.clone()))); + cur_chunk.clear(); + cur_highlight = new_highlight; + } + cur_chunk.push(*c) + } + lines.push((cur_chunk, Some(cur_highlight))); + (lines, line_count) +} + fn make_style_from_cell(fg: &AnsiColor, flags: &Flags, style: &TerminalStyle) -> HighlightStyle { let fg = Some(alac_color_to_gpui_color(fg, style)); let underline = if flags.contains(Flags::UNDERLINE) { @@ -337,3 +339,32 @@ fn alac_color_to_gpui_color(allac_color: &AnsiColor, style: &TerminalStyle) -> C alacritty_terminal::ansi::Color::Indexed(_) => Color::white(), //Color cube weirdness } } + +#[cfg(debug_assertions)] +fn draw_debug_grid(bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContext) { + let width = layout.cur_size.width(); + let height = layout.cur_size.height(); + //Alacritty uses 'as usize', so shall we. + for col in 0..(width / layout.em_width).round() as usize { + cx.scene.push_quad(Quad { + bounds: RectF::new( + bounds.origin() + vec2f((col + 1) as f32 * layout.em_width, 0.), + vec2f(1., height), + ), + background: Some(Color::green()), + border: Default::default(), + corner_radius: 0., + }); + } + for row in 0..((height / layout.line_height) + 1.0).round() as usize { + cx.scene.push_quad(Quad { + bounds: RectF::new( + bounds.origin() + vec2f(layout.em_width, row as f32 * layout.line_height), + vec2f(width, 1.), + ), + background: Some(Color::green()), + border: Default::default(), + corner_radius: 0., + }); + } +} diff --git a/styles/src/themes/cave.ts b/styles/src/themes/cave.ts index d588f84c536fe76b91bbd1be3123eea00dcc85aa..aab020d6269cb9b8a3b8bf097b28ea54b0774246 100644 --- a/styles/src/themes/cave.ts +++ b/styles/src/themes/cave.ts @@ -25,7 +25,4 @@ const ramps = { }; export const dark = createTheme(`${name}-dark`, false, ramps); -export const light = createTheme(`${name}-light`, true, ramps); - -console.log(JSON.stringify(dark.ramps.neutral.domain())) -console.log(JSON.stringify(light.ramps.neutral.domain())) \ No newline at end of file +export const light = createTheme(`${name}-light`, true, ramps); \ No newline at end of file