Merge branch 'master' into primary

This commit is contained in:
Mattias Eriksson 2024-02-02 10:35:45 +01:00
commit d38ac0a0f7
10 changed files with 1509 additions and 426 deletions

528
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -2,6 +2,7 @@
name = "cosmic-term"
version = "0.1.0"
edition = "2021"
rust-version = "1.71"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -27,7 +28,6 @@ git = "https://github.com/pop-os/cosmic-text.git"
git = "https://github.com/pop-os/libcosmic.git"
default-features = false
features = ["tokio", "winit"]
#path = "../libcosmic"
[features]
default = ["wgpu"]

View file

@ -9,17 +9,29 @@ theme = Theme
match-desktop = Match desktop
dark = Dark
light = Light
syntax-dark = Syntax dark
syntax-light = Syntax light
syntax-dark = Color scheme dark
syntax-light = Color scheme light
default-zoom-step = Zoom steps
### Font
font = Font
advanced-font-settings = Advanced Font Settings
default-font = Default font
default-font-stretch = Default font stretch
default-font-weight = Default font weight
default-dim-font-weight = Default dim font weight
default-bold-font-weight = Default bold font weight
use-bright-bold = Use bright colors with bold text
default-font-size = Default font size
default-zoom-step = Default zoom step
default-font = Font
default-font-size = Font size
default-font-stretch = Font stretch
default-font-weight = Normal font weight
default-dim-font-weight = Dim font weight
default-bold-font-weight = Bold font weight
use-bright-bold = Make bold text brighter
### Splits
splits = Splits
focus-follow-mouse = Typing focus follows mouse
### Advanced
advanced = Advanced
show-headerbar = Show header
show-header-description = Reveal the header from the right-click menu.
# Find
find-placeholder = Find...
@ -53,6 +65,3 @@ split-horizontal = Split horizontal
split-vertical = Split vertical
pane-toggle-maximize = Toggle maximized
menu-settings = Settings...
# Context menu
show-headerbar = Show header bar

50
i18n/ja/cosmic_term.ftl Normal file
View file

@ -0,0 +1,50 @@
# Context Pages
## Settings
settings = 設定
### Appearance
appearance = 外観
theme = テーマ
match-desktop = デスクトップに合わす
dark = 暗い
light = 明かり
syntax-dark = 暗いシンタックス
syntax-light = 明かりシンタックス
advanced-font-settings = 詳細なフォント設定
default-font = デフォルトフォント
default-font-stretch = デフォルトのフォント幅
default-font-weight = デフォルトのフォントの太さ
default-dim-font-weight = デフォルトの暗いフォントの太さ
default-bold-font-weight = デフォルトの太字の太さ
use-bright-bold = 太字を明るい色で表示する
default-font-size = デフォルトのフォントサイズ
default-zoom-step =ズームの間隔
# Find
find-placeholder = 検索...
find-previous = 前を検索
find-next = 次を検索
# Menu
## File
file = ファイル
new-tab = 新しいタブ
new-window = 新しいウィンドウ
close-tab = タブを閉じる
quit = 終了
## Edit
edit = 編集
copy = コピー
paste = 貼り付け
select-all = すべて選択
find = 検索
## View
view = 表示
menu-settings = 設定...
# Context menu
show-headerbar = ヘッダーバーを表す

View file

@ -43,6 +43,7 @@ pub struct Config {
pub use_bright_bold: bool,
pub syntax_theme_dark: String,
pub syntax_theme_light: String,
pub focus_follow_mouse: bool,
}
impl Default for Config {
@ -60,6 +61,7 @@ impl Default for Config {
use_bright_bold: false,
syntax_theme_dark: "COSMIC Dark".to_string(),
syntax_theme_light: "COSMIC Light".to_string(),
focus_follow_mouse: false,
}
}
}

View file

@ -15,7 +15,7 @@ use cosmic::{
keyboard::{Event as KeyEvent, KeyCode, Modifiers},
mouse::{Button as MouseButton, Event as MouseEvent},
subscription::{self, Subscription},
window, Alignment, Event, Length, Padding, Point,
window, Alignment, Color, Event, Length, Limits, Padding, Point,
},
style,
widget::{self, button, container, pane_grid, segmented_button, PaneGrid},
@ -26,13 +26,14 @@ use std::{
any::TypeId,
collections::{BTreeMap, BTreeSet, HashMap},
env, process,
sync::Mutex,
sync::{atomic::Ordering, Mutex},
time::Duration,
};
use tokio::sync::mpsc;
use config::{AppTheme, Config, CONFIG_VERSION};
mod config;
mod mouse_reporter;
use icon_cache::IconCache;
mod icon_cache;
@ -141,8 +142,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
settings = settings.client_decorations(false);
}
//TODO: allow size limits on iced_winit
//settings = settings.size_limits(Limits::NONE.min_width(400.0).min_height(200.0));
settings = settings.size_limits(Limits::NONE.min_width(360.0).min_height(180.0));
let flags = Flags {
config_handler,
@ -267,6 +267,7 @@ pub enum Message {
PaneDragged(pane_grid::DragEvent),
PaneResized(pane_grid::ResizeEvent),
Modifiers(Modifiers),
MouseEnter(pane_grid::Pane),
Paste(Option<segmented_button::Entity>),
PastePrimary(Option<segmented_button::Entity>),
PasteValue(Option<segmented_button::Entity>, String),
@ -274,7 +275,7 @@ pub enum Message {
UseBrightBold(bool),
ShowHeaderBar(bool),
SyntaxTheme(usize, bool),
SystemThemeModeChange(cosmic_theme::ThemeMode),
SystemThemeChange,
TabActivate(segmented_button::Entity),
TabActivateJump(usize),
TabClose(Option<segmented_button::Entity>),
@ -292,6 +293,7 @@ pub enum Message {
ZoomIn,
ZoomOut,
ZoomReset,
FocusFollowMouse(bool),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@ -530,6 +532,67 @@ impl App {
.iter()
.position(|zoom_step| zoom_step == &self.config.font_size_zoom_step_mul_100);
let appearance_section = widget::settings::view_section(fl!("appearance"))
.add(
widget::settings::item::builder(fl!("theme")).control(widget::dropdown(
&self.app_themes,
Some(app_theme_selected),
move |index| {
Message::AppTheme(match index {
1 => AppTheme::Dark,
2 => AppTheme::Light,
_ => AppTheme::System,
})
},
)),
)
.add(
//TODO: rename to color-scheme-dark?
widget::settings::item::builder(fl!("syntax-dark")).control(widget::dropdown(
&self.theme_names,
dark_selected,
move |index| Message::SyntaxTheme(index, true),
)),
)
.add(
//TODO: rename to color-scheme-light?
widget::settings::item::builder(fl!("syntax-light")).control(widget::dropdown(
&self.theme_names,
light_selected,
move |index| Message::SyntaxTheme(index, false),
)),
)
.add(
widget::settings::item::builder(fl!("default-zoom-step")).control(
widget::dropdown(&self.zoom_step_names, zoom_step_selected, |index| {
Message::DefaultZoomStep(index)
}),
),
);
//TODO: background opacity
let mut font_section = widget::settings::view_section(fl!("font"))
.add(
widget::settings::item::builder(fl!("default-font")).control(widget::dropdown(
&self.font_names,
font_selected,
|index| Message::DefaultFont(index),
)),
)
.add(
widget::settings::item::builder(fl!("default-font-size")).control(
widget::dropdown(&self.font_size_names, font_size_selected, |index| {
Message::DefaultFontSize(index)
}),
),
)
.add(
widget::settings::item::builder(fl!("advanced-font-settings")).toggler(
self.show_advanced_font_settings,
Message::ShowAdvancedFontSettings,
),
);
let advanced_font_settings = || {
let section = widget::settings::view_section("")
.add(
@ -567,6 +630,10 @@ impl App {
|index| Message::DefaultBoldFontWeight(index),
),
),
)
.add(
widget::settings::item::builder(fl!("use-bright-bold"))
.toggler(self.config.use_bright_bold, Message::UseBrightBold),
);
let padding = Padding {
top: 0.0,
@ -577,77 +644,28 @@ impl App {
widget::container(section).padding(padding)
};
let mut settings_view = widget::settings::view_section(fl!("appearance"))
.add(
widget::settings::item::builder(fl!("theme")).control(widget::dropdown(
&self.app_themes,
Some(app_theme_selected),
move |index| {
Message::AppTheme(match index {
1 => AppTheme::Dark,
2 => AppTheme::Light,
_ => AppTheme::System,
})
},
)),
)
.add(
widget::settings::item::builder(fl!("syntax-dark")).control(widget::dropdown(
&self.theme_names,
dark_selected,
move |index| Message::SyntaxTheme(index, true),
)),
)
.add(
widget::settings::item::builder(fl!("syntax-light")).control(widget::dropdown(
&self.theme_names,
light_selected,
move |index| Message::SyntaxTheme(index, false),
)),
)
.add(
widget::settings::item::builder(fl!("default-font")).control(widget::dropdown(
&self.font_names,
font_selected,
|index| Message::DefaultFont(index),
)),
)
.add(
widget::settings::item::builder(fl!("advanced-font-settings")).toggler(
self.show_advanced_font_settings,
Message::ShowAdvancedFontSettings,
),
);
if self.show_advanced_font_settings {
settings_view = settings_view.add(advanced_font_settings());
font_section = font_section.add(advanced_font_settings());
}
let settings_view = settings_view
.add(
widget::settings::item::builder(fl!("use-bright-bold"))
.toggler(self.config.use_bright_bold, Message::UseBrightBold),
)
.add(
widget::settings::item::builder(fl!("default-font-size")).control(
widget::dropdown(&self.font_size_names, font_size_selected, |index| {
Message::DefaultFontSize(index)
}),
),
)
.add(
widget::settings::item::builder(fl!("default-zoom-step")).control(
widget::dropdown(&self.zoom_step_names, zoom_step_selected, |index| {
Message::DefaultZoomStep(index)
}),
),
)
.add(
widget::settings::item::builder(fl!("show-headerbar"))
.toggler(self.config.show_headerbar, Message::ShowHeaderBar),
);
let splits_section = widget::settings::view_section(fl!("splits")).add(
widget::settings::item::builder(fl!("focus-follow-mouse"))
.toggler(self.config.focus_follow_mouse, Message::FocusFollowMouse),
);
widget::settings::view_column(vec![settings_view.into()]).into()
let advanced_section = widget::settings::view_section(fl!("advanced")).add(
widget::settings::item::builder(fl!("show-headerbar"))
.description(fl!("show-header-description"))
.toggler(self.config.show_headerbar, Message::ShowHeaderBar),
);
widget::settings::view_column(vec![
appearance_section.into(),
font_section.into(),
splits_section.into(),
advanced_section.into(),
])
.into()
}
fn create_and_focus_new_terminal(&mut self, pane: pane_grid::Pane) {
@ -719,8 +737,7 @@ impl Application for App {
/// Creates the application, and optionally emits command on initialize.
fn init(mut core: Core, flags: Self::Flags) -> (Self, Command<Self::Message>) {
//TODO: fix window resizing interfering with scrolling when not using content container
//core.window.content_container = false;
core.window.content_container = false;
core.window.show_headerbar = flags.config.show_headerbar;
// Update font name from config
@ -1079,6 +1096,10 @@ impl Application for App {
Message::Modifiers(modifiers) => {
self.modifiers = modifiers;
}
Message::MouseEnter(pane) => {
self.pane_model.focus = pane;
return self.update_focus();
}
Message::PaneClicked(pane) => {
self.pane_model.focus = pane;
return self.update_title(Some(pane));
@ -1163,7 +1184,13 @@ impl Application for App {
return self.save_config();
}
}
Message::SystemThemeModeChange(_theme_mode) => {
Message::FocusFollowMouse(focus_follow_mouse) => {
if focus_follow_mouse != self.config.focus_follow_mouse {
self.config.focus_follow_mouse = focus_follow_mouse;
return self.save_config();
}
}
Message::SystemThemeChange => {
return self.update_config();
}
Message::SyntaxTheme(index, dark) => match self.theme_names.get(index) {
@ -1413,9 +1440,28 @@ impl Application for App {
vec![menu_bar(&self.key_binds).into()]
}
fn header_end(&self) -> Vec<Element<Self::Message>> {
let cosmic_theme::Spacing { space_xxs, .. } = self.core().system_theme().cosmic().spacing;
vec![widget::button(widget::icon::from_name("list-add-symbolic"))
.on_press(Message::TabNew)
.padding(space_xxs)
.style(style::Button::Icon)
.into()]
}
/// Creates a view after each update.
fn view(&self) -> Element<Self::Message> {
let cosmic_theme::Spacing { space_xxs, .. } = self.core().system_theme().cosmic().spacing;
let cosmic_theme = self.core().system_theme().cosmic();
let cosmic_theme::Spacing { space_xxs, .. } = cosmic_theme.spacing;
{
let color = Color::from(cosmic_theme.bg_color());
let bytes = color.into_rgba8();
let data = (bytes[2] as u32)
| ((bytes[1] as u32) << 8)
| ((bytes[0] as u32) << 16)
| 0xFF000000;
terminal::WINDOW_BG_COLOR.store(data, Ordering::SeqCst);
}
let pane_grid = PaneGrid::new(&self.pane_model.panes, |pane, tab_model, _is_maximized| {
let mut tab_column = widget::column::with_capacity(1);
@ -1442,7 +1488,7 @@ impl Application for App {
.unwrap_or_else(widget::Id::unique);
match tab_model.data::<Mutex<Terminal>>(entity) {
Some(terminal) => {
let terminal_box = terminal_box(terminal)
let mut terminal_box = terminal_box(terminal)
.id(terminal_id)
.on_context_menu(move |position_opt| {
Message::TabContextMenu(entity, position_opt)
@ -1451,6 +1497,11 @@ impl Application for App {
Message::MiddleClick(pane, Some(entity_middle_click))
});
if self.config.focus_follow_mouse {
terminal_box =
terminal_box.on_mouse_enter(move || Message::MouseEnter(pane));
}
let context_menu = {
let terminal = terminal.lock().unwrap();
terminal.context_menu
@ -1543,6 +1594,7 @@ impl Application for App {
.width(Length::Fill)
.height(Length::Fill)
.padding(space_xxs)
.style(style::Container::Background)
.into()
}
@ -1550,6 +1602,7 @@ impl Application for App {
struct ConfigSubscription;
struct TerminalEventSubscription;
struct ThemeSubscription;
struct ThemeModeSubscription;
Subscription::batch([
event::listen_with(|event, _status| match event {
@ -1603,21 +1656,23 @@ impl Application for App {
}
Message::Config(update.config)
}),
cosmic_config::config_subscription::<_, cosmic_theme::ThemeMode>(
cosmic_config::config_subscription::<_, cosmic_theme::Theme>(
TypeId::of::<ThemeSubscription>(),
if self.core.system_theme_mode().is_dark {
cosmic_theme::DARK_THEME_ID
} else {
cosmic_theme::LIGHT_THEME_ID
}
.into(),
cosmic_theme::Theme::version(),
)
.map(|_update| Message::SystemThemeChange),
cosmic_config::config_subscription::<_, cosmic_theme::ThemeMode>(
TypeId::of::<ThemeModeSubscription>(),
cosmic_theme::THEME_MODE_ID.into(),
cosmic_theme::ThemeMode::version(),
)
.map(|update| {
if !update.errors.is_empty() {
log::debug!(
"errors loading theme mode {:?}: {:?}",
update.keys,
update.errors
);
}
Message::SystemThemeModeChange(update.config)
}),
.map(|_update| Message::SystemThemeChange),
])
}
}

238
src/mouse_reporter.rs Normal file
View file

@ -0,0 +1,238 @@
use cosmic::{
iced::mouse::{Event as MouseEvent, ScrollDelta},
iced::{keyboard::Modifiers, mouse::Button, Event},
};
use crate::terminal::Terminal;
const SCROLL_SPEED: u32 = 3;
#[derive(Default)]
pub struct MouseReporter {
last_movment_x: Option<u32>,
last_movment_y: Option<u32>,
button: Option<Button>,
}
impl MouseReporter {
fn button_number(&self, button: Button) -> Option<u8> {
match button {
Button::Left => Some(0),
Button::Middle => Some(1),
Button::Right => Some(2),
_ => None,
}
}
//Implemented according to
//https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
pub fn normal_mouse_code(
&mut self,
event: Event,
modifiers: &Modifiers,
is_utf8: bool,
x: u32,
y: u32,
) -> Option<Vec<u8>> {
//Buttons are handle slightly different between normal and sgr
//for normal/utf8 the button release is always reported as button 3
let Some(mut button) = (match event {
Event::Mouse(MouseEvent::ButtonPressed(b)) => {
self.button = Some(b);
self.button_number(b)
}
Event::Mouse(MouseEvent::ButtonReleased(_b)) => {
self.button = None;
Some(3)
}
Event::Mouse(MouseEvent::CursorMoved { .. }) => {
//Button pressed is reported as button 32 + 0,1,2 and event code M
//And only reported if a button is previously pressed
if (self.last_movment_x, self.last_movment_y) == (Some(x), Some(y)) {
return None;
} else {
self.last_movment_x = Some(x);
self.last_movment_y = Some(y);
}
//It seems that we should add 32 to signal movement even for normal mode
//On button-motion events, xterm adds 32 to the event code (the third
//character, Cb).
//For example, motion into cell x,y with button 1 down is reported as
//CSI M @ CxCy ( @ = 32 + 0 (button 1) + 32 (motion indicator) ).
self.button
.and_then(|button| self.button_number(button))
.map(|b| b + 32)
}
_ => None,
}) else {
return None;
};
if modifiers.shift() {
button += 4;
}
if modifiers.alt() {
button += 8;
}
if modifiers.control() {
button += 16;
}
//Normal mode have a max of 223 (255 - 32), while utf8 extend this to 2015
let max_point: usize = if is_utf8 { 2015 } else { 223 };
if x as usize >= max_point || y as usize >= max_point {
return None;
}
let utf8_encode_and_append = |mut pos: u32, dest: &mut Vec<u8>| {
pos += 1 + 32;
let mut utf8 = [0; 2]; //This is large enough since we have a max of 2015
dest.extend_from_slice(
(char::from_u32(pos).unwrap()) //This unwrap and encode_utf8 is safe due to our
//specific range, pos will max be 2047
.encode_utf8(&mut utf8)
.as_bytes(),
);
};
//SPEC: Likewise, Cb will be UTF-8 encoded, to reduce confusion with wheel mouse events.
//Always, or only when the the cooardinates is used? No other terminal seems to do this a
//all? Doing what they are doing for now.
let mut buf: Vec<u8> = vec![b'\x1b', b'[', b'M', 32 + button];
//Should we remove 32+button from previous line, and use this instead? Or only on >= 95
//utf8_encode_and_append(32 + button, &mut buf);
//For utf8 spec say: For positions less than 95, the resulting output is identical under both modes.
//But also: Under normal mouse mode, positions outside (160,94) result in byte pairs which can be interpreted as a single UTF-8
if is_utf8 && x >= 95 {
utf8_encode_and_append(x, &mut buf);
} else {
//SPEC: For positions less than 95, the resulting output is identical under both modes.
buf.push(32 + 1 + x as u8);
}
if is_utf8 && y >= 95 {
utf8_encode_and_append(y, &mut buf);
} else {
//SPEC For positions less than 95, the resulting output is identical under both modes.
buf.push(32 + 1 + y as u8);
}
Some(buf)
}
pub fn sgr_mouse_code(
&mut self,
event: Event,
modifiers: &Modifiers,
x: u32,
y: u32,
) -> Option<Vec<u8>> {
let Some((button_no, event_code)) = (match event {
Event::Mouse(MouseEvent::ButtonPressed(button)) => {
//Button pressed is reported as button 0,1,2 and event code M
self.button = Some(button);
Some((self.button_number(button), "M"))
}
Event::Mouse(MouseEvent::ButtonReleased(button)) => {
//Button pressed is reported as button 0,1,2 and event code m
self.button = None;
Some((self.button_number(button), "m"))
}
Event::Mouse(MouseEvent::CursorMoved { .. }) => {
//Button pressed is reported as button 32 + 0,1,2 and event code M
//And only reported if a button is previously pressed
if (self.last_movment_x, self.last_movment_y) == (Some(x), Some(y)) {
return None;
} else {
self.last_movment_x = Some(x);
self.last_movment_y = Some(y);
}
self.button
.map(|button| (self.button_number(button).map(|b| b + 32), "M"))
}
_ => None,
}) else {
return None;
};
if let Some(mut button_no) = button_no {
if modifiers.shift() {
button_no += 4;
}
if modifiers.alt() {
button_no += 8;
}
if modifiers.control() {
button_no += 16;
}
let term_code = format!("\x1b[<{};{};{}{}", button_no, x + 1, y + 1, event_code);
Some(term_code.as_bytes().to_vec())
} else {
None
}
}
pub fn report_sgr_mouse_wheel_scroll(
&self,
terminal: &Terminal,
term_cell_width: f32,
term_cell_height: f32,
delta: ScrollDelta,
modifiers: &Modifiers,
x: u32,
y: u32,
) {
let (delta_x, delta_y) = match delta {
ScrollDelta::Lines { x, y } => (x, y),
ScrollDelta::Pixels { x, y } => (x / term_cell_width, y / term_cell_height),
};
let (mut button_no, amount) = if delta_y > 0.0 {
(64, delta_y.abs()) //Wheel UP
} else if delta_y < 0.0 {
(65, delta_y.abs()) //Wheel Down
} else if delta_x < 0.0 {
(66, delta_x.abs()) //Wheel Left
} else if delta_x > 0.0 {
(67, delta_x.abs()) //Wheel Right
} else {
return;
};
if modifiers.shift() {
button_no += 4;
}
if modifiers.alt() {
button_no += 8;
}
if modifiers.control() {
button_no += 16;
}
let term_code = format!("\x1b[<{};{};{}M", button_no, x + 1, y + 1);
for _ in 0..amount as u32 {
terminal.input_no_scroll(term_code.as_bytes().to_vec());
}
}
//Emulate mouse wheel scroll with up/down arrows. Using mouse spec uses
//scroll-back and scroll-forw actions, which moves whole windows like page up/page down.
pub fn report_mouse_wheel_as_arrows(
&self,
terminal: &Terminal,
term_cell_width: f32,
term_cell_height: f32,
delta: ScrollDelta,
) {
let (_delta_x, delta_y) = match delta {
ScrollDelta::Lines { x, y } => (x, y),
ScrollDelta::Pixels { x, y } => (x / term_cell_width, y / term_cell_height),
};
//Send delta_y * SCROLL_SPEED number of Up/Down arrows
for _ in 0..(delta_y.abs() as u32 * SCROLL_SPEED) {
if delta_y > 0.0 {
terminal.input_no_scroll(b"\x1B[A".as_slice())
} else if delta_y < 0.0 {
terminal.input_no_scroll(b"\x1B[B".as_slice())
}
}
}
}

View file

@ -17,6 +17,7 @@ use alacritty_terminal::{
};
use cosmic::{
iced::advanced::graphics::text::font_system,
iced::mouse::ScrollDelta,
widget::{pane_grid, segmented_button},
};
use cosmic_text::{
@ -27,14 +28,17 @@ use std::{
borrow::Cow,
collections::HashMap,
mem,
sync::{Arc, Weak},
sync::{
atomic::{AtomicU32, Ordering},
Arc, Weak,
},
time::Instant,
};
use tokio::sync::mpsc;
pub use alacritty_terminal::grid::Scroll as TerminalScroll;
use crate::config::Config as AppConfig;
use crate::{config::Config as AppConfig, mouse_reporter::MouseReporter};
#[derive(Clone, Copy, Debug)]
pub struct Size {
@ -97,14 +101,22 @@ fn as_dim(mut color: Color) -> Color {
color
}
pub static WINDOW_BG_COLOR: AtomicU32 = AtomicU32::new(0xFF000000);
fn convert_color(colors: &Colors, color: Color) -> cosmic_text::Color {
let rgb = match color {
Color::Named(named_color) => match colors[named_color] {
Some(rgb) => rgb,
None => {
log::warn!("missing named color {:?}", named_color);
Rgb::default()
}
None => match named_color {
NamedColor::Background => {
// Allow using an unset background
return cosmic_text::Color(WINDOW_BG_COLOR.load(Ordering::SeqCst));
}
_ => {
log::warn!("missing named color {:?}", named_color);
Rgb::default()
}
},
},
Color::Spec(rgb) => rgb,
Color::Indexed(index) => match colors[index as usize] {
@ -189,6 +201,7 @@ pub struct Terminal {
search_regex_opt: Option<RegexSearch>,
search_value: String,
pub metadata_set: IndexSet<Metadata>,
mouse_reporter: MouseReporter,
}
impl Terminal {
@ -275,6 +288,7 @@ impl Terminal {
search_regex_opt: None,
search_value: String::new(),
metadata_set,
mouse_reporter: Default::default(),
}
}
@ -633,7 +647,10 @@ impl Terminal {
buffer.set_redraw(true);
}
if buffer.lines[line_i].set_text(text.clone(), attrs_list.clone()) {
// Tab skip/stop is handled by alacritty_terminal
if buffer.lines[line_i]
.set_text(text.replace('\t', " "), attrs_list.clone())
{
buffer.set_redraw(true);
}
line_i += 1;
@ -773,6 +790,60 @@ impl Terminal {
let term = self.term.lock();
viewport_to_point(term.grid().display_offset(), point)
}
pub fn report_mouse(
&mut self,
event: cosmic::iced::Event,
modifiers: &cosmic::iced::keyboard::Modifiers,
x: u32,
y: u32,
) {
let term_lock = self.term.lock();
let mode = term_lock.mode();
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)
}
} else {
if let Some(code) = self.mouse_reporter.normal_mouse_code(
event,
modifiers,
mode.contains(TermMode::UTF8_MOUSE),
x,
y,
) {
self.input_no_scroll(code)
}
}
}
pub fn scroll_mouse(
&mut self,
delta: ScrollDelta,
modifiers: &cosmic::iced::keyboard::Modifiers,
x: u32,
y: u32,
) {
let term_lock = self.term.lock();
let mode = term_lock.mode();
if mode.contains(TermMode::SGR_MOUSE) {
self.mouse_reporter.report_sgr_mouse_wheel_scroll(
self,
self.size().cell_width,
self.size().cell_height,
delta,
modifiers,
x,
y,
);
} else {
self.mouse_reporter.report_mouse_wheel_as_arrows(
self,
self.size().cell_width,
self.size().cell_height,
delta,
);
}
}
}
impl Drop for Terminal {

View file

@ -47,6 +47,8 @@ pub struct TerminalBox<'a, Message> {
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>>,
mouse_inside_boundary: Option<bool>,
on_middle_click: Option<Box<dyn Fn() -> Message + 'a>>,
}
@ -62,6 +64,8 @@ where
click_timing: Duration::from_millis(500),
context_menu: None,
on_context_menu: None,
on_mouse_enter: None,
mouse_inside_boundary: None,
on_middle_click: None,
}
}
@ -94,6 +98,11 @@ where
self
}
pub fn on_mouse_enter(mut self, on_mouse_enter: impl Fn() -> Message + 'a) -> Self {
self.on_mouse_enter = Some(Box::new(on_mouse_enter));
self
}
pub fn on_middle_click(mut self, on_middle_click: impl Fn() -> Message + 'a) -> Self {
self.on_middle_click = Some(Box::new(on_middle_click));
self
@ -592,6 +601,7 @@ where
let buffer_size = terminal.with_buffer(|buffer| buffer.size());
let is_app_cursor = terminal.term.lock().mode().contains(TermMode::APP_CURSOR);
let is_mouse_mode = terminal.term.lock().mode().intersects(TermMode::MOUSE_MODE);
let mut status = Status::Ignored;
match event {
@ -632,6 +642,10 @@ where
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;
@ -694,10 +708,102 @@ where
}
_ => (),
},
(_, _, true, _) => {
// Ignore alt keys
//TODO: alt keys for control characters
}
// 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 => {
@ -911,113 +1017,36 @@ where
}
Event::Mouse(MouseEvent::ButtonPressed(button)) => {
if let Some(p) = cursor_position.position_in(layout.bounds()) {
state.is_focused = true;
let x = p.x - self.padding.left;
let y = p.y - self.padding.top;
//TODO: better calculation of position
let col = x / terminal.size().cell_width;
let row = y / terminal.size().cell_height;
// Handle left click drag
if let Button::Left = button {
let x = p.x - self.padding.left;
let y = p.y - self.padding.top;
if x >= 0.0 && x < buffer_size.0 && y >= 0.0 && y < buffer_size.1 {
let click_kind =
if let Some((click_kind, click_time)) = state.click.take() {
if click_time.elapsed() < self.click_timing {
match click_kind {
ClickKind::Single => ClickKind::Double,
ClickKind::Double => ClickKind::Triple,
ClickKind::Triple => ClickKind::Single,
if is_mouse_mode {
terminal.report_mouse(event, &state.modifiers, col as u32, row as u32);
} else {
state.is_focused = true;
// Handle left click drag
if let Button::Left = button {
let x = p.x - self.padding.left;
let y = p.y - self.padding.top;
if x >= 0.0 && x < buffer_size.0 && y >= 0.0 && y < buffer_size.1 {
let click_kind =
if let Some((click_kind, click_time)) = state.click.take() {
if click_time.elapsed() < self.click_timing {
match click_kind {
ClickKind::Single => ClickKind::Double,
ClickKind::Double => ClickKind::Triple,
ClickKind::Triple => ClickKind::Single,
}
} else {
ClickKind::Single
}
} else {
ClickKind::Single
}
} else {
ClickKind::Single
};
//TODO: better calculation of position
let col = x / terminal.size().cell_width;
let row = y / terminal.size().cell_height;
let location = terminal.viewport_to_point(TermPoint::new(
row as usize,
TermColumn(col as usize),
));
let side = if col.fract() < 0.5 {
TermSide::Left
} else {
TermSide::Right
};
let selection = match click_kind {
ClickKind::Single => {
Selection::new(SelectionType::Simple, location, side)
}
ClickKind::Double => {
Selection::new(SelectionType::Semantic, location, side)
}
ClickKind::Triple => {
Selection::new(SelectionType::Lines, location, side)
}
};
{
let mut term = terminal.term.lock();
term.selection = Some(selection);
}
terminal.needs_update = true;
state.click = Some((click_kind, Instant::now()));
state.dragging = Some(Dragging::Buffer);
} else if scrollbar_rect.contains(Point::new(x, y)) {
if let Some(start_scroll) = terminal.scrollbar() {
state.dragging = Some(Dragging::Scrollbar {
start_y: y,
start_scroll,
});
}
} else if x >= scrollbar_rect.x
&& x < (scrollbar_rect.x + scrollbar_rect.width)
{
if terminal.scrollbar().is_some() {
let scroll_ratio =
terminal.with_buffer(|buffer| y / buffer.size().1);
terminal.scroll_to(scroll_ratio);
if let Some(start_scroll) = terminal.scrollbar() {
state.dragging = Some(Dragging::Scrollbar {
start_y: y,
start_scroll,
});
}
}
}
} else if button == Button::Middle {
if let Some(on_middle_click) = &self.on_middle_click {
shell.publish(on_middle_click());
}
}
// Update context menu state
if let Some(on_context_menu) = &self.on_context_menu {
shell.publish((on_context_menu)(match self.context_menu {
Some(_) => None,
None => match button {
Button::Right => Some(p),
_ => None,
},
}));
}
status = Status::Captured;
}
}
Event::Mouse(MouseEvent::ButtonReleased(Button::Left)) => {
state.dragging = None;
status = Status::Captured;
}
Event::Mouse(MouseEvent::CursorMoved { .. }) => {
if let Some(dragging) = &state.dragging {
if let Some(p) = cursor_position.position() {
let x = (p.x - layout.bounds().x) - self.padding.left;
let y = (p.y - layout.bounds().y) - self.padding.top;
match dragging {
Dragging::Buffer => {
//TODO: better calculation of position
let col = x / terminal.size().cell_width;
let row = y / terminal.size().cell_height;
};
let location = terminal.viewport_to_point(TermPoint::new(
row as usize,
TermColumn(col as usize),
@ -1027,56 +1056,189 @@ where
} else {
TermSide::Right
};
let selection = match click_kind {
ClickKind::Single => {
Selection::new(SelectionType::Simple, location, side)
}
ClickKind::Double => {
Selection::new(SelectionType::Semantic, location, side)
}
ClickKind::Triple => {
Selection::new(SelectionType::Lines, location, side)
}
};
{
let mut term = terminal.term.lock();
if let Some(selection) = &mut term.selection {
selection.update(location, side);
}
term.selection = Some(selection);
}
terminal.needs_update = true;
state.click = Some((click_kind, Instant::now()));
state.dragging = Some(Dragging::Buffer);
} else if scrollbar_rect.contains(Point::new(x, y)) {
if let Some(start_scroll) = terminal.scrollbar() {
state.dragging = Some(Dragging::Scrollbar {
start_y: y,
start_scroll,
});
}
} else if x >= scrollbar_rect.x
&& x < (scrollbar_rect.x + scrollbar_rect.width)
{
if terminal.scrollbar().is_some() {
let scroll_ratio =
terminal.with_buffer(|buffer| y / buffer.size().1);
terminal.scroll_to(scroll_ratio);
if let Some(start_scroll) = terminal.scrollbar() {
state.dragging = Some(Dragging::Scrollbar {
start_y: y,
start_scroll,
});
}
}
}
Dragging::Scrollbar {
start_y,
start_scroll,
} => {
let scroll_offset = terminal
.with_buffer(|buffer| ((y - start_y) / buffer.size().1));
terminal.scroll_to(start_scroll.0 + scroll_offset);
} else if button == Button::Middle {
if let Some(on_middle_click) = &self.on_middle_click {
shell.publish(on_middle_click());
}
}
// Update context menu state
if let Some(on_context_menu) = &self.on_context_menu {
shell.publish((on_context_menu)(match self.context_menu {
Some(_) => None,
None => match button {
Button::Right => Some(p),
_ => None,
},
}));
}
status = Status::Captured;
}
}
}
Event::Mouse(MouseEvent::ButtonReleased(Button::Left)) => {
state.dragging = None;
if let Some(p) = cursor_position.position_in(layout.bounds()) {
let x = p.x - self.padding.left;
let y = p.y - self.padding.top;
//TODO: better calculation of position
let col = x / terminal.size().cell_width;
let row = y / terminal.size().cell_height;
if is_mouse_mode {
terminal.report_mouse(event, &state.modifiers, col as u32, row as u32);
} else {
status = Status::Captured;
}
} else {
status = Status::Captured;
}
}
Event::Mouse(MouseEvent::WheelScrolled { delta }) => {
if let Some(_p) = cursor_position.position_in(layout.bounds()) {
match delta {
ScrollDelta::Lines { x: _, y } => {
//TODO: this adjustment is just a guess!
state.scroll_pixels = 0.0;
let lines = (-y * 6.0) as i32;
if lines != 0 {
terminal.scroll(TerminalScroll::Delta(-lines));
Event::Mouse(MouseEvent::ButtonReleased(_button)) => {
if let Some(p) = cursor_position.position_in(layout.bounds()) {
let x = p.x - self.padding.left;
let y = p.y - self.padding.top;
//TODO: better calculation of position
let col = x / terminal.size().cell_width;
let row = y / terminal.size().cell_height;
if is_mouse_mode {
terminal.report_mouse(event, &state.modifiers, col as u32, row as u32);
}
}
}
Event::Mouse(MouseEvent::CursorMoved { .. }) => {
if let Some(on_mouse_enter) = &self.on_mouse_enter {
let mouse_is_inside = cursor_position.position_in(layout.bounds()).is_some();
if let Some(known_is_inside) = self.mouse_inside_boundary {
if mouse_is_inside != known_is_inside {
if mouse_is_inside {
shell.publish(on_mouse_enter());
}
status = Status::Captured;
self.mouse_inside_boundary = Some(mouse_is_inside);
}
ScrollDelta::Pixels { x: _, y } => {
//TODO: this adjustment is just a guess!
state.scroll_pixels -= y * 6.0;
let mut lines = 0;
let metrics = terminal.with_buffer(|buffer| buffer.metrics());
while state.scroll_pixels <= -metrics.line_height {
lines -= 1;
state.scroll_pixels += metrics.line_height;
} else {
self.mouse_inside_boundary = Some(mouse_is_inside);
}
}
if let Some(p) = cursor_position.position() {
let x = (p.x - layout.bounds().x) - self.padding.left;
let y = (p.y - layout.bounds().y) - self.padding.top;
//TODO: better calculation of position
let col = x / terminal.size().cell_width;
let row = y / terminal.size().cell_height;
if is_mouse_mode {
terminal.report_mouse(event, &state.modifiers, col as u32, row as u32);
} else {
if let Some(dragging) = &state.dragging {
match dragging {
Dragging::Buffer => {
let location = terminal.viewport_to_point(TermPoint::new(
row as usize,
TermColumn(col as usize),
));
let side = if col.fract() < 0.5 {
TermSide::Left
} else {
TermSide::Right
};
{
let mut term = terminal.term.lock();
if let Some(selection) = &mut term.selection {
selection.update(location, side);
}
}
terminal.needs_update = true;
}
Dragging::Scrollbar {
start_y,
start_scroll,
} => {
let scroll_offset = terminal
.with_buffer(|buffer| ((y - start_y) / buffer.size().1));
terminal.scroll_to(start_scroll.0 + scroll_offset);
}
}
while state.scroll_pixels >= metrics.line_height {
lines += 1;
state.scroll_pixels -= metrics.line_height;
}
status = Status::Captured;
}
}
}
Event::Mouse(MouseEvent::WheelScrolled { delta }) => {
if let Some(p) = cursor_position.position_in(layout.bounds()) {
if is_mouse_mode {
let x = (p.x - layout.bounds().x) - self.padding.left;
let y = (p.y - layout.bounds().y) - self.padding.top;
//TODO: better calculation of position
let col = x / terminal.size().cell_width;
let row = y / terminal.size().cell_height;
terminal.scroll_mouse(delta, &state.modifiers, col as u32, row as u32);
} else {
match delta {
ScrollDelta::Lines { x: _, y } => {
//TODO: this adjustment is just a guess!
state.scroll_pixels = 0.0;
let lines = (-y * 6.0) as i32;
if lines != 0 {
terminal.scroll(TerminalScroll::Delta(-lines));
}
status = Status::Captured;
}
if lines != 0 {
terminal.scroll(TerminalScroll::Delta(-lines));
ScrollDelta::Pixels { x: _, y } => {
//TODO: this adjustment is just a guess!
state.scroll_pixels -= y * 6.0;
let mut lines = 0;
let metrics = terminal.with_buffer(|buffer| buffer.metrics());
while state.scroll_pixels <= -metrics.line_height {
lines -= 1;
state.scroll_pixels += metrics.line_height;
}
while state.scroll_pixels >= metrics.line_height {
lines += 1;
state.scroll_pixels -= metrics.line_height;
}
if lines != 0 {
terminal.scroll(TerminalScroll::Delta(-lines));
}
status = Status::Captured;
}
status = Status::Captured;
}
}
}

View file

@ -138,6 +138,313 @@ fn auto_colors() -> Colors {
colors
}
fn tango_palette() -> 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(0x2E3436));
colors[NamedColor::Red] = Some(encode_rgb(0xCC0000));
colors[NamedColor::Green] = Some(encode_rgb(0x4E9A06));
colors[NamedColor::Yellow] = Some(encode_rgb(0xC4A000));
colors[NamedColor::Blue] = Some(encode_rgb(0x3465A4));
colors[NamedColor::Magenta] = Some(encode_rgb(0x75507B));
colors[NamedColor::Cyan] = Some(encode_rgb(0x06989A));
colors[NamedColor::White] = Some(encode_rgb(0xD3D7CF));
colors[NamedColor::BrightBlack] = Some(encode_rgb(0x555753));
colors[NamedColor::BrightRed] = Some(encode_rgb(0xEF2929));
colors[NamedColor::BrightGreen] = Some(encode_rgb(0x8AE234));
colors[NamedColor::BrightYellow] = Some(encode_rgb(0xFCE94F));
colors[NamedColor::BrightBlue] = Some(encode_rgb(0x729FCF));
colors[NamedColor::BrightMagenta] = Some(encode_rgb(0xAD7FA8));
colors[NamedColor::BrightCyan] = Some(encode_rgb(0x34E2E2));
colors[NamedColor::BrightWhite] = Some(encode_rgb(0xEEEEEC));
colors
}
fn tango_dark() -> Colors {
let mut colors = tango_palette();
// Set special colors
colors[NamedColor::Foreground] = colors[NamedColor::White];
colors[NamedColor::Background] = colors[NamedColor::Black];
colors[NamedColor::BrightForeground] = colors[NamedColor::BrightWhite];
colors[NamedColor::Cursor] = colors[NamedColor::Foreground];
// Fill missing dim colors
ColorDerive::new()
// Dim less so colors are readable with default bg
.with_dim_lightness_adjustment(-0.10)
.fill_missing_dims(&mut colors);
colors
}
fn tango_light() -> Colors {
let mut colors = tango_palette();
// Set special colors
colors[NamedColor::Foreground] = colors[NamedColor::Black];
colors[NamedColor::Background] = colors[NamedColor::BrightWhite];
colors[NamedColor::BrightForeground] = colors[NamedColor::BrightBlack];
colors[NamedColor::Cursor] = colors[NamedColor::Foreground];
// Fill missing dim colors
ColorDerive::new().fill_missing_dims(&mut colors);
colors
}
fn linux_console_palette() -> 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(0x000000));
colors[NamedColor::Red] = Some(encode_rgb(0xAA0000));
colors[NamedColor::Green] = Some(encode_rgb(0x00AA00));
colors[NamedColor::Yellow] = Some(encode_rgb(0xAA5500));
colors[NamedColor::Blue] = Some(encode_rgb(0x0000AA));
colors[NamedColor::Magenta] = Some(encode_rgb(0xAA00AA));
colors[NamedColor::Cyan] = Some(encode_rgb(0x00AAAA));
colors[NamedColor::White] = Some(encode_rgb(0xAAAAAA));
colors[NamedColor::BrightBlack] = Some(encode_rgb(0x555555));
colors[NamedColor::BrightRed] = Some(encode_rgb(0xFF5555));
colors[NamedColor::BrightGreen] = Some(encode_rgb(0x55FF55));
colors[NamedColor::BrightYellow] = Some(encode_rgb(0xFFFF55));
colors[NamedColor::BrightBlue] = Some(encode_rgb(0x5555FF));
colors[NamedColor::BrightMagenta] = Some(encode_rgb(0xFF55FF));
colors[NamedColor::BrightCyan] = Some(encode_rgb(0x55FFFF));
colors[NamedColor::BrightWhite] = Some(encode_rgb(0xFFFFFF));
colors
}
fn linux_console() -> Colors {
let mut colors = linux_console_palette();
// Set special colors
colors[NamedColor::Foreground] = colors[NamedColor::BrightWhite];
colors[NamedColor::Background] = colors[NamedColor::Black];
colors[NamedColor::BrightForeground] = colors[NamedColor::White];
colors[NamedColor::Cursor] = colors[NamedColor::Foreground];
// Fill missing dim colors
ColorDerive::new()
// Dim less so colors are readable with default bg
.with_dim_lightness_adjustment(-0.10)
.fill_missing_dims(&mut colors);
colors
}
fn xterm_palette() -> 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(0x000000));
colors[NamedColor::Red] = Some(encode_rgb(0xCD0000));
colors[NamedColor::Green] = Some(encode_rgb(0x00CD00));
colors[NamedColor::Yellow] = Some(encode_rgb(0xCDCD00));
colors[NamedColor::Blue] = Some(encode_rgb(0x0000EE));
colors[NamedColor::Magenta] = Some(encode_rgb(0xCD00CD));
colors[NamedColor::Cyan] = Some(encode_rgb(0x00CDCD));
colors[NamedColor::White] = Some(encode_rgb(0xE5E5E5));
colors[NamedColor::BrightBlack] = Some(encode_rgb(0x7F7F7F));
colors[NamedColor::BrightRed] = Some(encode_rgb(0xFF0000));
colors[NamedColor::BrightGreen] = Some(encode_rgb(0x00FF00));
colors[NamedColor::BrightYellow] = Some(encode_rgb(0xFFFF00));
colors[NamedColor::BrightBlue] = Some(encode_rgb(0x5C5CFF));
colors[NamedColor::BrightMagenta] = Some(encode_rgb(0xFF00FF));
colors[NamedColor::BrightCyan] = Some(encode_rgb(0x00FFFF));
colors[NamedColor::BrightWhite] = Some(encode_rgb(0xFFFFFF));
colors
}
fn xterm_dark() -> Colors {
let mut colors = xterm_palette();
// Set special colors
colors[NamedColor::Foreground] = colors[NamedColor::BrightWhite];
colors[NamedColor::Background] = colors[NamedColor::Black];
colors[NamedColor::BrightForeground] = colors[NamedColor::White];
colors[NamedColor::Cursor] = colors[NamedColor::Foreground];
// Fill missing dim colors
ColorDerive::new()
// Dim less so colors are readable with default bg
.with_dim_lightness_adjustment(-0.12)
.fill_missing_dims(&mut colors);
colors
}
fn xterm_light() -> Colors {
let mut colors = xterm_palette();
// Set special colors
colors[NamedColor::Foreground] = colors[NamedColor::Black];
colors[NamedColor::Background] = colors[NamedColor::BrightWhite];
colors[NamedColor::BrightForeground] = colors[NamedColor::BrightBlack];
colors[NamedColor::Cursor] = colors[NamedColor::Foreground];
// Fill missing dim colors
ColorDerive::new().fill_missing_dims(&mut colors);
colors
}
fn rxvt_palette() -> 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(0x000000));
colors[NamedColor::Red] = Some(encode_rgb(0xCD0000));
colors[NamedColor::Green] = Some(encode_rgb(0x00CD00));
colors[NamedColor::Yellow] = Some(encode_rgb(0xCDCD00));
colors[NamedColor::Blue] = Some(encode_rgb(0x0000CD));
colors[NamedColor::Magenta] = Some(encode_rgb(0xCD00CD));
colors[NamedColor::Cyan] = Some(encode_rgb(0x00CDCD));
colors[NamedColor::White] = Some(encode_rgb(0xFAEBD7));
colors[NamedColor::BrightBlack] = Some(encode_rgb(0x404040));
colors[NamedColor::BrightRed] = Some(encode_rgb(0xFF0000));
colors[NamedColor::BrightGreen] = Some(encode_rgb(0x00FF00));
colors[NamedColor::BrightYellow] = Some(encode_rgb(0xFFFF00));
colors[NamedColor::BrightBlue] = Some(encode_rgb(0x0000FF));
colors[NamedColor::BrightMagenta] = Some(encode_rgb(0xFF00FF));
colors[NamedColor::BrightCyan] = Some(encode_rgb(0x00FFFF));
colors[NamedColor::BrightWhite] = Some(encode_rgb(0xFFFFFF));
colors
}
fn rxvt_dark() -> Colors {
let mut colors = rxvt_palette();
// Set special colors
colors[NamedColor::Foreground] = colors[NamedColor::BrightWhite];
colors[NamedColor::Background] = colors[NamedColor::Black];
colors[NamedColor::BrightForeground] = colors[NamedColor::White];
colors[NamedColor::Cursor] = colors[NamedColor::Foreground];
// Fill missing dim colors
ColorDerive::new()
// Dim less so colors are readable with default bg
.with_dim_lightness_adjustment(-0.12)
.fill_missing_dims(&mut colors);
colors
}
fn rxvt_light() -> Colors {
let mut colors = rxvt_palette();
// Set special colors
colors[NamedColor::Foreground] = colors[NamedColor::Black];
colors[NamedColor::Background] = colors[NamedColor::BrightWhite];
colors[NamedColor::BrightForeground] = colors[NamedColor::BrightBlack];
colors[NamedColor::Cursor] = colors[NamedColor::Foreground];
// Fill missing dim colors
ColorDerive::new().fill_missing_dims(&mut colors);
colors
}
fn solarized_palette() -> 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(0x073642));
colors[NamedColor::Red] = Some(encode_rgb(0xDC322F));
colors[NamedColor::Green] = Some(encode_rgb(0x859900));
colors[NamedColor::Yellow] = Some(encode_rgb(0xB58900));
colors[NamedColor::Blue] = Some(encode_rgb(0x268BD2));
colors[NamedColor::Magenta] = Some(encode_rgb(0xD33682));
colors[NamedColor::Cyan] = Some(encode_rgb(0x2AA198));
colors[NamedColor::White] = Some(encode_rgb(0xEEE8D5));
colors[NamedColor::BrightBlack] = Some(encode_rgb(0x002B36));
colors[NamedColor::BrightRed] = Some(encode_rgb(0xCB4B16));
colors[NamedColor::BrightGreen] = Some(encode_rgb(0x586E75));
colors[NamedColor::BrightYellow] = Some(encode_rgb(0x657B83));
colors[NamedColor::BrightBlue] = Some(encode_rgb(0x839496));
colors[NamedColor::BrightMagenta] = Some(encode_rgb(0x6C71C4));
colors[NamedColor::BrightCyan] = Some(encode_rgb(0x93A1A1));
colors[NamedColor::BrightWhite] = Some(encode_rgb(0xFDF6E3));
colors
}
fn solarized_dark() -> Colors {
let mut colors = solarized_palette();
// Set special colors
colors[NamedColor::Foreground] = colors[NamedColor::BrightBlue];
colors[NamedColor::Background] = colors[NamedColor::BrightBlack];
colors[NamedColor::BrightForeground] = colors[NamedColor::Blue];
colors[NamedColor::Cursor] = colors[NamedColor::Foreground];
// Fill missing dim colors
ColorDerive::new().fill_missing_dims(&mut colors);
colors
}
fn solarized_light() -> Colors {
let mut colors = solarized_palette();
// Set special colors
colors[NamedColor::Foreground] = colors[NamedColor::BrightYellow];
colors[NamedColor::Background] = colors[NamedColor::BrightWhite];
colors[NamedColor::BrightForeground] = colors[NamedColor::Yellow];
colors[NamedColor::Cursor] = colors[NamedColor::Foreground];
// Fill missing dim colors
ColorDerive::new().fill_missing_dims(&mut colors);
colors
}
fn cosmic_dark() -> Colors {
let mut colors = auto_colors();
@ -169,7 +476,7 @@ fn cosmic_dark() -> Colors {
// Set special colors
colors[NamedColor::Foreground] = colors[NamedColor::BrightWhite];
colors[NamedColor::Background] = colors[NamedColor::Black];
// Background comes from theme settings: colors[NamedColor::Background] = colors[NamedColor::Black];
colors[NamedColor::Cursor] = colors[NamedColor::BrightWhite];
colors[NamedColor::BrightForeground] = colors[NamedColor::BrightWhite];
@ -210,7 +517,7 @@ fn cosmic_light() -> Colors {
// Set special colors
colors[NamedColor::Foreground] = colors[NamedColor::Black];
colors[NamedColor::Background] = colors[NamedColor::BrightWhite];
// Background comes from theme settings: colors[NamedColor::Background] = colors[NamedColor::BrightWhite];
colors[NamedColor::Cursor] = colors[NamedColor::Black];
colors[NamedColor::BrightForeground] = colors[NamedColor::BrightBlack];
@ -351,6 +658,15 @@ fn pop_dark() -> Colors {
pub fn terminal_themes() -> HashMap<String, Colors> {
let mut themes = HashMap::new();
themes.insert("Tango Dark".to_string(), tango_dark());
themes.insert("Tango Light".to_string(), tango_light());
themes.insert("XTerm Dark".to_string(), xterm_dark());
themes.insert("XTerm Light".to_string(), xterm_light());
themes.insert("Linux Console".to_string(), linux_console());
themes.insert("Rxvt Dark".to_string(), rxvt_dark());
themes.insert("Rxvt Light".to_string(), rxvt_light());
themes.insert("Solarized Dark".to_string(), solarized_dark());
themes.insert("Solarized Light".to_string(), solarized_light());
themes.insert("COSMIC Dark".to_string(), cosmic_dark());
themes.insert("COSMIC Light".to_string(), cosmic_light());
themes.insert("gruvbox-dark".to_string(), gruvbox_dark());