commit
2ee57c1512
15 changed files with 1250 additions and 8 deletions
189
Cargo.lock
generated
189
Cargo.lock
generated
|
@ -59,6 +59,45 @@ dependencies = [
|
||||||
"memchr",
|
"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]]
|
[[package]]
|
||||||
name = "ansi_term"
|
name = "ansi_term"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
|
@ -2516,6 +2555,12 @@ dependencies = [
|
||||||
"safemem",
|
"safemem",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linked-hash-map"
|
||||||
|
version = "0.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lipsum"
|
name = "lipsum"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
|
@ -2725,7 +2770,7 @@ dependencies = [
|
||||||
"kernel32-sys",
|
"kernel32-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"miow",
|
"miow 0.2.2",
|
||||||
"net2",
|
"net2",
|
||||||
"slab",
|
"slab",
|
||||||
"winapi 0.2.8",
|
"winapi 0.2.8",
|
||||||
|
@ -2743,6 +2788,42 @@ dependencies = [
|
||||||
"windows-sys",
|
"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]]
|
[[package]]
|
||||||
name = "miow"
|
name = "miow"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
@ -2755,6 +2836,15 @@ dependencies = [
|
||||||
"ws2_32-sys",
|
"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]]
|
[[package]]
|
||||||
name = "multimap"
|
name = "multimap"
|
||||||
version = "0.8.3"
|
version = "0.8.3"
|
||||||
|
@ -2799,6 +2889,19 @@ dependencies = [
|
||||||
"winapi 0.3.9",
|
"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]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "7.1.1"
|
version = "7.1.1"
|
||||||
|
@ -4253,6 +4356,18 @@ dependencies = [
|
||||||
"serde",
|
"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]]
|
[[package]]
|
||||||
name = "servo-fontconfig"
|
name = "servo-fontconfig"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
@ -4365,6 +4480,18 @@ dependencies = [
|
||||||
"signal-hook-registry",
|
"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]]
|
[[package]]
|
||||||
name = "signal-hook-registry"
|
name = "signal-hook-registry"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
@ -4493,6 +4620,12 @@ version = "0.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "spsc-buffer"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "be6c3f39c37a4283ee4b43d1311c828f2e1fb0541e76ea0cb1a2abd9ef2f5b3b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlformat"
|
name = "sqlformat"
|
||||||
version = "0.1.8"
|
version = "0.1.8"
|
||||||
|
@ -4740,6 +4873,24 @@ dependencies = [
|
||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "terminal"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"alacritty_terminal",
|
||||||
|
"editor",
|
||||||
|
"futures",
|
||||||
|
"gpui",
|
||||||
|
"mio-extras",
|
||||||
|
"ordered-float",
|
||||||
|
"project",
|
||||||
|
"settings",
|
||||||
|
"smallvec",
|
||||||
|
"theme",
|
||||||
|
"util",
|
||||||
|
"workspace",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "text"
|
name = "text"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -5532,6 +5683,12 @@ version = "0.7.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "util"
|
name = "util"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -5617,6 +5774,26 @@ dependencies = [
|
||||||
"workspace",
|
"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]]
|
[[package]]
|
||||||
name = "waker-fn"
|
name = "waker-fn"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -5968,6 +6145,15 @@ version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
|
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]]
|
[[package]]
|
||||||
name = "zed"
|
name = "zed"
|
||||||
version = "0.42.0"
|
version = "0.42.0"
|
||||||
|
@ -6035,6 +6221,7 @@ dependencies = [
|
||||||
"smol",
|
"smol",
|
||||||
"sum_tree",
|
"sum_tree",
|
||||||
"tempdir",
|
"tempdir",
|
||||||
|
"terminal",
|
||||||
"text",
|
"text",
|
||||||
"theme",
|
"theme",
|
||||||
"theme_selector",
|
"theme_selector",
|
||||||
|
|
|
@ -403,5 +403,21 @@
|
||||||
"f2": "project_panel::Rename",
|
"f2": "project_panel::Rename",
|
||||||
"backspace": "project_panel::Delete"
|
"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::Up",
|
||||||
|
"down": "terminal::Down",
|
||||||
|
"tab": "terminal::Tab",
|
||||||
|
"cmd-v": "terminal::Paste"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
|
@ -703,6 +703,20 @@ impl<'a> EventContext<'a> {
|
||||||
self.view_stack.last().copied()
|
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<dyn Action>) {
|
pub fn dispatch_any_action(&mut self, action: Box<dyn Action>) {
|
||||||
self.dispatched_actions.push(DispatchDirective {
|
self.dispatched_actions.push(DispatchDirective {
|
||||||
dispatcher_view_id: self.view_stack.last().copied(),
|
dispatcher_view_id: self.view_stack.last().copied(),
|
||||||
|
|
25
crates/terminal/Cargo.toml
Normal file
25
crates/terminal/Cargo.toml
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
[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"
|
||||||
|
ordered-float = "2.1.1"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
gpui = { path = "../gpui", features = ["test-support"] }
|
475
crates/terminal/src/terminal.rs
Normal file
475
crates/terminal/src/terminal.rs
Normal file
|
@ -0,0 +1,475 @@
|
||||||
|
use alacritty_terminal::{
|
||||||
|
config::{Config, Program, PtyConfig},
|
||||||
|
event::{Event as AlacTermEvent, EventListener, Notify},
|
||||||
|
event_loop::{EventLoop, Msg, Notifier},
|
||||||
|
grid::Scroll,
|
||||||
|
sync::FairMutex,
|
||||||
|
term::{color::Rgb as AlacRgb, SizeInfo},
|
||||||
|
tty, Term,
|
||||||
|
};
|
||||||
|
|
||||||
|
use futures::{
|
||||||
|
channel::mpsc::{unbounded, UnboundedSender},
|
||||||
|
StreamExt,
|
||||||
|
};
|
||||||
|
use gpui::{
|
||||||
|
actions, color::Color, elements::*, impl_internal_actions, platform::CursorStyle,
|
||||||
|
ClipboardItem, Entity, MutableAppContext, View, ViewContext,
|
||||||
|
};
|
||||||
|
use project::{Project, ProjectPath};
|
||||||
|
use settings::Settings;
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
use std::{path::PathBuf, sync::Arc};
|
||||||
|
use workspace::{Item, Workspace};
|
||||||
|
|
||||||
|
use crate::terminal_element::{get_color_at_index, 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'
|
||||||
|
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";
|
||||||
|
|
||||||
|
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]
|
||||||
|
);
|
||||||
|
impl_internal_actions!(terminal, [Input, ScrollTerminal]);
|
||||||
|
|
||||||
|
pub fn init(cx: &mut MutableAppContext) {
|
||||||
|
cx.add_action(Terminal::deploy);
|
||||||
|
cx.add_action(Terminal::write_to_pty);
|
||||||
|
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); //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::up);
|
||||||
|
cx.add_action(Terminal::down);
|
||||||
|
cx.add_action(Terminal::tab);
|
||||||
|
cx.add_action(Terminal::paste);
|
||||||
|
cx.add_action(Terminal::scroll_terminal);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ZedListener(UnboundedSender<AlacTermEvent>);
|
||||||
|
|
||||||
|
impl EventListener for ZedListener {
|
||||||
|
fn send_event(&self, event: AlacTermEvent) {
|
||||||
|
self.0.unbounded_send(event).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///A terminal renderer.
|
||||||
|
pub struct Terminal {
|
||||||
|
pty_tx: Notifier,
|
||||||
|
term: Arc<FairMutex<Term<ZedListener>>>,
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Event {
|
||||||
|
TitleChanged,
|
||||||
|
CloseTerminal,
|
||||||
|
Activate,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Entity for Terminal {
|
||||||
|
type Event = Event;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
//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 {
|
||||||
|
match this.upgrade(&cx) {
|
||||||
|
Some(handle) => {
|
||||||
|
handle.update(&mut cx, |this, cx| {
|
||||||
|
this.process_terminal_event(event, cx);
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
None => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
let pty_config = PtyConfig {
|
||||||
|
shell: Some(Program::Just("zsh".to_string())),
|
||||||
|
working_directory,
|
||||||
|
hold: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let config = Config {
|
||||||
|
pty_config: pty_config.clone(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
//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...
|
||||||
|
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()),
|
||||||
|
pty,
|
||||||
|
pty_config.hold,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
//Kick things off
|
||||||
|
let pty_tx = Notifier(event_loop.channel());
|
||||||
|
let _io_thread = event_loop.spawn();
|
||||||
|
Terminal {
|
||||||
|
title: DEFAULT_TITLE.to_string(),
|
||||||
|
term,
|
||||||
|
pty_tx,
|
||||||
|
has_new_content: false,
|
||||||
|
has_bell: false,
|
||||||
|
cur_size: size_info,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///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<Self>,
|
||||||
|
) {
|
||||||
|
match event {
|
||||||
|
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(Event::TitleChanged);
|
||||||
|
} else {
|
||||||
|
cx.notify()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AlacTermEvent::PtyWrite(out) => self.write_to_pty(&Input(out), cx),
|
||||||
|
AlacTermEvent::MouseCursorDirty => {
|
||||||
|
//Calculate new cursor style.
|
||||||
|
//TODO
|
||||||
|
//Check on correctly handling mouse events for terminals
|
||||||
|
cx.platform().set_cursor_style(CursorStyle::Arrow); //???
|
||||||
|
}
|
||||||
|
AlacTermEvent::Title(title) => {
|
||||||
|
self.title = title;
|
||||||
|
cx.emit(Event::TitleChanged);
|
||||||
|
}
|
||||||
|
AlacTermEvent::ResetTitle => {
|
||||||
|
self.title = DEFAULT_TITLE.to_string();
|
||||||
|
cx.emit(Event::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) => {
|
||||||
|
let color = self.term.lock().colors()[index].unwrap_or_else(|| {
|
||||||
|
let term_style = &cx.global::<Settings>().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 => {
|
||||||
|
//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(Event::TitleChanged);
|
||||||
|
}
|
||||||
|
AlacTermEvent::Exit => self.quit(&Quit, cx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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>) {
|
||||||
|
self.term.lock().scroll_display(Scroll::Delta(scroll.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
///Create a new Terminal
|
||||||
|
fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
|
||||||
|
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
|
||||||
|
fn shutdown_pty(&mut self) {
|
||||||
|
self.pty_tx.0.send(Msg::Shutdown).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn quit(&mut self, _: &Quit, cx: &mut ViewContext<Self>) {
|
||||||
|
cx.emit(Event::CloseTerminal);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
|
||||||
|
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<Self>) {
|
||||||
|
//iTerm bell behavior, bell stays until terminal is interacted with
|
||||||
|
self.has_bell = false;
|
||||||
|
self.term.lock().scroll_display(Scroll::Bottom);
|
||||||
|
cx.emit(Event::TitleChanged);
|
||||||
|
self.pty_tx.notify(input.0.clone().into_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn up(&mut self, _: &Up, cx: &mut ViewContext<Self>) {
|
||||||
|
self.write_to_pty(&Input(UP_SEQ.to_string()), cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn down(&mut self, _: &Down, cx: &mut ViewContext<Self>) {
|
||||||
|
self.write_to_pty(&Input(DOWN_SEQ.to_string()), cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tab(&mut self, _: &Tab, cx: &mut ViewContext<Self>) {
|
||||||
|
self.write_to_pty(&Input(TAB_CHAR.to_string()), cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_sigint(&mut self, _: &Sigint, cx: &mut ViewContext<Self>) {
|
||||||
|
self.write_to_pty(&Input(ETX_CHAR.to_string()), cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn escape(&mut self, _: &Escape, cx: &mut ViewContext<Self>) {
|
||||||
|
self.write_to_pty(&Input(ESC_CHAR.to_string()), cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn del(&mut self, _: &Del, cx: &mut ViewContext<Self>) {
|
||||||
|
self.write_to_pty(&Input(DEL_CHAR.to_string()), cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn carriage_return(&mut self, _: &Return, cx: &mut ViewContext<Self>) {
|
||||||
|
self.write_to_pty(&Input(CARRIAGE_RETURN_CHAR.to_string()), cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn left(&mut self, _: &Left, cx: &mut ViewContext<Self>) {
|
||||||
|
self.write_to_pty(&Input(LEFT_SEQ.to_string()), cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn right(&mut self, _: &Right, cx: &mut ViewContext<Self>) {
|
||||||
|
self.write_to_pty(&Input(RIGHT_SEQ.to_string()), cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Terminal {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.shutdown_pty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl View for Terminal {
|
||||||
|
fn ui_name() -> &'static str {
|
||||||
|
"Terminal"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox {
|
||||||
|
TerminalEl::new(cx.handle())
|
||||||
|
.contained()
|
||||||
|
// .with_style(theme.terminal.container)
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
|
cx.emit(Event::Activate);
|
||||||
|
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::<Settings>();
|
||||||
|
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<ProjectPath> {
|
||||||
|
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<Self>) {}
|
||||||
|
|
||||||
|
fn can_save(&self, _cx: &gpui::AppContext) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save(
|
||||||
|
&mut self,
|
||||||
|
_project: gpui::ModelHandle<Project>,
|
||||||
|
_cx: &mut ViewContext<Self>,
|
||||||
|
) -> gpui::Task<gpui::anyhow::Result<()>> {
|
||||||
|
unreachable!("save should not have been called");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_as(
|
||||||
|
&mut self,
|
||||||
|
_project: gpui::ModelHandle<Project>,
|
||||||
|
_abs_path: std::path::PathBuf,
|
||||||
|
_cx: &mut ViewContext<Self>,
|
||||||
|
) -> gpui::Task<gpui::anyhow::Result<()>> {
|
||||||
|
unreachable!("save_as should not have been called");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reload(
|
||||||
|
&mut self,
|
||||||
|
_project: gpui::ModelHandle<Project>,
|
||||||
|
_cx: &mut ViewContext<Self>,
|
||||||
|
) -> gpui::Task<gpui::anyhow::Result<()>> {
|
||||||
|
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, &Event::TitleChanged)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_close_item_on_event(event: &Self::Event) -> bool {
|
||||||
|
matches!(event, &Event::CloseTerminal)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_activate_item_on_event(event: &Self::Event) -> bool {
|
||||||
|
matches!(event, &Event::Activate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_alac_rgb(color: Color) -> AlacRgb {
|
||||||
|
AlacRgb {
|
||||||
|
r: color.r,
|
||||||
|
g: color.g,
|
||||||
|
b: color.g,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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, None));
|
||||||
|
|
||||||
|
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::<String>();
|
||||||
|
content.contains("7")
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
437
crates/terminal/src/terminal_element.rs
Normal file
437
crates/terminal/src/terminal_element.rs
Normal file
|
@ -0,0 +1,437 @@
|
||||||
|
use alacritty_terminal::{
|
||||||
|
ansi::Color as AnsiColor,
|
||||||
|
grid::{GridIterator, Indexed},
|
||||||
|
index::Point,
|
||||||
|
term::{
|
||||||
|
cell::{Cell, Flags},
|
||||||
|
SizeInfo,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use gpui::{
|
||||||
|
color::Color,
|
||||||
|
elements::*,
|
||||||
|
fonts::{HighlightStyle, TextStyle, Underline},
|
||||||
|
geometry::{rect::RectF, vector::vec2f},
|
||||||
|
json::json,
|
||||||
|
text_layout::Line,
|
||||||
|
Event, MouseRegion, PaintContext, Quad, WeakViewHandle,
|
||||||
|
};
|
||||||
|
use ordered_float::OrderedFloat;
|
||||||
|
use settings::Settings;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use theme::TerminalStyle;
|
||||||
|
|
||||||
|
use crate::{Input, ScrollTerminal, Terminal};
|
||||||
|
|
||||||
|
const ALACRITTY_SCROLL_MULTIPLIER: f32 = 3.;
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
const DEBUG_GRID: bool = false;
|
||||||
|
|
||||||
|
pub struct TerminalEl {
|
||||||
|
view: WeakViewHandle<Terminal>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TerminalEl {
|
||||||
|
pub fn new(view: WeakViewHandle<Terminal>) -> TerminalEl {
|
||||||
|
TerminalEl { view }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LayoutState {
|
||||||
|
lines: Vec<Line>,
|
||||||
|
line_height: f32,
|
||||||
|
em_width: f32,
|
||||||
|
cursor: Option<(RectF, Color)>,
|
||||||
|
cur_size: SizeInfo,
|
||||||
|
background_color: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 view = self.view.upgrade(cx).unwrap();
|
||||||
|
let size = constraint.max;
|
||||||
|
let settings = cx.global::<Settings>();
|
||||||
|
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: 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,
|
||||||
|
font_properties: Default::default(),
|
||||||
|
underline: Default::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
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,
|
||||||
|
size.y(),
|
||||||
|
cell_width,
|
||||||
|
line_height,
|
||||||
|
0.,
|
||||||
|
0.,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
view.update(cx.app, |view, _cx| {
|
||||||
|
view.set_size(new_size);
|
||||||
|
});
|
||||||
|
|
||||||
|
let settings = cx.global::<Settings>();
|
||||||
|
let terminal_theme = &settings.theme.terminal;
|
||||||
|
let term = view.read(cx).term.lock();
|
||||||
|
|
||||||
|
let content = term.renderable_content();
|
||||||
|
let (chunks, line_count) = build_chunks(content.display_iter, &terminal_theme);
|
||||||
|
|
||||||
|
let shaped_lines = layout_highlighted_chunks(
|
||||||
|
chunks.iter().map(|(text, style)| (text.as_str(), *style)),
|
||||||
|
&text_style,
|
||||||
|
cx.text_layout_cache,
|
||||||
|
&cx.font_cache,
|
||||||
|
usize::MAX,
|
||||||
|
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(cell_width, line_height),
|
||||||
|
),
|
||||||
|
terminal_theme.cursor,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
constraint.max,
|
||||||
|
LayoutState {
|
||||||
|
lines: shaped_lines,
|
||||||
|
line_height,
|
||||||
|
em_width: cell_width,
|
||||||
|
cursor,
|
||||||
|
cur_size: new_size,
|
||||||
|
background_color: terminal_theme.background,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
|
||||||
|
//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;
|
||||||
|
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, 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),
|
||||||
|
border: Default::default(),
|
||||||
|
corner_radius: 0.,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
if DEBUG_GRID {
|
||||||
|
draw_debug_grid(bounds, layout, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.scene.pop_layer();
|
||||||
|
}
|
||||||
|
|
||||||
|
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, position, ..
|
||||||
|
} => {
|
||||||
|
if visible_bounds.contains_point(*position) {
|
||||||
|
let vertical_scroll =
|
||||||
|
(delta.y() / layout.line_height) * ALACRITTY_SCROLL_MULTIPLIER;
|
||||||
|
cx.dispatch_action(ScrollTerminal(vertical_scroll.round() as i32));
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::KeyDown {
|
||||||
|
input: Some(input), ..
|
||||||
|
} => {
|
||||||
|
if cx.is_parent_view_focused() {
|
||||||
|
cx.dispatch_action(Input(input.to_string()));
|
||||||
|
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 {
|
||||||
|
json!({
|
||||||
|
"type": "TerminalElement",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn build_chunks(
|
||||||
|
grid_iterator: GridIterator<Cell>,
|
||||||
|
theme: &TerminalStyle,
|
||||||
|
) -> (Vec<(String, Option<HighlightStyle>)>, usize) {
|
||||||
|
let mut lines: Vec<(String, Option<HighlightStyle>)> = 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) {
|
||||||
|
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, style: &TerminalStyle) -> Color {
|
||||||
|
match allac_color {
|
||||||
|
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,
|
||||||
|
}, //Theme defined
|
||||||
|
alacritty_terminal::ansi::Color::Spec(rgb) => Color::new(rgb.r, rgb.g, rgb.b, 1),
|
||||||
|
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();
|
||||||
|
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.,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn test_rgb_for_index() {
|
||||||
|
//Test every possible value in the color cube
|
||||||
|
for i in 16..=231 {
|
||||||
|
let (r, g, b) = crate::terminal_element::rgb_for_index(&(i as u8));
|
||||||
|
assert_eq!(i, 16 + 36 * r + 6 * g + b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,6 +33,7 @@ pub struct Theme {
|
||||||
pub contact_notification: ContactNotification,
|
pub contact_notification: ContactNotification,
|
||||||
pub update_notification: UpdateNotification,
|
pub update_notification: UpdateNotification,
|
||||||
pub tooltip: TooltipStyle,
|
pub tooltip: TooltipStyle,
|
||||||
|
pub terminal: TerminalStyle,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default)]
|
#[derive(Deserialize, Default)]
|
||||||
|
@ -633,3 +634,36 @@ pub struct HoverPopover {
|
||||||
pub prose: TextStyle,
|
pub prose: TextStyle,
|
||||||
pub highlight: Color,
|
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,
|
||||||
|
}
|
||||||
|
|
|
@ -46,6 +46,7 @@ rpc = { path = "../rpc" }
|
||||||
settings = { path = "../settings" }
|
settings = { path = "../settings" }
|
||||||
sum_tree = { path = "../sum_tree" }
|
sum_tree = { path = "../sum_tree" }
|
||||||
text = { path = "../text" }
|
text = { path = "../text" }
|
||||||
|
terminal = { path = "../terminal" }
|
||||||
theme = { path = "../theme" }
|
theme = { path = "../theme" }
|
||||||
theme_selector = { path = "../theme_selector" }
|
theme_selector = { path = "../theme_selector" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
|
|
|
@ -36,6 +36,7 @@ use std::{
|
||||||
thread,
|
thread,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
use terminal;
|
||||||
use theme::{ThemeRegistry, DEFAULT_THEME_NAME};
|
use theme::{ThemeRegistry, DEFAULT_THEME_NAME};
|
||||||
use util::{ResultExt, TryFutureExt};
|
use util::{ResultExt, TryFutureExt};
|
||||||
use workspace::{self, AppState, NewFile, OpenPaths};
|
use workspace::{self, AppState, NewFile, OpenPaths};
|
||||||
|
@ -181,6 +182,7 @@ fn main() {
|
||||||
diagnostics::init(cx);
|
diagnostics::init(cx);
|
||||||
search::init(cx);
|
search::init(cx);
|
||||||
vim::init(cx);
|
vim::init(cx);
|
||||||
|
terminal::init(cx);
|
||||||
|
|
||||||
let db = cx.background().block(db);
|
let db = cx.background().block(db);
|
||||||
let (settings_file, keymap_file) = cx.background().block(config_files).unwrap();
|
let (settings_file, keymap_file) = cx.background().block(config_files).unwrap();
|
||||||
|
|
1
styles/package-lock.json
generated
1
styles/package-lock.json
generated
|
@ -5,7 +5,6 @@
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "styles",
|
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -14,6 +14,7 @@ import projectDiagnostics from "./projectDiagnostics";
|
||||||
import contactNotification from "./contactNotification";
|
import contactNotification from "./contactNotification";
|
||||||
import updateNotification from "./updateNotification";
|
import updateNotification from "./updateNotification";
|
||||||
import tooltip from "./tooltip";
|
import tooltip from "./tooltip";
|
||||||
|
import terminal from "./terminal";
|
||||||
|
|
||||||
export const panel = {
|
export const panel = {
|
||||||
padding: { top: 12, bottom: 12 },
|
padding: { top: 12, bottom: 12 },
|
||||||
|
@ -41,5 +42,6 @@ export default function app(theme: Theme): Object {
|
||||||
contactNotification: contactNotification(theme),
|
contactNotification: contactNotification(theme),
|
||||||
updateNotification: updateNotification(theme),
|
updateNotification: updateNotification(theme),
|
||||||
tooltip: tooltip(theme),
|
tooltip: tooltip(theme),
|
||||||
|
terminal: terminal(theme),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
35
styles/src/styleTree/terminal.ts
Normal file
35
styles/src/styleTree/terminal.ts
Normal file
|
@ -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(),
|
||||||
|
};
|
||||||
|
}
|
|
@ -13,15 +13,25 @@ export function colorRamp(color: Color): Scale {
|
||||||
export function createTheme(
|
export function createTheme(
|
||||||
name: string,
|
name: string,
|
||||||
isLight: boolean,
|
isLight: boolean,
|
||||||
ramps: { [rampName: string]: Scale },
|
color_ramps: { [rampName: string]: Scale },
|
||||||
): Theme {
|
): 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) {
|
if (isLight) {
|
||||||
for (var rampName in ramps) {
|
for (var rampName in color_ramps) {
|
||||||
ramps[rampName] = ramps[rampName].domain([1, 0]);
|
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 {
|
} 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;
|
let blend = isLight ? 0.12 : 0.24;
|
||||||
|
@ -237,6 +247,7 @@ export function createTheme(
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
|
isLight,
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
borderColor,
|
borderColor,
|
||||||
textColor,
|
textColor,
|
||||||
|
@ -245,5 +256,6 @@ export function createTheme(
|
||||||
syntax,
|
syntax,
|
||||||
player,
|
player,
|
||||||
shadow,
|
shadow,
|
||||||
|
ramps,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { Scale } from "chroma-js";
|
||||||
import { FontWeight } from "../../common";
|
import { FontWeight } from "../../common";
|
||||||
import { withOpacity } from "../../utils/color";
|
import { withOpacity } from "../../utils/color";
|
||||||
|
|
||||||
|
@ -60,6 +61,7 @@ export interface Syntax {
|
||||||
|
|
||||||
export default interface Theme {
|
export default interface Theme {
|
||||||
name: string;
|
name: string;
|
||||||
|
isLight: boolean,
|
||||||
backgroundColor: {
|
backgroundColor: {
|
||||||
// Basically just Title Bar
|
// Basically just Title Bar
|
||||||
// Lowest background level
|
// Lowest background level
|
||||||
|
@ -155,4 +157,5 @@ export default interface Theme {
|
||||||
8: Player;
|
8: Player;
|
||||||
},
|
},
|
||||||
shadow: string;
|
shadow: string;
|
||||||
|
ramps: { [rampName: string]: Scale };
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue