Merge pull request #1294 from zed-industries/terminal-modal

Proposal: Terminal modal
This commit is contained in:
Keith Simmons 2022-07-08 11:11:03 -07:00 committed by GitHub
commit a82e56918e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 208 additions and 85 deletions

View File

@ -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),
}
}

View File

@ -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<Workspace>) {
if let Some(stored_terminal) = cx.default_global::<Option<ViewHandle<Terminal>>>().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<Terminal>,
event: &Event,
cx: &mut ViewContext<Workspace>,
) {
// Dismiss the modal if the terminal quit
if let Event::CloseTerminal = event {
cx.set_global::<Option<ViewHandle<Terminal>>>(None);
if workspace
.modal()
.cloned()
.and_then(|modal| modal.downcast::<Terminal>())
.is_some()
{
workspace.dismiss_modal(cx)
}
}
}

View File

@ -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<PathBuf>,
}
@ -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<Self>, working_directory: Option<PathBuf>) -> Self {
fn new(cx: &mut ViewContext<Self>, working_directory: Option<PathBuf>, 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::<Settings>().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::<Settings>();
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<Self>) {
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<ProjectPath> {
@ -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| {

View File

@ -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::<Settings>()).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<SelectionRange>,
) -> Vec<LayoutLine> {
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::<Vec<LayoutCell>>();
@ -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)

View File

@ -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,

View File

@ -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),
}
};
}