This commit is contained in:
Antonio Scandurra 2023-10-23 17:36:49 +02:00
parent 21d4546a86
commit 05cbceec24
13 changed files with 2720 additions and 8 deletions

29
Cargo.lock generated
View File

@ -8374,6 +8374,35 @@ dependencies = [
"util",
]
[[package]]
name = "terminal2"
version = "0.1.0"
dependencies = [
"alacritty_terminal",
"anyhow",
"db2",
"dirs 4.0.0",
"futures 0.3.28",
"gpui2",
"itertools 0.10.5",
"lazy_static",
"libc",
"mio-extras",
"ordered-float 2.10.0",
"procinfo",
"rand 0.8.5",
"schemars",
"serde",
"serde_derive",
"settings2",
"shellexpand",
"smallvec",
"smol",
"theme2",
"thiserror",
"util",
]
[[package]]
name = "terminal_view"
version = "0.1.0"

View File

@ -78,6 +78,7 @@ members = [
"crates/storybook2",
"crates/sum_tree",
"crates/terminal",
"crates/terminal2",
"crates/text",
"crates/theme",
"crates/theme2",

View File

@ -9,11 +9,11 @@ use refineable::Refineable;
use smallvec::SmallVec;
use crate::{
current_platform, image_cache::ImageCache, Action, AppMetadata, AssetSource, Context,
DispatchPhase, DisplayId, Executor, FocusEvent, FocusHandle, FocusId, KeyBinding, Keymap,
LayoutId, MainThread, MainThreadOnly, Platform, SharedString, SubscriberSet, Subscription,
SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, Window, WindowContext,
WindowHandle, WindowId,
current_platform, image_cache::ImageCache, Action, AppMetadata, AssetSource, ClipboardItem,
Context, DispatchPhase, DisplayId, Executor, FocusEvent, FocusHandle, FocusId, KeyBinding,
Keymap, LayoutId, MainThread, MainThreadOnly, Platform, SharedString, SubscriberSet,
Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, Window,
WindowContext, WindowHandle, WindowId,
};
use anyhow::{anyhow, Result};
use collections::{HashMap, HashSet, VecDeque};
@ -697,6 +697,14 @@ impl MainThread<AppContext> {
self.platform().activate(ignoring_other_apps);
}
pub fn write_to_clipboard(&self, item: ClipboardItem) {
self.platform().write_to_clipboard(item)
}
pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
self.platform().read_from_clipboard()
}
pub fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
self.platform().write_credentials(url, username, password)
}

View File

@ -1,6 +1,7 @@
use core::fmt::Debug;
use derive_more::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign};
use refineable::Refineable;
use serde_derive::{Deserialize, Serialize};
use std::{
cmp::{self, PartialOrd},
fmt,
@ -647,7 +648,21 @@ where
impl<T> Copy for Corners<T> where T: Copy + Clone + Default + Debug {}
#[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, Neg, PartialEq, PartialOrd)]
#[derive(
Clone,
Copy,
Default,
Add,
AddAssign,
Sub,
SubAssign,
Div,
Neg,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
)]
#[repr(transparent)]
pub struct Pixels(pub(crate) f32);
@ -684,6 +699,10 @@ impl MulAssign<f32> for Pixels {
impl Pixels {
pub const MAX: Pixels = Pixels(f32::MAX);
pub fn floor(&self) -> Self {
Self(self.0.floor())
}
pub fn round(&self) -> Self {
Self(self.0.round())
}
@ -1008,7 +1027,7 @@ pub fn rems(rems: f32) -> Rems {
Rems(rems)
}
pub fn px(pixels: f32) -> Pixels {
pub const fn px(pixels: f32) -> Pixels {
Pixels(pixels)
}

View File

@ -1653,6 +1653,12 @@ impl<'a, 'w, S: 'static> std::ops::DerefMut for ViewContext<'a, 'w, S> {
// #[derive(Clone, Copy, Eq, PartialEq, Hash)]
slotmap::new_key_type! { pub struct WindowId; }
impl WindowId {
pub fn as_u64(&self) -> u64 {
self.0.as_ffi()
}
}
#[derive(PartialEq, Eq)]
pub struct WindowHandle<S> {
id: WindowId,
@ -1694,6 +1700,12 @@ pub struct AnyWindowHandle {
state_type: TypeId,
}
impl AnyWindowHandle {
pub fn window_id(&self) -> WindowId {
self.id
}
}
#[cfg(any(test, feature = "test"))]
impl From<SmallVec<[u32; 16]>> for StackingOrder {
fn from(small_vec: SmallVec<[u32; 16]>) -> Self {

View File

@ -1245,7 +1245,7 @@ impl LocalWorktree {
.unbounded_send((self.snapshot(), Arc::from([]), Arc::from([])))
.ok();
let worktree_id = cx.entity_id().as_u64();
let worktree_id = cx.entity_id().;
let _maintain_remote_snapshot = cx.executor().spawn(async move {
let mut is_first = true;
while let Some((snapshot, entry_changes, repo_changes)) = snapshots_rx.next().await {

View File

@ -0,0 +1,38 @@
[package]
name = "terminal2"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
path = "src/terminal2.rs"
doctest = false
[dependencies]
gpui2 = { path = "../gpui2" }
settings2 = { path = "../settings2" }
db2 = { path = "../db2" }
theme2 = { path = "../theme2" }
util = { path = "../util" }
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty", rev = "33306142195b354ef3485ca2b1d8a85dfc6605ca" }
procinfo = { git = "https://github.com/zed-industries/wezterm", rev = "5cd757e5f2eb039ed0c6bb6512223e69d5efc64d", default-features = false }
smallvec.workspace = true
smol.workspace = true
mio-extras = "2.0.6"
futures.workspace = true
ordered-float.workspace = true
itertools = "0.10"
dirs = "4.0.0"
shellexpand = "2.1.0"
libc = "0.2"
anyhow.workspace = true
schemars.workspace = true
thiserror.workspace = true
lazy_static.workspace = true
serde.workspace = true
serde_derive.workspace = true
[dev-dependencies]
rand.workspace = true

View File

@ -0,0 +1,130 @@
use alacritty_terminal::{ansi::Color as AnsiColor, term::color::Rgb as AlacRgb};
use gpui2::color::Color;
use theme2::TerminalStyle;
///Converts a 2, 8, or 24 bit color ANSI color to the GPUI equivalent
pub fn convert_color(alac_color: &AnsiColor, style: &TerminalStyle) -> Color {
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,
},
//'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),
}
}
///Converts an 8 bit ANSI color to it's GPUI equivalent.
///Accepts usize for compatibility 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 {
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,
//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
let step = (u8::MAX as f32 / 5.).floor() as u8; //Split the RGB range into 5 chunks, with floor so no overflow
Color::new(r * step, g * step, b * step, u8::MAX) //Map the ANSI-RGB components to an RGB color
}
//232-255 are a 24 step grayscale from black to white
232..=255 => {
let i = *index as u8 - 232; //Align index to 0..24
let step = (u8::MAX as f32 / 24.).floor() as u8; //Split the RGB grayscale values into 24 chunks
Color::new(i * step, i * step, i * step, u8::MAX) //Map the ANSI-grayscale components to the RGB-grayscale
}
//For compatibility 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
_ => Color::new(0, 0, 0, 255),
}
}
///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!((&16..=&231).contains(&i));
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)
}
//Convenience method to convert from a GPUI color to an alacritty Rgb
pub fn to_alac_rgb(color: Color) -> AlacRgb {
AlacRgb::new(color.r, color.g, color.g)
}
#[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::mappings::colors::rgb_for_index(&(i as u8));
assert_eq!(i, 16 + 36 * r + 6 * g + b);
}
}
}

View File

@ -0,0 +1,457 @@
/// The mappings defined in this file where created from reading the alacritty source
use alacritty_terminal::term::TermMode;
use gpui2::keymap_matcher::Keystroke;
#[derive(Debug, PartialEq, Eq)]
pub enum Modifiers {
None,
Alt,
Ctrl,
Shift,
CtrlShift,
Other,
}
impl Modifiers {
fn new(ks: &Keystroke) -> Self {
match (ks.alt, ks.ctrl, ks.shift, ks.cmd) {
(false, false, false, false) => Modifiers::None,
(true, false, false, false) => Modifiers::Alt,
(false, true, false, false) => Modifiers::Ctrl,
(false, false, true, false) => Modifiers::Shift,
(false, true, true, false) => Modifiers::CtrlShift,
_ => Modifiers::Other,
}
}
fn any(&self) -> bool {
match &self {
Modifiers::None => false,
Modifiers::Alt => true,
Modifiers::Ctrl => true,
Modifiers::Shift => true,
Modifiers::CtrlShift => true,
Modifiers::Other => true,
}
}
}
pub fn to_esc_str(keystroke: &Keystroke, mode: &TermMode, alt_is_meta: bool) -> Option<String> {
let modifiers = Modifiers::new(keystroke);
// Manual Bindings including modifiers
let manual_esc_str = match (keystroke.key.as_ref(), &modifiers) {
//Basic special keys
("tab", Modifiers::None) => Some("\x09".to_string()),
("escape", Modifiers::None) => Some("\x1b".to_string()),
("enter", Modifiers::None) => Some("\x0d".to_string()),
("enter", Modifiers::Shift) => Some("\x0d".to_string()),
("backspace", Modifiers::None) => Some("\x7f".to_string()),
//Interesting escape codes
("tab", Modifiers::Shift) => Some("\x1b[Z".to_string()),
("backspace", Modifiers::Alt) => Some("\x1b\x7f".to_string()),
("backspace", Modifiers::Shift) => Some("\x7f".to_string()),
("home", Modifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => {
Some("\x1b[1;2H".to_string())
}
("end", Modifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => {
Some("\x1b[1;2F".to_string())
}
("pageup", Modifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => {
Some("\x1b[5;2~".to_string())
}
("pagedown", Modifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => {
Some("\x1b[6;2~".to_string())
}
("home", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
Some("\x1bOH".to_string())
}
("home", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
Some("\x1b[H".to_string())
}
("end", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
Some("\x1bOF".to_string())
}
("end", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
Some("\x1b[F".to_string())
}
("up", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
Some("\x1bOA".to_string())
}
("up", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
Some("\x1b[A".to_string())
}
("down", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
Some("\x1bOB".to_string())
}
("down", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
Some("\x1b[B".to_string())
}
("right", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
Some("\x1bOC".to_string())
}
("right", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
Some("\x1b[C".to_string())
}
("left", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
Some("\x1bOD".to_string())
}
("left", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
Some("\x1b[D".to_string())
}
("back", Modifiers::None) => Some("\x7f".to_string()),
("insert", Modifiers::None) => Some("\x1b[2~".to_string()),
("delete", Modifiers::None) => Some("\x1b[3~".to_string()),
("pageup", Modifiers::None) => Some("\x1b[5~".to_string()),
("pagedown", Modifiers::None) => Some("\x1b[6~".to_string()),
("f1", Modifiers::None) => Some("\x1bOP".to_string()),
("f2", Modifiers::None) => Some("\x1bOQ".to_string()),
("f3", Modifiers::None) => Some("\x1bOR".to_string()),
("f4", Modifiers::None) => Some("\x1bOS".to_string()),
("f5", Modifiers::None) => Some("\x1b[15~".to_string()),
("f6", Modifiers::None) => Some("\x1b[17~".to_string()),
("f7", Modifiers::None) => Some("\x1b[18~".to_string()),
("f8", Modifiers::None) => Some("\x1b[19~".to_string()),
("f9", Modifiers::None) => Some("\x1b[20~".to_string()),
("f10", Modifiers::None) => Some("\x1b[21~".to_string()),
("f11", Modifiers::None) => Some("\x1b[23~".to_string()),
("f12", Modifiers::None) => Some("\x1b[24~".to_string()),
("f13", Modifiers::None) => Some("\x1b[25~".to_string()),
("f14", Modifiers::None) => Some("\x1b[26~".to_string()),
("f15", Modifiers::None) => Some("\x1b[28~".to_string()),
("f16", Modifiers::None) => Some("\x1b[29~".to_string()),
("f17", Modifiers::None) => Some("\x1b[31~".to_string()),
("f18", Modifiers::None) => Some("\x1b[32~".to_string()),
("f19", Modifiers::None) => Some("\x1b[33~".to_string()),
("f20", Modifiers::None) => Some("\x1b[34~".to_string()),
// NumpadEnter, Action::Esc("\n".into());
//Mappings for caret notation keys
("a", Modifiers::Ctrl) => Some("\x01".to_string()), //1
("A", Modifiers::CtrlShift) => Some("\x01".to_string()), //1
("b", Modifiers::Ctrl) => Some("\x02".to_string()), //2
("B", Modifiers::CtrlShift) => Some("\x02".to_string()), //2
("c", Modifiers::Ctrl) => Some("\x03".to_string()), //3
("C", Modifiers::CtrlShift) => Some("\x03".to_string()), //3
("d", Modifiers::Ctrl) => Some("\x04".to_string()), //4
("D", Modifiers::CtrlShift) => Some("\x04".to_string()), //4
("e", Modifiers::Ctrl) => Some("\x05".to_string()), //5
("E", Modifiers::CtrlShift) => Some("\x05".to_string()), //5
("f", Modifiers::Ctrl) => Some("\x06".to_string()), //6
("F", Modifiers::CtrlShift) => Some("\x06".to_string()), //6
("g", Modifiers::Ctrl) => Some("\x07".to_string()), //7
("G", Modifiers::CtrlShift) => Some("\x07".to_string()), //7
("h", Modifiers::Ctrl) => Some("\x08".to_string()), //8
("H", Modifiers::CtrlShift) => Some("\x08".to_string()), //8
("i", Modifiers::Ctrl) => Some("\x09".to_string()), //9
("I", Modifiers::CtrlShift) => Some("\x09".to_string()), //9
("j", Modifiers::Ctrl) => Some("\x0a".to_string()), //10
("J", Modifiers::CtrlShift) => Some("\x0a".to_string()), //10
("k", Modifiers::Ctrl) => Some("\x0b".to_string()), //11
("K", Modifiers::CtrlShift) => Some("\x0b".to_string()), //11
("l", Modifiers::Ctrl) => Some("\x0c".to_string()), //12
("L", Modifiers::CtrlShift) => Some("\x0c".to_string()), //12
("m", Modifiers::Ctrl) => Some("\x0d".to_string()), //13
("M", Modifiers::CtrlShift) => Some("\x0d".to_string()), //13
("n", Modifiers::Ctrl) => Some("\x0e".to_string()), //14
("N", Modifiers::CtrlShift) => Some("\x0e".to_string()), //14
("o", Modifiers::Ctrl) => Some("\x0f".to_string()), //15
("O", Modifiers::CtrlShift) => Some("\x0f".to_string()), //15
("p", Modifiers::Ctrl) => Some("\x10".to_string()), //16
("P", Modifiers::CtrlShift) => Some("\x10".to_string()), //16
("q", Modifiers::Ctrl) => Some("\x11".to_string()), //17
("Q", Modifiers::CtrlShift) => Some("\x11".to_string()), //17
("r", Modifiers::Ctrl) => Some("\x12".to_string()), //18
("R", Modifiers::CtrlShift) => Some("\x12".to_string()), //18
("s", Modifiers::Ctrl) => Some("\x13".to_string()), //19
("S", Modifiers::CtrlShift) => Some("\x13".to_string()), //19
("t", Modifiers::Ctrl) => Some("\x14".to_string()), //20
("T", Modifiers::CtrlShift) => Some("\x14".to_string()), //20
("u", Modifiers::Ctrl) => Some("\x15".to_string()), //21
("U", Modifiers::CtrlShift) => Some("\x15".to_string()), //21
("v", Modifiers::Ctrl) => Some("\x16".to_string()), //22
("V", Modifiers::CtrlShift) => Some("\x16".to_string()), //22
("w", Modifiers::Ctrl) => Some("\x17".to_string()), //23
("W", Modifiers::CtrlShift) => Some("\x17".to_string()), //23
("x", Modifiers::Ctrl) => Some("\x18".to_string()), //24
("X", Modifiers::CtrlShift) => Some("\x18".to_string()), //24
("y", Modifiers::Ctrl) => Some("\x19".to_string()), //25
("Y", Modifiers::CtrlShift) => Some("\x19".to_string()), //25
("z", Modifiers::Ctrl) => Some("\x1a".to_string()), //26
("Z", Modifiers::CtrlShift) => Some("\x1a".to_string()), //26
("@", Modifiers::Ctrl) => Some("\x00".to_string()), //0
("[", Modifiers::Ctrl) => Some("\x1b".to_string()), //27
("\\", Modifiers::Ctrl) => Some("\x1c".to_string()), //28
("]", Modifiers::Ctrl) => Some("\x1d".to_string()), //29
("^", Modifiers::Ctrl) => Some("\x1e".to_string()), //30
("_", Modifiers::Ctrl) => Some("\x1f".to_string()), //31
("?", Modifiers::Ctrl) => Some("\x7f".to_string()), //127
_ => None,
};
if manual_esc_str.is_some() {
return manual_esc_str;
}
// Automated bindings applying modifiers
if modifiers.any() {
let modifier_code = modifier_code(keystroke);
let modified_esc_str = match keystroke.key.as_ref() {
"up" => Some(format!("\x1b[1;{}A", modifier_code)),
"down" => Some(format!("\x1b[1;{}B", modifier_code)),
"right" => Some(format!("\x1b[1;{}C", modifier_code)),
"left" => Some(format!("\x1b[1;{}D", modifier_code)),
"f1" => Some(format!("\x1b[1;{}P", modifier_code)),
"f2" => Some(format!("\x1b[1;{}Q", modifier_code)),
"f3" => Some(format!("\x1b[1;{}R", modifier_code)),
"f4" => Some(format!("\x1b[1;{}S", modifier_code)),
"F5" => Some(format!("\x1b[15;{}~", modifier_code)),
"f6" => Some(format!("\x1b[17;{}~", modifier_code)),
"f7" => Some(format!("\x1b[18;{}~", modifier_code)),
"f8" => Some(format!("\x1b[19;{}~", modifier_code)),
"f9" => Some(format!("\x1b[20;{}~", modifier_code)),
"f10" => Some(format!("\x1b[21;{}~", modifier_code)),
"f11" => Some(format!("\x1b[23;{}~", modifier_code)),
"f12" => Some(format!("\x1b[24;{}~", modifier_code)),
"f13" => Some(format!("\x1b[25;{}~", modifier_code)),
"f14" => Some(format!("\x1b[26;{}~", modifier_code)),
"f15" => Some(format!("\x1b[28;{}~", modifier_code)),
"f16" => Some(format!("\x1b[29;{}~", modifier_code)),
"f17" => Some(format!("\x1b[31;{}~", modifier_code)),
"f18" => Some(format!("\x1b[32;{}~", modifier_code)),
"f19" => Some(format!("\x1b[33;{}~", modifier_code)),
"f20" => Some(format!("\x1b[34;{}~", modifier_code)),
_ if modifier_code == 2 => None,
"insert" => Some(format!("\x1b[2;{}~", modifier_code)),
"pageup" => Some(format!("\x1b[5;{}~", modifier_code)),
"pagedown" => Some(format!("\x1b[6;{}~", modifier_code)),
"end" => Some(format!("\x1b[1;{}F", modifier_code)),
"home" => Some(format!("\x1b[1;{}H", modifier_code)),
_ => None,
};
if modified_esc_str.is_some() {
return modified_esc_str;
}
}
let alt_meta_binding = if alt_is_meta && modifiers == Modifiers::Alt && keystroke.key.is_ascii()
{
Some(format!("\x1b{}", keystroke.key))
} else {
None
};
if alt_meta_binding.is_some() {
return alt_meta_binding;
}
None
}
/// Code Modifiers
/// ---------+---------------------------
/// 2 | Shift
/// 3 | Alt
/// 4 | Shift + Alt
/// 5 | Control
/// 6 | Shift + Control
/// 7 | Alt + Control
/// 8 | Shift + Alt + Control
/// ---------+---------------------------
/// from: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keys
fn modifier_code(keystroke: &Keystroke) -> u32 {
let mut modifier_code = 0;
if keystroke.shift {
modifier_code |= 1;
}
if keystroke.alt {
modifier_code |= 1 << 1;
}
if keystroke.ctrl {
modifier_code |= 1 << 2;
}
modifier_code + 1
}
#[cfg(test)]
mod test {
use gpui2::keymap_matcher::Keystroke;
use super::*;
#[test]
fn test_scroll_keys() {
//These keys should be handled by the scrolling element directly
//Need to signify this by returning 'None'
let shift_pageup = Keystroke::parse("shift-pageup").unwrap();
let shift_pagedown = Keystroke::parse("shift-pagedown").unwrap();
let shift_home = Keystroke::parse("shift-home").unwrap();
let shift_end = Keystroke::parse("shift-end").unwrap();
let none = TermMode::NONE;
assert_eq!(to_esc_str(&shift_pageup, &none, false), None);
assert_eq!(to_esc_str(&shift_pagedown, &none, false), None);
assert_eq!(to_esc_str(&shift_home, &none, false), None);
assert_eq!(to_esc_str(&shift_end, &none, false), None);
let alt_screen = TermMode::ALT_SCREEN;
assert_eq!(
to_esc_str(&shift_pageup, &alt_screen, false),
Some("\x1b[5;2~".to_string())
);
assert_eq!(
to_esc_str(&shift_pagedown, &alt_screen, false),
Some("\x1b[6;2~".to_string())
);
assert_eq!(
to_esc_str(&shift_home, &alt_screen, false),
Some("\x1b[1;2H".to_string())
);
assert_eq!(
to_esc_str(&shift_end, &alt_screen, false),
Some("\x1b[1;2F".to_string())
);
let pageup = Keystroke::parse("pageup").unwrap();
let pagedown = Keystroke::parse("pagedown").unwrap();
let any = TermMode::ANY;
assert_eq!(
to_esc_str(&pageup, &any, false),
Some("\x1b[5~".to_string())
);
assert_eq!(
to_esc_str(&pagedown, &any, false),
Some("\x1b[6~".to_string())
);
}
#[test]
fn test_plain_inputs() {
let ks = Keystroke {
ctrl: false,
alt: false,
shift: false,
cmd: false,
function: false,
key: "🖖🏻".to_string(), //2 char string
ime_key: None,
};
assert_eq!(to_esc_str(&ks, &TermMode::NONE, false), None);
}
#[test]
fn test_application_mode() {
let app_cursor = TermMode::APP_CURSOR;
let none = TermMode::NONE;
let up = Keystroke::parse("up").unwrap();
let down = Keystroke::parse("down").unwrap();
let left = Keystroke::parse("left").unwrap();
let right = Keystroke::parse("right").unwrap();
assert_eq!(to_esc_str(&up, &none, false), Some("\x1b[A".to_string()));
assert_eq!(to_esc_str(&down, &none, false), Some("\x1b[B".to_string()));
assert_eq!(to_esc_str(&right, &none, false), Some("\x1b[C".to_string()));
assert_eq!(to_esc_str(&left, &none, false), Some("\x1b[D".to_string()));
assert_eq!(
to_esc_str(&up, &app_cursor, false),
Some("\x1bOA".to_string())
);
assert_eq!(
to_esc_str(&down, &app_cursor, false),
Some("\x1bOB".to_string())
);
assert_eq!(
to_esc_str(&right, &app_cursor, false),
Some("\x1bOC".to_string())
);
assert_eq!(
to_esc_str(&left, &app_cursor, false),
Some("\x1bOD".to_string())
);
}
#[test]
fn test_ctrl_codes() {
let letters_lower = 'a'..='z';
let letters_upper = 'A'..='Z';
let mode = TermMode::ANY;
for (lower, upper) in letters_lower.zip(letters_upper) {
assert_eq!(
to_esc_str(
&Keystroke::parse(&format!("ctrl-{}", lower)).unwrap(),
&mode,
false
),
to_esc_str(
&Keystroke::parse(&format!("ctrl-shift-{}", upper)).unwrap(),
&mode,
false
),
"On letter: {}/{}",
lower,
upper
)
}
}
#[test]
fn alt_is_meta() {
let ascii_printable = ' '..='~';
for character in ascii_printable {
assert_eq!(
to_esc_str(
&Keystroke::parse(&format!("alt-{}", character)).unwrap(),
&TermMode::NONE,
true
)
.unwrap(),
format!("\x1b{}", character)
);
}
let gpui_keys = [
"up", "down", "right", "left", "f1", "f2", "f3", "f4", "F5", "f6", "f7", "f8", "f9",
"f10", "f11", "f12", "f13", "f14", "f15", "f16", "f17", "f18", "f19", "f20", "insert",
"pageup", "pagedown", "end", "home",
];
for key in gpui_keys {
assert_ne!(
to_esc_str(
&Keystroke::parse(&format!("alt-{}", key)).unwrap(),
&TermMode::NONE,
true
)
.unwrap(),
format!("\x1b{}", key)
);
}
}
#[test]
fn test_modifier_code_calc() {
// Code Modifiers
// ---------+---------------------------
// 2 | Shift
// 3 | Alt
// 4 | Shift + Alt
// 5 | Control
// 6 | Shift + Control
// 7 | Alt + Control
// 8 | Shift + Alt + Control
// ---------+---------------------------
// from: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keys
assert_eq!(2, modifier_code(&Keystroke::parse("shift-A").unwrap()));
assert_eq!(3, modifier_code(&Keystroke::parse("alt-A").unwrap()));
assert_eq!(4, modifier_code(&Keystroke::parse("shift-alt-A").unwrap()));
assert_eq!(5, modifier_code(&Keystroke::parse("ctrl-A").unwrap()));
assert_eq!(6, modifier_code(&Keystroke::parse("shift-ctrl-A").unwrap()));
assert_eq!(7, modifier_code(&Keystroke::parse("alt-ctrl-A").unwrap()));
assert_eq!(
8,
modifier_code(&Keystroke::parse("shift-ctrl-alt-A").unwrap())
);
}
}

View File

@ -0,0 +1,3 @@
pub mod colors;
pub mod keys;
pub mod mouse;

View File

@ -0,0 +1,336 @@
use std::cmp::{max, min};
use std::iter::repeat;
use alacritty_terminal::grid::Dimensions;
/// Most of the code, and specifically the constants, in this are copied from Alacritty,
/// with modifications for our circumstances
use alacritty_terminal::index::{Column as GridCol, Line as GridLine, Point, Side};
use alacritty_terminal::term::TermMode;
use gpui2::platform;
use gpui2::scene::MouseScrollWheel;
use gpui2::{
geometry::vector::Vector2F,
platform::{MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent},
};
use crate::TerminalSize;
struct Modifiers {
ctrl: bool,
shift: bool,
alt: bool,
}
impl Modifiers {
fn from_moved(e: &MouseMovedEvent) -> Self {
Modifiers {
ctrl: e.ctrl,
shift: e.shift,
alt: e.alt,
}
}
fn from_button(e: &MouseButtonEvent) -> Self {
Modifiers {
ctrl: e.ctrl,
shift: e.shift,
alt: e.alt,
}
}
fn from_scroll(scroll: &ScrollWheelEvent) -> Self {
Modifiers {
ctrl: scroll.ctrl,
shift: scroll.shift,
alt: scroll.alt,
}
}
}
enum MouseFormat {
SGR,
Normal(bool),
}
impl MouseFormat {
fn from_mode(mode: TermMode) -> Self {
if mode.contains(TermMode::SGR_MOUSE) {
MouseFormat::SGR
} else if mode.contains(TermMode::UTF8_MOUSE) {
MouseFormat::Normal(true)
} else {
MouseFormat::Normal(false)
}
}
}
#[derive(Debug)]
enum MouseButton {
LeftButton = 0,
MiddleButton = 1,
RightButton = 2,
LeftMove = 32,
MiddleMove = 33,
RightMove = 34,
NoneMove = 35,
ScrollUp = 64,
ScrollDown = 65,
Other = 99,
}
impl MouseButton {
fn from_move(e: &MouseMovedEvent) -> Self {
match e.pressed_button {
Some(b) => match b {
platform::MouseButton::Left => MouseButton::LeftMove,
platform::MouseButton::Middle => MouseButton::MiddleMove,
platform::MouseButton::Right => MouseButton::RightMove,
platform::MouseButton::Navigate(_) => MouseButton::Other,
},
None => MouseButton::NoneMove,
}
}
fn from_button(e: &MouseButtonEvent) -> Self {
match e.button {
platform::MouseButton::Left => MouseButton::LeftButton,
platform::MouseButton::Right => MouseButton::MiddleButton,
platform::MouseButton::Middle => MouseButton::RightButton,
platform::MouseButton::Navigate(_) => MouseButton::Other,
}
}
fn from_scroll(e: &ScrollWheelEvent) -> Self {
if e.delta.raw().y() > 0. {
MouseButton::ScrollUp
} else {
MouseButton::ScrollDown
}
}
fn is_other(&self) -> bool {
match self {
MouseButton::Other => true,
_ => false,
}
}
}
pub fn scroll_report(
point: Point,
scroll_lines: i32,
e: &MouseScrollWheel,
mode: TermMode,
) -> Option<impl Iterator<Item = Vec<u8>>> {
if mode.intersects(TermMode::MOUSE_MODE) {
mouse_report(
point,
MouseButton::from_scroll(e),
true,
Modifiers::from_scroll(e),
MouseFormat::from_mode(mode),
)
.map(|report| repeat(report).take(max(scroll_lines, 1) as usize))
} else {
None
}
}
pub fn alt_scroll(scroll_lines: i32) -> Vec<u8> {
let cmd = if scroll_lines > 0 { b'A' } else { b'B' };
let mut content = Vec::with_capacity(scroll_lines.abs() as usize * 3);
for _ in 0..scroll_lines.abs() {
content.push(0x1b);
content.push(b'O');
content.push(cmd);
}
content
}
pub fn mouse_button_report(
point: Point,
e: &MouseButtonEvent,
pressed: bool,
mode: TermMode,
) -> Option<Vec<u8>> {
let button = MouseButton::from_button(e);
if !button.is_other() && mode.intersects(TermMode::MOUSE_MODE) {
mouse_report(
point,
button,
pressed,
Modifiers::from_button(e),
MouseFormat::from_mode(mode),
)
} else {
None
}
}
pub fn mouse_moved_report(point: Point, e: &MouseMovedEvent, mode: TermMode) -> Option<Vec<u8>> {
let button = MouseButton::from_move(e);
if !button.is_other() && mode.intersects(TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG) {
//Only drags are reported in drag mode, so block NoneMove.
if mode.contains(TermMode::MOUSE_DRAG) && matches!(button, MouseButton::NoneMove) {
None
} else {
mouse_report(
point,
button,
true,
Modifiers::from_moved(e),
MouseFormat::from_mode(mode),
)
}
} else {
None
}
}
pub fn mouse_side(pos: Vector2F, cur_size: TerminalSize) -> alacritty_terminal::index::Direction {
if cur_size.cell_width as usize == 0 {
return Side::Right;
}
let x = pos.0.x() as usize;
let cell_x = x.saturating_sub(cur_size.cell_width as usize) % cur_size.cell_width as usize;
let half_cell_width = (cur_size.cell_width / 2.0) as usize;
let additional_padding = (cur_size.width() - cur_size.cell_width * 2.) % cur_size.cell_width;
let end_of_grid = cur_size.width() - cur_size.cell_width - additional_padding;
//Width: Pixels or columns?
if cell_x > half_cell_width
// Edge case when mouse leaves the window.
|| x as f32 >= end_of_grid
{
Side::Right
} else {
Side::Left
}
}
pub fn grid_point(pos: Vector2F, cur_size: TerminalSize, display_offset: usize) -> Point {
let col = pos.x() / cur_size.cell_width;
let col = min(GridCol(col as usize), cur_size.last_column());
let line = pos.y() / cur_size.line_height;
let line = min(line as i32, cur_size.bottommost_line().0);
Point::new(GridLine(line - display_offset as i32), col)
}
///Generate the bytes to send to the terminal, from the cell location, a mouse event, and the terminal mode
fn mouse_report(
point: Point,
button: MouseButton,
pressed: bool,
modifiers: Modifiers,
format: MouseFormat,
) -> Option<Vec<u8>> {
if point.line < 0 {
return None;
}
let mut mods = 0;
if modifiers.shift {
mods += 4;
}
if modifiers.alt {
mods += 8;
}
if modifiers.ctrl {
mods += 16;
}
match format {
MouseFormat::SGR => {
Some(sgr_mouse_report(point, button as u8 + mods, pressed).into_bytes())
}
MouseFormat::Normal(utf8) => {
if pressed {
normal_mouse_report(point, button as u8 + mods, utf8)
} else {
normal_mouse_report(point, 3 + mods, utf8)
}
}
}
}
fn normal_mouse_report(point: Point, button: u8, utf8: bool) -> Option<Vec<u8>> {
let Point { line, column } = point;
let max_point = if utf8 { 2015 } else { 223 };
if line >= max_point || column >= max_point {
return None;
}
let mut msg = vec![b'\x1b', b'[', b'M', 32 + button];
let mouse_pos_encode = |pos: usize| -> Vec<u8> {
let pos = 32 + 1 + pos;
let first = 0xC0 + pos / 64;
let second = 0x80 + (pos & 63);
vec![first as u8, second as u8]
};
if utf8 && column >= 95 {
msg.append(&mut mouse_pos_encode(column.0));
} else {
msg.push(32 + 1 + column.0 as u8);
}
if utf8 && line >= 95 {
msg.append(&mut mouse_pos_encode(line.0 as usize));
} else {
msg.push(32 + 1 + line.0 as u8);
}
Some(msg)
}
fn sgr_mouse_report(point: Point, button: u8, pressed: bool) -> String {
let c = if pressed { 'M' } else { 'm' };
let msg = format!(
"\x1b[<{};{};{}{}",
button,
point.column + 1,
point.line + 1,
c
);
msg
}
#[cfg(test)]
mod test {
use crate::mappings::mouse::grid_point;
#[test]
fn test_mouse_to_selection() {
let term_width = 100.;
let term_height = 200.;
let cell_width = 10.;
let line_height = 20.;
let mouse_pos_x = 100.; //Window relative
let mouse_pos_y = 100.; //Window relative
let origin_x = 10.;
let origin_y = 20.;
let cur_size = crate::TerminalSize::new(
line_height,
cell_width,
gpui::geometry::vector::vec2f(term_width, term_height),
);
let mouse_pos = gpui::geometry::vector::vec2f(mouse_pos_x, mouse_pos_y);
let origin = gpui::geometry::vector::vec2f(origin_x, origin_y); //Position of terminal window, 1 'cell' in
let mouse_pos = mouse_pos - origin;
let point = grid_point(mouse_pos, cur_size, 0);
assert_eq!(
point,
alacritty_terminal::index::Point::new(
alacritty_terminal::index::Line(((mouse_pos_y - origin_y) / line_height) as i32),
alacritty_terminal::index::Column(((mouse_pos_x - origin_x) / cell_width) as usize),
)
);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,164 @@
use std::{collections::HashMap, path::PathBuf};
use gpui2::{fonts, AppContext};
use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize};
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum TerminalDockPosition {
Left,
Bottom,
Right,
}
#[derive(Deserialize)]
pub struct TerminalSettings {
pub shell: Shell,
pub working_directory: WorkingDirectory,
font_size: Option<f32>,
pub font_family: Option<String>,
pub line_height: TerminalLineHeight,
pub font_features: Option<fonts::Features>,
pub env: HashMap<String, String>,
pub blinking: TerminalBlink,
pub alternate_scroll: AlternateScroll,
pub option_as_meta: bool,
pub copy_on_select: bool,
pub dock: TerminalDockPosition,
pub default_width: f32,
pub default_height: f32,
pub detect_venv: VenvSettings,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum VenvSettings {
#[default]
Off,
On {
activate_script: Option<ActivateScript>,
directories: Option<Vec<PathBuf>>,
},
}
pub struct VenvSettingsContent<'a> {
pub activate_script: ActivateScript,
pub directories: &'a [PathBuf],
}
impl VenvSettings {
pub fn as_option(&self) -> Option<VenvSettingsContent> {
match self {
VenvSettings::Off => None,
VenvSettings::On {
activate_script,
directories,
} => Some(VenvSettingsContent {
activate_script: activate_script.unwrap_or(ActivateScript::Default),
directories: directories.as_deref().unwrap_or(&[]),
}),
}
}
}
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ActivateScript {
#[default]
Default,
Csh,
Fish,
Nushell,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
pub struct TerminalSettingsContent {
pub shell: Option<Shell>,
pub working_directory: Option<WorkingDirectory>,
pub font_size: Option<f32>,
pub font_family: Option<String>,
pub line_height: Option<TerminalLineHeight>,
pub font_features: Option<fonts::Features>,
pub env: Option<HashMap<String, String>>,
pub blinking: Option<TerminalBlink>,
pub alternate_scroll: Option<AlternateScroll>,
pub option_as_meta: Option<bool>,
pub copy_on_select: Option<bool>,
pub dock: Option<TerminalDockPosition>,
pub default_width: Option<f32>,
pub default_height: Option<f32>,
pub detect_venv: Option<VenvSettings>,
}
impl TerminalSettings {
pub fn font_size(&self, cx: &AppContext) -> Option<f32> {
self.font_size
.map(|size| theme2::adjusted_font_size(size, cx))
}
}
impl settings2::Setting for TerminalSettings {
const KEY: Option<&'static str> = Some("terminal");
type FileContent = TerminalSettingsContent;
fn load(
default_value: &Self::FileContent,
user_values: &[&Self::FileContent],
_: &AppContext,
) -> anyhow::Result<Self> {
Self::load_via_json_merge(default_value, user_values)
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]
#[serde(rename_all = "snake_case")]
pub enum TerminalLineHeight {
#[default]
Comfortable,
Standard,
Custom(f32),
}
impl TerminalLineHeight {
pub fn value(&self) -> f32 {
match self {
TerminalLineHeight::Comfortable => 1.618,
TerminalLineHeight::Standard => 1.3,
TerminalLineHeight::Custom(line_height) => f32::max(*line_height, 1.),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum TerminalBlink {
Off,
TerminalControlled,
On,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum Shell {
System,
Program(String),
WithArguments { program: String, args: Vec<String> },
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum AlternateScroll {
On,
Off,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum WorkingDirectory {
CurrentProjectDirectory,
FirstProjectDirectory,
AlwaysHome,
Always { directory: String },
}