From b36bf0c56d8711ce0b1798b8e55cccccbd49ac63 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 20 Jun 2022 17:36:13 -0700 Subject: [PATCH 01/20] Finally on solid conceptual ground, able to move ahead confidently with Alacritty code --- Cargo.lock | 188 +++++++++++++- assets/keymaps/default.json | 3 +- crates/terminal/Cargo.toml | 21 ++ crates/terminal/src/terminal.rs | 418 ++++++++++++++++++++++++++++++++ crates/zed/Cargo.toml | 1 + crates/zed/src/main.rs | 1 + styles/package-lock.json | 1 - 7 files changed, 630 insertions(+), 3 deletions(-) create mode 100644 crates/terminal/Cargo.toml create mode 100644 crates/terminal/src/terminal.rs diff --git a/Cargo.lock b/Cargo.lock index d1b0e62ca7..ccf5b2428d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,6 +43,45 @@ dependencies = [ "memchr", ] +[[package]] +name = "alacritty_config_derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77044c45bdb871e501b5789ad16293ecb619e5733b60f4bb01d1cb31c463c336" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "alacritty_terminal" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fb5d4af84e39f9754d039ff6de2233c8996dbae0af74910156e559e5766e2f" +dependencies = [ + "alacritty_config_derive", + "base64 0.13.0", + "bitflags", + "dirs 3.0.2", + "libc", + "log", + "mio 0.6.23", + "mio-anonymous-pipes", + "mio-extras", + "miow 0.3.7", + "nix", + "parking_lot 0.11.2", + "regex-automata", + "serde", + "serde_yaml", + "signal-hook", + "signal-hook-mio", + "unicode-width", + "vte", + "winapi 0.3.9", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -2500,6 +2539,12 @@ dependencies = [ "safemem", ] +[[package]] +name = "linked-hash-map" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" + [[package]] name = "lipsum" version = "0.8.2" @@ -2724,7 +2769,7 @@ dependencies = [ "kernel32-sys", "libc", "log", - "miow", + "miow 0.2.2", "net2", "slab", "winapi 0.2.8", @@ -2742,6 +2787,42 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "mio-anonymous-pipes" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bc513025fe5005a3aa561b50fdb2cda5a150b84800ae02acd8aa9ed62ca1a6b" +dependencies = [ + "mio 0.6.23", + "miow 0.3.7", + "parking_lot 0.11.2", + "spsc-buffer", + "winapi 0.3.9", +] + +[[package]] +name = "mio-extras" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" +dependencies = [ + "lazycell", + "log", + "mio 0.6.23", + "slab", +] + +[[package]] +name = "mio-uds" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" +dependencies = [ + "iovec", + "libc", + "mio 0.6.23", +] + [[package]] name = "miow" version = "0.2.2" @@ -2754,6 +2835,15 @@ dependencies = [ "ws2_32-sys", ] +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "multimap" version = "0.8.3" @@ -2798,6 +2888,19 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "nix" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", + "memoffset", +] + [[package]] name = "nom" version = "7.1.1" @@ -4252,6 +4355,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707d15895415db6628332b737c838b88c598522e4dc70647e59b72312924aebc" +dependencies = [ + "indexmap", + "ryu", + "serde", + "yaml-rust", +] + [[package]] name = "servo-fontconfig" version = "0.5.1" @@ -4364,6 +4479,18 @@ dependencies = [ "signal-hook-registry", ] +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio 0.6.23", + "mio-uds", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -4492,6 +4619,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spsc-buffer" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be6c3f39c37a4283ee4b43d1311c828f2e1fb0541e76ea0cb1a2abd9ef2f5b3b" + [[package]] name = "sqlformat" version = "0.1.8" @@ -4739,6 +4872,23 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "terminal" +version = "0.1.0" +dependencies = [ + "alacritty_terminal", + "editor", + "futures", + "gpui", + "mio-extras", + "project", + "settings", + "smallvec", + "theme", + "util", + "workspace", +] + [[package]] name = "text" version = "0.1.0" @@ -5531,6 +5681,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" + [[package]] name = "util" version = "0.1.0" @@ -5616,6 +5772,26 @@ dependencies = [ "workspace", ] +[[package]] +name = "vte" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983" +dependencies = [ + "utf8parse", + "vte_generate_state_changes", +] + +[[package]] +name = "vte_generate_state_changes" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "waker-fn" version = "1.1.0" @@ -5967,6 +6143,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "zed" version = "0.42.0" @@ -6034,6 +6219,7 @@ dependencies = [ "smol", "sum_tree", "tempdir", + "terminal", "text", "theme", "theme_selector", diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 0f1e005891..e24996899c 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -226,7 +226,8 @@ "cmd-p": "file_finder::Toggle", "cmd-shift-P": "command_palette::Toggle", "cmd-shift-M": "diagnostics::Deploy", - "cmd-alt-s": "workspace::SaveAll" + "cmd-alt-s": "workspace::SaveAll", + "shift-cmd-T": "terminal::Deploy" } }, // Bindings from Sublime Text diff --git a/crates/terminal/Cargo.toml b/crates/terminal/Cargo.toml new file mode 100644 index 0000000000..4d0cbb3cfc --- /dev/null +++ b/crates/terminal/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "terminal" +version = "0.1.0" +edition = "2021" + +[lib] +path = "src/terminal.rs" +doctest = false + +[dependencies] +alacritty_terminal = "0.16.1" +editor = { path = "../editor" } +util = { path = "../util" } +gpui = { path = "../gpui" } +theme = { path = "../theme" } +settings = { path = "../settings" } +workspace = { path = "../workspace" } +project = { path = "../project" } +smallvec = { version = "1.6", features = ["union"] } +mio-extras = "2.0.6" +futures = "0.3" diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs new file mode 100644 index 0000000000..8c4e264cc8 --- /dev/null +++ b/crates/terminal/src/terminal.rs @@ -0,0 +1,418 @@ +use std::sync::Arc; + +use alacritty_terminal::{ + // ansi::Handler, + config::{Config, Program, PtyConfig}, + event::{Event, EventListener, Notify}, + event_loop::{EventLoop, Notifier}, + grid::{Indexed, Scroll}, + index::Point, + sync::FairMutex, + term::{cell::Cell, SizeInfo}, + tty, + Term, +}; +use futures::{ + channel::mpsc::{unbounded, UnboundedSender}, + StreamExt, +}; +use gpui::{ + actions, + color::Color, + elements::*, + fonts::{with_font_cache, TextStyle}, + geometry::{rect::RectF, vector::vec2f}, + impl_internal_actions, + keymap::Keystroke, + text_layout::Line, + Entity, + Event::KeyDown, + MutableAppContext, Quad, View, ViewContext, +}; +use project::{Project, ProjectPath}; +use settings::Settings; +use smallvec::SmallVec; +use workspace::{Item, Workspace}; + +//ASCII Control characters on a keyboard +const BACKSPACE: char = 8_u8 as char; +const TAB: char = 9_u8 as char; +const CARRIAGE_RETURN: char = 13_u8 as char; +const ESC: char = 27_u8 as char; +const DEL: char = 127_u8 as char; + +#[derive(Clone, Debug, PartialEq, Eq)] +enum Direction { + LEFT, + RIGHT, +} + +impl Default for Direction { + fn default() -> Self { + Direction::LEFT + } +} + +#[derive(Clone, Default, Debug, PartialEq, Eq)] +struct KeyInput(char); +#[derive(Clone, Default, Debug, PartialEq, Eq)] +struct DirectionInput(Direction); + +actions!(terminal, [Deploy]); +impl_internal_actions!(terminal, [KeyInput, DirectionInput]); + +pub fn init(cx: &mut MutableAppContext) { + cx.add_action(TerminalView::deploy); + cx.add_action(TerminalView::write_key_to_pty); + cx.add_action(TerminalView::move_cursor); +} + +#[derive(Clone)] +pub struct ZedListener(UnboundedSender); + +impl EventListener for ZedListener { + fn send_event(&self, event: Event) { + self.0.unbounded_send(event).ok(); + } +} + +struct TerminalView { + pty_tx: Notifier, + term: Arc>>, + title: String, +} + +impl Entity for TerminalView { + type Event = (); +} + +impl TerminalView { + fn new(cx: &mut ViewContext) -> Self { + let (events_tx, mut events_rx) = unbounded(); + cx.spawn(|this, mut cx| async move { + while let Some(event) = events_rx.next().await { + this.update(&mut cx, |this, cx| { + this.process_terminal_event(event, cx); + cx.notify(); + }); + } + }) + .detach(); + + let pty_config = PtyConfig { + shell: Some(Program::Just("zsh".to_string())), + working_directory: None, + hold: false, + }; + + let config = Config { + pty_config: pty_config.clone(), + ..Default::default() + }; + let size_info = SizeInfo::new(400., 100.0, 5., 5., 0., 0., false); + + let term = Term::new(&config, size_info, ZedListener(events_tx.clone())); + let term = Arc::new(FairMutex::new(term)); + + let pty = tty::new(&pty_config, &size_info, None).expect("Could not create tty"); + + let event_loop = EventLoop::new( + term.clone(), + ZedListener(events_tx.clone()), + pty, + pty_config.hold, + false, + ); + + let pty_tx = Notifier(event_loop.channel()); + let _io_thread = event_loop.spawn(); //todo cleanup + + TerminalView { + title: "Terminal".to_string(), + term, + pty_tx, + } + } + + fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext) { + workspace.add_item(Box::new(cx.add_view(|cx| TerminalView::new(cx))), cx); + } + + fn process_terminal_event( + &mut self, + event: alacritty_terminal::event::Event, + cx: &mut ViewContext, + ) { + match event { + alacritty_terminal::event::Event::Wakeup => cx.notify(), + alacritty_terminal::event::Event::PtyWrite(out) => self.pty_tx.notify(out.into_bytes()), + _ => {} + } + // + } + + fn write_key_to_pty(&mut self, action: &KeyInput, cx: &mut ViewContext) { + let mut bytes = vec![0; action.0.len_utf8()]; + action.0.encode_utf8(&mut bytes[..]); + self.pty_tx.notify(bytes); + } + + fn move_cursor(&mut self, action: &DirectionInput, cx: &mut ViewContext) { + let term = self.term.lock(); + match action.0 { + Direction::LEFT => { + self.pty_tx.notify("\x1b[C".to_string().into_bytes()); + } + Direction::RIGHT => { + self.pty_tx.notify("\x1b[D".to_string().into_bytes()); + } + } + } +} + +impl View for TerminalView { + fn ui_name() -> &'static str { + "TerminalView" + } + + fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox { + let _theme = cx.global::().theme.clone(); + + TerminalEl::new(self.term.clone()) + .contained() + // .with_style(theme.terminal.container) + .boxed() + } +} + +struct TerminalEl { + term: Arc>>, +} + +impl TerminalEl { + fn new(term: Arc>>) -> TerminalEl { + TerminalEl { term } + } +} + +struct LayoutState { + lines: Vec, + line_height: f32, +} + +impl Element for TerminalEl { + type LayoutState = LayoutState; + type PaintState = (); + + fn layout( + &mut self, + constraint: gpui::SizeConstraint, + cx: &mut gpui::LayoutContext, + ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) { + let term = self.term.lock(); + let content = term.renderable_content(); + + let mut lines = vec![]; + let mut cur_line = vec![]; + let mut last_line = 0; + for cell in content.display_iter { + let Indexed { + point: Point { line, .. }, + cell: Cell { c, .. }, + } = cell; + + if line != last_line { + lines.push(cur_line); + cur_line = vec![]; + last_line = line.0; + } + cur_line.push(c); + } + let line = lines + .into_iter() + .map(|char_vec| char_vec.into_iter().collect::()) + .fold("".to_string(), |grid, line| grid + &line + "\n"); + + let chunks = vec![(&line[..], None)].into_iter(); + + let text_style = with_font_cache(cx.font_cache.clone(), || TextStyle { + color: Color::white(), + ..Default::default() + }); + + let shaped_lines = layout_highlighted_chunks( + chunks, + &text_style, + cx.text_layout_cache, + &cx.font_cache, + usize::MAX, + line.matches('\n').count() + 1, + ); + let line_height = cx.font_cache.line_height(text_style.font_size); + + ( + constraint.max, + LayoutState { + lines: shaped_lines, + line_height, + }, + ) + } + + fn paint( + &mut self, + bounds: gpui::geometry::rect::RectF, + visible_bounds: gpui::geometry::rect::RectF, + layout: &mut Self::LayoutState, + cx: &mut gpui::PaintContext, + ) -> Self::PaintState { + let mut origin = bounds.origin(); + + for line in &layout.lines { + let boundaries = RectF::new(origin, vec2f(bounds.width(), layout.line_height)); + + if boundaries.intersects(visible_bounds) { + line.paint(origin, visible_bounds, layout.line_height, cx); + } + + origin.set_y(boundaries.max_y()); + } + + let term = self.term.lock(); + let cursor = term.renderable_content().cursor; + + let bounds = RectF::new( + vec2f( + cursor.point.column.0 as f32 * 10.0 + 150.0, + cursor.point.line.0 as f32 * 10.0 + 150.0, + ), + vec2f(10.0, 10.0), + ); + + cx.scene.push_quad(Quad { + bounds, + background: Some(Color::red()), + border: Default::default(), + corner_radius: 0., + }); + } + + fn dispatch_event( + &mut self, + event: &gpui::Event, + _bounds: gpui::geometry::rect::RectF, + _visible_bounds: gpui::geometry::rect::RectF, + _layout: &mut Self::LayoutState, + _paint: &mut Self::PaintState, + cx: &mut gpui::EventContext, + ) -> bool { + match event { + KeyDown { + input: Some(input), .. + } => { + dbg!(event); + cx.dispatch_action(KeyInput(input.chars().next().unwrap())); + true + } //TODO: Write control characters (ctrl-c) to pty + KeyDown { + keystroke: Keystroke { key, .. }, + input: None, + .. + } => { + dbg!(event); + if key == "backspace" { + cx.dispatch_action(KeyInput(DEL)); + true + } else if key == "enter" { + //There may be some subtlety here in how our terminal works + cx.dispatch_action(KeyInput(CARRIAGE_RETURN)); + true + } else if key == "tab" { + cx.dispatch_action(KeyInput(TAB)); + true + } else if key == "left" { + cx.dispatch_action(DirectionInput(Direction::LEFT)); + true + } else if key == "right" { + cx.dispatch_action(DirectionInput(Direction::RIGHT)); + true + // } else if key == "escape" { //TODO + // cx.dispatch_action(KeyInput(ESC)); + // true + } else { + false + } + } + _ => false, + } + } + + fn debug( + &self, + _bounds: gpui::geometry::rect::RectF, + _layout: &Self::LayoutState, + _paint: &Self::PaintState, + _cx: &gpui::DebugContext, + ) -> gpui::serde_json::Value { + unreachable!("Should never be called hopefully") + } +} + +impl Item for TerminalView { + fn tab_content(&self, style: &theme::Tab, cx: &gpui::AppContext) -> ElementBox { + let settings = cx.global::(); + let search_theme = &settings.theme.search; + Flex::row() + .with_child( + Label::new(self.title.clone(), style.label.clone()) + .aligned() + .contained() + .with_margin_left(search_theme.tab_icon_spacing) + .boxed(), + ) + .boxed() + } + + fn project_path(&self, _cx: &gpui::AppContext) -> Option { + None + } + + fn project_entry_ids(&self, _cx: &gpui::AppContext) -> SmallVec<[project::ProjectEntryId; 3]> { + todo!() + } + + fn is_singleton(&self, _cx: &gpui::AppContext) -> bool { + false + } + + fn set_nav_history(&mut self, _: workspace::ItemNavHistory, _: &mut ViewContext) {} + + fn can_save(&self, _cx: &gpui::AppContext) -> bool { + false + } + + fn save( + &mut self, + _project: gpui::ModelHandle, + _cx: &mut ViewContext, + ) -> gpui::Task> { + unreachable!("save should not have been called"); + } + + fn save_as( + &mut self, + _project: gpui::ModelHandle, + _abs_path: std::path::PathBuf, + _cx: &mut ViewContext, + ) -> gpui::Task> { + unreachable!("save_as should not have been called"); + } + + fn reload( + &mut self, + _project: gpui::ModelHandle, + _cx: &mut ViewContext, + ) -> gpui::Task> { + gpui::Task::ready(Ok(())) + } +} diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 56472be040..b2d62d66db 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -46,6 +46,7 @@ rpc = { path = "../rpc" } settings = { path = "../settings" } sum_tree = { path = "../sum_tree" } text = { path = "../text" } +terminal = { path = "../terminal" } theme = { path = "../theme" } theme_selector = { path = "../theme_selector" } util = { path = "../util" } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 10aa717c0d..e04b92dc0a 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -181,6 +181,7 @@ fn main() { diagnostics::init(cx); search::init(cx); vim::init(cx); + terminal::init(cx); let db = cx.background().block(db); let (settings_file, keymap_file) = cx.background().block(config_files).unwrap(); diff --git a/styles/package-lock.json b/styles/package-lock.json index 49304dc2fa..2eb6d3a1bf 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": { From 31bc758f35a9e30763c0072febc55fc554cdbfa1 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 22 Jun 2022 11:23:09 -0700 Subject: [PATCH 02/20] Forgot to commit last night --- assets/keymaps/default.json | 18 +- crates/terminal/src/terminal.rs | 322 ++++++++++++++++++++------------ crates/theme/src/theme.rs | 1 + 3 files changed, 224 insertions(+), 117 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index e24996899c..19029decdf 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -226,8 +226,7 @@ "cmd-p": "file_finder::Toggle", "cmd-shift-P": "command_palette::Toggle", "cmd-shift-M": "diagnostics::Deploy", - "cmd-alt-s": "workspace::SaveAll", - "shift-cmd-T": "terminal::Deploy" + "cmd-alt-s": "workspace::SaveAll" } }, // Bindings from Sublime Text @@ -353,5 +352,20 @@ "f2": "project_panel::Rename", "backspace": "project_panel::Delete" } + }, + { + "context": "Terminal", + "bindings": { + "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::HistoryBack", + "down": "terminal::HistoryForward", + "tab": "terminal::AutoComplete" + } } ] \ No newline at end of file diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 8c4e264cc8..f6237eed2b 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -1,16 +1,14 @@ use std::sync::Arc; use alacritty_terminal::{ - // ansi::Handler, config::{Config, Program, PtyConfig}, event::{Event, EventListener, Notify}, - event_loop::{EventLoop, Notifier}, - grid::{Indexed, Scroll}, + event_loop::{EventLoop, Msg, Notifier}, + grid::Indexed, index::Point, sync::FairMutex, term::{cell::Cell, SizeInfo}, - tty, - Term, + tty, Term, }; use futures::{ channel::mpsc::{unbounded, UnboundedSender}, @@ -23,7 +21,7 @@ use gpui::{ fonts::{with_font_cache, TextStyle}, geometry::{rect::RectF, vector::vec2f}, impl_internal_actions, - keymap::Keystroke, + json::json, text_layout::Line, Entity, Event::KeyDown, @@ -35,36 +33,52 @@ use smallvec::SmallVec; use workspace::{Item, Workspace}; //ASCII Control characters on a keyboard -const BACKSPACE: char = 8_u8 as char; -const TAB: char = 9_u8 as char; -const CARRIAGE_RETURN: char = 13_u8 as char; -const ESC: char = 27_u8 as char; -const DEL: char = 127_u8 as char; - -#[derive(Clone, Debug, PartialEq, Eq)] -enum Direction { - LEFT, - RIGHT, -} - -impl Default for Direction { - fn default() -> Self { - Direction::LEFT - } -} +//Consts -> Structs -> Impls -> Functions, Vaguely in order of importance +const ETX_CHAR: char = 3_u8 as char; //'End of text', the control code for 'ctrl-c' +const TAB_CHAR: char = 9_u8 as char; +const CARRIAGE_RETURN_CHAR: char = 13_u8 as char; +const ESC_CHAR: char = 27_u8 as char; +const DEL_CHAR: char = 127_u8 as char; +const LEFT_SEQ: &str = "\x1b[D"; +const RIGHT_SEQ: &str = "\x1b[C"; +const UP_SEQ: &str = "\x1b[A"; +const DOWN_SEQ: &str = "\x1b[B"; +const DEFAULT_TITLE: &str = "Terminal"; #[derive(Clone, Default, Debug, PartialEq, Eq)] -struct KeyInput(char); -#[derive(Clone, Default, Debug, PartialEq, Eq)] -struct DirectionInput(Direction); +struct Input(String); -actions!(terminal, [Deploy]); -impl_internal_actions!(terminal, [KeyInput, DirectionInput]); +actions!( + terminal, + [ + Deploy, + SIGINT, + ESCAPE, + Quit, + DEL, + RETURN, + LEFT, + RIGHT, + HistoryBack, + HistoryForward, + AutoComplete + ] +); +impl_internal_actions!(terminal, [Input]); pub fn init(cx: &mut MutableAppContext) { - cx.add_action(TerminalView::deploy); - cx.add_action(TerminalView::write_key_to_pty); - cx.add_action(TerminalView::move_cursor); + cx.add_action(Terminal::deploy); + cx.add_action(Terminal::write_to_pty); + cx.add_action(Terminal::send_sigint); //TODO figure out how to do this properly + cx.add_action(Terminal::escape); + cx.add_action(Terminal::quit); + cx.add_action(Terminal::del); + cx.add_action(Terminal::carriage_return); + cx.add_action(Terminal::left); + cx.add_action(Terminal::right); + cx.add_action(Terminal::history_back); + cx.add_action(Terminal::history_forward); + cx.add_action(Terminal::autocomplete); } #[derive(Clone)] @@ -76,46 +90,59 @@ impl EventListener for ZedListener { } } -struct TerminalView { +struct Terminal { pty_tx: Notifier, term: Arc>>, title: String, } -impl Entity for TerminalView { +impl Entity for Terminal { type Event = (); } -impl TerminalView { +impl Terminal { fn new(cx: &mut ViewContext) -> Self { + //Spawn a task so the Alacritty EventLoop to communicate with us let (events_tx, mut events_rx) = unbounded(); - cx.spawn(|this, mut cx| async move { + cx.spawn_weak(|this, mut cx| async move { while let Some(event) = events_rx.next().await { - this.update(&mut cx, |this, cx| { - this.process_terminal_event(event, cx); - cx.notify(); - }); + match this.upgrade(&cx) { + Some(handle) => { + handle.update(&mut cx, |this, cx| { + this.process_terminal_event(event, cx); + cx.notify(); + }); + } + None => break, + } } }) .detach(); + //TODO: Load from settings let pty_config = PtyConfig { shell: Some(Program::Just("zsh".to_string())), working_directory: None, hold: false, }; + //TODO: Properly configure this let config = Config { pty_config: pty_config.clone(), ..Default::default() }; + + //TODO: derive this let size_info = SizeInfo::new(400., 100.0, 5., 5., 0., 0., false); + //Set up the terminal... let term = Term::new(&config, size_info, ZedListener(events_tx.clone())); let term = Arc::new(FairMutex::new(term)); + //Setup the pty... let pty = tty::new(&pty_config, &size_info, None).expect("Could not create tty"); + //And connect them together let event_loop = EventLoop::new( term.clone(), ZedListener(events_tx.clone()), @@ -124,18 +151,18 @@ impl TerminalView { false, ); + //Kick things off let pty_tx = Notifier(event_loop.channel()); - let _io_thread = event_loop.spawn(); //todo cleanup - - TerminalView { - title: "Terminal".to_string(), + let _io_thread = event_loop.spawn(); + Terminal { + title: DEFAULT_TITLE.to_string(), term, pty_tx, } } fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext) { - workspace.add_item(Box::new(cx.add_view(|cx| TerminalView::new(cx))), cx); + workspace.add_item(Box::new(cx.add_view(|cx| Terminal::new(cx))), cx); } fn process_terminal_event( @@ -144,35 +171,94 @@ impl TerminalView { cx: &mut ViewContext, ) { match event { - alacritty_terminal::event::Event::Wakeup => cx.notify(), - alacritty_terminal::event::Event::PtyWrite(out) => self.pty_tx.notify(out.into_bytes()), - _ => {} + Event::Wakeup => cx.notify(), + Event::PtyWrite(out) => self.write_to_pty(&Input(out), cx), + Event::MouseCursorDirty => todo!(), //I think this is outside of Zed's loop + Event::Title(title) => self.title = title, + Event::ResetTitle => self.title = DEFAULT_TITLE.to_string(), + Event::ClipboardStore(_, _) => todo!(), + Event::ClipboardLoad(_, _) => todo!(), + Event::ColorRequest(_, _) => todo!(), + Event::CursorBlinkingChange => todo!(), + Event::Bell => todo!(), + Event::Exit => todo!(), + Event::MouseCursorDirty => todo!(), } // } - fn write_key_to_pty(&mut self, action: &KeyInput, cx: &mut ViewContext) { - let mut bytes = vec![0; action.0.len_utf8()]; - action.0.encode_utf8(&mut bytes[..]); - self.pty_tx.notify(bytes); + fn shutdown_pty(&mut self) { + self.pty_tx.0.send(Msg::Shutdown).ok(); } - fn move_cursor(&mut self, action: &DirectionInput, cx: &mut ViewContext) { - let term = self.term.lock(); - match action.0 { - Direction::LEFT => { - self.pty_tx.notify("\x1b[C".to_string().into_bytes()); - } - Direction::RIGHT => { - self.pty_tx.notify("\x1b[D".to_string().into_bytes()); - } - } + fn history_back(&mut self, _: &HistoryBack, cx: &mut ViewContext) { + self.write_to_pty(&Input(UP_SEQ.to_string()), cx); + + //Noop.. for now... + //This might just need to be forwarded to the terminal? + //Behavior changes based on mode... + } + + fn history_forward(&mut self, _: &HistoryForward, cx: &mut ViewContext) { + self.write_to_pty(&Input(DOWN_SEQ.to_string()), cx); + //Noop.. for now... + //This might just need to be forwarded to the terminal by the pty? + //Behvaior changes based on mode + } + + fn autocomplete(&mut self, _: &AutoComplete, cx: &mut ViewContext) { + self.write_to_pty(&Input(TAB_CHAR.to_string()), cx); + //Noop.. for now... + //This might just need to be forwarded to the terminal by the pty? + //Behvaior changes based on mode + } + + fn write_to_pty(&mut self, input: &Input, _cx: &mut ViewContext) { + self.pty_tx.notify(input.0.clone().into_bytes()); + } + + 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) { + self.write_to_pty(&Input(ESC_CHAR.to_string()), cx); + } + + 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) { + self.write_to_pty(&Input(CARRIAGE_RETURN_CHAR.to_string()), cx); + } + + 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) { + self.write_to_pty(&Input(RIGHT_SEQ.to_string()), cx); + } + + fn quit(&mut self, _: &Quit, _cx: &mut ViewContext) { + //TODO + // cx.dispatch_action(cx.window_id(), workspace::CloseItem()); + } + + // ShowHistory, + // AutoComplete +} + +impl Drop for Terminal { + fn drop(&mut self) { + self.shutdown_pty(); } } -impl View for TerminalView { +impl View for Terminal { fn ui_name() -> &'static str { - "TerminalView" + "Terminal" } fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox { @@ -198,6 +284,7 @@ impl TerminalEl { struct LayoutState { lines: Vec, line_height: f32, + cursor: RectF, } impl Element for TerminalEl { @@ -209,9 +296,55 @@ impl Element for TerminalEl { constraint: gpui::SizeConstraint, cx: &mut gpui::LayoutContext, ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) { - let term = self.term.lock(); + let size = constraint.max; + //Get terminal content + let mut term = self.term.lock(); + + //Set up text rendering + + let text_style = with_font_cache(cx.font_cache.clone(), || TextStyle { + color: Color::white(), + ..Default::default() + }); + let line_height = cx.font_cache.line_height(text_style.font_size); + let em_width = cx + .font_cache() + .em_width(text_style.font_id, text_style.font_size); + + term.resize(SizeInfo::new( + size.x(), + size.y(), + em_width, + line_height, + 0., + 0., + false, + )); + let content = term.renderable_content(); + // //Dual owned system from Neovide + // let mut block_width = cursor_row_layout.x_for_index(cursor_column + 1) - cursor_character_x; + // if block_width == 0.0 { + // block_width = layout.em_width; + // } + let cursor = RectF::new( + vec2f( + content.cursor.point.column.0 as f32 * em_width, + content.cursor.point.line.0 as f32 * line_height, + ), + vec2f(em_width, line_height), + ); + + // let cursor = Cursor { + // color: selection_style.cursor, + // block_width, + // origin: content_origin + vec2f(x, y), + // line_height: layout.line_height, + // shape: self.cursor_shape, + // block_text, + // } + let mut lines = vec![]; let mut cur_line = vec![]; let mut last_line = 0; @@ -235,11 +368,6 @@ impl Element for TerminalEl { let chunks = vec![(&line[..], None)].into_iter(); - let text_style = with_font_cache(cx.font_cache.clone(), || TextStyle { - color: Color::white(), - ..Default::default() - }); - let shaped_lines = layout_highlighted_chunks( chunks, &text_style, @@ -248,13 +376,13 @@ impl Element for TerminalEl { usize::MAX, line.matches('\n').count() + 1, ); - let line_height = cx.font_cache.line_height(text_style.font_size); ( constraint.max, LayoutState { lines: shaped_lines, line_height, + cursor, }, ) } @@ -278,19 +406,11 @@ impl Element for TerminalEl { origin.set_y(boundaries.max_y()); } - let term = self.term.lock(); - let cursor = term.renderable_content().cursor; - - let bounds = RectF::new( - vec2f( - cursor.point.column.0 as f32 * 10.0 + 150.0, - cursor.point.line.0 as f32 * 10.0 + 150.0, - ), - vec2f(10.0, 10.0), - ); + let new_origin = bounds.origin() + layout.cursor.origin(); + let new_cursor = RectF::new(new_origin, layout.cursor.size()); cx.scene.push_quad(Quad { - bounds, + bounds: new_cursor, background: Some(Color::red()), border: Default::default(), corner_radius: 0., @@ -310,38 +430,8 @@ impl Element for TerminalEl { KeyDown { input: Some(input), .. } => { - dbg!(event); - cx.dispatch_action(KeyInput(input.chars().next().unwrap())); + cx.dispatch_action(Input(input.to_string())); true - } //TODO: Write control characters (ctrl-c) to pty - KeyDown { - keystroke: Keystroke { key, .. }, - input: None, - .. - } => { - dbg!(event); - if key == "backspace" { - cx.dispatch_action(KeyInput(DEL)); - true - } else if key == "enter" { - //There may be some subtlety here in how our terminal works - cx.dispatch_action(KeyInput(CARRIAGE_RETURN)); - true - } else if key == "tab" { - cx.dispatch_action(KeyInput(TAB)); - true - } else if key == "left" { - cx.dispatch_action(DirectionInput(Direction::LEFT)); - true - } else if key == "right" { - cx.dispatch_action(DirectionInput(Direction::RIGHT)); - true - // } else if key == "escape" { //TODO - // cx.dispatch_action(KeyInput(ESC)); - // true - } else { - false - } } _ => false, } @@ -354,11 +444,13 @@ impl Element for TerminalEl { _paint: &Self::PaintState, _cx: &gpui::DebugContext, ) -> gpui::serde_json::Value { - unreachable!("Should never be called hopefully") + json!({ + "type": "TerminalElement", + }) } } -impl Item for TerminalView { +impl Item for Terminal { fn tab_content(&self, style: &theme::Tab, cx: &gpui::AppContext) -> ElementBox { let settings = cx.global::(); let search_theme = &settings.theme.search; @@ -378,7 +470,7 @@ impl Item for TerminalView { } fn project_entry_ids(&self, _cx: &gpui::AppContext) -> SmallVec<[project::ProjectEntryId; 3]> { - todo!() + SmallVec::new() } fn is_singleton(&self, _cx: &gpui::AppContext) -> bool { diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index ae269c00cb..37a408d85f 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -33,6 +33,7 @@ pub struct Theme { pub contact_notification: ContactNotification, pub update_notification: UpdateNotification, pub tooltip: TooltipStyle, + // pub terminal: Terminal, } #[derive(Deserialize, Default)] From f58a15bbb1a348abbdccaf63872d6c8231fa2522 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 22 Jun 2022 11:23:29 -0700 Subject: [PATCH 03/20] Removed final stuff from theme.rs --- crates/theme/src/theme.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 37a408d85f..ae269c00cb 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -33,7 +33,6 @@ pub struct Theme { pub contact_notification: ContactNotification, pub update_notification: UpdateNotification, pub tooltip: TooltipStyle, - // pub terminal: Terminal, } #[derive(Deserialize, Default)] From 9e55c60b6a99daf644111b7c98b9c6eeec75ed12 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 24 Jun 2022 15:02:54 -0700 Subject: [PATCH 04/20] working on selection and scrolling in terminals --- Cargo.lock | 1 + assets/keymaps/default.json | 7 +- crates/terminal/Cargo.toml | 1 + crates/terminal/src/terminal.rs | 473 +++++++++++++++++++++----------- 4 files changed, 326 insertions(+), 156 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ccf5b2428d..1a59f23918 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4881,6 +4881,7 @@ dependencies = [ "futures", "gpui", "mio-extras", + "ordered-float", "project", "settings", "smallvec", diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 19029decdf..f9254176a9 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -363,9 +363,10 @@ "enter": "terminal::RETURN", "left": "terminal::LEFT", "right": "terminal::RIGHT", - "up": "terminal::HistoryBack", - "down": "terminal::HistoryForward", - "tab": "terminal::AutoComplete" + "up": "terminal::UP", + "down": "terminal::DOWN", + "tab": "terminal::TAB", + "cmd-k": "terminal::Clear" } } ] \ No newline at end of file diff --git a/crates/terminal/Cargo.toml b/crates/terminal/Cargo.toml index 4d0cbb3cfc..3cb4d631f3 100644 --- a/crates/terminal/Cargo.toml +++ b/crates/terminal/Cargo.toml @@ -19,3 +19,4 @@ project = { path = "../project" } smallvec = { version = "1.6", features = ["union"] } mio-extras = "2.0.6" futures = "0.3" +ordered-float = "2.1.1" diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index f6237eed2b..c8f936bda9 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -1,13 +1,18 @@ use std::sync::Arc; use alacritty_terminal::{ + ansi::Color as AnsiColor, config::{Config, Program, PtyConfig}, - event::{Event, EventListener, Notify}, + event::{Event as AlacTermEvent, EventListener, Notify}, event_loop::{EventLoop, Msg, Notifier}, grid::Indexed, index::Point, sync::FairMutex, - term::{cell::Cell, SizeInfo}, + term::{ + cell::{Cell, Flags}, + color::Rgb, + SizeInfo, + }, tty, Term, }; use futures::{ @@ -18,15 +23,17 @@ use gpui::{ actions, color::Color, elements::*, - fonts::{with_font_cache, TextStyle}, + fonts::{with_font_cache, HighlightStyle, TextStyle, Underline}, geometry::{rect::RectF, vector::vec2f}, impl_internal_actions, json::json, + platform::CursorStyle, text_layout::Line, - Entity, + ClipboardItem, Entity, Event::KeyDown, MutableAppContext, Quad, View, ViewContext, }; +use ordered_float::OrderedFloat; use project::{Project, ProjectPath}; use settings::Settings; use smallvec::SmallVec; @@ -43,6 +50,7 @@ const LEFT_SEQ: &str = "\x1b[D"; const RIGHT_SEQ: &str = "\x1b[C"; const UP_SEQ: &str = "\x1b[A"; const DOWN_SEQ: &str = "\x1b[B"; +const CLEAR_SEQ: &str = "\x1b[2J"; const DEFAULT_TITLE: &str = "Terminal"; #[derive(Clone, Default, Debug, PartialEq, Eq)] @@ -50,59 +58,57 @@ struct Input(String); actions!( terminal, - [ - Deploy, - SIGINT, - ESCAPE, - Quit, - DEL, - RETURN, - LEFT, - RIGHT, - HistoryBack, - HistoryForward, - AutoComplete - ] + [Deploy, SIGINT, ESCAPE, Quit, DEL, RETURN, LEFT, RIGHT, UP, DOWN, TAB, Clear] ); impl_internal_actions!(terminal, [Input]); pub fn init(cx: &mut MutableAppContext) { cx.add_action(Terminal::deploy); cx.add_action(Terminal::write_to_pty); - cx.add_action(Terminal::send_sigint); //TODO figure out how to do this properly + cx.add_action(Terminal::send_sigint); cx.add_action(Terminal::escape); cx.add_action(Terminal::quit); cx.add_action(Terminal::del); - cx.add_action(Terminal::carriage_return); + cx.add_action(Terminal::carriage_return); //TODO figure out how to do this properly. Should we be checking the terminal mode? cx.add_action(Terminal::left); cx.add_action(Terminal::right); - cx.add_action(Terminal::history_back); - cx.add_action(Terminal::history_forward); - cx.add_action(Terminal::autocomplete); + cx.add_action(Terminal::up); + cx.add_action(Terminal::down); + cx.add_action(Terminal::tab); + cx.add_action(Terminal::clear); } #[derive(Clone)] -pub struct ZedListener(UnboundedSender); +pub struct ZedListener(UnboundedSender); impl EventListener for ZedListener { - fn send_event(&self, event: Event) { + fn send_event(&self, event: AlacTermEvent) { self.0.unbounded_send(event).ok(); } } +///A terminal renderer. 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 +} + +enum ZedTermEvent { + TitleChanged, + CloseTerminal, } impl Entity for Terminal { - type Event = (); + type Event = ZedTermEvent; } impl Terminal { + ///Create a new Terminal view. This spawns a task, a thread, and opens the TTY devices fn new(cx: &mut ViewContext) -> Self { - //Spawn a task so the Alacritty EventLoop to communicate with us + //Spawn a task so the Alacritty EventLoop can communicate with us in a view context let (events_tx, mut events_rx) = unbounded(); cx.spawn_weak(|this, mut cx| async move { while let Some(event) = events_rx.next().await { @@ -158,65 +164,109 @@ impl Terminal { title: DEFAULT_TITLE.to_string(), term, pty_tx, + has_new_content: false, + has_bell: false, } } - fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext) { - workspace.add_item(Box::new(cx.add_view(|cx| Terminal::new(cx))), cx); - } - + ///Takes events from Alacritty and translates them to behavior on this view fn process_terminal_event( &mut self, event: alacritty_terminal::event::Event, cx: &mut ViewContext, ) { match event { - Event::Wakeup => cx.notify(), - Event::PtyWrite(out) => self.write_to_pty(&Input(out), cx), - Event::MouseCursorDirty => todo!(), //I think this is outside of Zed's loop - Event::Title(title) => self.title = title, - Event::ResetTitle => self.title = DEFAULT_TITLE.to_string(), - Event::ClipboardStore(_, _) => todo!(), - Event::ClipboardLoad(_, _) => todo!(), - Event::ColorRequest(_, _) => todo!(), - Event::CursorBlinkingChange => todo!(), - Event::Bell => todo!(), - Event::Exit => todo!(), - Event::MouseCursorDirty => todo!(), + AlacTermEvent::Wakeup => { + if !cx.is_self_focused() { + //Need to figure out how to trigger a redraw when not in focus + self.has_new_content = true; //Change tab content + cx.emit(ZedTermEvent::TitleChanged); + } else { + cx.notify() + } + } + AlacTermEvent::PtyWrite(out) => self.write_to_pty(&Input(out), cx), + //TODO: + //What this is supposed to do is check the cursor state, then set it on the platform. + //See Processor::reset_mouse_cursor() and Processor::cursor_state() in alacritty/src/input.rs + //to see how this is Calculated. Question: Does this flow make sense with how GPUI hadles + //the mouse? + AlacTermEvent::MouseCursorDirty => { + //Calculate new cursor style. + //Check on correctly handling mouse events for terminals + cx.platform().set_cursor_style(CursorStyle::Arrow); //??? + println!("Mouse cursor dirty") + } + AlacTermEvent::Title(title) => { + self.title = title; + cx.emit(ZedTermEvent::TitleChanged); + } + AlacTermEvent::ResetTitle => { + self.title = DEFAULT_TITLE.to_string(); + cx.emit(ZedTermEvent::TitleChanged); + } + AlacTermEvent::ClipboardStore(_, data) => { + cx.write_to_clipboard(ClipboardItem::new(data)) + } + AlacTermEvent::ClipboardLoad(_, format) => self.write_to_pty( + &Input(format( + &cx.read_from_clipboard() + .map(|ci| ci.text().to_string()) + .unwrap_or("".to_string()), + )), + cx, + ), + AlacTermEvent::ColorRequest(index, format) => { + //TODO test this as well + //TODO: change to getting the display colors, like alacrityy, instead of a default + let color = self.term.lock().colors()[index].unwrap_or(Rgb::default()); + self.write_to_pty(&Input(format(color)), cx) + } + AlacTermEvent::CursorBlinkingChange => { + //So, it's our job to set a timer and cause the cursor to blink here + //Which means that I'm going to put this off until someone @ Zed looks at it + } + AlacTermEvent::Bell => { + self.has_bell = true; + cx.emit(ZedTermEvent::TitleChanged); + } + AlacTermEvent::Exit => self.quit(&Quit, cx), } - // } + ///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); + } + + ///Send the shutdown message to Alacritty fn shutdown_pty(&mut self) { self.pty_tx.0.send(Msg::Shutdown).ok(); } - fn history_back(&mut self, _: &HistoryBack, cx: &mut ViewContext) { - self.write_to_pty(&Input(UP_SEQ.to_string()), cx); - - //Noop.. for now... - //This might just need to be forwarded to the terminal? - //Behavior changes based on mode... + fn quit(&mut self, _: &Quit, cx: &mut ViewContext) { + cx.emit(ZedTermEvent::CloseTerminal); } - fn history_forward(&mut self, _: &HistoryForward, cx: &mut ViewContext) { - self.write_to_pty(&Input(DOWN_SEQ.to_string()), cx); - //Noop.. for now... - //This might just need to be forwarded to the terminal by the pty? - //Behvaior changes based on mode - } - - fn autocomplete(&mut self, _: &AutoComplete, cx: &mut ViewContext) { - self.write_to_pty(&Input(TAB_CHAR.to_string()), cx); - //Noop.. for now... - //This might just need to be forwarded to the terminal by the pty? - //Behvaior changes based on mode - } - - fn write_to_pty(&mut self, input: &Input, _cx: &mut ViewContext) { + fn write_to_pty(&mut self, input: &Input, cx: &mut ViewContext) { + //iTerm bell behavior, bell stays until terminal is interacted with + self.has_bell = false; + cx.emit(ZedTermEvent::TitleChanged); self.pty_tx.notify(input.0.clone().into_bytes()); } + 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) { + self.write_to_pty(&Input(DOWN_SEQ.to_string()), cx); + } + + 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) { self.write_to_pty(&Input(ETX_CHAR.to_string()), cx); } @@ -241,13 +291,9 @@ impl Terminal { self.write_to_pty(&Input(RIGHT_SEQ.to_string()), cx); } - fn quit(&mut self, _: &Quit, _cx: &mut ViewContext) { - //TODO - // cx.dispatch_action(cx.window_id(), workspace::CloseItem()); + fn clear(&mut self, _: &Clear, cx: &mut ViewContext) { + self.write_to_pty(&Input(CLEAR_SEQ.to_string()), cx); } - - // ShowHistory, - // AutoComplete } impl Drop for Terminal { @@ -269,6 +315,98 @@ impl View for Terminal { // .with_style(theme.terminal.container) .boxed() } + + fn on_focus(&mut self, _: &mut ViewContext) { + self.has_new_content = false; + } +} + +impl Item for Terminal { + fn tab_content(&self, tab_theme: &theme::Tab, cx: &gpui::AppContext) -> ElementBox { + let settings = cx.global::(); + let search_theme = &settings.theme.search; //TODO properly integrate themes + + let mut flex = Flex::row(); + + if self.has_bell { + flex.add_child( + Svg::new("icons/zap.svg") + .with_color(tab_theme.label.text.color) + .constrained() + .with_width(search_theme.tab_icon_width) + .aligned() + .boxed(), + ); + }; + + flex.with_child( + Label::new(self.title.clone(), tab_theme.label.clone()) + .aligned() + .contained() + .with_margin_left(if self.has_bell { + search_theme.tab_icon_spacing + } else { + 0. + }) + .boxed(), + ) + .boxed() + } + + fn project_path(&self, _cx: &gpui::AppContext) -> Option { + None + } + + fn project_entry_ids(&self, _cx: &gpui::AppContext) -> SmallVec<[project::ProjectEntryId; 3]> { + SmallVec::new() + } + + fn is_singleton(&self, _cx: &gpui::AppContext) -> bool { + false + } + + fn set_nav_history(&mut self, _: workspace::ItemNavHistory, _: &mut ViewContext) {} + + fn can_save(&self, _cx: &gpui::AppContext) -> bool { + false + } + + fn save( + &mut self, + _project: gpui::ModelHandle, + _cx: &mut ViewContext, + ) -> gpui::Task> { + unreachable!("save should not have been called"); + } + + fn save_as( + &mut self, + _project: gpui::ModelHandle, + _abs_path: std::path::PathBuf, + _cx: &mut ViewContext, + ) -> gpui::Task> { + unreachable!("save_as should not have been called"); + } + + fn reload( + &mut self, + _project: gpui::ModelHandle, + _cx: &mut ViewContext, + ) -> gpui::Task> { + gpui::Task::ready(Ok(())) + } + + fn is_dirty(&self, _: &gpui::AppContext) -> bool { + self.has_new_content + } + + fn should_update_tab_on_event(event: &Self::Event) -> bool { + matches!(event, &ZedTermEvent::TitleChanged) + } + + fn should_close_item_on_event(event: &Self::Event) -> bool { + matches!(event, &ZedTermEvent::CloseTerminal) + } } struct TerminalEl { @@ -286,7 +424,39 @@ struct LayoutState { line_height: f32, cursor: RectF, } +/* TODO point calculation for selection + * take the current point's x: + * - subtract padding + * - divide by cell width + * - take the minimum of the x coord and the last colum of the size info + * Take the current point's y: + * - Subtract padding + * - Divide by cell height + * - Take the minimum of the y coord and the last line + * + * With this x and y, pass to term::viewport_to_point (module function) + * Also pass in the display offset from the term.grid().display_offset() + * (Display offset is for scrolling) + */ +/* TODO Selection + * 1. On click, calculate the single, double, and triple click based on timings + * 2. Convert mouse location to a terminal point + * 3. Generate each of the three kinds of selection needed + * 4. Assign a selection to the terminal's selection variable + * How to render? + * 1. On mouse moved, calculate a terminal point + * 2. if (lmb_pressed || rmb_pressed) && (self.ctx.modifiers().shift() || !self.ctx.mouse_mode() + * 3. Take the selection from the terminal, call selection.update(), and put it back + */ + +/* TODO Scroll + * 1. Convert scroll to a pixel delta (alacritty/src/input > Processor::mouse_wheel_input) + * 2. Divide by cell height + * 3. Create an alacritty_terminal::Scroll::Delta() object and call `self.terminal.scroll_display(scroll);` + * 4. Maybe do a cx.notify, just in case. + * 5. Also update the selected area, just check out for the logic alacritty/src/event.rs > ActionContext::scroll + */ impl Element for TerminalEl { type LayoutState = LayoutState; type PaintState = (); @@ -323,11 +493,6 @@ impl Element for TerminalEl { let content = term.renderable_content(); - // //Dual owned system from Neovide - // let mut block_width = cursor_row_layout.x_for_index(cursor_column + 1) - cursor_character_x; - // if block_width == 0.0 { - // block_width = layout.em_width; - // } let cursor = RectF::new( vec2f( content.cursor.point.column.0 as f32 * em_width, @@ -336,45 +501,50 @@ impl Element for TerminalEl { vec2f(em_width, line_height), ); - // let cursor = Cursor { - // color: selection_style.cursor, - // block_width, - // origin: content_origin + vec2f(x, y), - // line_height: layout.line_height, - // shape: self.cursor_shape, - // block_text, - // } - - let mut lines = vec![]; - let mut cur_line = vec![]; + let mut lines: Vec<(String, Option)> = vec![]; let mut last_line = 0; + + 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, .. }, + 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); + HighlightStyle { + color: Some(alac_color_to_gpui_color(fg)), + ..Default::default() + }; + if line != last_line { - lines.push(cur_line); - cur_line = vec![]; + cur_chunk.push('\n'); last_line = line.0; } - cur_line.push(c); - } - let line = lines - .into_iter() - .map(|char_vec| char_vec.into_iter().collect::()) - .fold("".to_string(), |grid, line| grid + &line + "\n"); - let chunks = vec![(&line[..], None)].into_iter(); + 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 shaped_lines = layout_highlighted_chunks( - chunks, + lines.iter().map(|(text, style)| (text.as_str(), *style)), &text_style, cx.text_layout_cache, &cx.font_cache, usize::MAX, - line.matches('\n').count() + 1, + last_line as usize, ); ( @@ -450,61 +620,58 @@ impl Element for TerminalEl { } } -impl Item for Terminal { - fn tab_content(&self, style: &theme::Tab, cx: &gpui::AppContext) -> ElementBox { - let settings = cx.global::(); - let search_theme = &settings.theme.search; - Flex::row() - .with_child( - Label::new(self.title.clone(), style.label.clone()) - .aligned() - .contained() - .with_margin_left(search_theme.tab_icon_spacing) - .boxed(), - ) - .boxed() - } - - fn project_path(&self, _cx: &gpui::AppContext) -> Option { +fn make_style_from_cell(fg: &AnsiColor, flags: &Flags) -> HighlightStyle { + let fg = Some(alac_color_to_gpui_color(fg)); + let underline = if flags.contains(Flags::UNDERLINE) { + Some(Underline { + color: fg, + squiggly: false, + thickness: OrderedFloat(1.), + }) + } else { None - } - - fn project_entry_ids(&self, _cx: &gpui::AppContext) -> SmallVec<[project::ProjectEntryId; 3]> { - SmallVec::new() - } - - fn is_singleton(&self, _cx: &gpui::AppContext) -> bool { - false - } - - fn set_nav_history(&mut self, _: workspace::ItemNavHistory, _: &mut ViewContext) {} - - fn can_save(&self, _cx: &gpui::AppContext) -> bool { - false - } - - fn save( - &mut self, - _project: gpui::ModelHandle, - _cx: &mut ViewContext, - ) -> gpui::Task> { - unreachable!("save should not have been called"); - } - - fn save_as( - &mut self, - _project: gpui::ModelHandle, - _abs_path: std::path::PathBuf, - _cx: &mut ViewContext, - ) -> gpui::Task> { - unreachable!("save_as should not have been called"); - } - - fn reload( - &mut self, - _project: gpui::ModelHandle, - _cx: &mut ViewContext, - ) -> gpui::Task> { - gpui::Task::ready(Ok(())) + }; + HighlightStyle { + color: fg, + underline, + ..Default::default() + } +} + +fn alac_color_to_gpui_color(allac_color: &AnsiColor) -> Color { + match allac_color { + alacritty_terminal::ansi::Color::Named(n) => match n { + alacritty_terminal::ansi::NamedColor::Black => Color::black(), + alacritty_terminal::ansi::NamedColor::Red => Color::red(), + alacritty_terminal::ansi::NamedColor::Green => Color::green(), + alacritty_terminal::ansi::NamedColor::Yellow => Color::yellow(), + alacritty_terminal::ansi::NamedColor::Blue => Color::blue(), + alacritty_terminal::ansi::NamedColor::Magenta => Color::new(188, 63, 188, 1), + alacritty_terminal::ansi::NamedColor::Cyan => Color::new(17, 168, 205, 1), + alacritty_terminal::ansi::NamedColor::White => Color::white(), + alacritty_terminal::ansi::NamedColor::BrightBlack => Color::new(102, 102, 102, 1), + alacritty_terminal::ansi::NamedColor::BrightRed => Color::new(102, 102, 102, 1), + alacritty_terminal::ansi::NamedColor::BrightGreen => Color::new(35, 209, 139, 1), + alacritty_terminal::ansi::NamedColor::BrightYellow => Color::new(245, 245, 67, 1), + alacritty_terminal::ansi::NamedColor::BrightBlue => Color::new(59, 142, 234, 1), + alacritty_terminal::ansi::NamedColor::BrightMagenta => Color::new(214, 112, 214, 1), + alacritty_terminal::ansi::NamedColor::BrightCyan => Color::new(41, 184, 219, 1), + alacritty_terminal::ansi::NamedColor::BrightWhite => Color::new(229, 229, 229, 1), + alacritty_terminal::ansi::NamedColor::Foreground => Color::white(), + alacritty_terminal::ansi::NamedColor::Background => Color::black(), + alacritty_terminal::ansi::NamedColor::Cursor => Color::white(), + alacritty_terminal::ansi::NamedColor::DimBlack => Color::white(), + alacritty_terminal::ansi::NamedColor::DimRed => Color::white(), + alacritty_terminal::ansi::NamedColor::DimGreen => Color::white(), + alacritty_terminal::ansi::NamedColor::DimYellow => Color::white(), + alacritty_terminal::ansi::NamedColor::DimBlue => Color::white(), + alacritty_terminal::ansi::NamedColor::DimMagenta => Color::white(), + alacritty_terminal::ansi::NamedColor::DimCyan => Color::white(), + alacritty_terminal::ansi::NamedColor::DimWhite => Color::white(), + alacritty_terminal::ansi::NamedColor::BrightForeground => Color::white(), + alacritty_terminal::ansi::NamedColor::DimForeground => Color::white(), + }, //Theme defined + alacritty_terminal::ansi::Color::Spec(rgb) => Color::new(rgb.r, rgb.g, rgb.b, 1), + alacritty_terminal::ansi::Color::Indexed(_) => Color::white(), //Color cube weirdness } } From 2b1fa07e89782b8ce5c9790c9a5a1564fdfe7308 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 24 Jun 2022 15:12:30 -0700 Subject: [PATCH 05/20] Working on selection --- crates/terminal/src/terminal.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index c8f936bda9..ed5d0b00ff 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -7,6 +7,7 @@ use alacritty_terminal::{ event_loop::{EventLoop, Msg, Notifier}, grid::Indexed, index::Point, + selection, sync::FairMutex, term::{ cell::{Cell, Flags}, @@ -15,8 +16,10 @@ use alacritty_terminal::{ }, tty, Term, }; +use editor::CursorShape; use futures::{ channel::mpsc::{unbounded, UnboundedSender}, + future::select, StreamExt, }; use gpui::{ @@ -492,6 +495,7 @@ impl Element for TerminalEl { )); let content = term.renderable_content(); + let selection = content.selection; let cursor = RectF::new( vec2f( @@ -510,6 +514,9 @@ impl Element for TerminalEl { color: Some(Color::white()), ..Default::default() }; + + let select_boxes = vec![]; + for cell in content.display_iter { let Indexed { point: Point { line, .. }, @@ -518,6 +525,9 @@ impl Element for TerminalEl { }, //TODO: Learn what 'CellExtra does' } = cell; + if let Some(selection) = selection { + selection.contains_cell(&cell, ???, CursorShape::Block); + } let new_highlight = make_style_from_cell(fg, flags); HighlightStyle { color: Some(alac_color_to_gpui_color(fg)), From 24d671ed3f0cd795eda2b5ff1f8c313ae7c5c532 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 27 Jun 2022 18:31:42 -0700 Subject: [PATCH 06/20] First bits of polish --- assets/keymaps/default.json | 3 +- crates/terminal/src/element.rs | 331 +++++++++++++++++++++++++++++++ crates/terminal/src/terminal.rs | 340 +++----------------------------- crates/zed/src/main.rs | 1 + 4 files changed, 361 insertions(+), 314 deletions(-) create mode 100644 crates/terminal/src/element.rs diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index f9254176a9..232fd4db1f 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -366,7 +366,8 @@ "up": "terminal::UP", "down": "terminal::DOWN", "tab": "terminal::TAB", - "cmd-k": "terminal::Clear" + "cmd-k": "terminal::Clear", + "cmd-v": "terminal::Paste" } } ] \ No newline at end of file diff --git a/crates/terminal/src/element.rs b/crates/terminal/src/element.rs new file mode 100644 index 0000000000..c58220ac88 --- /dev/null +++ b/crates/terminal/src/element.rs @@ -0,0 +1,331 @@ +use alacritty_terminal::{ + ansi::Color as AnsiColor, + event_loop::Msg, + grid::{Indexed, Scroll}, + index::Point, + sync::FairMutex, + term::{ + cell::{Cell, Flags}, + RenderableCursor, SizeInfo, + }, + Term, +}; +use gpui::{ + color::Color, + elements::*, + fonts::{HighlightStyle, TextStyle, Underline}, + geometry::{rect::RectF, vector::vec2f}, + json::json, + text_layout::Line, + Event, PaintContext, Quad, +}; +use mio_extras::channel::Sender; +use ordered_float::OrderedFloat; +use settings::Settings; +use std::sync::Arc; + +use crate::{Input, ZedListener}; + +const DEBUG_GRID: bool = false; +const ALACRITTY_SCROLL_MULTIPLIER: f32 = 3.; + +pub struct TerminalEl { + term: Arc>>, + pty_tx: Sender, + size: SizeInfo, +} + +impl TerminalEl { + pub fn new( + term: Arc>>, + pty_tx: Sender, + size: SizeInfo, + ) -> TerminalEl { + TerminalEl { term, pty_tx, size } + } +} + +pub struct LayoutState { + lines: Vec, + line_height: f32, + em_width: f32, + cursor: Option, +} + +impl Element for TerminalEl { + type LayoutState = LayoutState; + type PaintState = (); + + fn layout( + &mut self, + constraint: gpui::SizeConstraint, + cx: &mut gpui::LayoutContext, + ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) { + let size = constraint.max; + let settings = cx.global::(); + let theme = &settings.theme.editor; + //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: theme.text_color, + font_family_id: settings.buffer_font_family, + font_family_name, + font_id, + 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 + .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 cursor = None; + 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; + + if cell.point == content.cursor.point { + cursor = make_cursor(em_width, line_height, content.cursor); + } + + let new_highlight = make_style_from_cell(fg, flags); + + 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))); + + let shaped_lines = layout_highlighted_chunks( + lines.iter().map(|(text, style)| (text.as_str(), *style)), + &text_style, + cx.text_layout_cache, + &cx.font_cache, + usize::MAX, + line_count, + ); + + ( + constraint.max, + LayoutState { + lines: shaped_lines, + line_height, + em_width, + cursor, + }, + ) + } + + fn paint( + &mut self, + bounds: gpui::geometry::rect::RectF, + visible_bounds: gpui::geometry::rect::RectF, + layout: &mut Self::LayoutState, + cx: &mut gpui::PaintContext, + ) -> Self::PaintState { + let origin = bounds.origin() + vec2f(layout.em_width, 0.); + + let mut line_origin = origin; + for line in &layout.lines { + let boundaries = RectF::new(line_origin, vec2f(bounds.width(), layout.line_height)); + + if boundaries.intersects(visible_bounds) { + line.paint(line_origin, visible_bounds, layout.line_height, cx); + } + + line_origin.set_y(boundaries.max_y()); + } + + if let Some(c) = layout.cursor { + let new_origin = origin + c.origin(); + let new_cursor = RectF::new(new_origin, c.size()); + cx.scene.push_quad(Quad { + bounds: new_cursor, + background: Some(Color::red()), + border: Default::default(), + corner_radius: 0., + }); + } + + if DEBUG_GRID { + draw_debug_grid(bounds, layout, cx); + } + } + + fn dispatch_event( + &mut self, + event: &gpui::Event, + _bounds: gpui::geometry::rect::RectF, + _visible_bounds: gpui::geometry::rect::RectF, + layout: &mut Self::LayoutState, + _paint: &mut Self::PaintState, + cx: &mut gpui::EventContext, + ) -> bool { + match event { + Event::ScrollWheel { delta, .. } => { + 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); + true + } + Event::KeyDown { + input: Some(input), .. + } => { + cx.dispatch_action(Input(input.to_string())); + true + } + _ => false, + } + } + + fn debug( + &self, + _bounds: gpui::geometry::rect::RectF, + _layout: &Self::LayoutState, + _paint: &Self::PaintState, + _cx: &gpui::DebugContext, + ) -> gpui::serde_json::Value { + json!({ + "type": "TerminalElement", + }) + } +} + +fn make_style_from_cell(fg: &AnsiColor, flags: &Flags) -> HighlightStyle { + let fg = Some(alac_color_to_gpui_color(fg)); + let underline = if flags.contains(Flags::UNDERLINE) { + Some(Underline { + color: fg, + squiggly: false, + thickness: OrderedFloat(1.), + }) + } else { + None + }; + HighlightStyle { + color: fg, + underline, + ..Default::default() + } +} + +fn alac_color_to_gpui_color(allac_color: &AnsiColor) -> Color { + match allac_color { + alacritty_terminal::ansi::Color::Named(n) => match n { + alacritty_terminal::ansi::NamedColor::Black => Color::black(), + alacritty_terminal::ansi::NamedColor::Red => Color::red(), + alacritty_terminal::ansi::NamedColor::Green => Color::green(), + alacritty_terminal::ansi::NamedColor::Yellow => Color::yellow(), + alacritty_terminal::ansi::NamedColor::Blue => Color::blue(), + alacritty_terminal::ansi::NamedColor::Magenta => Color::new(188, 63, 188, 1), + alacritty_terminal::ansi::NamedColor::Cyan => Color::new(17, 168, 205, 1), + alacritty_terminal::ansi::NamedColor::White => Color::white(), + alacritty_terminal::ansi::NamedColor::BrightBlack => Color::new(102, 102, 102, 1), + alacritty_terminal::ansi::NamedColor::BrightRed => Color::new(102, 102, 102, 1), + alacritty_terminal::ansi::NamedColor::BrightGreen => Color::new(35, 209, 139, 1), + alacritty_terminal::ansi::NamedColor::BrightYellow => Color::new(245, 245, 67, 1), + alacritty_terminal::ansi::NamedColor::BrightBlue => Color::new(59, 142, 234, 1), + alacritty_terminal::ansi::NamedColor::BrightMagenta => Color::new(214, 112, 214, 1), + alacritty_terminal::ansi::NamedColor::BrightCyan => Color::new(41, 184, 219, 1), + alacritty_terminal::ansi::NamedColor::BrightWhite => Color::new(229, 229, 229, 1), + alacritty_terminal::ansi::NamedColor::Foreground => Color::white(), + alacritty_terminal::ansi::NamedColor::Background => Color::black(), + alacritty_terminal::ansi::NamedColor::Cursor => Color::white(), + alacritty_terminal::ansi::NamedColor::DimBlack => Color::white(), + alacritty_terminal::ansi::NamedColor::DimRed => Color::white(), + alacritty_terminal::ansi::NamedColor::DimGreen => Color::white(), + alacritty_terminal::ansi::NamedColor::DimYellow => Color::white(), + alacritty_terminal::ansi::NamedColor::DimBlue => Color::white(), + alacritty_terminal::ansi::NamedColor::DimMagenta => Color::white(), + alacritty_terminal::ansi::NamedColor::DimCyan => Color::white(), + alacritty_terminal::ansi::NamedColor::DimWhite => Color::white(), + alacritty_terminal::ansi::NamedColor::BrightForeground => Color::white(), + alacritty_terminal::ansi::NamedColor::DimForeground => Color::white(), + }, //Theme defined + alacritty_terminal::ansi::Color::Spec(rgb) => Color::new(rgb.r, rgb.g, rgb.b, 1), + alacritty_terminal::ansi::Color::Indexed(_) => Color::white(), //Color cube weirdness + } +} + +fn make_cursor(em_width: f32, line_height: f32, cursor: RenderableCursor) -> Option { + Some(RectF::new( + vec2f( + cursor.point.column.0 as f32 * em_width, + cursor.point.line.0 as f32 * line_height, + ), + vec2f(em_width, line_height), + )) +} + +fn draw_debug_grid(bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContext) { + for col in 0..(bounds.0[2] / layout.em_width) as usize { + let rect_origin = bounds.origin() + vec2f(col as f32 * layout.em_width, 0.); + let line = RectF::new(rect_origin, vec2f(1., bounds.0[3])); + cx.scene.push_quad(Quad { + bounds: line, + background: Some(Color::green()), + border: Default::default(), + corner_radius: 0., + }); + } + for row in 0..(bounds.0[3] / layout.line_height) as usize { + let rect_origin = bounds.origin() + vec2f(0., row as f32 * layout.line_height); + let line = RectF::new(rect_origin, vec2f(bounds.0[2], 1.)); + cx.scene.push_quad(Quad { + bounds: line, + background: Some(Color::green()), + border: Default::default(), + corner_radius: 0., + }); + } +} diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index ed5d0b00ff..d8335e423b 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -1,47 +1,29 @@ -use std::sync::Arc; - use alacritty_terminal::{ - ansi::Color as AnsiColor, config::{Config, Program, PtyConfig}, event::{Event as AlacTermEvent, EventListener, Notify}, event_loop::{EventLoop, Msg, Notifier}, - grid::Indexed, - index::Point, - selection, + grid::Scroll, sync::FairMutex, - term::{ - cell::{Cell, Flags}, - color::Rgb, - SizeInfo, - }, + term::{color::Rgb, SizeInfo}, tty, Term, }; -use editor::CursorShape; + use futures::{ channel::mpsc::{unbounded, UnboundedSender}, - future::select, StreamExt, }; use gpui::{ - actions, - color::Color, - elements::*, - fonts::{with_font_cache, HighlightStyle, TextStyle, Underline}, - geometry::{rect::RectF, vector::vec2f}, - impl_internal_actions, - json::json, - platform::CursorStyle, - text_layout::Line, - ClipboardItem, Entity, - Event::KeyDown, - MutableAppContext, Quad, View, ViewContext, + actions, elements::*, impl_internal_actions, platform::CursorStyle, ClipboardItem, Entity, + MutableAppContext, View, ViewContext, }; -use ordered_float::OrderedFloat; use project::{Project, ProjectPath}; use settings::Settings; use smallvec::SmallVec; +use std::sync::Arc; use workspace::{Item, Workspace}; +use crate::element::TerminalEl; + //ASCII Control characters on a keyboard //Consts -> Structs -> Impls -> Functions, Vaguely in order of importance const ETX_CHAR: char = 3_u8 as char; //'End of text', the control code for 'ctrl-c' @@ -53,15 +35,17 @@ const LEFT_SEQ: &str = "\x1b[D"; const RIGHT_SEQ: &str = "\x1b[C"; const UP_SEQ: &str = "\x1b[A"; const DOWN_SEQ: &str = "\x1b[B"; -const CLEAR_SEQ: &str = "\x1b[2J"; +const CLEAR_SEQ: &str = "\x1b[H\x1b[2J"; const DEFAULT_TITLE: &str = "Terminal"; +pub mod element; + #[derive(Clone, Default, Debug, PartialEq, Eq)] -struct Input(String); +pub struct Input(pub String); actions!( terminal, - [Deploy, SIGINT, ESCAPE, Quit, DEL, RETURN, LEFT, RIGHT, UP, DOWN, TAB, Clear] + [SIGINT, ESCAPE, DEL, RETURN, LEFT, RIGHT, UP, DOWN, TAB, Clear, Paste, Deploy, Quit] ); impl_internal_actions!(terminal, [Input]); @@ -79,6 +63,7 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(Terminal::down); cx.add_action(Terminal::tab); cx.add_action(Terminal::clear); + cx.add_action(Terminal::paste); } #[derive(Clone)] @@ -128,20 +113,18 @@ impl Terminal { }) .detach(); - //TODO: Load from settings let pty_config = PtyConfig { shell: Some(Program::Just("zsh".to_string())), working_directory: None, hold: false, }; - //TODO: Properly configure this let config = Config { pty_config: pty_config.clone(), ..Default::default() }; - //TODO: derive this + //TODO figure out how to derive this better let size_info = SizeInfo::new(400., 100.0, 5., 5., 0., 0., false); //Set up the terminal... @@ -189,16 +172,11 @@ impl Terminal { } } AlacTermEvent::PtyWrite(out) => self.write_to_pty(&Input(out), cx), - //TODO: - //What this is supposed to do is check the cursor state, then set it on the platform. - //See Processor::reset_mouse_cursor() and Processor::cursor_state() in alacritty/src/input.rs - //to see how this is Calculated. Question: Does this flow make sense with how GPUI hadles - //the mouse? AlacTermEvent::MouseCursorDirty => { //Calculate new cursor style. + //TODO //Check on correctly handling mouse events for terminals cx.platform().set_cursor_style(CursorStyle::Arrow); //??? - println!("Mouse cursor dirty") } AlacTermEvent::Title(title) => { self.title = title; @@ -251,9 +229,16 @@ impl Terminal { cx.emit(ZedTermEvent::CloseTerminal); } + fn paste(&mut self, _: &Paste, cx: &mut ViewContext) { + if let Some(item) = cx.read_from_clipboard() { + self.write_to_pty(&Input(item.text().to_owned()), cx); + } + } + fn write_to_pty(&mut self, input: &Input, cx: &mut ViewContext) { //iTerm bell behavior, bell stays until terminal is interacted with self.has_bell = false; + self.term.lock().scroll_display(Scroll::Bottom); cx.emit(ZedTermEvent::TitleChanged); self.pty_tx.notify(input.0.clone().into_bytes()); } @@ -313,7 +298,10 @@ impl View for Terminal { fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox { let _theme = cx.global::().theme.clone(); - TerminalEl::new(self.term.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) .contained() // .with_style(theme.terminal.container) .boxed() @@ -411,277 +399,3 @@ impl Item for Terminal { matches!(event, &ZedTermEvent::CloseTerminal) } } - -struct TerminalEl { - term: Arc>>, -} - -impl TerminalEl { - fn new(term: Arc>>) -> TerminalEl { - TerminalEl { term } - } -} - -struct LayoutState { - lines: Vec, - line_height: f32, - cursor: RectF, -} -/* TODO point calculation for selection - * take the current point's x: - * - subtract padding - * - divide by cell width - * - take the minimum of the x coord and the last colum of the size info - * Take the current point's y: - * - Subtract padding - * - Divide by cell height - * - Take the minimum of the y coord and the last line - * - * With this x and y, pass to term::viewport_to_point (module function) - * Also pass in the display offset from the term.grid().display_offset() - * (Display offset is for scrolling) - */ - -/* TODO Selection - * 1. On click, calculate the single, double, and triple click based on timings - * 2. Convert mouse location to a terminal point - * 3. Generate each of the three kinds of selection needed - * 4. Assign a selection to the terminal's selection variable - * How to render? - * 1. On mouse moved, calculate a terminal point - * 2. if (lmb_pressed || rmb_pressed) && (self.ctx.modifiers().shift() || !self.ctx.mouse_mode() - * 3. Take the selection from the terminal, call selection.update(), and put it back - */ - -/* TODO Scroll - * 1. Convert scroll to a pixel delta (alacritty/src/input > Processor::mouse_wheel_input) - * 2. Divide by cell height - * 3. Create an alacritty_terminal::Scroll::Delta() object and call `self.terminal.scroll_display(scroll);` - * 4. Maybe do a cx.notify, just in case. - * 5. Also update the selected area, just check out for the logic alacritty/src/event.rs > ActionContext::scroll - */ -impl Element for TerminalEl { - type LayoutState = LayoutState; - type PaintState = (); - - fn layout( - &mut self, - constraint: gpui::SizeConstraint, - cx: &mut gpui::LayoutContext, - ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) { - let size = constraint.max; - //Get terminal content - let mut term = self.term.lock(); - - //Set up text rendering - - let text_style = with_font_cache(cx.font_cache.clone(), || TextStyle { - color: Color::white(), - ..Default::default() - }); - let line_height = cx.font_cache.line_height(text_style.font_size); - let em_width = cx - .font_cache() - .em_width(text_style.font_id, text_style.font_size); - - term.resize(SizeInfo::new( - size.x(), - size.y(), - em_width, - line_height, - 0., - 0., - false, - )); - - let content = term.renderable_content(); - let selection = content.selection; - - let cursor = RectF::new( - vec2f( - content.cursor.point.column.0 as f32 * em_width, - content.cursor.point.line.0 as f32 * line_height, - ), - vec2f(em_width, line_height), - ); - - let mut lines: Vec<(String, Option)> = vec![]; - let mut last_line = 0; - - let mut cur_chunk = String::new(); - - let mut cur_highlight = HighlightStyle { - color: Some(Color::white()), - ..Default::default() - }; - - let select_boxes = vec![]; - - 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; - - if let Some(selection) = selection { - selection.contains_cell(&cell, ???, CursorShape::Block); - } - let new_highlight = make_style_from_cell(fg, flags); - HighlightStyle { - color: Some(alac_color_to_gpui_color(fg)), - ..Default::default() - }; - - if line != last_line { - 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))); - - let shaped_lines = layout_highlighted_chunks( - lines.iter().map(|(text, style)| (text.as_str(), *style)), - &text_style, - cx.text_layout_cache, - &cx.font_cache, - usize::MAX, - last_line as usize, - ); - - ( - constraint.max, - LayoutState { - lines: shaped_lines, - line_height, - cursor, - }, - ) - } - - fn paint( - &mut self, - bounds: gpui::geometry::rect::RectF, - visible_bounds: gpui::geometry::rect::RectF, - layout: &mut Self::LayoutState, - cx: &mut gpui::PaintContext, - ) -> Self::PaintState { - let mut origin = bounds.origin(); - - for line in &layout.lines { - let boundaries = RectF::new(origin, vec2f(bounds.width(), layout.line_height)); - - if boundaries.intersects(visible_bounds) { - line.paint(origin, visible_bounds, layout.line_height, cx); - } - - origin.set_y(boundaries.max_y()); - } - - let new_origin = bounds.origin() + layout.cursor.origin(); - let new_cursor = RectF::new(new_origin, layout.cursor.size()); - - cx.scene.push_quad(Quad { - bounds: new_cursor, - background: Some(Color::red()), - border: Default::default(), - corner_radius: 0., - }); - } - - fn dispatch_event( - &mut self, - event: &gpui::Event, - _bounds: gpui::geometry::rect::RectF, - _visible_bounds: gpui::geometry::rect::RectF, - _layout: &mut Self::LayoutState, - _paint: &mut Self::PaintState, - cx: &mut gpui::EventContext, - ) -> bool { - match event { - KeyDown { - input: Some(input), .. - } => { - cx.dispatch_action(Input(input.to_string())); - true - } - _ => false, - } - } - - fn debug( - &self, - _bounds: gpui::geometry::rect::RectF, - _layout: &Self::LayoutState, - _paint: &Self::PaintState, - _cx: &gpui::DebugContext, - ) -> gpui::serde_json::Value { - json!({ - "type": "TerminalElement", - }) - } -} - -fn make_style_from_cell(fg: &AnsiColor, flags: &Flags) -> HighlightStyle { - let fg = Some(alac_color_to_gpui_color(fg)); - let underline = if flags.contains(Flags::UNDERLINE) { - Some(Underline { - color: fg, - squiggly: false, - thickness: OrderedFloat(1.), - }) - } else { - None - }; - HighlightStyle { - color: fg, - underline, - ..Default::default() - } -} - -fn alac_color_to_gpui_color(allac_color: &AnsiColor) -> Color { - match allac_color { - alacritty_terminal::ansi::Color::Named(n) => match n { - alacritty_terminal::ansi::NamedColor::Black => Color::black(), - alacritty_terminal::ansi::NamedColor::Red => Color::red(), - alacritty_terminal::ansi::NamedColor::Green => Color::green(), - alacritty_terminal::ansi::NamedColor::Yellow => Color::yellow(), - alacritty_terminal::ansi::NamedColor::Blue => Color::blue(), - alacritty_terminal::ansi::NamedColor::Magenta => Color::new(188, 63, 188, 1), - alacritty_terminal::ansi::NamedColor::Cyan => Color::new(17, 168, 205, 1), - alacritty_terminal::ansi::NamedColor::White => Color::white(), - alacritty_terminal::ansi::NamedColor::BrightBlack => Color::new(102, 102, 102, 1), - alacritty_terminal::ansi::NamedColor::BrightRed => Color::new(102, 102, 102, 1), - alacritty_terminal::ansi::NamedColor::BrightGreen => Color::new(35, 209, 139, 1), - alacritty_terminal::ansi::NamedColor::BrightYellow => Color::new(245, 245, 67, 1), - alacritty_terminal::ansi::NamedColor::BrightBlue => Color::new(59, 142, 234, 1), - alacritty_terminal::ansi::NamedColor::BrightMagenta => Color::new(214, 112, 214, 1), - alacritty_terminal::ansi::NamedColor::BrightCyan => Color::new(41, 184, 219, 1), - alacritty_terminal::ansi::NamedColor::BrightWhite => Color::new(229, 229, 229, 1), - alacritty_terminal::ansi::NamedColor::Foreground => Color::white(), - alacritty_terminal::ansi::NamedColor::Background => Color::black(), - alacritty_terminal::ansi::NamedColor::Cursor => Color::white(), - alacritty_terminal::ansi::NamedColor::DimBlack => Color::white(), - alacritty_terminal::ansi::NamedColor::DimRed => Color::white(), - alacritty_terminal::ansi::NamedColor::DimGreen => Color::white(), - alacritty_terminal::ansi::NamedColor::DimYellow => Color::white(), - alacritty_terminal::ansi::NamedColor::DimBlue => Color::white(), - alacritty_terminal::ansi::NamedColor::DimMagenta => Color::white(), - alacritty_terminal::ansi::NamedColor::DimCyan => Color::white(), - alacritty_terminal::ansi::NamedColor::DimWhite => Color::white(), - alacritty_terminal::ansi::NamedColor::BrightForeground => Color::white(), - alacritty_terminal::ansi::NamedColor::DimForeground => Color::white(), - }, //Theme defined - alacritty_terminal::ansi::Color::Spec(rgb) => Color::new(rgb.r, rgb.g, rgb.b, 1), - alacritty_terminal::ansi::Color::Indexed(_) => Color::white(), //Color cube weirdness - } -} diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index e04b92dc0a..c7a7e24c5a 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -36,6 +36,7 @@ use std::{ thread, time::Duration, }; +use terminal; use theme::{ThemeRegistry, DEFAULT_THEME_NAME}; use util::{ResultExt, TryFutureExt}; use workspace::{self, AppState, NewFile, OpenPaths}; From f28fb5797f7d0f954925f6dd1252f948c7cfe363 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 28 Jun 2022 15:44:41 -0700 Subject: [PATCH 07/20] Fixed scrolling and cursor location --- crates/terminal/src/element.rs | 64 ++++++++++------------------------ 1 file changed, 19 insertions(+), 45 deletions(-) diff --git a/crates/terminal/src/element.rs b/crates/terminal/src/element.rs index c58220ac88..111a2fd654 100644 --- a/crates/terminal/src/element.rs +++ b/crates/terminal/src/element.rs @@ -6,7 +6,7 @@ use alacritty_terminal::{ sync::FairMutex, term::{ cell::{Cell, Flags}, - RenderableCursor, SizeInfo, + SizeInfo, }, Term, }; @@ -17,7 +17,7 @@ use gpui::{ geometry::{rect::RectF, vector::vec2f}, json::json, text_layout::Line, - Event, PaintContext, Quad, + Event, Quad, }; use mio_extras::channel::Sender; use ordered_float::OrderedFloat; @@ -26,7 +26,6 @@ use std::sync::Arc; use crate::{Input, ZedListener}; -const DEBUG_GRID: bool = false; const ALACRITTY_SCROLL_MULTIPLIER: f32 = 3.; pub struct TerminalEl { @@ -105,7 +104,6 @@ impl Element for TerminalEl { //Start rendering let content = term.renderable_content(); - let mut cursor = None; let mut lines: Vec<(String, Option)> = vec![]; let mut last_line = 0; let mut line_count = 1; @@ -124,10 +122,6 @@ impl Element for TerminalEl { }, //TODO: Learn what 'CellExtra does' } = cell; - if cell.point == content.cursor.point { - cursor = make_cursor(em_width, line_height, content.cursor); - } - let new_highlight = make_style_from_cell(fg, flags); if line != last_line { @@ -154,6 +148,20 @@ impl Element for TerminalEl { line_count, ); + let cursor_line = content.cursor.point.line.0 + content.display_offset as i32; + let mut cursor = None; + if let Some(layout_line) = cursor_line + .try_into() + .ok() + .and_then(|cursor_line: usize| shaped_lines.get(cursor_line)) + { + 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), + )); + } + ( constraint.max, LayoutState { @@ -172,6 +180,7 @@ impl Element for TerminalEl { layout: &mut Self::LayoutState, cx: &mut gpui::PaintContext, ) -> Self::PaintState { + cx.scene.push_layer(Some(visible_bounds)); let origin = bounds.origin() + vec2f(layout.em_width, 0.); let mut line_origin = origin; @@ -190,15 +199,13 @@ impl Element for TerminalEl { let new_cursor = RectF::new(new_origin, c.size()); cx.scene.push_quad(Quad { bounds: new_cursor, - background: Some(Color::red()), + background: Some(Color::white()), border: Default::default(), corner_radius: 0., }); } - if DEBUG_GRID { - draw_debug_grid(bounds, layout, cx); - } + cx.scene.pop_layer(); } fn dispatch_event( @@ -296,36 +303,3 @@ fn alac_color_to_gpui_color(allac_color: &AnsiColor) -> Color { alacritty_terminal::ansi::Color::Indexed(_) => Color::white(), //Color cube weirdness } } - -fn make_cursor(em_width: f32, line_height: f32, cursor: RenderableCursor) -> Option { - Some(RectF::new( - vec2f( - cursor.point.column.0 as f32 * em_width, - cursor.point.line.0 as f32 * line_height, - ), - vec2f(em_width, line_height), - )) -} - -fn draw_debug_grid(bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContext) { - for col in 0..(bounds.0[2] / layout.em_width) as usize { - let rect_origin = bounds.origin() + vec2f(col as f32 * layout.em_width, 0.); - let line = RectF::new(rect_origin, vec2f(1., bounds.0[3])); - cx.scene.push_quad(Quad { - bounds: line, - background: Some(Color::green()), - border: Default::default(), - corner_radius: 0., - }); - } - for row in 0..(bounds.0[3] / layout.line_height) as usize { - let rect_origin = bounds.origin() + vec2f(0., row as f32 * layout.line_height); - let line = RectF::new(rect_origin, vec2f(bounds.0[2], 1.)); - cx.scene.push_quad(Quad { - bounds: line, - background: Some(Color::green()), - border: Default::default(), - corner_radius: 0., - }); - } -} From 38ed70d5ccb4f6f2205d393bf12a3737a160e614 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 28 Jun 2022 17:07:18 -0700 Subject: [PATCH 08/20] Added theme support --- crates/terminal/src/element.rs | 72 +++++++++++++++--------------- crates/theme/src/theme.rs | 34 ++++++++++++++ styles/src/styleTree/app.ts | 2 + styles/src/styleTree/terminal.ts | 35 +++++++++++++++ styles/src/themes/cave.ts | 3 ++ styles/src/themes/common/base16.ts | 22 ++++++--- styles/src/themes/common/theme.ts | 3 ++ 7 files changed, 131 insertions(+), 40 deletions(-) create mode 100644 styles/src/styleTree/terminal.ts diff --git a/crates/terminal/src/element.rs b/crates/terminal/src/element.rs index 111a2fd654..04828e8d65 100644 --- a/crates/terminal/src/element.rs +++ b/crates/terminal/src/element.rs @@ -23,6 +23,7 @@ use mio_extras::channel::Sender; use ordered_float::OrderedFloat; use settings::Settings; use std::sync::Arc; +use theme::TerminalStyle; use crate::{Input, ZedListener}; @@ -62,7 +63,8 @@ impl Element for TerminalEl { ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) { let size = constraint.max; let settings = cx.global::(); - let theme = &settings.theme.editor; + let editor_theme = &settings.theme.editor; + let terminal_theme = &settings.theme.terminal; //Get terminal let mut term = self.term.lock(); @@ -78,7 +80,7 @@ impl Element for TerminalEl { let font_size = settings.buffer_font_size; let text_style = TextStyle { - color: theme.text_color, + color: editor_theme.text_color, font_family_id: settings.buffer_font_family, font_family_name, font_id, @@ -122,7 +124,7 @@ impl Element for TerminalEl { }, //TODO: Learn what 'CellExtra does' } = cell; - let new_highlight = make_style_from_cell(fg, flags); + let new_highlight = make_style_from_cell(fg, flags, &terminal_theme); if line != last_line { line_count += 1; @@ -248,8 +250,8 @@ impl Element for TerminalEl { } } -fn make_style_from_cell(fg: &AnsiColor, flags: &Flags) -> HighlightStyle { - let fg = Some(alac_color_to_gpui_color(fg)); +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) { Some(Underline { color: fg, @@ -266,38 +268,38 @@ fn make_style_from_cell(fg: &AnsiColor, flags: &Flags) -> HighlightStyle { } } -fn alac_color_to_gpui_color(allac_color: &AnsiColor) -> Color { +fn alac_color_to_gpui_color(allac_color: &AnsiColor, style: &TerminalStyle) -> Color { match allac_color { alacritty_terminal::ansi::Color::Named(n) => match n { - alacritty_terminal::ansi::NamedColor::Black => Color::black(), - alacritty_terminal::ansi::NamedColor::Red => Color::red(), - alacritty_terminal::ansi::NamedColor::Green => Color::green(), - alacritty_terminal::ansi::NamedColor::Yellow => Color::yellow(), - alacritty_terminal::ansi::NamedColor::Blue => Color::blue(), - alacritty_terminal::ansi::NamedColor::Magenta => Color::new(188, 63, 188, 1), - alacritty_terminal::ansi::NamedColor::Cyan => Color::new(17, 168, 205, 1), - alacritty_terminal::ansi::NamedColor::White => Color::white(), - alacritty_terminal::ansi::NamedColor::BrightBlack => Color::new(102, 102, 102, 1), - alacritty_terminal::ansi::NamedColor::BrightRed => Color::new(102, 102, 102, 1), - alacritty_terminal::ansi::NamedColor::BrightGreen => Color::new(35, 209, 139, 1), - alacritty_terminal::ansi::NamedColor::BrightYellow => Color::new(245, 245, 67, 1), - alacritty_terminal::ansi::NamedColor::BrightBlue => Color::new(59, 142, 234, 1), - alacritty_terminal::ansi::NamedColor::BrightMagenta => Color::new(214, 112, 214, 1), - alacritty_terminal::ansi::NamedColor::BrightCyan => Color::new(41, 184, 219, 1), - alacritty_terminal::ansi::NamedColor::BrightWhite => Color::new(229, 229, 229, 1), - alacritty_terminal::ansi::NamedColor::Foreground => Color::white(), - alacritty_terminal::ansi::NamedColor::Background => Color::black(), - alacritty_terminal::ansi::NamedColor::Cursor => Color::white(), - alacritty_terminal::ansi::NamedColor::DimBlack => Color::white(), - alacritty_terminal::ansi::NamedColor::DimRed => Color::white(), - alacritty_terminal::ansi::NamedColor::DimGreen => Color::white(), - alacritty_terminal::ansi::NamedColor::DimYellow => Color::white(), - alacritty_terminal::ansi::NamedColor::DimBlue => Color::white(), - alacritty_terminal::ansi::NamedColor::DimMagenta => Color::white(), - alacritty_terminal::ansi::NamedColor::DimCyan => Color::white(), - alacritty_terminal::ansi::NamedColor::DimWhite => Color::white(), - alacritty_terminal::ansi::NamedColor::BrightForeground => Color::white(), - alacritty_terminal::ansi::NamedColor::DimForeground => Color::white(), + alacritty_terminal::ansi::NamedColor::Black => style.black, + alacritty_terminal::ansi::NamedColor::Red => style.red, + alacritty_terminal::ansi::NamedColor::Green => style.green, + alacritty_terminal::ansi::NamedColor::Yellow => style.yellow, + alacritty_terminal::ansi::NamedColor::Blue => style.blue, + alacritty_terminal::ansi::NamedColor::Magenta => style.magenta, + alacritty_terminal::ansi::NamedColor::Cyan => style.cyan, + alacritty_terminal::ansi::NamedColor::White => style.white, + alacritty_terminal::ansi::NamedColor::BrightBlack => style.bright_black, + alacritty_terminal::ansi::NamedColor::BrightRed => style.bright_red, + alacritty_terminal::ansi::NamedColor::BrightGreen => style.bright_green, + alacritty_terminal::ansi::NamedColor::BrightYellow => style.bright_yellow, + alacritty_terminal::ansi::NamedColor::BrightBlue => style.bright_blue, + alacritty_terminal::ansi::NamedColor::BrightMagenta => style.bright_magenta, + alacritty_terminal::ansi::NamedColor::BrightCyan => style.bright_cyan, + alacritty_terminal::ansi::NamedColor::BrightWhite => style.bright_white, + alacritty_terminal::ansi::NamedColor::Foreground => style.foreground, + alacritty_terminal::ansi::NamedColor::Background => style.background, + alacritty_terminal::ansi::NamedColor::Cursor => style.cursor, + alacritty_terminal::ansi::NamedColor::DimBlack => style.dim_black, + alacritty_terminal::ansi::NamedColor::DimRed => style.dim_red, + alacritty_terminal::ansi::NamedColor::DimGreen => style.dim_green, + alacritty_terminal::ansi::NamedColor::DimYellow => style.dim_yellow, + alacritty_terminal::ansi::NamedColor::DimBlue => style.dim_blue, + alacritty_terminal::ansi::NamedColor::DimMagenta => style.dim_magenta, + alacritty_terminal::ansi::NamedColor::DimCyan => style.dim_cyan, + alacritty_terminal::ansi::NamedColor::DimWhite => style.dim_white, + alacritty_terminal::ansi::NamedColor::BrightForeground => style.bright_foreground, + alacritty_terminal::ansi::NamedColor::DimForeground => style.dim_foreground, }, //Theme defined alacritty_terminal::ansi::Color::Spec(rgb) => Color::new(rgb.r, rgb.g, rgb.b, 1), alacritty_terminal::ansi::Color::Indexed(_) => Color::white(), //Color cube weirdness diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index ae269c00cb..184b1880f0 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -33,6 +33,7 @@ pub struct Theme { pub contact_notification: ContactNotification, pub update_notification: UpdateNotification, pub tooltip: TooltipStyle, + pub terminal: TerminalStyle, } #[derive(Deserialize, Default)] @@ -633,3 +634,36 @@ pub struct HoverPopover { pub prose: TextStyle, pub highlight: Color, } + +#[derive(Clone, Deserialize, Default)] +pub struct TerminalStyle { + pub black: Color, + pub red: Color, + pub green: Color, + pub yellow: Color, + pub blue: Color, + pub magenta: Color, + pub cyan: Color, + pub white: Color, + pub bright_black: Color, + pub bright_red: Color, + pub bright_green: Color, + pub bright_yellow: Color, + pub bright_blue: Color, + pub bright_magenta: Color, + pub bright_cyan: Color, + pub bright_white: Color, + pub foreground: Color, + pub background: Color, + pub cursor: Color, + pub dim_black: Color, + pub dim_red: Color, + pub dim_green: Color, + pub dim_yellow: Color, + pub dim_blue: Color, + pub dim_magenta: Color, + pub dim_cyan: Color, + pub dim_white: Color, + pub bright_foreground: Color, + pub dim_foreground: Color, +} diff --git a/styles/src/styleTree/app.ts b/styles/src/styleTree/app.ts index e015895e9c..fe67cf701d 100644 --- a/styles/src/styleTree/app.ts +++ b/styles/src/styleTree/app.ts @@ -14,6 +14,7 @@ import projectDiagnostics from "./projectDiagnostics"; import contactNotification from "./contactNotification"; import updateNotification from "./updateNotification"; import tooltip from "./tooltip"; +import terminal from "./terminal"; export const panel = { padding: { top: 12, bottom: 12 }, @@ -41,5 +42,6 @@ export default function app(theme: Theme): Object { contactNotification: contactNotification(theme), updateNotification: updateNotification(theme), tooltip: tooltip(theme), + terminal: terminal(theme), }; } diff --git a/styles/src/styleTree/terminal.ts b/styles/src/styleTree/terminal.ts new file mode 100644 index 0000000000..ef9e4f93dd --- /dev/null +++ b/styles/src/styleTree/terminal.ts @@ -0,0 +1,35 @@ +import Theme from "../themes/common/theme"; + +export default function terminal(theme: Theme) { + return { + black: theme.ramps.neutral(0).hex(), + red: theme.ramps.red(0.5).hex(), + green: theme.ramps.green(0.5).hex(), + yellow: theme.ramps.yellow(0.5).hex(), + blue: theme.ramps.blue(0.5).hex(), + magenta: theme.ramps.magenta(0.5).hex(), + cyan: theme.ramps.cyan(0.5).hex(), + white: theme.ramps.neutral(7).hex(), + brightBlack: theme.ramps.neutral(2).hex(), + brightRed: theme.ramps.red(0.25).hex(), + brightGreen: theme.ramps.green(0.25).hex(), + brightYellow: theme.ramps.yellow(0.25).hex(), + brightBlue: theme.ramps.blue(0.25).hex(), + brightMagenta: theme.ramps.magenta(0.25).hex(), + brightCyan: theme.ramps.cyan(0.25).hex(), + brightWhite: theme.ramps.neutral(7).hex(), + foreground: theme.ramps.neutral(7).hex(), + background: theme.ramps.neutral(0).hex(), + cursor: theme.ramps.neutral(7).hex(), + dimBlack: theme.ramps.neutral(7).hex(), + dimRed: theme.ramps.red(0.75).hex(), + dimGreen: theme.ramps.green(0.75).hex(), + dimYellow: theme.ramps.yellow(0.75).hex(), + dimBlue: theme.ramps.blue(0.75).hex(), + dimMagenta: theme.ramps.magenta(0.75).hex(), + dimCyan: theme.ramps.cyan(0.75).hex(), + dimWhite: theme.ramps.neutral(5).hex(), + brightForeground: theme.ramps.neutral(7).hex(), + dimForeground: theme.ramps.neutral(0).hex(), + }; +} \ No newline at end of file diff --git a/styles/src/themes/cave.ts b/styles/src/themes/cave.ts index 2e66f4baf4..d588f84c53 100644 --- a/styles/src/themes/cave.ts +++ b/styles/src/themes/cave.ts @@ -26,3 +26,6 @@ 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 diff --git a/styles/src/themes/common/base16.ts b/styles/src/themes/common/base16.ts index 21a02cde25..729cf32ee5 100644 --- a/styles/src/themes/common/base16.ts +++ b/styles/src/themes/common/base16.ts @@ -13,15 +13,25 @@ export function colorRamp(color: Color): Scale { export function createTheme( name: string, isLight: boolean, - ramps: { [rampName: string]: Scale }, + color_ramps: { [rampName: string]: Scale }, ): Theme { + let ramps: typeof color_ramps = {}; + // Chromajs mutates the underlying ramp when you call domain. This causes problems because + // we now store the ramps object in the theme so that we can pull colors out of them. + // So instead of calling domain and storing the result, we have to construct new ramps for each + // theme so that we don't modify the passed in ramps. + // This combined with an error in the type definitions for chroma js means we have to cast the colors + // function to any in order to get the colors back out from the original ramps. if (isLight) { - for (var rampName in ramps) { - ramps[rampName] = ramps[rampName].domain([1, 0]); + for (var rampName in color_ramps) { + ramps[rampName] = chroma.scale((color_ramps[rampName].colors as any)()).domain([1, 0]); } - ramps.neutral = ramps.neutral.domain([7, 0]); + ramps.neutral = chroma.scale((color_ramps.neutral.colors as any)()).domain([7, 0]); } else { - ramps.neutral = ramps.neutral.domain([0, 7]); + for (var rampName in color_ramps) { + ramps[rampName] = chroma.scale((color_ramps[rampName].colors as any)()).domain([0, 1]); + } + ramps.neutral = chroma.scale((color_ramps.neutral.colors as any)()).domain([0, 7]); } let blend = isLight ? 0.12 : 0.24; @@ -237,6 +247,7 @@ export function createTheme( return { name, + isLight, backgroundColor, borderColor, textColor, @@ -245,5 +256,6 @@ export function createTheme( syntax, player, shadow, + ramps, }; } diff --git a/styles/src/themes/common/theme.ts b/styles/src/themes/common/theme.ts index 92b1f8eff8..7f32f48974 100644 --- a/styles/src/themes/common/theme.ts +++ b/styles/src/themes/common/theme.ts @@ -1,3 +1,4 @@ +import { Scale } from "chroma-js"; import { FontWeight } from "../../common"; import { withOpacity } from "../../utils/color"; @@ -60,6 +61,7 @@ export interface Syntax { export default interface Theme { name: string; + isLight: boolean, backgroundColor: { // Basically just Title Bar // Lowest background level @@ -155,4 +157,5 @@ export default interface Theme { 8: Player; }, shadow: string; + ramps: { [rampName: string]: Scale }; } From e3834409ddc4f96b1c3ff7c7b7d154ffa0395f35 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 28 Jun 2022 18:28:13 -0700 Subject: [PATCH 09/20] Fixed focus issues with scrolling and input --- assets/keymaps/default.json | 1 - crates/gpui/src/presenter.rs | 14 +++++++++ crates/terminal/src/element.rs | 56 ++++++++++++++++++++++++++------- crates/terminal/src/terminal.rs | 41 +++++++++++++++--------- 4 files changed, 84 insertions(+), 28 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 232fd4db1f..8da4b2a9ca 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -366,7 +366,6 @@ "up": "terminal::UP", "down": "terminal::DOWN", "tab": "terminal::TAB", - "cmd-k": "terminal::Clear", "cmd-v": "terminal::Paste" } } diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 88e4d0a498..6285b1be99 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -703,6 +703,20 @@ impl<'a> EventContext<'a> { self.view_stack.last().copied() } + pub fn is_parent_view_focused(&self) -> bool { + if let Some(parent_view_id) = self.view_stack.last() { + self.app.focused_view_id(self.window_id) == Some(*parent_view_id) + } else { + false + } + } + + pub fn focus_parent_view(&mut self) { + if let Some(parent_view_id) = self.view_stack.last() { + self.app.focus(self.window_id, Some(*parent_view_id)) + } + } + pub fn dispatch_any_action(&mut self, action: Box) { self.dispatched_actions.push(DispatchDirective { dispatcher_view_id: self.view_stack.last().copied(), diff --git a/crates/terminal/src/element.rs b/crates/terminal/src/element.rs index 04828e8d65..08d49ec6d8 100644 --- a/crates/terminal/src/element.rs +++ b/crates/terminal/src/element.rs @@ -17,12 +17,12 @@ use gpui::{ geometry::{rect::RectF, vector::vec2f}, json::json, text_layout::Line, - Event, Quad, + Event, MouseRegion, Quad, }; use mio_extras::channel::Sender; use ordered_float::OrderedFloat; use settings::Settings; -use std::sync::Arc; +use std::{rc::Rc, sync::Arc}; use theme::TerminalStyle; use crate::{Input, ZedListener}; @@ -33,6 +33,7 @@ pub struct TerminalEl { term: Arc>>, pty_tx: Sender, size: SizeInfo, + view_id: usize, } impl TerminalEl { @@ -40,8 +41,14 @@ impl TerminalEl { term: Arc>>, pty_tx: Sender, size: SizeInfo, + view_id: usize, ) -> TerminalEl { - TerminalEl { term, pty_tx, size } + TerminalEl { + term, + pty_tx, + size, + view_id, + } } } @@ -183,6 +190,21 @@ impl Element for TerminalEl { cx: &mut gpui::PaintContext, ) -> Self::PaintState { cx.scene.push_layer(Some(visible_bounds)); + + cx.scene.push_mouse_region(MouseRegion { + view_id: self.view_id, + discriminant: None, + bounds: visible_bounds, + hover: None, + mouse_down: Some(Rc::new(|_, cx| cx.focus_parent_view())), + click: None, + right_mouse_down: None, + right_click: None, + drag: None, + mouse_down_out: None, + right_mouse_down_out: None, + }); + let origin = bounds.origin() + vec2f(layout.em_width, 0.); let mut line_origin = origin; @@ -214,24 +236,34 @@ impl Element for TerminalEl { &mut self, event: &gpui::Event, _bounds: gpui::geometry::rect::RectF, - _visible_bounds: gpui::geometry::rect::RectF, + visible_bounds: gpui::geometry::rect::RectF, layout: &mut Self::LayoutState, _paint: &mut Self::PaintState, cx: &mut gpui::EventContext, ) -> bool { match event { - Event::ScrollWheel { delta, .. } => { - 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); - true + Event::ScrollWheel { + delta, position, .. + } => { + 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); + true + } else { + false + } } Event::KeyDown { input: Some(input), .. } => { - cx.dispatch_action(Input(input.to_string())); - true + if cx.is_parent_view_focused() { + cx.dispatch_action(Input(input.to_string())); + true + } else { + false + } } _ => false, } diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index d8335e423b..f375ddbd91 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -84,13 +84,14 @@ struct Terminal { has_bell: bool, //Currently using iTerm bell, show bell emoji in tab until input is received } -enum ZedTermEvent { +enum Event { TitleChanged, CloseTerminal, + Activate, } impl Entity for Terminal { - type Event = ZedTermEvent; + type Event = Event; } impl Terminal { @@ -166,7 +167,7 @@ impl Terminal { if !cx.is_self_focused() { //Need to figure out how to trigger a redraw when not in focus self.has_new_content = true; //Change tab content - cx.emit(ZedTermEvent::TitleChanged); + cx.emit(Event::TitleChanged); } else { cx.notify() } @@ -180,11 +181,11 @@ impl Terminal { } AlacTermEvent::Title(title) => { self.title = title; - cx.emit(ZedTermEvent::TitleChanged); + cx.emit(Event::TitleChanged); } AlacTermEvent::ResetTitle => { self.title = DEFAULT_TITLE.to_string(); - cx.emit(ZedTermEvent::TitleChanged); + cx.emit(Event::TitleChanged); } AlacTermEvent::ClipboardStore(_, data) => { cx.write_to_clipboard(ClipboardItem::new(data)) @@ -209,7 +210,7 @@ impl Terminal { } AlacTermEvent::Bell => { self.has_bell = true; - cx.emit(ZedTermEvent::TitleChanged); + cx.emit(Event::TitleChanged); } AlacTermEvent::Exit => self.quit(&Quit, cx), } @@ -226,7 +227,7 @@ impl Terminal { } fn quit(&mut self, _: &Quit, cx: &mut ViewContext) { - cx.emit(ZedTermEvent::CloseTerminal); + cx.emit(Event::CloseTerminal); } fn paste(&mut self, _: &Paste, cx: &mut ViewContext) { @@ -239,7 +240,7 @@ impl Terminal { //iTerm bell behavior, bell stays until terminal is interacted with self.has_bell = false; self.term.lock().scroll_display(Scroll::Bottom); - cx.emit(ZedTermEvent::TitleChanged); + cx.emit(Event::TitleChanged); self.pty_tx.notify(input.0.clone().into_bytes()); } @@ -301,13 +302,19 @@ impl View for Terminal { //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) - .contained() - // .with_style(theme.terminal.container) - .boxed() + TerminalEl::new( + self.term.clone(), + self.pty_tx.0.clone(), + size_info, + cx.view_id(), + ) + .contained() + // .with_style(theme.terminal.container) + .boxed() } - fn on_focus(&mut self, _: &mut ViewContext) { + fn on_focus(&mut self, cx: &mut ViewContext) { + cx.emit(Event::Activate); self.has_new_content = false; } } @@ -392,10 +399,14 @@ impl Item for Terminal { } fn should_update_tab_on_event(event: &Self::Event) -> bool { - matches!(event, &ZedTermEvent::TitleChanged) + matches!(event, &Event::TitleChanged) } fn should_close_item_on_event(event: &Self::Event) -> bool { - matches!(event, &ZedTermEvent::CloseTerminal) + matches!(event, &Event::CloseTerminal) + } + + fn should_activate_item_on_event(event: &Self::Event) -> bool { + matches!(event, &Event::Activate) } } From db95c0d0e1130acbe50e68d6feac1585cc38d021 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 28 Jun 2022 18:45:26 -0700 Subject: [PATCH 10/20] Removed dead clear code --- crates/terminal/src/terminal.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index f375ddbd91..421cbc0433 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -1,4 +1,5 @@ use alacritty_terminal::{ + ansi::{ClearMode, Handler}, config::{Config, Program, PtyConfig}, event::{Event as AlacTermEvent, EventListener, Notify}, event_loop::{EventLoop, Msg, Notifier}, @@ -35,7 +36,6 @@ const LEFT_SEQ: &str = "\x1b[D"; const RIGHT_SEQ: &str = "\x1b[C"; const UP_SEQ: &str = "\x1b[A"; const DOWN_SEQ: &str = "\x1b[B"; -const CLEAR_SEQ: &str = "\x1b[H\x1b[2J"; const DEFAULT_TITLE: &str = "Terminal"; pub mod element; @@ -62,7 +62,6 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(Terminal::up); cx.add_action(Terminal::down); cx.add_action(Terminal::tab); - cx.add_action(Terminal::clear); cx.add_action(Terminal::paste); } @@ -279,10 +278,6 @@ impl Terminal { fn right(&mut self, _: &RIGHT, cx: &mut ViewContext) { self.write_to_pty(&Input(RIGHT_SEQ.to_string()), cx); } - - fn clear(&mut self, _: &Clear, cx: &mut ViewContext) { - self.write_to_pty(&Input(CLEAR_SEQ.to_string()), cx); - } } impl Drop for Terminal { From ff44ddc0771e6909fb20465ca487caf9d6ac1799 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 29 Jun 2022 09:38:02 -0700 Subject: [PATCH 11/20] =?UTF-8?q?Fixed=20warnings=20=F0=9F=98=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/terminal/src/terminal.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 421cbc0433..8fc01f2f9a 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -1,5 +1,4 @@ use alacritty_terminal::{ - ansi::{ClearMode, Handler}, config::{Config, Program, PtyConfig}, event::{Event as AlacTermEvent, EventListener, Notify}, event_loop::{EventLoop, Msg, Notifier}, From 93dfc63f1c43d932153af1144e1af494527d4b96 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 29 Jun 2022 13:07:44 -0700 Subject: [PATCH 12/20] Added a test with max, cludged a fix for resizing --- .swp | Bin 0 -> 12288 bytes assets/keymaps/default.json | 18 +- crates/terminal/Cargo.toml | 3 + crates/terminal/src/terminal.rs | 97 +++++--- .../src/{element.rs => terminal_element.rs} | 215 ++++++++++-------- styles/src/themes/cave.ts | 5 +- 6 files changed, 203 insertions(+), 135 deletions(-) create mode 100644 .swp rename crates/terminal/src/{element.rs => terminal_element.rs} (67%) diff --git a/.swp b/.swp new file mode 100644 index 0000000000000000000000000000000000000000..5f9940d5044d36923e5abbe428dace63080fbef8 GIT binary patch literal 12288 zcmeI%Jxjwt90u@z-JF#A1zMe3YF%7)bI4ft*yKWTk^j+?20?IA5fM?u7aYWI&`;)P z@JW*_LPslw=aT2%@^Z&-3pp7a9|fKL{!mah%c?5h7b2&r4bStinOfKe)qTDfgyTb1 z+dD`n{anXkzVH!Vpa2DyF0ek@8}1IuWp}o_ovrq0={w+p6rcbFC_n)UP=EpypuqnL zoVk`0u-PC1t>rP3Q&Lo6rcbFR!|^}M7kRyS4jE)zxn>3 zVf=-BA|J>*@`k)3FUT|Ugghb-$USn0+#)x~H8Owa3Yu`s6rcbFC_n)UP=Epypa2CZ zK!HCJFwz|J4py1UH1eVnn^dzbjgzcM<3gPEQD~id>x@-rJgUezwWgdz&uZk=S>w^G U=P>j|Y!pc}o+L$FolX<^0jKv(3;+NC literal 0 HcmV?d00001 diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 8da4b2a9ca..c4e98d9094 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 3cb4d631f3..175c741421 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 8fc01f2f9a..9adcb3122e 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 08d49ec6d8..e5149d6cb5 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.; + * MAGIC_VISUAL_WIDTH_MULTIPLIER; - //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; - } + 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); + }); + + let settings = cx.global::(); + let terminal_theme = &settings.theme.terminal; + let term = view.read(cx).term.lock(); - //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); - - 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))); + 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 d588f84c53..aab020d626 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 From 61e8c321bc98ba404483004c2dd8de61cf901e27 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 29 Jun 2022 13:08:02 -0700 Subject: [PATCH 13/20] Remove swp --- .swp | Bin 12288 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .swp diff --git a/.swp b/.swp deleted file mode 100644 index 5f9940d5044d36923e5abbe428dace63080fbef8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI%Jxjwt90u@z-JF#A1zMe3YF%7)bI4ft*yKWTk^j+?20?IA5fM?u7aYWI&`;)P z@JW*_LPslw=aT2%@^Z&-3pp7a9|fKL{!mah%c?5h7b2&r4bStinOfKe)qTDfgyTb1 z+dD`n{anXkzVH!Vpa2DyF0ek@8}1IuWp}o_ovrq0={w+p6rcbFC_n)UP=EpypuqnL zoVk`0u-PC1t>rP3Q&Lo6rcbFR!|^}M7kRyS4jE)zxn>3 zVf=-BA|J>*@`k)3FUT|Ugghb-$USn0+#)x~H8Owa3Yu`s6rcbFC_n)UP=Epypa2CZ zK!HCJFwz|J4py1UH1eVnn^dzbjgzcM<3gPEQD~id>x@-rJgUezwWgdz&uZk=S>w^G U=P>j|Y!pc}o+L$FolX<^0jKv(3;+NC From bc728c160d2bb7e5cfaa8d905134321d15784921 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 29 Jun 2022 13:19:25 -0700 Subject: [PATCH 14/20] Properly fixed the issues with the grid :D --- crates/terminal/src/terminal_element.rs | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index e5149d6cb5..d7dcafa1d6 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -24,10 +24,9 @@ use theme::TerminalStyle; 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; +const DEBUG_GRID: bool = true; pub struct TerminalEl { view: WeakViewHandle, @@ -60,17 +59,14 @@ impl Element for TerminalEl { let size = constraint.max; let settings = cx.global::(); let editor_theme = &settings.theme.editor; + let font_cache = cx.font_cache(); //Set up text rendering let text_style = TextStyle { color: editor_theme.text_color, font_family_id: settings.buffer_font_family, - font_family_name: cx - .font_cache() - .family_name(settings.buffer_font_family) - .unwrap(), - font_id: cx - .font_cache() + font_family_name: font_cache.family_name(settings.buffer_font_family).unwrap(), + font_id: font_cache .select_font(settings.buffer_font_family, &Default::default()) .unwrap(), font_size: settings.buffer_font_size, @@ -78,14 +74,11 @@ impl Element for TerminalEl { underline: Default::default(), }; - let line_height = cx.font_cache.line_height(text_style.font_size); - let cell_width = cx - .font_cache() - .em_width(text_style.font_id, text_style.font_size) - * MAGIC_VISUAL_WIDTH_MULTIPLIER; + let line_height = font_cache.line_height(text_style.font_size); + let cell_width = font_cache.em_advance(text_style.font_id, text_style.font_size); let new_size = SizeInfo::new( - size.x() - cell_width, //Padding. Really should make this more explicit + size.x() - cell_width, size.y(), cell_width, line_height, From 1c038b81abe8293a6a1eecca5e147a3eee86ffb5 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 29 Jun 2022 16:22:05 -0700 Subject: [PATCH 15/20] Finished current working directory stuff --- crates/terminal/src/terminal.rs | 19 +++++++++++++------ crates/terminal/src/terminal_element.rs | 2 +- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 9adcb3122e..d6ecfce3c3 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -19,7 +19,7 @@ use gpui::{ use project::{Project, ProjectPath}; use settings::Settings; use smallvec::SmallVec; -use std::sync::Arc; +use std::{path::PathBuf, sync::Arc}; use workspace::{Item, Workspace}; use crate::terminal_element::TerminalEl; @@ -99,7 +99,7 @@ impl Entity for Terminal { impl Terminal { ///Create a new Terminal view. This spawns a task, a thread, and opens the TTY devices - fn new(cx: &mut ViewContext) -> Self { + fn new(cx: &mut ViewContext, working_directory: Option) -> Self { //Spawn a task so the Alacritty EventLoop can communicate with us in a view context let (events_tx, mut events_rx) = unbounded(); cx.spawn_weak(|this, mut cx| async move { @@ -119,7 +119,7 @@ impl Terminal { let pty_config = PtyConfig { shell: Some(Program::Just("zsh".to_string())), - working_directory: None, + working_directory, hold: false, }; @@ -128,7 +128,7 @@ impl Terminal { ..Default::default() }; - //TODO figure out how to derive this better + //The details here don't matter, the terminal will be resized on layout let size_info = SizeInfo::new(200., 100.0, 5., 5., 0., 0., false); //Set up the terminal... @@ -234,7 +234,14 @@ impl Terminal { ///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); + let project = workspace.project().read(cx); + let abs_path = project + .active_entry() + .and_then(|entry_id| project.worktree_for_entry(entry_id, cx)) + .and_then(|worktree_handle| worktree_handle.read(cx).as_local()) + .map(|wt| wt.abs_path().to_path_buf()); + + workspace.add_item(Box::new(cx.add_view(|cx| Terminal::new(cx, abs_path))), cx); } ///Send the shutdown message to Alacritty @@ -421,7 +428,7 @@ mod tests { #[gpui::test] async fn test_terminal(cx: &mut TestAppContext) { - let terminal = cx.add_view(Default::default(), |cx| Terminal::new(cx)); + let terminal = cx.add_view(Default::default(), |cx| Terminal::new(cx, None)); terminal.update(cx, |terminal, cx| { terminal.write_to_pty(&Input(("expr 3 + 4".to_string()).to_string()), cx); diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index d7dcafa1d6..2002b5c144 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -26,7 +26,7 @@ use crate::{Input, ScrollTerminal, Terminal}; const ALACRITTY_SCROLL_MULTIPLIER: f32 = 3.; #[cfg(debug_assertions)] -const DEBUG_GRID: bool = true; +const DEBUG_GRID: bool = false; pub struct TerminalEl { view: WeakViewHandle, From cde11fe4e7297069e935d08fd57d3cfe09ed373e Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 29 Jun 2022 18:27:27 -0700 Subject: [PATCH 16/20] Support for all 24 bits of colors --- crates/terminal/src/terminal.rs | 39 ++++++++++++--- crates/terminal/src/terminal_element.rs | 64 ++++++++++++++++++++++++- 2 files changed, 95 insertions(+), 8 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index d6ecfce3c3..6e62ce2a9f 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -4,7 +4,7 @@ use alacritty_terminal::{ event_loop::{EventLoop, Msg, Notifier}, grid::Scroll, sync::FairMutex, - term::{color::Rgb, SizeInfo}, + term::{color::Rgb as AlacRgb, SizeInfo}, tty, Term, }; @@ -13,8 +13,8 @@ use futures::{ StreamExt, }; use gpui::{ - actions, elements::*, impl_internal_actions, platform::CursorStyle, ClipboardItem, Entity, - MutableAppContext, View, ViewContext, + actions, color::Color, elements::*, impl_internal_actions, platform::CursorStyle, + ClipboardItem, Entity, MutableAppContext, View, ViewContext, }; use project::{Project, ProjectPath}; use settings::Settings; @@ -22,7 +22,7 @@ use smallvec::SmallVec; use std::{path::PathBuf, sync::Arc}; use workspace::{Item, Workspace}; -use crate::terminal_element::TerminalEl; +use crate::terminal_element::{get_color_at_index, TerminalEl}; //ASCII Control characters on a keyboard //Consts -> Structs -> Impls -> Functions, Vaguely in order of importance @@ -203,9 +203,26 @@ impl Terminal { cx, ), AlacTermEvent::ColorRequest(index, format) => { - //TODO test this as well - //TODO: change to getting the display colors, like alacrityy, instead of a default - let color = self.term.lock().colors()[index].unwrap_or(Rgb::default()); + let color = self.term.lock().colors()[index].unwrap_or_else(|| { + let term_style = &cx.global::().theme.terminal; + match index { + 0..=255 => to_alac_rgb(get_color_at_index(&(index as u8), term_style)), + 256 => to_alac_rgb(term_style.foreground), + 257 => to_alac_rgb(term_style.background), + 258 => to_alac_rgb(term_style.cursor), + 259 => to_alac_rgb(term_style.dim_black), + 260 => to_alac_rgb(term_style.dim_red), + 261 => to_alac_rgb(term_style.dim_green), + 262 => to_alac_rgb(term_style.dim_yellow), + 263 => to_alac_rgb(term_style.dim_blue), + 264 => to_alac_rgb(term_style.dim_magenta), + 265 => to_alac_rgb(term_style.dim_cyan), + 266 => to_alac_rgb(term_style.dim_white), + 267 => to_alac_rgb(term_style.bright_foreground), + 268 => to_alac_rgb(term_style.black), //Dim Background, non-standard + _ => AlacRgb { r: 0, g: 0, b: 0 }, + } + }); self.write_to_pty(&Input(format(color)), cx) } AlacTermEvent::CursorBlinkingChange => { @@ -420,6 +437,14 @@ impl Item for Terminal { } } +fn to_alac_rgb(color: Color) -> AlacRgb { + AlacRgb { + r: color.r, + g: color.g, + b: color.g, + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index 2002b5c144..b0c01391ca 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -329,10 +329,59 @@ fn alac_color_to_gpui_color(allac_color: &AnsiColor, style: &TerminalStyle) -> C alacritty_terminal::ansi::NamedColor::DimForeground => style.dim_foreground, }, //Theme defined alacritty_terminal::ansi::Color::Spec(rgb) => Color::new(rgb.r, rgb.g, rgb.b, 1), - alacritty_terminal::ansi::Color::Indexed(_) => Color::white(), //Color cube weirdness + alacritty_terminal::ansi::Color::Indexed(i) => get_color_at_index(i, style), //Color cube weirdness } } +pub fn get_color_at_index(index: &u8, style: &TerminalStyle) -> Color { + match index { + 0 => style.black, + 1 => style.red, + 2 => style.green, + 3 => style.yellow, + 4 => style.blue, + 5 => style.magenta, + 6 => style.cyan, + 7 => style.white, + 8 => style.bright_black, + 9 => style.bright_red, + 10 => style.bright_green, + 11 => style.bright_yellow, + 12 => style.bright_blue, + 13 => style.bright_magenta, + 14 => style.bright_cyan, + 15 => style.bright_white, + 16..=231 => { + let (r, g, b) = rgb_for_index(index); //Split the index into it's rgb components + let step = (u8::MAX as f32 / 5.).round() as u8; //Split the GPUI range into 5 chunks + Color::new(r * step, g * step, b * step, 1) //Map the rgb components to GPUI's range + } + //Grayscale from black to white, 0 to 24 + 232..=255 => { + let i = 24 - (index - 232); //Align index to 24..0 + let step = (u8::MAX as f32 / 24.).round() as u8; //Split the 256 range grayscale into 24 chunks + Color::new(i * step, i * step, i * step, 1) //Map the rgb components to GPUI's range + } + } +} + +///Generates the rgb channels in [0, 5] for a given index into the 6x6x6 ANSI color cube +///See: [8 bit ansi color](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit). +/// +///Wikipedia gives a formula for calculating the index for a given color: +/// +///index = 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5) +/// +///This function does the reverse, calculating the r, g, and b components from a given index. +fn rgb_for_index(i: &u8) -> (u8, u8, u8) { + debug_assert!(i >= &16 && i <= &231); + let i = i - 16; + let r = (i - (i % 36)) / 36; + let g = ((i % 36) - (i % 6)) / 6; + let b = (i % 36) % 6; + (r, g, b) +} + #[cfg(debug_assertions)] fn draw_debug_grid(bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContext) { let width = layout.cur_size.width(); @@ -361,3 +410,16 @@ fn draw_debug_grid(bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContex }); } } + +mod tests { + use crate::terminal_element::rgb_for_index; + + #[test] + fn test_rgb_for_index() { + //Test every possible value in the color cube + for i in 16..=231 { + let (r, g, b) = rgb_for_index(&(i as u8)); + assert_eq!(i, 16 + 36 * r + 6 * g + b); + } + } +} From 75a45562449ec7ef62b216f5cab09c4f6ebdf3ce Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 29 Jun 2022 18:29:36 -0700 Subject: [PATCH 17/20] Fixed unused import --- crates/terminal/src/terminal_element.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index b0c01391ca..0b02d918fc 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -412,8 +412,6 @@ fn draw_debug_grid(bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContex } mod tests { - use crate::terminal_element::rgb_for_index; - #[test] fn test_rgb_for_index() { //Test every possible value in the color cube From ab5247c62e8bb4a287129285effe6d0d603e6a59 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 29 Jun 2022 18:31:52 -0700 Subject: [PATCH 18/20] Actually correctly flag tests --- crates/terminal/src/terminal_element.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index 0b02d918fc..2aa32b6367 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -411,6 +411,7 @@ fn draw_debug_grid(bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContex } } +#[cfg(test)] mod tests { #[test] fn test_rgb_for_index() { From 5bc0acd88cf90f1d14035cac53ce76520da1549e Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 29 Jun 2022 18:34:02 -0700 Subject: [PATCH 19/20] Directly qualified function makes cargo happy --- crates/terminal/src/terminal_element.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index 2aa32b6367..64cf93c21a 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -417,7 +417,7 @@ mod tests { fn test_rgb_for_index() { //Test every possible value in the color cube for i in 16..=231 { - let (r, g, b) = rgb_for_index(&(i as u8)); + let (r, g, b) = crate::terminal_element::rgb_for_index(&(i as u8)); assert_eq!(i, 16 + 36 * r + 6 * g + b); } } From 06107afdd4f0586099988c8eb7679f12b3bf3ada Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 29 Jun 2022 18:50:08 -0700 Subject: [PATCH 20/20] Added background colors and matched the cursor color --- crates/terminal/src/terminal_element.rs | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index 64cf93c21a..d81292d0c2 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -42,8 +42,9 @@ pub struct LayoutState { lines: Vec, line_height: f32, em_width: f32, - cursor: Option, + cursor: Option<(RectF, Color)>, cur_size: SizeInfo, + background_color: Color, } impl Element for TerminalEl { @@ -114,9 +115,12 @@ impl Element for TerminalEl { .and_then(|cursor_line: usize| shaped_lines.get(cursor_line)) { 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(cell_width, line_height), + cursor = Some(( + RectF::new( + vec2f(cursor_x, cursor_line as f32 * line_height), + vec2f(cell_width, line_height), + ), + terminal_theme.cursor, )); } @@ -128,6 +132,7 @@ impl Element for TerminalEl { em_width: cell_width, cursor, cur_size: new_size, + background_color: terminal_theme.background, }, ) } @@ -155,6 +160,14 @@ impl Element for TerminalEl { right_mouse_down_out: None, }); + //Background + cx.scene.push_quad(Quad { + bounds: visible_bounds, + background: Some(layout.background_color), + border: Default::default(), + corner_radius: 0., + }); + let origin = bounds.origin() + vec2f(layout.em_width, 0.); //Padding let mut line_origin = origin; @@ -168,12 +181,12 @@ impl Element for TerminalEl { line_origin.set_y(boundaries.max_y()); } - if let Some(c) = layout.cursor { + if let Some((c, color)) = layout.cursor { let new_origin = origin + c.origin(); let new_cursor = RectF::new(new_origin, c.size()); cx.scene.push_quad(Quad { bounds: new_cursor, - background: Some(Color::white()), + background: Some(color), border: Default::default(), corner_radius: 0., });