Merge branch 'master' into primary

This commit is contained in:
Mattias Eriksson 2024-02-21 09:26:09 +01:00
commit 5b2bbe84cb
21 changed files with 2064 additions and 1382 deletions

View file

@ -10,6 +10,8 @@ use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::sync::OnceLock;
use crate::fl;
pub const CONFIG_VERSION: u64 = 1;
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
@ -29,6 +31,35 @@ impl AppTheme {
}
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(transparent)]
pub struct ProfileId(pub u64);
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Profile {
pub name: String,
#[serde(default)]
pub command: String,
#[serde(default)]
pub syntax_theme_dark: String,
#[serde(default)]
pub syntax_theme_light: String,
#[serde(default)]
pub tab_title: String,
}
impl Default for Profile {
fn default() -> Self {
Self {
name: fl!("new-profile"),
command: String::new(),
syntax_theme_dark: "COSMIC Dark".to_string(),
syntax_theme_light: "COSMIC Light".to_string(),
tab_title: String::new(),
}
}
}
#[derive(Clone, CosmicConfigEntry, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Config {
pub app_theme: AppTheme,
@ -39,6 +70,8 @@ pub struct Config {
pub bold_font_weight: u16,
pub font_stretch: u16,
pub font_size_zoom_step_mul_100: u16,
pub opacity: u8,
pub profiles: BTreeMap<ProfileId, Profile>,
pub show_headerbar: bool,
pub use_bright_bold: bool,
pub syntax_theme_dark: String,
@ -50,18 +83,20 @@ impl Default for Config {
fn default() -> Self {
Self {
app_theme: AppTheme::System,
bold_font_weight: Weight::BOLD.0,
dim_font_weight: Weight::NORMAL.0,
focus_follow_mouse: false,
font_name: "Fira Mono".to_string(),
font_size: 14,
font_weight: Weight::NORMAL.0,
dim_font_weight: Weight::NORMAL.0,
bold_font_weight: Weight::BOLD.0,
font_stretch: Stretch::Normal.to_number(),
font_size_zoom_step_mul_100: 100,
font_stretch: Stretch::Normal.to_number(),
font_weight: Weight::NORMAL.0,
opacity: 100,
profiles: BTreeMap::new(),
show_headerbar: true,
use_bright_bold: false,
syntax_theme_dark: "COSMIC Dark".to_string(),
syntax_theme_light: "COSMIC Light".to_string(),
focus_follow_mouse: false,
use_bright_bold: false,
}
}
}
@ -81,13 +116,46 @@ impl Config {
Metrics::new(font_size, line_height)
}
pub fn opacity_ratio(&self) -> f32 {
(self.opacity as f32) / 100.0
}
// Get a sorted and adjusted for duplicates list of profiles names and ids
pub fn profile_names(&self) -> Vec<(String, ProfileId)> {
let mut profile_names = Vec::<(String, ProfileId)>::with_capacity(self.profiles.len());
for (profile_id, profile) in self.profiles.iter() {
let mut name = profile.name.clone();
let mut copies = 1;
while profile_names.iter().find(|x| x.0 == name).is_some() {
copies += 1;
name = format!("{} ({})", profile.name, copies);
}
profile_names.push((name, *profile_id));
}
profile_names.sort_by(|a, b| lexical_sort::natural_lexical_cmp(&a.0, &b.0));
profile_names
}
// Get current syntax theme based on dark mode
pub fn syntax_theme(&self) -> &str {
pub fn syntax_theme(&self, profile_id_opt: Option<ProfileId>) -> &str {
let dark = self.app_theme.theme().theme_type.is_dark();
if dark {
&self.syntax_theme_dark
} else {
&self.syntax_theme_light
match profile_id_opt.and_then(|profile_id| self.profiles.get(&profile_id)) {
Some(profile) => {
if dark {
&profile.syntax_theme_dark
} else {
&profile.syntax_theme_light
}
}
None => {
if dark {
&self.syntax_theme_dark
} else {
&self.syntax_theme_light
}
}
}
}

View file

@ -31,6 +31,8 @@ impl IconCache {
}
bundle!("edit-clear-symbolic", 16);
bundle!("edit-delete-symbolic", 16);
bundle!("list-add-symbolic", 16);
bundle!("go-down-symbolic", 16);
bundle!("go-up-symbolic", 16);
bundle!("window-close-symbolic", 16);

View file

@ -1,4 +1,7 @@
use cosmic::iced::keyboard::{KeyCode, Modifiers};
use cosmic::{
iced::keyboard::{Key, Modifiers},
iced_core::keyboard::key::Named,
};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fmt};
@ -15,12 +18,12 @@ pub enum Modifier {
#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct KeyBind {
pub modifiers: Vec<Modifier>,
pub key_code: KeyCode,
pub key: Key,
}
impl KeyBind {
pub fn matches(&self, modifiers: Modifiers, key_code: KeyCode) -> bool {
self.key_code == key_code
pub fn matches(&self, modifiers: Modifiers, key: &Key) -> bool {
key == &self.key
&& modifiers.logo() == self.modifiers.contains(&Modifier::Super)
&& modifiers.control() == self.modifiers.contains(&Modifier::Ctrl)
&& modifiers.alt() == self.modifiers.contains(&Modifier::Alt)
@ -33,7 +36,11 @@ impl fmt::Display for KeyBind {
for modifier in self.modifiers.iter() {
write!(f, "{:?} + ", modifier)?;
}
write!(f, "{:?}", self.key_code)
match &self.key {
Key::Character(c) => write!(f, "{}", c.to_uppercase()),
Key::Named(named) => write!(f, "{:?}", named),
other => write!(f, "{:?}", other),
}
}
}
@ -42,11 +49,11 @@ pub fn key_binds() -> HashMap<KeyBind, Action> {
let mut key_binds = HashMap::new();
macro_rules! bind {
([$($modifier:ident),+ $(,)?], $key_code:ident, $action:ident) => {{
([$($modifier:ident),+ $(,)?], $key:expr, $action:ident) => {{
key_binds.insert(
KeyBind {
modifiers: vec![$(Modifier::$modifier),+],
key_code: KeyCode::$key_code,
key: $key,
},
Action::$action,
);
@ -54,52 +61,56 @@ pub fn key_binds() -> HashMap<KeyBind, Action> {
}
// Standard key bindings
bind!([Ctrl, Shift], A, SelectAll);
bind!([Ctrl, Shift], C, Copy);
bind!([Ctrl, Shift], F, Find);
bind!([Ctrl, Shift], N, WindowNew);
bind!([Ctrl, Shift], Q, WindowClose);
bind!([Ctrl, Shift], T, TabNew);
bind!([Ctrl, Shift], V, Paste);
bind!([Shift], Insert, PastePrimary);
bind!([Ctrl, Shift], W, TabClose);
bind!([Ctrl, Shift], Key::Character("A".into()), SelectAll);
bind!([Ctrl, Shift], Key::Character("C".into()), Copy);
bind!([Ctrl, Shift], Key::Character("F".into()), Find);
bind!([Ctrl, Shift], Key::Character("N".into()), WindowNew);
bind!([Ctrl, Shift], Key::Character("Q".into()), WindowClose);
bind!([Ctrl, Shift], Key::Character("T".into()), TabNew);
bind!([Ctrl, Shift], Key::Character("V".into()), Paste);
bind!([Shift], Key::Named(Named::Insert), PastePrimary);
bind!([Ctrl, Shift], Key::Character("W".into()), TabClose);
// Ctrl+Alt+D splits horizontally, Ctrl+Alt+R splits vertically, Ctrl+Shift+X maximizes split
//TODO: Adjust bindings as desired by UX
bind!([Ctrl, Alt], D, PaneSplitHorizontal);
bind!([Ctrl, Alt], R, PaneSplitVertical);
bind!([Ctrl, Shift], X, PaneToggleMaximized);
bind!([Ctrl, Alt], Key::Character("d".into()), PaneSplitHorizontal);
bind!([Ctrl, Alt], Key::Character("r".into()), PaneSplitVertical);
bind!(
[Ctrl, Shift],
Key::Character("X".into()),
PaneToggleMaximized
);
// Ctrl+Tab and Ctrl+Shift+Tab cycle through tabs
// Ctrl+Tab is not a special key for terminals and is free to use
bind!([Ctrl], Tab, TabNext);
bind!([Ctrl, Shift], Tab, TabPrev);
bind!([Ctrl], Key::Named(Named::Tab), TabNext);
bind!([Ctrl, Shift], Key::Named(Named::Tab), TabPrev);
// Ctrl+Shift+# activates tabs by index
bind!([Ctrl, Shift], Key1, TabActivate0);
bind!([Ctrl, Shift], Key2, TabActivate1);
bind!([Ctrl, Shift], Key3, TabActivate2);
bind!([Ctrl, Shift], Key4, TabActivate3);
bind!([Ctrl, Shift], Key5, TabActivate4);
bind!([Ctrl, Shift], Key6, TabActivate5);
bind!([Ctrl, Shift], Key7, TabActivate6);
bind!([Ctrl, Shift], Key8, TabActivate7);
bind!([Ctrl, Shift], Key9, TabActivate8);
bind!([Ctrl, Shift], Key::Character("!".into()), TabActivate0);
bind!([Ctrl, Shift], Key::Character("@".into()), TabActivate1);
bind!([Ctrl, Shift], Key::Character("#".into()), TabActivate2);
bind!([Ctrl, Shift], Key::Character("$".into()), TabActivate3);
bind!([Ctrl, Shift], Key::Character("%".into()), TabActivate4);
bind!([Ctrl, Shift], Key::Character("^".into()), TabActivate5);
bind!([Ctrl, Shift], Key::Character("&".into()), TabActivate6);
bind!([Ctrl, Shift], Key::Character("*".into()), TabActivate7);
bind!([Ctrl, Shift], Key::Character("(".into()), TabActivate8);
// Ctrl+0, Ctrl+-, and Ctrl+= are not special keys for terminals and are free to use
bind!([Ctrl], Key0, ZoomReset);
bind!([Ctrl], Minus, ZoomOut);
bind!([Ctrl], Equals, ZoomIn);
bind!([Ctrl], Key::Character("0".into()), ZoomReset);
bind!([Ctrl], Key::Character("-".into()), ZoomOut);
bind!([Ctrl], Key::Character("=".into()), ZoomIn);
// Ctrl+Arrows and Ctrl+HJKL move between splits
bind!([Ctrl, Shift], Left, PaneFocusLeft);
bind!([Ctrl, Shift], H, PaneFocusLeft);
bind!([Ctrl, Shift], Down, PaneFocusDown);
bind!([Ctrl, Shift], J, PaneFocusDown);
bind!([Ctrl, Shift], Up, PaneFocusUp);
bind!([Ctrl, Shift], K, PaneFocusUp);
bind!([Ctrl, Shift], Right, PaneFocusRight);
bind!([Ctrl, Shift], L, PaneFocusRight);
bind!([Ctrl, Shift], Key::Named(Named::ArrowLeft), PaneFocusLeft);
bind!([Ctrl, Shift], Key::Character("H".into()), PaneFocusLeft);
bind!([Ctrl, Shift], Key::Named(Named::ArrowDown), PaneFocusDown);
bind!([Ctrl, Shift], Key::Character("J".into()), PaneFocusDown);
bind!([Ctrl, Shift], Key::Named(Named::ArrowUp), PaneFocusUp);
bind!([Ctrl, Shift], Key::Character("K".into()), PaneFocusUp);
bind!([Ctrl, Shift], Key::Named(Named::ArrowRight), PaneFocusRight);
bind!([Ctrl, Shift], Key::Character("L".into()), PaneFocusRight);
key_binds
}

File diff suppressed because it is too large Load diff

View file

@ -6,6 +6,7 @@ use cosmic::{
widget::{column, horizontal_rule, horizontal_space},
Alignment, Background, Length,
},
iced_core::Border,
theme,
widget::{
self,
@ -95,16 +96,19 @@ pub fn context_menu<'a>(
icon_color: Some(component.on.into()),
text_color: Some(component.on.into()),
background: Some(Background::Color(component.base.into())),
border_radius: 8.0.into(),
border_width: 1.0,
border_color: component.divider.into(),
border: Border {
radius: 8.0.into(),
width: 1.0,
color: component.divider.into(),
},
..Default::default()
}
}))
.width(Length::Fixed(240.0))
.into()
}
pub fn menu_bar<'a>(key_binds: &HashMap<KeyBind, Action>) -> Element<'a, Message> {
pub fn menu_bar<'a>(config: &Config, key_binds: &HashMap<KeyBind, Action>) -> Element<'a, Message> {
//TODO: port to libcosmic
let menu_root = |label| {
widget::button(widget::text(label))
@ -112,6 +116,9 @@ pub fn menu_bar<'a>(key_binds: &HashMap<KeyBind, Action>) -> Element<'a, Message
.style(theme::Button::MenuRoot)
};
let menu_folder =
|label| menu_button!(widget::text(label), horizontal_space(Length::Fill), ">");
let find_key = |action: &Action| -> String {
for (key_bind, key_action) in key_binds.iter() {
if action == key_action {
@ -133,6 +140,12 @@ pub fn menu_bar<'a>(key_binds: &HashMap<KeyBind, Action>) -> Element<'a, Message
)
};
let mut profile_items = Vec::with_capacity(config.profiles.len());
for (name, id) in config.profile_names() {
profile_items.push(menu_item(name, Action::ProfileOpen(id)));
}
//TODO: what to do if there are no profiles?
MenuBar::new(vec![
MenuTree::with_children(
menu_root(fl!("file")),
@ -140,6 +153,9 @@ pub fn menu_bar<'a>(key_binds: &HashMap<KeyBind, Action>) -> Element<'a, Message
menu_item(fl!("new-tab"), Action::TabNew),
menu_item(fl!("new-window"), Action::WindowNew),
MenuTree::new(horizontal_rule(1)),
MenuTree::with_children(menu_folder(fl!("profile")), profile_items),
menu_item(fl!("menu-profiles"), Action::Profiles),
MenuTree::new(horizontal_rule(1)),
menu_item(fl!("close-tab"), Action::TabClose),
MenuTree::new(horizontal_rule(1)),
menu_item(fl!("quit"), Action::WindowClose),

View file

@ -172,6 +172,7 @@ impl MouseReporter {
}
}
#[allow(clippy::too_many_arguments)]
pub fn report_sgr_mouse_wheel_scroll(
&self,
terminal: &Terminal,

View file

@ -9,7 +9,7 @@ use alacritty_terminal::{
cell::Flags,
color::{self, Colors},
search::RegexSearch,
viewport_to_point, Config, TermMode,
viewport_to_point, Config, TermDamage, TermMode,
},
tty::{self, Options},
vte::ansi::{Color, NamedColor, Rgb},
@ -27,7 +27,7 @@ use indexmap::IndexSet;
use std::{
borrow::Cow,
collections::HashMap,
mem,
io, mem,
sync::{
atomic::{AtomicU32, Ordering},
Arc, Weak,
@ -38,7 +38,10 @@ use tokio::sync::mpsc;
pub use alacritty_terminal::grid::Scroll as TerminalScroll;
use crate::{config::Config as AppConfig, mouse_reporter::MouseReporter};
use crate::{
config::{Config as AppConfig, ProfileId},
mouse_reporter::MouseReporter,
};
#[derive(Clone, Copy, Debug)]
pub struct Size {
@ -187,21 +190,23 @@ impl Metadata {
}
pub struct Terminal {
default_attrs: Attrs<'static>,
buffer: Arc<Buffer>,
size: Size,
pub term: Arc<FairMutex<Term<EventProxy>>>,
colors: Colors,
dim_font_weight: Weight,
bold_font_weight: Weight,
use_bright_bold: bool,
notifier: Notifier,
pub context_menu: Option<cosmic::iced::Point>,
pub metadata_set: IndexSet<Metadata>,
pub needs_update: bool,
pub profile_id_opt: Option<ProfileId>,
pub tab_title_override: Option<String>,
pub term: Arc<FairMutex<Term<EventProxy>>>,
bold_font_weight: Weight,
buffer: Arc<Buffer>,
colors: Colors,
default_attrs: Attrs<'static>,
dim_font_weight: Weight,
mouse_reporter: MouseReporter,
notifier: Notifier,
search_regex_opt: Option<RegexSearch>,
search_value: String,
pub metadata_set: IndexSet<Metadata>,
mouse_reporter: MouseReporter,
size: Size,
use_bright_bold: bool,
}
impl Terminal {
@ -214,7 +219,9 @@ impl Terminal {
options: Options,
app_config: &AppConfig,
colors: Colors,
) -> Self {
profile_id_opt: Option<ProfileId>,
tab_title_override: Option<String>,
) -> Result<Self, io::Error> {
let font_stretch = app_config.typed_font_stretch();
let font_weight = app_config.font_weight;
let dim_font_weight = app_config.dim_font_weight;
@ -242,12 +249,12 @@ impl Terminal {
let (cell_width, cell_height) = {
let mut font_system = font_system().write().unwrap();
let mut font_system = font_system.raw();
buffer.set_wrap(&mut font_system, Wrap::None);
let font_system = font_system.raw();
buffer.set_wrap(font_system, Wrap::None);
// Use size of space to determine cell size
buffer.set_text(&mut font_system, " ", default_attrs, Shaping::Advanced);
let layout = buffer.line_layout(&mut font_system, 0).unwrap();
buffer.set_text(font_system, " ", default_attrs, Shaping::Advanced);
let layout = buffer.line_layout(font_system, 0).unwrap();
let w = layout[0].w;
buffer.set_monospace_width(font_system, Some(w));
(w, metrics.line_height)
@ -267,29 +274,31 @@ impl Terminal {
)));
let window_id = 0;
let pty = tty::new(&options, size.into(), window_id).unwrap();
let pty = tty::new(&options, size.into(), window_id)?;
let pty_event_loop = EventLoop::new(term.clone(), event_proxy, pty, options.hold, false);
let notifier = Notifier(pty_event_loop.channel());
let _pty_join_handle = pty_event_loop.spawn();
Self {
colors,
dim_font_weight: Weight(dim_font_weight),
Ok(Self {
bold_font_weight: Weight(bold_font_weight),
use_bright_bold,
default_attrs,
buffer: Arc::new(buffer),
size,
term,
notifier,
colors,
context_menu: None,
needs_update: true,
search_regex_opt: None,
search_value: String::new(),
default_attrs,
dim_font_weight: Weight(dim_font_weight),
metadata_set,
mouse_reporter: Default::default(),
}
needs_update: true,
notifier,
profile_id_opt,
search_regex_opt: None,
search_value: String::new(),
size,
tab_title_override,
term,
use_bright_bold,
})
}
pub fn buffer_weak(&self) -> Weak<Buffer> {
@ -371,6 +380,8 @@ impl Terminal {
buffer.set_size(font_system.raw(), width as f32, height as f32);
});
self.needs_update = true;
log::debug!("resize {:?}", instant.elapsed());
}
}
@ -459,7 +470,7 @@ impl Terminal {
};
// Find next search match
match term.search_next(
if let Some(search_match) = term.search_next(
search_regex,
search_origin,
if forwards {
@ -471,21 +482,18 @@ impl Terminal {
if forwards { Side::Left } else { Side::Right },
None,
) {
Some(search_match) => {
// Scroll to match
if forwards {
term.scroll_to_point(*search_match.end());
} else {
term.scroll_to_point(*search_match.start());
}
// Set selection to match
let mut selection =
Selection::new(SelectionType::Simple, *search_match.start(), Side::Left);
selection.update(*search_match.end(), Side::Right);
term.selection = Some(selection);
// Scroll to match
if forwards {
term.scroll_to_point(*search_match.end());
} else {
term.scroll_to_point(*search_match.start());
}
None => {}
// Set selection to match
let mut selection =
Selection::new(SelectionType::Simple, *search_match.start(), Side::Left);
selection.update(*search_match.end(), Side::Right);
term.selection = Some(selection);
}
}
@ -551,7 +559,7 @@ impl Terminal {
update_cell_size = true;
}
if let Some(colors) = themes.get(config.syntax_theme()) {
if let Some(colors) = themes.get(config.syntax_theme(self.profile_id_opt)) {
let mut changed = false;
for i in 0..color::COUNT {
if self.colors[i] != colors[i] {
@ -560,19 +568,7 @@ impl Terminal {
}
}
if changed {
self.metadata_set.clear();
let default_bg = convert_color(&colors, Color::Named(NamedColor::Background));
let default_fg = convert_color(&colors, Color::Named(NamedColor::Foreground));
let default_metadata = Metadata::new(default_bg, default_fg);
let (default_metadata_idx, _) = self.metadata_set.insert_full(default_metadata);
self.default_attrs = Attrs::new()
.family(Family::Monospace)
.weight(Weight(config.font_weight))
.stretch(config.typed_font_stretch())
.color(default_fg)
.metadata(default_metadata_idx);
self.update_colors(config);
update = true;
}
}
@ -584,6 +580,22 @@ impl Terminal {
}
}
pub fn update_colors(&mut self, config: &AppConfig) {
self.metadata_set.clear();
let default_bg = convert_color(&self.colors, Color::Named(NamedColor::Background));
let default_fg = convert_color(&self.colors, Color::Named(NamedColor::Foreground));
let default_metadata = Metadata::new(default_bg, default_fg);
let (default_metadata_idx, _) = self.metadata_set.insert_full(default_metadata);
self.default_attrs = Attrs::new()
.family(Family::Monospace)
.weight(Weight(config.font_weight))
.stretch(config.typed_font_stretch())
.color(default_fg)
.metadata(default_metadata_idx);
}
pub fn update_cell_size(&mut self) {
let default_attrs = self.default_attrs;
let (cell_width, cell_height) = {
@ -634,7 +646,14 @@ impl Terminal {
let mut text = String::from(LRI);
let mut attrs_list = AttrsList::new(self.default_attrs);
{
let term = self.term.lock();
let mut term = self.term.lock();
//TODO: use damage?
match term.damage() {
TermDamage::Full => {}
TermDamage::Partial(_damage_lines) => {}
}
term.reset_damage();
let grid = term.grid();
for indexed in grid.display_iter() {
if indexed.point.line != last_point.unwrap_or(indexed.point).line {
@ -647,10 +666,7 @@ impl Terminal {
buffer.set_redraw(true);
}
// Tab skip/stop is handled by alacritty_terminal
if buffer.lines[line_i]
.set_text(text.replace('\t', " "), attrs_list.clone())
{
if buffer.lines[line_i].set_text(text.clone(), attrs_list.clone()) {
buffer.set_redraw(true);
}
line_i += 1;
@ -668,7 +684,11 @@ impl Terminal {
}
let start = text.len();
text.push(indexed.cell.c);
// Tab skip/stop is handled by alacritty_terminal
text.push(match indexed.cell.c {
'\t' => ' ',
c => c,
});
if let Some(zerowidth) = indexed.cell.zerowidth() {
for &c in zerowidth {
text.push(c);
@ -775,9 +795,11 @@ impl Terminal {
buffer.set_redraw(true);
}
// Shape and trim shape run cache
{
let mut font_system = font_system().write().unwrap();
buffer.shape_until_scroll(font_system.raw(), true);
font_system.raw().shape_run_cache.trim(1024);
}
}
@ -800,6 +822,7 @@ impl Terminal {
) {
let term_lock = self.term.lock();
let mode = term_lock.mode();
#[allow(clippy::collapsible_else_if)]
if mode.contains(TermMode::SGR_MOUSE) {
if let Some(code) = self.mouse_reporter.sgr_mouse_code(event, modifiers, x, y) {
self.input_no_scroll(code)
@ -849,6 +872,6 @@ impl Terminal {
impl Drop for Terminal {
fn drop(&mut self) {
// Ensure shutdown on terminal drop
let _ = self.notifier.0.send(Msg::Shutdown);
self.notifier.0.send(Msg::Shutdown);
}
}

View file

@ -8,14 +8,15 @@ use alacritty_terminal::{
use cosmic::{
cosmic_theme::palette::{blend::Compose, WithAlpha},
iced::{
advanced::graphics::text::{font_system, Raw},
advanced::graphics::text::Raw,
event::{Event, Status},
keyboard::{Event as KeyEvent, KeyCode, Modifiers},
keyboard::{Event as KeyEvent, Key, Modifiers},
mouse::{self, Button, Event as MouseEvent, ScrollDelta},
Color, Element, Length, Padding, Point, Rectangle, Size, Vector,
},
iced_core::{
clipboard::Clipboard,
keyboard::key::Named,
layout::{self, Layout},
renderer::{self, Quad, Renderer as _},
text::Renderer as _,
@ -24,7 +25,7 @@ use cosmic::{
operation::{self, Operation, OperationOutputWrapper},
tree, Id, Widget,
},
Shell,
Border, Shell,
},
theme::Theme,
Renderer,
@ -34,22 +35,30 @@ use indexmap::IndexSet;
use std::{
cell::Cell,
cmp,
collections::HashMap,
sync::Mutex,
time::{Duration, Instant},
};
use crate::{terminal::Metadata, Terminal, TerminalScroll};
use crate::{
key_bind::{key_binds, KeyBind},
terminal::Metadata,
Action, Terminal, TerminalScroll,
};
pub struct TerminalBox<'a, Message> {
terminal: &'a Mutex<Terminal>,
id: Option<Id>,
border: Border,
padding: Padding,
click_timing: Duration,
context_menu: Option<Point>,
on_context_menu: Option<Box<dyn Fn(Option<Point>) -> Message + 'a>>,
on_mouse_enter: Option<Box<dyn Fn() -> Message + 'a>>,
opacity: Option<f32>,
mouse_inside_boundary: Option<bool>,
on_middle_click: Option<Box<dyn Fn() -> Message + 'a>>,
key_binds: HashMap<KeyBind, Action>,
}
impl<'a, Message> TerminalBox<'a, Message>
@ -60,13 +69,16 @@ where
Self {
terminal,
id: None,
border: Border::default(),
padding: Padding::new(0.0),
click_timing: Duration::from_millis(500),
context_menu: None,
on_context_menu: None,
on_mouse_enter: None,
opacity: None,
mouse_inside_boundary: None,
on_middle_click: None,
key_binds: key_binds(),
}
}
@ -75,6 +87,11 @@ where
self
}
pub fn border<B: Into<Border>>(mut self, border: B) -> Self {
self.border = border.into();
self
}
pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
self.padding = padding.into();
self
@ -107,16 +124,21 @@ where
self.on_middle_click = Some(Box::new(on_middle_click));
self
}
pub fn opacity(mut self, opacity: f32) -> Self {
self.opacity = Some(opacity);
self
}
}
pub fn terminal_box<'a, Message>(terminal: &'a Mutex<Terminal>) -> TerminalBox<'a, Message>
pub fn terminal_box<Message>(terminal: &Mutex<Terminal>) -> TerminalBox<'_, Message>
where
Message: Clone,
{
TerminalBox::new(terminal)
}
impl<'a, Message> Widget<Message, Renderer> for TerminalBox<'a, Message>
impl<'a, Message> Widget<Message, cosmic::Theme, Renderer> for TerminalBox<'a, Message>
where
Message: Clone,
{
@ -128,12 +150,8 @@ where
tree::State::new(State::new())
}
fn width(&self) -> Length {
Length::Fill
}
fn height(&self) -> Length {
Length::Fill
fn size(&self) -> Size<Length> {
Size::new(Length::Fill, Length::Fill)
}
fn layout(
@ -154,12 +172,6 @@ where
terminal.needs_update = false;
}
// Ensure terminal is shaped
terminal.with_buffer_mut(|buffer| {
let mut font_system = font_system().write().unwrap();
buffer.shape_until_scroll(font_system.raw(), true);
});
// Calculate layout lines
terminal.with_buffer(|buffer| {
let mut layout_lines = 0;
@ -173,7 +185,7 @@ where
let height = layout_lines as f32 * buffer.metrics().line_height;
let size = Size::new(limits.max().width, height);
layout::Node::new(limits.resolve(size))
layout::Node::new(limits.resolve(Length::Fill, Length::Fill, size))
})
}
@ -199,9 +211,8 @@ where
) -> mouse::Interaction {
let state = tree.state.downcast_ref::<State>();
match &state.dragging {
Some(Dragging::Scrollbar { .. }) => return mouse::Interaction::Idle,
_ => {}
if let Some(Dragging::Scrollbar { .. }) = &state.dragging {
return mouse::Interaction::Idle;
}
if let Some(p) = cursor_position.position_in(layout.bounds()) {
@ -235,8 +246,7 @@ where
let cosmic_theme = theme.cosmic();
let scrollbar_w = cosmic_theme.spacing.space_xxs as f32;
let view_position =
layout.position() + [self.padding.left as f32, self.padding.top as f32].into();
let view_position = layout.position() + [self.padding.left, self.padding.top].into();
let view_w = cmp::min(viewport.width as i32, layout.bounds().width as i32)
- self.padding.horizontal() as i32
- scrollbar_w as i32;
@ -259,12 +269,6 @@ where
terminal.needs_update = false;
}
// Ensure terminal is shaped
terminal.with_buffer_mut(|buffer| {
let mut font_system = font_system().write().unwrap();
buffer.shape_until_scroll(font_system.raw(), true);
});
// Render default background
{
let meta = &terminal.metadata_set[terminal.default_attrs().metadata];
@ -272,19 +276,18 @@ where
renderer.fill_quad(
Quad {
bounds: Rectangle::new(
view_position,
Size::new(view_w as f32 + scrollbar_w, view_h as f32),
),
border_radius: 0.0.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
bounds: layout.bounds(),
border: self.border,
..Default::default()
},
Color::new(
background_color.r() as f32 / 255.0,
background_color.g() as f32 / 255.0,
background_color.b() as f32 / 255.0,
background_color.a() as f32 / 255.0,
match self.opacity {
Some(opacity) => opacity,
None => background_color.a() as f32 / 255.0,
},
),
);
}
@ -327,10 +330,6 @@ where
renderer: &mut Renderer,
is_focused: bool,
) {
if self.metadata == self.default_metadata {
return;
}
let cosmic_text_to_iced_color = |color: cosmic_text::Color| {
Color::new(
color.r() as f32 / 255.0,
@ -356,9 +355,7 @@ where
self.view_position + $pos_offset,
Size::new($width, $style_line_height),
),
border_radius: 0.0.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
..Default::default()
}
};
($pos_offset:expr, $style_line_height:expr) => {
@ -367,11 +364,13 @@ where
}
let metadata = &self.metadata_set[self.metadata];
let color = shade(metadata.bg, is_focused);
renderer.fill_quad(
mk_quad!(mk_pos_offset!(0.0, self.line_height), self.line_height),
cosmic_text_to_iced_color(color),
);
if metadata.bg != self.metadata_set[self.default_metadata].bg {
let color = shade(metadata.bg, is_focused);
renderer.fill_quad(
mk_quad!(mk_pos_offset!(0.0, self.line_height), self.line_height),
cosmic_text_to_iced_color(color),
);
}
if !metadata.flags.is_empty() {
let style_line_height =
@ -462,7 +461,7 @@ where
dot_width = dot_width.min(full_width - accu_width);
let dot_bottom_offset = match accu_width as u32 % 8 {
3 | 4 | 5 => bottom_offset + style_line_height,
3..=5 => bottom_offset + style_line_height,
2 | 6 => bottom_offset + 2.0 * style_line_height / 3.0,
1 | 7 => bottom_offset + 1.0 * style_line_height / 3.0,
_ => bottom_offset,
@ -515,10 +514,7 @@ where
Size::new(scrollbar_w, scrollbar_h),
);
let pressed = match &state.dragging {
Some(Dragging::Scrollbar { .. }) => true,
_ => false,
};
let pressed = matches!(&state.dragging, Some(Dragging::Scrollbar { .. }));
let mut hover = false;
if let Some(p) = cursor_position.position_in(layout.bounds()) {
@ -568,9 +564,12 @@ where
renderer.fill_quad(
Quad {
bounds: scrollbar_draw,
border_radius: (scrollbar_draw.width / 2.0).into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
border: Border {
radius: (scrollbar_draw.width / 2.0).into(),
width: 0.0,
color: Color::TRANSPARENT,
},
..Default::default()
},
scrollbar_color,
);
@ -606,238 +605,100 @@ where
let mut status = Status::Ignored;
match event {
Event::Keyboard(KeyEvent::KeyPressed {
key_code,
key: Key::Named(named),
modifiers,
}) if state.is_focused => match (
modifiers.logo(),
modifiers.control(),
modifiers.alt(),
modifiers.shift(),
) {
(true, _, _, _) => {
// Ignore super keys
..
}) if state.is_focused => {
for (key_bind, _) in self.key_binds.iter() {
if key_bind.matches(modifiers, &Key::Named(named)) {
return Status::Captured;
}
}
(_, true, _, _) => match key_code {
KeyCode::Up => {
terminal.input_scroll(b"\x1B[1;5A".as_slice());
let mod_no = calculate_modifier_number(state);
let escape_code = match named {
Named::Insert => csi("2", "~", mod_no),
Named::Delete => csi("3", "~", mod_no),
Named::PageUp => csi("5", "~", mod_no),
Named::PageDown => csi("6", "~", mod_no),
Named::ArrowUp => {
if is_app_cursor {
ss3("A", mod_no)
} else {
csi("A", "", mod_no)
}
}
Named::ArrowDown => {
if is_app_cursor {
ss3("B", mod_no)
} else {
csi("B", "", mod_no)
}
}
Named::ArrowRight => {
if is_app_cursor {
ss3("C", mod_no)
} else {
csi("C", "", mod_no)
}
}
Named::ArrowLeft => {
if is_app_cursor {
ss3("D", mod_no)
} else {
csi("D", "", mod_no)
}
}
Named::End => {
if is_app_cursor {
ss3("F", mod_no)
} else {
csi("F", "", mod_no)
}
}
Named::Home => {
if is_app_cursor {
ss3("H", mod_no)
} else {
csi("H", "", mod_no)
}
}
Named::F1 => ss3("P", mod_no),
Named::F2 => ss3("Q", mod_no),
Named::F3 => ss3("R", mod_no),
Named::F4 => ss3("S", mod_no),
Named::F5 => csi("15", "~", mod_no),
Named::F6 => csi("17", "~", mod_no),
Named::F7 => csi("18", "~", mod_no),
Named::F8 => csi("19", "~", mod_no),
Named::F9 => csi("20", "~", mod_no),
Named::F10 => csi("21", "~", mod_no),
Named::F11 => csi("23", "~", mod_no),
Named::F12 => csi("24", "~", mod_no),
_ => None,
};
if let Some(escape_code) = escape_code {
terminal.input_scroll(escape_code);
return Status::Captured;
}
//Special handle Enter, Escape, Backspace and Tab as described in
//https://sw.kovidgoyal.net/kitty/keyboard-protocol/#legacy-key-event-encoding
//Also special handle Ctrl-_ to behave like xterm
let alt_prefix = if modifiers.alt() { "\x1B" } else { "" };
match named {
Named::Backspace => {
let code = if modifiers.control() { "\x08" } else { "\x7f" };
terminal
.input_scroll(format!("{}{}", alt_prefix, code).as_bytes().to_vec());
status = Status::Captured;
}
KeyCode::Down => {
terminal.input_scroll(b"\x1B[1;5B".as_slice());
Named::Enter => {
terminal
.input_scroll(format!("{}{}", alt_prefix, "\x0D").as_bytes().to_vec());
status = Status::Captured;
}
KeyCode::Right => {
terminal.input_scroll(b"\x1B[1;5C".as_slice());
status = Status::Captured;
}
KeyCode::Left => {
terminal.input_scroll(b"\x1B[1;5D".as_slice());
status = Status::Captured;
}
KeyCode::End => {
terminal.input_scroll(b"\x1B[1;5F".as_slice());
status = Status::Captured;
}
KeyCode::Home => {
terminal.input_scroll(b"\x1B[1;5H".as_slice());
status = Status::Captured;
}
KeyCode::Insert => {
terminal.input_scroll(b"\x1B[2;5~".as_slice());
status = Status::Captured;
}
KeyCode::Delete => {
terminal.input_scroll(b"\x1B[3;5~".as_slice());
status = Status::Captured;
}
KeyCode::PageUp => {
terminal.input_scroll(b"\x1B[5;5~".as_slice());
status = Status::Captured;
}
KeyCode::PageDown => {
terminal.input_scroll(b"\x1B[6;5~".as_slice());
status = Status::Captured;
}
KeyCode::F1 => {
terminal.input_scroll(b"\x1BO;5P".as_slice());
status = Status::Captured;
}
KeyCode::F2 => {
terminal.input_scroll(b"\x1BO;5Q".as_slice());
status = Status::Captured;
}
KeyCode::F3 => {
terminal.input_scroll(b"\x1BO;5R".as_slice());
status = Status::Captured;
}
KeyCode::F4 => {
terminal.input_scroll(b"\x1BO;5S".as_slice());
status = Status::Captured;
}
KeyCode::F5 => {
terminal.input_scroll(b"\x1B[15;5~".as_slice());
status = Status::Captured;
}
KeyCode::F6 => {
terminal.input_scroll(b"\x1B[17;5~".as_slice());
status = Status::Captured;
}
KeyCode::F7 => {
terminal.input_scroll(b"\x1B[18;5~".as_slice());
status = Status::Captured;
}
KeyCode::F8 => {
terminal.input_scroll(b"\x1B[19;5~".as_slice());
status = Status::Captured;
}
KeyCode::F9 => {
terminal.input_scroll(b"\x1B[20;5~".as_slice());
status = Status::Captured;
}
KeyCode::F10 => {
terminal.input_scroll(b"\x1B[21;5~".as_slice());
status = Status::Captured;
}
KeyCode::F11 => {
terminal.input_scroll(b"\x1B[23;5~".as_slice());
status = Status::Captured;
}
KeyCode::F12 => {
terminal.input_scroll(b"\x1B[24;5~".as_slice());
status = Status::Captured;
}
_ => (),
},
// Handle alt keys
(_, _, true, _) => match key_code {
KeyCode::Up => {
terminal.input_scroll(b"\x1B[1;3A".as_slice());
status = Status::Captured;
}
KeyCode::Down => {
terminal.input_scroll(b"\x1B[1;3B".as_slice());
status = Status::Captured;
}
KeyCode::Right => {
terminal.input_scroll(b"\x1B[1;3C".as_slice());
status = Status::Captured;
}
KeyCode::Left => {
terminal.input_scroll(b"\x1B[1;3D".as_slice());
status = Status::Captured;
}
KeyCode::End => {
terminal.input_scroll(b"\x1B[1;3F".as_slice());
status = Status::Captured;
}
KeyCode::Home => {
terminal.input_scroll(b"\x1B[1;3H".as_slice());
status = Status::Captured;
}
KeyCode::Insert => {
terminal.input_scroll(b"\x1B[2;3~".as_slice());
status = Status::Captured;
}
KeyCode::Delete => {
terminal.input_scroll(b"\x1B[3;3~".as_slice());
status = Status::Captured;
}
KeyCode::PageUp => {
terminal.input_scroll(b"\x1B[5;3~".as_slice());
status = Status::Captured;
}
KeyCode::PageDown => {
terminal.input_scroll(b"\x1B[6;3~".as_slice());
status = Status::Captured;
}
KeyCode::F1 => {
terminal.input_scroll(b"\x1B[1;3P".as_slice());
status = Status::Captured;
}
KeyCode::F2 => {
terminal.input_scroll(b"\x1B1;3Q".as_slice());
status = Status::Captured;
}
KeyCode::F3 => {
terminal.input_scroll(b"\x1B1;3R".as_slice());
status = Status::Captured;
}
KeyCode::F4 => {
terminal.input_scroll(b"\x1B1;3S".as_slice());
status = Status::Captured;
}
KeyCode::F5 => {
terminal.input_scroll(b"\x1B[15;3~".as_slice());
status = Status::Captured;
}
KeyCode::F6 => {
terminal.input_scroll(b"\x1B[17;3~".as_slice());
status = Status::Captured;
}
KeyCode::F7 => {
terminal.input_scroll(b"\x1B[18;3~".as_slice());
status = Status::Captured;
}
KeyCode::F8 => {
terminal.input_scroll(b"\x1B[19;3~".as_slice());
status = Status::Captured;
}
KeyCode::F9 => {
terminal.input_scroll(b"\x1B[20;3~".as_slice());
status = Status::Captured;
}
KeyCode::F10 => {
terminal.input_scroll(b"\x1B[21;3~".as_slice());
status = Status::Captured;
}
KeyCode::F11 => {
terminal.input_scroll(b"\x1B[23;3~".as_slice());
status = Status::Captured;
}
KeyCode::F12 => {
terminal.input_scroll(b"\x1B[24;3~".as_slice());
status = Status::Captured;
}
KeyCode::Backspace => {
terminal.input_scroll(b"\x1B\x7F".as_slice());
status = Status::Captured;
}
_ => (),
},
// Handle shift keys
(_, _, _, true) => match key_code {
KeyCode::End => {
terminal.scroll(TerminalScroll::Bottom);
}
KeyCode::Home => {
terminal.scroll(TerminalScroll::Top);
}
KeyCode::PageDown => {
terminal.scroll(TerminalScroll::PageDown);
}
KeyCode::PageUp => {
terminal.scroll(TerminalScroll::PageUp);
}
KeyCode::Tab => {
terminal.input_scroll(b"\x1B[Z".as_slice());
}
_ => {}
},
// Handle keys with no modifiers
(_, _, _, false) => match key_code {
KeyCode::Backspace => {
terminal.input_scroll(b"\x7F".as_slice());
status = Status::Captured;
}
KeyCode::Tab => {
terminal.input_scroll(b"\t".as_slice());
status = Status::Captured;
}
KeyCode::Enter => {
terminal.input_scroll(b"\r".as_slice());
status = Status::Captured;
}
KeyCode::Escape => {
Named::Escape => {
//Escape with any modifier will cancel selection
let had_selection = {
let mut term = terminal.term.lock();
term.selection.take().is_some()
@ -845,138 +706,60 @@ where
if had_selection {
terminal.update();
} else {
terminal.input_scroll(b"\x1B".as_slice());
terminal.input_scroll(
format!("{}{}", alt_prefix, "\x1B").as_bytes().to_vec(),
);
}
status = Status::Captured;
}
KeyCode::Up => {
let code = if is_app_cursor { b"\x1BOA" } else { b"\x1B[A" };
terminal.input_scroll(code.as_slice());
Named::Space => {
terminal.input_scroll(format!("{}{}", alt_prefix, " ").as_bytes().to_vec());
status = Status::Captured;
}
KeyCode::Down => {
let code = if is_app_cursor { b"\x1BOB" } else { b"\x1B[B" };
terminal.input_scroll(code.as_slice());
Named::Tab => {
let code = if modifiers.shift() { "\x1b[Z" } else { "\x09" };
terminal
.input_scroll(format!("{}{}", alt_prefix, code).as_bytes().to_vec());
status = Status::Captured;
}
KeyCode::Right => {
let code = if is_app_cursor { b"\x1BOC" } else { b"\x1B[C" };
terminal.input_scroll(code.as_slice());
status = Status::Captured;
}
KeyCode::Left => {
let code = if is_app_cursor { b"\x1BOD" } else { b"\x1B[D" };
terminal.input_scroll(code.as_slice());
status = Status::Captured;
}
KeyCode::End => {
let code = if is_app_cursor { b"\x1BOF" } else { b"\x1B[F" };
terminal.input_scroll(code.as_slice());
status = Status::Captured;
}
KeyCode::Home => {
let code = if is_app_cursor { b"\x1BOH" } else { b"\x1B[H" };
terminal.input_scroll(code.as_slice());
status = Status::Captured;
}
KeyCode::Insert => {
terminal.input_scroll(b"\x1B[2~".as_slice());
status = Status::Captured;
}
KeyCode::Delete => {
terminal.input_scroll(b"\x1B[3~".as_slice());
status = Status::Captured;
}
KeyCode::PageUp => {
terminal.input_scroll(b"\x1B[5~".as_slice());
status = Status::Captured;
}
KeyCode::PageDown => {
terminal.input_scroll(b"\x1B[6~".as_slice());
status = Status::Captured;
}
KeyCode::F1 => {
terminal.input_scroll(b"\x1BOP".as_slice());
status = Status::Captured;
}
KeyCode::F2 => {
terminal.input_scroll(b"\x1BOQ".as_slice());
status = Status::Captured;
}
KeyCode::F3 => {
terminal.input_scroll(b"\x1BOR".as_slice());
status = Status::Captured;
}
KeyCode::F4 => {
terminal.input_scroll(b"\x1BOS".as_slice());
status = Status::Captured;
}
KeyCode::F5 => {
terminal.input_scroll(b"\x1B[15~".as_slice());
status = Status::Captured;
}
KeyCode::F6 => {
terminal.input_scroll(b"\x1B[17~".as_slice());
status = Status::Captured;
}
KeyCode::F7 => {
terminal.input_scroll(b"\x1B[18~".as_slice());
status = Status::Captured;
}
KeyCode::F8 => {
terminal.input_scroll(b"\x1B[19~".as_slice());
status = Status::Captured;
}
KeyCode::F9 => {
terminal.input_scroll(b"\x1B[20~".as_slice());
status = Status::Captured;
}
KeyCode::F10 => {
terminal.input_scroll(b"\x1B[21~".as_slice());
status = Status::Captured;
}
KeyCode::F11 => {
terminal.input_scroll(b"\x1B[23~".as_slice());
status = Status::Captured;
}
KeyCode::F12 => {
terminal.input_scroll(b"\x1B[24~".as_slice());
status = Status::Captured;
}
_ => (),
},
},
_ => {}
}
}
Event::Keyboard(KeyEvent::ModifiersChanged(modifiers)) => {
state.modifiers = modifiers;
}
Event::Keyboard(KeyEvent::CharacterReceived(character)) if state.is_focused => {
Event::Keyboard(KeyEvent::KeyPressed {
text,
modifiers,
key,
..
}) if state.is_focused => {
for (key_bind, _) in self.key_binds.iter() {
if key_bind.matches(modifiers, &key) {
return Status::Captured;
}
}
let character = text.and_then(|c| c.chars().next()).unwrap_or_default();
match (
state.modifiers.logo(),
state.modifiers.control(),
state.modifiers.alt(),
state.modifiers.shift(),
modifiers.logo(),
modifiers.control(),
modifiers.alt(),
modifiers.shift(),
) {
(true, _, _, _) => {
// Ignore super
}
(false, true, true, false) => {
(false, true, true, _) => {
// Handle ctrl-alt for non-control characters
// Or should I try to minimize this to only
// catch control sequences that conflicts with
// keykodes for Split
// if character != '\u{4}' && character != '\u{12}' {
// is there any valid case for control characters with modifers
// ctrl-alt?
if !character.is_control() {
let mut buf = [0, 0, 0, 0];
let str = character.encode_utf8(&mut buf);
terminal.input_scroll(str.as_bytes().to_vec());
// and control characters 0-32
if !character.is_control() || (character as u32) < 32 {
// Handle alt for non-control characters
let mut buf = [0x1B, 0, 0, 0, 0];
let len = {
let str = character.encode_utf8(&mut buf[1..]);
str.len() + 1
};
terminal.input_scroll(buf[..len].to_vec());
status = Status::Captured;
}
}
@ -990,7 +773,14 @@ where
}
}
(false, true, _, true) => {
// Ignore ctrl+shift
//This is normally Ctrl+Minus, but since that
//is taken by zoom, we send that code for
//Ctrl+Underline instead, like xterm and
//gnome-terminal
if key == Key::Character("_".into()) {
terminal.input_scroll(b"\x1F".as_slice());
status = Status::Captured;
}
}
(false, false, true, _) => {
if !character.is_control() {
@ -1029,6 +819,7 @@ where
state.is_focused = true;
// Handle left click drag
#[allow(clippy::collapsible_if)]
if let Button::Left = button {
let x = p.x - self.padding.left;
let y = p.y - self.padding.top;
@ -1264,7 +1055,7 @@ fn shade(color: cosmic_text::Color, is_focused: bool) -> cosmic_text::Color {
}
}
impl<'a, Message> From<TerminalBox<'a, Message>> for Element<'a, Message, Renderer>
impl<'a, Message> From<TerminalBox<'a, Message>> for Element<'a, Message, cosmic::Theme, Renderer>
where
Message: Clone + 'a,
{
@ -1323,3 +1114,52 @@ impl operation::Focusable for State {
self.is_focused = false;
}
}
/*
shift 0b1 (1)
alt 0b10 (2)
ctrl 0b100 (4)
super 0b1000 (8)
hyper 0b10000 (16)
meta 0b100000 (32)
caps_lock 0b1000000 (64)
num_lock 0b10000000 (128)
*/
fn calculate_modifier_number(state: &mut State) -> u8 {
let mut mod_no = 0;
if state.modifiers.shift() {
mod_no |= 1;
}
if state.modifiers.alt() {
mod_no |= 2;
}
if state.modifiers.control() {
mod_no |= 4;
}
if state.modifiers.logo() {
mod_no |= 8;
}
mod_no + 1
}
#[inline(always)]
fn csi(code: &str, suffix: &str, modifiers: u8) -> Option<Vec<u8>> {
if modifiers == 1 {
Some(format!("\x1B[{}{}", code, suffix).as_bytes().to_vec())
} else {
Some(
format!("\x1B[{};{}{}", code, modifiers, suffix)
.as_bytes()
.to_vec(),
)
}
}
#[inline(always)]
fn ss3(code: &str, modifiers: u8) -> Option<Vec<u8>> {
if modifiers == 1 {
Some(format!("\x1B\x4F{}", code).as_bytes().to_vec())
} else {
Some(format!("\x1B[1;{}{}", modifiers, code).as_bytes().to_vec())
}
}

View file

@ -656,6 +656,166 @@ fn pop_dark() -> Colors {
colors
}
fn selenized_white() -> Colors {
let mut colors = auto_colors();
let encode_rgb = |data: u32| -> Rgb {
Rgb {
r: (data >> 16) as u8,
g: (data >> 8) as u8,
b: data as u8,
}
};
colors[NamedColor::Black] = Some(encode_rgb(0xEBEBEB));
colors[NamedColor::Red] = Some(encode_rgb(0xD6000C));
colors[NamedColor::Green] = Some(encode_rgb(0x1D9700));
colors[NamedColor::Yellow] = Some(encode_rgb(0xC49700));
colors[NamedColor::Blue] = Some(encode_rgb(0x0064E4));
colors[NamedColor::Magenta] = Some(encode_rgb(0xDD0F9D));
colors[NamedColor::Cyan] = Some(encode_rgb(0x00AD9C));
colors[NamedColor::White] = Some(encode_rgb(0x878787));
colors[NamedColor::BrightBlack] = Some(encode_rgb(0xCDCDCD));
colors[NamedColor::BrightRed] = Some(encode_rgb(0xBF0000));
colors[NamedColor::BrightGreen] = Some(encode_rgb(0x008400));
colors[NamedColor::BrightYellow] = Some(encode_rgb(0xAF8500));
colors[NamedColor::BrightBlue] = Some(encode_rgb(0x0054CF));
colors[NamedColor::BrightMagenta] = Some(encode_rgb(0xC7008B));
colors[NamedColor::BrightCyan] = Some(encode_rgb(0x009A8A));
colors[NamedColor::BrightWhite] = Some(encode_rgb(0x282828));
// Set special colors
colors[NamedColor::Background] = Some(encode_rgb(0xFFFFFF));
colors[NamedColor::Foreground] = Some(encode_rgb(0x474747));
colors[NamedColor::Cursor] = colors[NamedColor::Black];
// Fill missing dim colors
ColorDerive::new().fill_missing_dims(&mut colors);
colors
}
fn selenized_light() -> Colors {
let mut colors = auto_colors();
let encode_rgb = |data: u32| -> Rgb {
Rgb {
r: (data >> 16) as u8,
g: (data >> 8) as u8,
b: data as u8,
}
};
colors[NamedColor::Black] = Some(encode_rgb(0xECE3CC));
colors[NamedColor::Red] = Some(encode_rgb(0xD2212D));
colors[NamedColor::Green] = Some(encode_rgb(0x489100));
colors[NamedColor::Yellow] = Some(encode_rgb(0xAD8900));
colors[NamedColor::Blue] = Some(encode_rgb(0x0072D4));
colors[NamedColor::Magenta] = Some(encode_rgb(0xCA4898));
colors[NamedColor::Cyan] = Some(encode_rgb(0x009C8F));
colors[NamedColor::White] = Some(encode_rgb(0x909995));
colors[NamedColor::BrightBlack] = Some(encode_rgb(0xD5CDB6));
colors[NamedColor::BrightRed] = Some(encode_rgb(0xCC1729));
colors[NamedColor::BrightGreen] = Some(encode_rgb(0x428B00));
colors[NamedColor::BrightYellow] = Some(encode_rgb(0xA78300));
colors[NamedColor::BrightBlue] = Some(encode_rgb(0x006DCE));
colors[NamedColor::BrightMagenta] = Some(encode_rgb(0xC44392));
colors[NamedColor::BrightCyan] = Some(encode_rgb(0x00978A));
colors[NamedColor::BrightWhite] = Some(encode_rgb(0x3A4D53));
// Set special colors
colors[NamedColor::Background] = Some(encode_rgb(0xFBF3DB));
colors[NamedColor::Foreground] = Some(encode_rgb(0x53676D));
colors[NamedColor::Cursor] = colors[NamedColor::Black];
// Fill missing dim colors
ColorDerive::new().fill_missing_dims(&mut colors);
colors
}
fn selenized_dark() -> Colors {
let mut colors = auto_colors();
let encode_rgb = |data: u32| -> Rgb {
Rgb {
r: (data >> 16) as u8,
g: (data >> 8) as u8,
b: data as u8,
}
};
colors[NamedColor::Black] = Some(encode_rgb(0x184956));
colors[NamedColor::Red] = Some(encode_rgb(0xFA5750));
colors[NamedColor::Green] = Some(encode_rgb(0x75B938));
colors[NamedColor::Yellow] = Some(encode_rgb(0xDBB32D));
colors[NamedColor::Blue] = Some(encode_rgb(0x4695F7));
colors[NamedColor::Magenta] = Some(encode_rgb(0xF275BE));
colors[NamedColor::Cyan] = Some(encode_rgb(0x41C7B9));
colors[NamedColor::White] = Some(encode_rgb(0x72898F));
colors[NamedColor::BrightBlack] = Some(encode_rgb(0x2D5B69));
colors[NamedColor::BrightRed] = Some(encode_rgb(0xFF665C));
colors[NamedColor::BrightGreen] = Some(encode_rgb(0x84C747));
colors[NamedColor::BrightYellow] = Some(encode_rgb(0xEBC13D));
colors[NamedColor::BrightBlue] = Some(encode_rgb(0x58A3FF));
colors[NamedColor::BrightMagenta] = Some(encode_rgb(0xFF84CD));
colors[NamedColor::BrightCyan] = Some(encode_rgb(0x53D6C7));
colors[NamedColor::BrightWhite] = Some(encode_rgb(0xCAD8D9));
// Set special colors
colors[NamedColor::Background] = Some(encode_rgb(0x103C48));
colors[NamedColor::Foreground] = Some(encode_rgb(0xADBCBC));
colors[NamedColor::Cursor] = colors[NamedColor::White];
// Fill missing dim colors
ColorDerive::new().fill_missing_dims(&mut colors);
colors
}
fn selenized_black() -> Colors {
let mut colors = auto_colors();
let encode_rgb = |data: u32| -> Rgb {
Rgb {
r: (data >> 16) as u8,
g: (data >> 8) as u8,
b: data as u8,
}
};
colors[NamedColor::Black] = Some(encode_rgb(0x252525));
colors[NamedColor::Red] = Some(encode_rgb(0xED4A46));
colors[NamedColor::Green] = Some(encode_rgb(0x70B433));
colors[NamedColor::Yellow] = Some(encode_rgb(0xDBB32D));
colors[NamedColor::Blue] = Some(encode_rgb(0x368AEB));
colors[NamedColor::Magenta] = Some(encode_rgb(0xEB6EB7));
colors[NamedColor::Cyan] = Some(encode_rgb(0x3FC5B7));
colors[NamedColor::White] = Some(encode_rgb(0x777777));
colors[NamedColor::BrightBlack] = Some(encode_rgb(0x3B3B3B));
colors[NamedColor::BrightRed] = Some(encode_rgb(0xFF5E56));
colors[NamedColor::BrightGreen] = Some(encode_rgb(0x83C746));
colors[NamedColor::BrightYellow] = Some(encode_rgb(0xEFC541));
colors[NamedColor::BrightBlue] = Some(encode_rgb(0x4F9CFE));
colors[NamedColor::BrightMagenta] = Some(encode_rgb(0xFF81CA));
colors[NamedColor::BrightCyan] = Some(encode_rgb(0x56D8C9));
colors[NamedColor::BrightWhite] = Some(encode_rgb(0xDEDEDE));
// Set special colors
colors[NamedColor::Background] = Some(encode_rgb(0x181818));
colors[NamedColor::Foreground] = Some(encode_rgb(0xB9B9B9));
colors[NamedColor::Cursor] = colors[NamedColor::White];
// Fill missing dim colors
ColorDerive::new().fill_missing_dims(&mut colors);
colors
}
pub fn terminal_themes() -> HashMap<String, Colors> {
let mut themes = HashMap::new();
themes.insert("Tango Dark".to_string(), tango_dark());
@ -672,5 +832,9 @@ pub fn terminal_themes() -> HashMap<String, Colors> {
themes.insert("gruvbox-dark".to_string(), gruvbox_dark());
themes.insert("OneHalfDark".to_string(), one_half_dark());
themes.insert("Pop Dark".to_string(), pop_dark());
themes.insert("Selenized Black".to_string(), selenized_black());
themes.insert("Selenized Dark".to_string(), selenized_dark());
themes.insert("Selenized Light".to_string(), selenized_light());
themes.insert("Selenized White".to_string(), selenized_white());
themes
}