diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index bea53ece45..8aaf66eaf1 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -303,7 +303,8 @@ "cmd-shift-P": "command_palette::Toggle", "cmd-shift-M": "diagnostics::Deploy", "cmd-shift-E": "project_panel::Toggle", - "cmd-alt-s": "workspace::SaveAll" + "cmd-alt-s": "workspace::SaveAll", + "shift-space t": "terminal::DeployModal" } }, // Bindings from Sublime Text @@ -419,5 +420,11 @@ "tab": "terminal::Tab", "cmd-v": "terminal::Paste" } + }, + { + "context": "ModalTerminal", + "bindings": { + "escape": "terminal::DeployModal" + } } ] \ No newline at end of file diff --git a/crates/terminal/src/modal.rs b/crates/terminal/src/modal.rs new file mode 100644 index 0000000000..60e14fa8a9 --- /dev/null +++ b/crates/terminal/src/modal.rs @@ -0,0 +1,44 @@ +use gpui::{ViewContext, ViewHandle}; +use workspace::Workspace; + +use crate::{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()) + .map(|wt| wt.abs_path().to_path_buf()); + + 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 eb7f2a0a90..00b1e44d5f 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -1,3 +1,7 @@ +pub mod gpui_func_tools; +mod modal; +pub mod terminal_element; + use alacritty_terminal::{ config::{Config, Program, PtyConfig}, event::{Event as AlacTermEvent, EventListener, Notify}, @@ -17,6 +21,7 @@ use gpui::{ actions, color::Color, elements::*, impl_internal_actions, platform::CursorStyle, ClipboardItem, Entity, MutableAppContext, View, ViewContext, }; +use modal::deploy_modal; use project::{Project, ProjectPath}; use settings::Settings; use smallvec::SmallVec; @@ -37,9 +42,6 @@ const UP_SEQ: &str = "\x1b[A"; const DOWN_SEQ: &str = "\x1b[B"; const DEFAULT_TITLE: &str = "Terminal"; -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); @@ -50,7 +52,22 @@ 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, + DeployModal, + ] ); impl_internal_actions!(terminal, [Input, ScrollTerminal]); @@ -70,6 +87,7 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(Terminal::tab); 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 @@ -90,6 +108,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, } ///Upward flowing events, for changing the title and such @@ -105,7 +124,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 { @@ -172,6 +191,7 @@ impl Terminal { has_new_content: false, has_bell: false, cur_size: size_info, + modal, } } @@ -218,7 +238,7 @@ impl Terminal { ), AlacTermEvent::ColorRequest(index, format) => { let color = self.term.lock().colors()[index].unwrap_or_else(|| { - let term_style = &cx.global::().theme.terminal; + let term_style = &cx.global::().theme.terminal.colors; match index { 0..=255 => to_alac_rgb(get_color_at_index(&(index as u8), term_style)), //These additional values are required to match the Alacritty Colors object's behavior @@ -274,7 +294,10 @@ impl Terminal { .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); + workspace.add_item( + Box::new(cx.add_view(|cx| Terminal::new(cx, abs_path, false))), + cx, + ); } ///Send the shutdown message to Alacritty @@ -367,13 +390,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 { @@ -488,7 +524,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)); 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 408fb0dcec..b4e43bfc4f 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -25,7 +25,7 @@ use itertools::Itertools; use ordered_float::OrderedFloat; use settings::Settings; use std::rc::Rc; -use theme::TerminalStyle; +use theme::{TerminalColors, TerminalStyle}; use crate::{gpui_func_tools::paint_layer, Input, ScrollTerminal, Terminal}; @@ -110,7 +110,8 @@ impl Element for TerminalEl { //Now that we're done with the mutable portion, grab the immutable settings and view again let terminal_theme = &(cx.global::()).theme.terminal; - let term = view_handle.read(cx).term.lock(); + let view = view_handle.read(cx); + let term = view.term.lock(); let grid = term.grid(); let cursor_point = grid.cursor.point; @@ -123,6 +124,7 @@ impl Element for TerminalEl { &text_style, terminal_theme, cx.text_layout_cache, + view.modal, ); let cells = layout_cells @@ -152,7 +154,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(), }, )], @@ -178,12 +180,18 @@ impl Element for TerminalEl { cursor_position, block_width, line_height.0, - terminal_theme.cursor, + terminal_theme.colors.cursor, CursorShape::Block, Some(block_text.clone()), ) }); + let background_color = if view.modal { + terminal_theme.colors.modal_background + } else { + terminal_theme.colors.background + }; + ( constraint.max, LayoutState { @@ -193,7 +201,7 @@ impl Element for TerminalEl { cursor, cur_size, background_rects, - background_color: terminal_theme.background, + background_color, }, ) } @@ -348,6 +356,7 @@ fn layout_cells( text_style: &TextStyle, terminal_theme: &TerminalStyle, text_layout_cache: &TextLayoutCache, + modal: bool, ) -> Vec { let mut line_count: i32 = 0; let lines = grid.group_by(|i| i.point.line); @@ -358,7 +367,7 @@ fn layout_cells( line.map(|indexed_cell| { 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); let layout_cell = text_layout_cache.layout_str( cell_text, @@ -368,7 +377,7 @@ fn layout_cells( LayoutCell::new( Point::new(line_count - 1, 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::>() @@ -410,9 +419,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) @@ -431,67 +445,73 @@ fn cell_style(indexed: &Indexed<&Cell>, style: &TerminalStyle, text_style: &Text } ///Converts a 2, 8, or 24 bit color ANSI color to the GPUI equivalent -fn convert_color(alac_color: &AnsiColor, style: &TerminalStyle) -> Color { +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, style), + alacritty_terminal::ansi::Color::Indexed(i) => get_color_at_index(i, colors), } } ///Converts an 8 bit ANSI color to it's GPUI equivalent. -pub fn get_color_at_index(index: &u8, style: &TerminalStyle) -> Color { +pub fn get_color_at_index(index: &u8, 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); //Split the index into it's ANSI-RGB components diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 184b1880f0..a3af61a41c 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -637,6 +637,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, @@ -655,6 +661,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