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

1291
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -11,8 +11,10 @@ alacritty_terminal = "0.20"
env_logger = "0.10"
lazy_static = "1"
indexmap = "2"
lexical-sort = "0.3.1"
log = "0.4"
serde = { version = "1", features = ["serde_derive"] }
shlex = "1"
tokio = { version = "1", features = ["sync"] }
# Internationalization
i18n-embed = { version = "0.13", features = ["fluent-system", "desktop-requester"] }
@ -21,8 +23,13 @@ rust-embed = "6"
paste = "1.0"
palette = "0.7"
[dependencies.smol_str]
version = "0.2.1"
features = ["serde"]
[dependencies.cosmic-text]
git = "https://github.com/pop-os/cosmic-text.git"
features = ["shape-run-cache"]
[dependencies.libcosmic]
git = "https://github.com/pop-os/libcosmic.git"
@ -36,7 +43,6 @@ wgpu = ["libcosmic/wgpu"]
[patch.crates-io]
# https://github.com/rust-lang/libc/pull/3512
libc = { git = "https://gitlab.redox-os.org/redox-os/liblibc.git", branch = "redox_0.2.151" }
smithay-client-toolkit = { git = "https://github.com/pop-os/client-toolkit", branch = "wayland-resize" }
[profile.release-with-debug]
inherits = "release"

1
debian/control vendored
View file

@ -13,4 +13,5 @@ Homepage: https://github.com/pop-os/cosmic-term
Package: cosmic-term
Architecture: amd64 arm64
Depends: ${misc:Depends}, ${shlibs:Depends}
Provides: x-terminal-emulator
Description: Cosmic Terminal

View file

@ -1,5 +1,16 @@
new-terminal = New terminal
# Context Pages
## Profiles
profiles = Profiles
name = Name
command-line = Command line
tab-title = Tab title
tab-title-description = Override the default tab title
add-profile = Add profile
new-profile = New profile
## Settings
settings = Settings
@ -12,6 +23,7 @@ light = Light
syntax-dark = Color scheme dark
syntax-light = Color scheme light
default-zoom-step = Zoom steps
opacity = Background opacity
### Font
font = Font
@ -44,6 +56,8 @@ find-next = Find next
file = File
new-tab = New tab
new-window = New window
profile = Profile
menu-profiles = Profiles...
close-tab = Close tab
quit = Quit

View file

@ -1,25 +1,47 @@
# Context Pages
## Profiles
profiles = プロファイル
name = 名前
command-line = コマンドライン
tab-title = タブのイトル
tab-title-description = デフォルトのタブタイトルを無効にします
add-profile = プロファイルを追加
new-profile = 新しいプロファイル
## 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 = デフォルトの太字の太さ
match-desktop = システム設定に従う
dark = ダーク
light = ライト
syntax-dark = ダークシンタックスハイライト
syntax-light = ライトシンタックスハイライト
default-zoom-step = 拡大縮小の間隔
opacity = 不透明度
### Font
font = フォント
advanced-font-settings = フォントの詳細設定
default-font = フォント名
default-font-size = サイズ
default-font-stretch = 幅
default-font-weight = スタイル
default-dim-font-weight = 細字のスタイル
default-bold-font-weight = 太字のスタイル
use-bright-bold = 太字を明るい色で表示する
default-font-size = デフォルトのフォントサイズ
default-zoom-step =ズームの間隔
### Splits
splits = ウィンドウの分割
focus-follow-mouse = フォーカスをマウスに追従
### Advanced
advanced = 高度な設定
show-headerbar = ヘッダーを表示する
show-header-description = 右クリックでヘッダーを表示する
# Find
find-placeholder = 検索...
@ -32,6 +54,8 @@ find-next = 次を検索
file = ファイル
new-tab = 新しいタブ
new-window = 新しいウィンドウ
profile = プロファイル
menu-profiles = プロファイル...
close-tab = タブを閉じる
quit = 終了
@ -44,7 +68,12 @@ find = 検索
## View
view = 表示
zoom-in = 拡大
zoom-reset = デフォルトに戻す
zoom-out = 縮小
next-tab = 次のタブ
previous-tab = 前のタブ
split-horizontal = 上下に分割
split-vertical = 左右に分割
pane-toggle-maximize = ペインの最大化を切替
menu-settings = 設定...
# Context menu
show-headerbar = ヘッダーバーを表す

View file

@ -11,20 +11,32 @@ dark = Ciemny
light = Jasny
syntax-dark = Ciemna składnia
syntax-light = Jasna składnia
advanced-font-settings = Advanced Font Settings
default-zoom-step = Domyślny poziom przybliżenia
### Font
font = Czcionka
advanced-font-settings = Zaawansowane Ustawienia Czcionek
default-font = Domyślna czcionka
default-font-size = Domyślny rozmiar czcionki
default-font-stretch = Domyślne rozciągnięcie czcionki
default-font-weight = Domyślna grubość czcionki
default-dim-font-weight = Domyślna grubość przyciemnionej czcionki
default-bold-font-weight = Domyślna grubość pogrubionej czcionki
use-bright-bold = Użyj jasnych kolorów przy pogrubionym tekście
default-font-size = Domyślny rozmiar czcionki
default-zoom-step = Domyślny poziom przybliżenia
use-bright-bold = Rozjaśnij pogrubiony tekst
### Splits
splits = Podziel
focus-follow-mouse = Aktywne okno pisania odpowiada położeniu myszki
### Advanced
advanced = Zaawansowane
show-headerbar = Pokaż pasek nagłówka
show-header-description = Ukazuje pasek nagłówka z menu wybieranego prawym przyciskiem myszki.
# Find
find-placeholder = Find...
find-previous = Find previous
find-next = Find next
find-placeholder = Szukaj...
find-previous = Szukaj poprzedni
find-next = Szukaj następny
# Menu
@ -44,7 +56,12 @@ find = Szukaj
## View
view = Widok
zoom-in = Większy tekst
zoom-reset = Domyślny rozmiar tekstu
zoom-out = Mniejszy tekst
next-tab = Natępna karta
previous-tab = Poprzednia karta
split-horizontal = Podziel w poziomie
split-vertical = Podziel w pionie
pane-toggle-maximize = Przełącznik maksymalizacji
menu-settings = Ustawienia...
# Context menu
show-headerbar = Pokaż pasek nagłówka

View file

@ -9,17 +9,29 @@ theme = Тема
match-desktop = Как в системе
dark = Темная
light = Светлая
syntax-dark = Синтаксис темный
syntax-light = Синтаксис светлый
syntax-dark = Цветовая схема темная
syntax-light = Цветовая схема светлая
default-zoom-step = Шаги масштабирования
### Font
font = Шрифт
advanced-font-settings = Дополнительные настройки шрифта
default-font = Шрифт по умолчанию
default-font-stretch = Растяжение шрифта по умолчанию
default-font-weight = Масса шрифта по умолчанию
default-dim-font-weight = Масса dim шрифта по умолчанию
default-bold-font-weight = Масса жирного шрифта по умолчанию
use-bright-bold = Использовать яркие цвета и жирный текст
default-font-size = Размер шрифта по умолчанию
default-zoom-step = Шаг масштабирования по умолчанию
default-font-size = Размер шрифта
default-font-stretch = Растяжение шрифта
default-font-weight = Масса шрифта
default-dim-font-weight = Масса dim шрифта
default-bold-font-weight = Масса жирного шрифта
use-bright-bold = Сделать жирный текст ярче
### Splits
splits = Деления
focus-follow-mouse = Фокус при наборе следует за мышью
### Advanced
advanced = Дополнительно
show-headerbar = Отображать заголовок
show-header-description = Раскройте заголовок из меню правой кнопки мыши.
# Find
find-placeholder = Найти...
@ -44,7 +56,12 @@ find = Найти
## View
view = Вид
zoom-in = Текст крупнее
zoom-reset = Размер текста по умолчанию
zoom-out = Текст меньше
next-tab = Следующая вкладка
previous-tab = Предыдущая вкладка
split-horizontal = Разделение по горизонтали
split-vertical = Разделение по вертикали
pane-toggle-maximize = Переключить на весь экран
menu-settings = Параметры...
# Context menu
show-headerbar = Отображать заголовок

View file

@ -0,0 +1,67 @@
# Context sidor
## Settings
settings = Inställningar
### Appearance
appearance = Utseende
theme = Tema
match-desktop = Matcha skrivbordet
dark = Mörkt
light = Ljust
syntax-dark = Färgschema mörkt
syntax-light = Färgschema ljust
default-zoom-step = Zoom steg
### Teckensnitt
font = Teckensnitt
advanced-font-settings = Avancerade teckensnittsinställningar
default-font = Teckensnitt
default-font-size = Teckenstorlek
default-font-stretch = Teckenstretch
default-font-weight = Normal teckensnittsvikt
default-dim-font-weight = Dämpad teckensnittsvikt
default-bold-font-weight = Fet teckensnittsvikt
use-bright-bold = Gör fet text ljusare
### Delar
splits = Delar
focus-follow-mouse = Skrivfokus följer mus
### Avancerat
advanced = Avancerat
show-headerbar = Visa rubrikrad
show-header-description = Visa rubrikrad från högerklicksmenyn.
# Sök
find-placeholder = Sök…
find-previous = Hitta föregående
find-next = Hitta nästa
# Meny
## Fil
file = Fil
new-tab = Ny flik
new-window = Nytt fönster
close-tab = Stäng flik
quit = Avsluta
## Redigera
edit = Redigera
copy = Kopiera
paste = Klistra in
select-all = Välj alla
find = Sök
## Visa
view = Visa
zoom-in = Zooma in
zoom-reset = Återställ zoom
zoom-out = Zooma ut
next-tab = Nästa flik
previous-tab = Föregående flik
split-horizontal = Dela horisontellt
split-vertical = Dela vertikalt
pane-toggle-maximize = Växla maximerad
menu-settings = Inställningar…

78
i18n/tr/cosmic-term.ftl Normal file
View file

@ -0,0 +1,78 @@
# Context Pages
## Profiles
profiles = Profiller
name = İsim
command-line = Komut satırı
tab-title = Sekme başlığı
tab-title-description = Varsayılan sekme başlığını geçersiz kılar
add-profile = Profil ekle
new-profile = Yeni profil
## Settings
settings = Ayarlar
### Appearance
appearance = Görünüm
theme = Tema
match-desktop = Masaüstüyle eşle
dark = Karanlık
light = Aydınlık
syntax-dark = Karanlık renk şeması
syntax-light = Aydınlık renk şeması
default-zoom-step = Yakınlaştırma basamakları
### Font
font = Yazı tipi
advanced-font-settings = Gelişmiş Yazı tipi Seçenekleri
default-font = Yazı tipi
default-font-size = Yazı tipi boyutu
default-font-stretch = Yazı tipi esnekliği
default-font-weight = Normal yazı tipi ağırlığı
default-dim-font-weight = Soluk yazı tipi ağırlığı
default-bold-font-weight = Kalın yazı tipi ağırlığı
use-bright-bold = Kalın metni daha parlak yap
### Splits
splits = Bölmeler
focus-follow-mouse = Yazma odağı fareyi takip etsin
### Advanced
advanced = Gelişmiş
show-headerbar = Başlığı göster
show-header-description = Sağ tıklama menüsünden başlığı gösterin.
# Find
find-placeholder = Bul...
find-previous = Öncekini bul
find-next = Sonrakini bul
# Menu
## File
file = Dosyal
new-tab = Yeni sekme
new-window = Yeni pencere
profile = Profil
menu-profiles = Profiller...
close-tab = Sekmeyi kapat
quit = Çık
## Edit
edit = Düzenle
copy = Kopyala
paste = Yapıştır
select-all = Hepsini seç
find = Bul
## View
view = Görünüş
zoom-in = Daha büyük metin
zoom-reset = Varsayılan metin boyutu
zoom-out = Daha küçük metin
next-tab = Sonraki sekme
previous-tab = Önceki sekme
split-horizontal = Yatay böl
split-vertical = Dikey böl
pane-toggle-maximize = En üste geç
menu-settings = Ayarlar...

View file

@ -46,9 +46,13 @@ check *args:
# Runs a clippy check with JSON message format
check-json: (check '--message-format=json')
dev *args:
cargo fmt
just run {{args}}
# Run with debug logs
run *args:
env RUST_LOG=debug RUST_BACKTRACE=full cargo run --release {{args}}
env RUST_LOG=cosmic_term=debug RUST_BACKTRACE=full cargo run --release {{args}}
# Installs files
install:

View file

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 4.98999V13.99C2 14.514 2.476 14.99 3 14.99H12C12.524 14.99 13 14.514 13 13.99V4.98999H2Z" fill="#232323"/>
<path d="M1 2.99001V3.99001L14 3.98701V2.99001C14 1.99001 13 1.98701 13 1.98701H10C10 1.98701 10 0.987 9 0.987H6C5 0.987 5 1.98701 5 1.98701H2C2 1.98701 1 1.99001 1 2.99001Z" fill="#232323"/>
</svg>

After

Width:  |  Height:  |  Size: 414 B

View file

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 3C7.73478 3 7.48043 3.10536 7.29289 3.29289C7.10536 3.48043 7 3.73478 7 4V6.996L4 7C3.73478 7 3.48043 7.10536 3.29289 7.29289C3.10536 7.48043 3 7.73478 3 8C3 8.26522 3.10536 8.51957 3.29289 8.70711C3.48043 8.89464 3.73478 9 4 9L7 8.996V12C7 12.2652 7.10536 12.5196 7.29289 12.7071C7.48043 12.8946 7.73478 13 8 13C8.26522 13 8.51957 12.8946 8.70711 12.7071C8.89464 12.5196 9 12.2652 9 12V8.996L12 9C12.2652 9 12.5196 8.89464 12.7071 8.70711C12.8946 8.51957 13 8.26522 13 8C13 7.73478 12.8946 7.48043 12.7071 7.29289C12.5196 7.10536 12.2652 7 12 7L9 6.996V4C9 3.73478 8.89464 3.48043 8.70711 3.29289C8.51957 3.10536 8.26522 3 8 3Z" fill="#232323"/>
</svg>

After

Width:  |  Height:  |  Size: 762 B

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
}