diff --git a/crates/terminal/src/color_translation.rs b/crates/terminal/src/color_translation.rs index 78c2a569db..946a22d304 100644 --- a/crates/terminal/src/color_translation.rs +++ b/crates/terminal/src/color_translation.rs @@ -1,71 +1,77 @@ use alacritty_terminal::{ansi::Color as AnsiColor, term::color::Rgb as AlacRgb}; use gpui::color::Color; -use theme::TerminalStyle; +use theme::TerminalColors; ///Converts a 2, 8, or 24 bit color ANSI color to the GPUI equivalent -pub fn convert_color(alac_color: &AnsiColor, style: &TerminalStyle) -> Color { +pub fn convert_color(alac_color: &AnsiColor, colors: &TerminalColors, modal: bool) -> Color { + let background = if modal { + colors.modal_background + } else { + colors.background + }; + match alac_color { //Named and theme defined colors alacritty_terminal::ansi::Color::Named(n) => match n { - 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, + alacritty_terminal::ansi::NamedColor::Black => colors.black, + alacritty_terminal::ansi::NamedColor::Red => colors.red, + alacritty_terminal::ansi::NamedColor::Green => colors.green, + alacritty_terminal::ansi::NamedColor::Yellow => colors.yellow, + alacritty_terminal::ansi::NamedColor::Blue => colors.blue, + alacritty_terminal::ansi::NamedColor::Magenta => colors.magenta, + alacritty_terminal::ansi::NamedColor::Cyan => colors.cyan, + alacritty_terminal::ansi::NamedColor::White => colors.white, + alacritty_terminal::ansi::NamedColor::BrightBlack => colors.bright_black, + alacritty_terminal::ansi::NamedColor::BrightRed => colors.bright_red, + alacritty_terminal::ansi::NamedColor::BrightGreen => colors.bright_green, + alacritty_terminal::ansi::NamedColor::BrightYellow => colors.bright_yellow, + alacritty_terminal::ansi::NamedColor::BrightBlue => colors.bright_blue, + alacritty_terminal::ansi::NamedColor::BrightMagenta => colors.bright_magenta, + alacritty_terminal::ansi::NamedColor::BrightCyan => colors.bright_cyan, + alacritty_terminal::ansi::NamedColor::BrightWhite => colors.bright_white, + alacritty_terminal::ansi::NamedColor::Foreground => colors.foreground, + alacritty_terminal::ansi::NamedColor::Background => background, + alacritty_terminal::ansi::NamedColor::Cursor => colors.cursor, + alacritty_terminal::ansi::NamedColor::DimBlack => colors.dim_black, + alacritty_terminal::ansi::NamedColor::DimRed => colors.dim_red, + alacritty_terminal::ansi::NamedColor::DimGreen => colors.dim_green, + alacritty_terminal::ansi::NamedColor::DimYellow => colors.dim_yellow, + alacritty_terminal::ansi::NamedColor::DimBlue => colors.dim_blue, + alacritty_terminal::ansi::NamedColor::DimMagenta => colors.dim_magenta, + alacritty_terminal::ansi::NamedColor::DimCyan => colors.dim_cyan, + alacritty_terminal::ansi::NamedColor::DimWhite => colors.dim_white, + alacritty_terminal::ansi::NamedColor::BrightForeground => colors.bright_foreground, + alacritty_terminal::ansi::NamedColor::DimForeground => colors.dim_foreground, }, //'True' colors alacritty_terminal::ansi::Color::Spec(rgb) => Color::new(rgb.r, rgb.g, rgb.b, u8::MAX), //8 bit, indexed colors - alacritty_terminal::ansi::Color::Indexed(i) => get_color_at_index(&(*i as usize), style), + alacritty_terminal::ansi::Color::Indexed(i) => get_color_at_index(&(*i as usize), colors), } } ///Converts an 8 bit ANSI color to it's GPUI equivalent. ///Accepts usize for compatability with the alacritty::Colors interface, ///Other than that use case, should only be called with values in the [0,255] range -pub fn get_color_at_index(index: &usize, style: &TerminalStyle) -> Color { +pub fn get_color_at_index(index: &usize, colors: &TerminalColors) -> Color { match index { //0-15 are the same as the named colors above - 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, + 0 => colors.black, + 1 => colors.red, + 2 => colors.green, + 3 => colors.yellow, + 4 => colors.blue, + 5 => colors.magenta, + 6 => colors.cyan, + 7 => colors.white, + 8 => colors.bright_black, + 9 => colors.bright_red, + 10 => colors.bright_green, + 11 => colors.bright_yellow, + 12 => colors.bright_blue, + 13 => colors.bright_magenta, + 14 => colors.bright_cyan, + 15 => colors.bright_white, //16-231 are mapped to their RGB colors on a 0-5 range per channel 16..=231 => { let (r, g, b) = rgb_for_index(&(*index as u8)); //Split the index into it's ANSI-RGB components @@ -79,19 +85,19 @@ pub fn get_color_at_index(index: &usize, style: &TerminalStyle) -> Color { Color::new(i * step, i * step, i * step, u8::MAX) //Map the ANSI-grayscale components to the RGB-grayscale } //For compatability with the alacritty::Colors interface - 256 => style.foreground, - 257 => style.background, - 258 => style.cursor, - 259 => style.dim_black, - 260 => style.dim_red, - 261 => style.dim_green, - 262 => style.dim_yellow, - 263 => style.dim_blue, - 264 => style.dim_magenta, - 265 => style.dim_cyan, - 266 => style.dim_white, - 267 => style.bright_foreground, - 268 => style.black, //'Dim Background', non-standard color + 256 => colors.foreground, + 257 => colors.background, + 258 => colors.cursor, + 259 => colors.dim_black, + 260 => colors.dim_red, + 261 => colors.dim_green, + 262 => colors.dim_yellow, + 263 => colors.dim_blue, + 264 => colors.dim_magenta, + 265 => colors.dim_cyan, + 266 => colors.dim_white, + 267 => colors.bright_foreground, + 268 => colors.black, //'Dim Background', non-standard color _ => Color::new(0, 0, 0, 255), } } diff --git a/crates/terminal/src/modal.rs b/crates/terminal/src/modal.rs new file mode 100644 index 0000000000..2a1c16ae4b --- /dev/null +++ b/crates/terminal/src/modal.rs @@ -0,0 +1,44 @@ +use gpui::{ViewContext, ViewHandle}; +use workspace::Workspace; + +use crate::{get_working_directory, DeployModal, Event, Terminal}; + +pub fn deploy_modal(workspace: &mut Workspace, _: &DeployModal, cx: &mut ViewContext) { + if let Some(stored_terminal) = cx.default_global::>>().clone() { + workspace.toggle_modal(cx, |_, _| stored_terminal); + } else { + 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()) + .and_then(get_working_directory); + + let displaced_modal = workspace.toggle_modal(cx, |_, cx| { + let this = cx.add_view(|cx| Terminal::new(cx, abs_path, true)); + cx.subscribe(&this, on_event).detach(); + this + }); + cx.set_global(displaced_modal); + } +} + +pub fn on_event( + workspace: &mut Workspace, + _: ViewHandle, + event: &Event, + cx: &mut ViewContext, +) { + // Dismiss the modal if the terminal quit + if let Event::CloseTerminal = event { + cx.set_global::>>(None); + if workspace + .modal() + .cloned() + .and_then(|modal| modal.downcast::()) + .is_some() + { + workspace.dismiss_modal(cx) + } + } +} diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 46591f1481..017bb5f00a 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -1,3 +1,8 @@ +pub mod color_translation; +pub mod gpui_func_tools; +mod modal; +pub mod terminal_element; + use alacritty_terminal::{ config::{Config, PtyConfig}, event::{Event as AlacTermEvent, EventListener, Notify}, @@ -18,6 +23,7 @@ use gpui::{ actions, elements::*, impl_internal_actions, platform::CursorStyle, ClipboardItem, Entity, MutableAppContext, View, ViewContext, }; +use modal::deploy_modal; use project::{LocalWorktree, Project, ProjectPath}; use settings::Settings; use smallvec::SmallVec; @@ -42,10 +48,6 @@ const DEBUG_TERMINAL_HEIGHT: f32 = 200.; const DEBUG_CELL_WIDTH: f32 = 5.; const DEBUG_LINE_HEIGHT: f32 = 5.; -pub mod color_translation; -pub mod gpui_func_tools; -pub mod terminal_element; - ///Action for carrying the input to the PTY #[derive(Clone, Default, Debug, PartialEq, Eq)] pub struct Input(pub String); @@ -56,7 +58,23 @@ pub struct ScrollTerminal(pub i32); actions!( terminal, - [Sigint, Escape, Del, Return, Left, Right, Up, Down, Tab, Clear, Copy, Paste, Deploy, Quit] + [ + Sigint, + Escape, + Del, + Return, + Left, + Right, + Up, + Down, + Tab, + Clear, + Copy, + Paste, + Deploy, + Quit, + DeployModal, + ] ); impl_internal_actions!(terminal, [Input, ScrollTerminal]); @@ -77,6 +95,7 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(Terminal::copy); cx.add_action(Terminal::paste); cx.add_action(Terminal::scroll_terminal); + cx.add_action(deploy_modal); } ///A translation struct for Alacritty to communicate with us from their event loop @@ -97,6 +116,7 @@ pub struct Terminal { has_new_content: bool, has_bell: bool, //Currently using iTerm bell, show bell emoji in tab until input is received cur_size: SizeInfo, + modal: bool, associated_directory: Option, } @@ -113,7 +133,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, working_directory: Option) -> Self { + fn new(cx: &mut ViewContext, working_directory: Option, modal: bool) -> 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 { @@ -186,6 +206,7 @@ impl Terminal { has_new_content: false, has_bell: false, cur_size: size_info, + modal, associated_directory: working_directory, } } @@ -234,7 +255,7 @@ impl Terminal { AlacTermEvent::ColorRequest(index, format) => { let color = self.term.lock().colors()[index].unwrap_or_else(|| { let term_style = &cx.global::().theme.terminal; - to_alac_rgb(get_color_at_index(&index, term_style)) + to_alac_rgb(get_color_at_index(&index, &term_style.colors)) }); self.write_to_pty(&Input(format(color)), cx) } @@ -273,7 +294,10 @@ impl Terminal { .and_then(|worktree_handle| worktree_handle.read(cx).as_local()) .and_then(get_working_directory); - workspace.add_item(Box::new(cx.add_view(|cx| Terminal::new(cx, abs_path))), cx); + workspace.add_item( + Box::new(cx.add_view(|cx| Terminal::new(cx, abs_path, false))), + cx, + ); } ///Send the shutdown message to Alacritty @@ -376,13 +400,26 @@ impl View for Terminal { } fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox { - TerminalEl::new(cx.handle()).contained().boxed() + let element = TerminalEl::new(cx.handle()).contained(); + if self.modal { + let settings = cx.global::(); + let container_style = settings.theme.terminal.modal_container; + element.with_style(container_style).boxed() + } else { + element.boxed() + } } fn on_focus(&mut self, cx: &mut ViewContext) { cx.emit(Event::Activate); self.has_new_content = false; } + + fn keymap_context(&self, _: &gpui::AppContext) -> gpui::keymap::Context { + let mut context = Self::default_keymap_context(); + context.set.insert("ModalTerminal".into()); + context + } } impl Item for Terminal { @@ -421,7 +458,7 @@ impl Item for Terminal { //From what I can tell, there's no way to tell the current working //Directory of the terminal from outside the terminal. There might be //solutions to this, but they are non-trivial and require more IPC - Some(Terminal::new(cx, self.associated_directory.clone())) + Some(Terminal::new(cx, self.associated_directory.clone(), false)) } fn project_path(&self, _cx: &gpui::AppContext) -> Option { @@ -509,7 +546,7 @@ mod tests { //and produce noticable output? #[gpui::test] async fn test_terminal(cx: &mut TestAppContext) { - let terminal = cx.add_view(Default::default(), |cx| Terminal::new(cx, None)); + let terminal = cx.add_view(Default::default(), |cx| Terminal::new(cx, None, false)); cx.set_condition_duration(Duration::from_secs(2)); terminal.update(cx, |terminal, cx| { terminal.write_to_pty(&Input(("expr 3 + 4".to_string()).to_string()), cx); @@ -610,7 +647,7 @@ mod tests { ///If this test is failing for you, check that DEBUG_TERMINAL_WIDTH is wide enough to fit your entire command prompt! #[gpui::test] async fn test_copy(cx: &mut TestAppContext) { - let terminal = cx.add_view(Default::default(), |cx| Terminal::new(cx, None)); + let terminal = cx.add_view(Default::default(), |cx| Terminal::new(cx, None, false)); cx.set_condition_duration(Duration::from_secs(2)); terminal.update(cx, |terminal, cx| { diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index 1ad6aed6ae..ec48996e78 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -26,9 +26,10 @@ use gpui::{ use itertools::Itertools; use ordered_float::OrderedFloat; use settings::Settings; +use theme::TerminalStyle; + use std::{cmp::min, ops::Range, rc::Rc, sync::Arc}; use std::{fmt::Debug, ops::Sub}; -use theme::TerminalStyle; use crate::{ color_translation::convert_color, gpui_func_tools::paint_layer, Input, ScrollTerminal, @@ -128,12 +129,13 @@ impl Element for TerminalEl { view_handle.update(cx.app, |view, _cx| view.set_size(cur_size)); //Now that we're done with the mutable portion, grab the immutable settings and view again + let view = view_handle.read(cx); + let (selection_color, terminal_theme) = { let theme = &(cx.global::()).theme; (theme.editor.selection.selection, &theme.terminal) }; let terminal_mutex = view_handle.read(cx).term.clone(); - let term = terminal_mutex.lock(); let grid = term.grid(); let cursor_point = grid.cursor.point; @@ -146,6 +148,7 @@ impl Element for TerminalEl { &text_style, terminal_theme, cx.text_layout_cache, + view.modal, content.selection, ); @@ -156,7 +159,7 @@ impl Element for TerminalEl { cursor_text.len(), RunStyle { font_id: text_style.font_id, - color: terminal_theme.background, + color: terminal_theme.colors.background, underline: Default::default(), }, )], @@ -182,13 +185,19 @@ impl Element for TerminalEl { cursor_position, block_width, line_height.0, - terminal_theme.cursor, + terminal_theme.colors.cursor, CursorShape::Block, Some(block_text.clone()), ) }); drop(term); + let background_color = if view.modal { + terminal_theme.colors.modal_background + } else { + terminal_theme.colors.background + }; + ( constraint.max, LayoutState { @@ -197,7 +206,7 @@ impl Element for TerminalEl { em_width: cell_width, cursor, cur_size, - background_color: terminal_theme.background, + background_color, terminal: terminal_mutex, selection_color, }, @@ -379,7 +388,7 @@ impl Element for TerminalEl { } } -fn mouse_to_cell_data( +pub fn mouse_to_cell_data( pos: Vector2F, origin: Vector2F, cur_size: SizeInfo, @@ -428,6 +437,7 @@ fn layout_lines( text_style: &TextStyle, terminal_theme: &TerminalStyle, text_layout_cache: &TextLayoutCache, + modal: bool, selection_range: Option, ) -> Vec { let lines = grid.group_by(|i| i.point.line); @@ -450,7 +460,7 @@ fn layout_lines( let cell_text = &indexed_cell.c.to_string(); - let cell_style = cell_style(&indexed_cell, terminal_theme, text_style); + let cell_style = cell_style(&indexed_cell, terminal_theme, text_style, modal); //This is where we might be able to get better performance let layout_cell = text_layout_cache.layout_str( @@ -462,7 +472,7 @@ fn layout_lines( LayoutCell::new( Point::new(line_index as i32, indexed_cell.point.column.0 as i32), layout_cell, - convert_color(&indexed_cell.bg, terminal_theme), + convert_color(&indexed_cell.bg, &terminal_theme.colors, modal), ) }) .collect::>(); @@ -508,9 +518,14 @@ fn get_cursor_shape( } ///Convert the Alacritty cell styles to GPUI text styles and background color -fn cell_style(indexed: &Indexed<&Cell>, style: &TerminalStyle, text_style: &TextStyle) -> RunStyle { +fn cell_style( + indexed: &Indexed<&Cell>, + style: &TerminalStyle, + text_style: &TextStyle, + modal: bool, +) -> RunStyle { let flags = indexed.cell.flags; - let fg = convert_color(&indexed.cell.fg, style); + let fg = convert_color(&indexed.cell.fg, &style.colors, modal); let underline = flags .contains(Flags::UNDERLINE) diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 058dd5a331..51f5fb7fcc 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -633,6 +633,12 @@ pub struct HoverPopover { #[derive(Clone, Deserialize, Default)] pub struct TerminalStyle { + pub colors: TerminalColors, + pub modal_container: ContainerStyle, +} + +#[derive(Clone, Deserialize, Default)] +pub struct TerminalColors { pub black: Color, pub red: Color, pub green: Color, @@ -651,6 +657,7 @@ pub struct TerminalStyle { pub bright_white: Color, pub foreground: Color, pub background: Color, + pub modal_background: Color, pub cursor: Color, pub dim_black: Color, pub dim_red: Color, diff --git a/styles/src/styleTree/terminal.ts b/styles/src/styleTree/terminal.ts index ef9e4f93dd..bc133f09c8 100644 --- a/styles/src/styleTree/terminal.ts +++ b/styles/src/styleTree/terminal.ts @@ -1,7 +1,8 @@ import Theme from "../themes/common/theme"; +import { border, modalShadow } from "./components"; export default function terminal(theme: Theme) { - return { + let colors = { black: theme.ramps.neutral(0).hex(), red: theme.ramps.red(0.5).hex(), green: theme.ramps.green(0.5).hex(), @@ -20,6 +21,7 @@ export default function terminal(theme: Theme) { brightWhite: theme.ramps.neutral(7).hex(), foreground: theme.ramps.neutral(7).hex(), background: theme.ramps.neutral(0).hex(), + modalBackground: theme.ramps.neutral(1).hex(), cursor: theme.ramps.neutral(7).hex(), dimBlack: theme.ramps.neutral(7).hex(), dimRed: theme.ramps.red(0.75).hex(), @@ -32,4 +34,16 @@ export default function terminal(theme: Theme) { brightForeground: theme.ramps.neutral(7).hex(), dimForeground: theme.ramps.neutral(0).hex(), }; + + return { + colors, + modalContainer: { + background: colors.modalBackground, + cornerRadius: 8, + padding: 8, + margin: 25, + border: border(theme, "primary"), + shadow: modalShadow(theme), + } + }; } \ No newline at end of file