zellij/zellij-utils/src/data.rs
Aram Drevekenin 79bf6ab868
feat(config): switch to kdl (#1759)
* chore(config): default kdl keybindings config

* tests

* work

* refactor(config): move stuff around

* work

* tab merge layout

* work

* work

* layouts working

* work

* layout tests

* work

* work

* feat(parsing): kdl layouts without config

* refactor(kdl): move stuff around

* work

* tests(layout): add cases and fix bugs

* work

* fix(kdl): various bugs

* chore(layouts): move all layouts to kdl

* feat(kdl): shared keybidns

* fix(layout): do not count fixed panes toward percentile

* fix(keybinds): missing keybinds and actions

* fix(config): adjust default tips

* refactor(config): move stuff around

* fix(tests): make e2e tests pass

* fix(kdl): add verbose parsing errors

* fix(kdl): focused tab

* fix(layout): corret default_tab_template behavior

* style(code): fix compile warnings

* feat(cli): send actions through the cli

* fix(cli): exit only when action is done

* fix(cli): open embedded pane from floating pane

* fix(cli): send actions to other sessions

* feat(cli): command alias

* feat(converter): convert old config

* feat(converter): convert old layout and theme files

* feat(kdl): pretty errors

* feat(client): convert old YAML files on startup

* fix: various bugs and styling issues

* fix: e2e tests

* fix(screen): propagate errors after merge

* style(clippy): lower clippy level

* fix(tests): own session_name variable

* style(fmt): rustfmt

* fix(cli): various action fixes

* style(fmt): rustfmt

* fix(themes): loading of theme files

* style(fmt): rustfmt

* fix(tests): theme fixtures

* fix(layouts): better errors on unknown nodes

* fix(kdl): clarify valid node terminator error

* fix(e2e): adjust close tab test

* fix(e2e): adjust close tab test again

* style(code): cleanup some comments
2022-10-05 07:44:00 +02:00

507 lines
17 KiB
Rust

use crate::input::actions::Action;
use crate::input::config::ConversionError;
use clap::ArgEnum;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::str::FromStr;
use strum_macros::{EnumDiscriminants, EnumIter, EnumString, ToString};
pub type ClientId = u16; // TODO: merge with crate type?
pub fn client_id_to_colors(
client_id: ClientId,
colors: Palette,
) -> Option<(PaletteColor, PaletteColor)> {
// (primary color, secondary color)
match client_id {
1 => Some((colors.magenta, colors.black)),
2 => Some((colors.blue, colors.black)),
3 => Some((colors.purple, colors.black)),
4 => Some((colors.yellow, colors.black)),
5 => Some((colors.cyan, colors.black)),
6 => Some((colors.gold, colors.black)),
7 => Some((colors.red, colors.black)),
8 => Some((colors.silver, colors.black)),
9 => Some((colors.pink, colors.black)),
10 => Some((colors.brown, colors.black)),
_ => None,
}
}
pub fn single_client_color(colors: Palette) -> (PaletteColor, PaletteColor) {
(colors.green, colors.black)
}
// TODO: Add a shortened string representation (beyond `Display::fmt` below) that can be used when
// screen space is scarce. Useful for e.g. "ENTER", "SPACE", "TAB" to display as Unicode
// representations instead.
// NOTE: Do not reorder the key variants since that influences what the `status_bar` plugin
// displays!
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
pub enum Key {
PageDown,
PageUp,
Left,
Down,
Up,
Right,
Home,
End,
Backspace,
Delete,
Insert,
F(u8),
Char(char),
Alt(CharOrArrow),
Ctrl(char),
BackTab,
Null,
Esc,
}
impl FromStr for Key {
type Err = Box<dyn std::error::Error>;
fn from_str(key_str: &str) -> Result<Self, Self::Err> {
let mut modifier: Option<&str> = None;
let mut main_key: Option<&str> = None;
for (index, part) in key_str.split_ascii_whitespace().enumerate() {
if index == 0 && (part == "Ctrl" || part == "Alt") {
modifier = Some(part);
} else if main_key.is_none() {
main_key = Some(part)
}
}
match (modifier, main_key) {
(Some("Ctrl"), Some(main_key)) => {
let mut key_chars = main_key.chars();
let key_count = main_key.chars().count();
if key_count == 1 {
let key_char = key_chars.next().unwrap();
Ok(Key::Ctrl(key_char))
} else {
Err(format!("Failed to parse key: {}", key_str).into())
}
},
(Some("Alt"), Some(main_key)) => {
match main_key {
// why crate::data::Direction and not just Direction?
// Because it's a different type that we export in this wasm mandated soup - we
// don't like it either! This will be solved as we chip away at our tech-debt
"Left" => Ok(Key::Alt(CharOrArrow::Direction(Direction::Left))),
"Right" => Ok(Key::Alt(CharOrArrow::Direction(Direction::Right))),
"Up" => Ok(Key::Alt(CharOrArrow::Direction(Direction::Up))),
"Down" => Ok(Key::Alt(CharOrArrow::Direction(Direction::Down))),
_ => {
let mut key_chars = main_key.chars();
let key_count = main_key.chars().count();
if key_count == 1 {
let key_char = key_chars.next().unwrap();
Ok(Key::Alt(CharOrArrow::Char(key_char)))
} else {
Err(format!("Failed to parse key: {}", key_str).into())
}
},
}
},
(None, Some(main_key)) => match main_key {
"Backspace" => Ok(Key::Backspace),
"Left" => Ok(Key::Left),
"Right" => Ok(Key::Right),
"Up" => Ok(Key::Up),
"Down" => Ok(Key::Down),
"Home" => Ok(Key::Home),
"End" => Ok(Key::End),
"PageUp" => Ok(Key::PageUp),
"PageDown" => Ok(Key::PageDown),
"Tab" => Ok(Key::BackTab),
"Delete" => Ok(Key::Delete),
"Insert" => Ok(Key::Insert),
"Space" => Ok(Key::Char(' ')),
"Enter" => Ok(Key::Char('\n')),
"Esc" => Ok(Key::Esc),
_ => {
let mut key_chars = main_key.chars();
let key_count = main_key.chars().count();
if key_count == 1 {
let key_char = key_chars.next().unwrap();
Ok(Key::Char(key_char))
} else if key_count > 1 {
if let Some(first_char) = key_chars.next() {
if first_char == 'F' {
let f_index: String = key_chars.collect();
let f_index: u8 = f_index
.parse()
.map_err(|e| format!("Failed to parse F index: {}", e))?;
if f_index >= 1 && f_index <= 12 {
return Ok(Key::F(f_index));
}
}
}
Err(format!("Failed to parse key: {}", key_str).into())
} else {
Err(format!("Failed to parse key: {}", key_str).into())
}
},
},
_ => Err(format!("Failed to parse key: {}", key_str).into()),
}
}
}
impl fmt::Display for Key {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Key::Backspace => write!(f, "BACKSPACE"),
Key::Left => write!(f, "{}", Direction::Left),
Key::Right => write!(f, "{}", Direction::Right),
Key::Up => write!(f, "{}", Direction::Up),
Key::Down => write!(f, "{}", Direction::Down),
Key::Home => write!(f, "HOME"),
Key::End => write!(f, "END"),
Key::PageUp => write!(f, "PgUp"),
Key::PageDown => write!(f, "PgDn"),
Key::BackTab => write!(f, "TAB"),
Key::Delete => write!(f, "DEL"),
Key::Insert => write!(f, "INS"),
Key::F(n) => write!(f, "F{}", n),
Key::Char(c) => match c {
'\n' => write!(f, "ENTER"),
'\t' => write!(f, "TAB"),
' ' => write!(f, "SPACE"),
_ => write!(f, "{}", c),
},
Key::Alt(c) => write!(f, "Alt+{}", c),
Key::Ctrl(c) => write!(f, "Ctrl+{}", Key::Char(*c)),
Key::Null => write!(f, "NULL"),
Key::Esc => write!(f, "ESC"),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
#[serde(untagged)]
pub enum CharOrArrow {
Char(char),
Direction(Direction),
}
impl fmt::Display for CharOrArrow {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CharOrArrow::Char(c) => write!(f, "{}", Key::Char(*c)),
CharOrArrow::Direction(d) => write!(f, "{}", d),
}
}
}
/// The four directions (left, right, up, down).
#[derive(Eq, Clone, Copy, Debug, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)]
pub enum Direction {
Left,
Right,
Up,
Down,
}
impl fmt::Display for Direction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Direction::Left => write!(f, ""),
Direction::Right => write!(f, ""),
Direction::Up => write!(f, ""),
Direction::Down => write!(f, ""),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
// FIXME: This should be extended to handle different button clicks (not just
// left click) and the `ScrollUp` and `ScrollDown` events could probably be
// merged into a single `Scroll(isize)` event.
pub enum Mouse {
ScrollUp(usize), // number of lines
ScrollDown(usize), // number of lines
LeftClick(isize, usize), // line and column
RightClick(isize, usize), // line and column
Hold(isize, usize), // line and column
Release(isize, usize), // line and column
}
#[derive(Debug, Clone, PartialEq, EnumDiscriminants, ToString, Serialize, Deserialize)]
#[strum_discriminants(derive(EnumString, Hash, Serialize, Deserialize))]
#[strum_discriminants(name(EventType))]
#[non_exhaustive]
pub enum Event {
ModeUpdate(ModeInfo),
TabUpdate(Vec<TabInfo>),
Key(Key),
Mouse(Mouse),
Timer(f64),
CopyToClipboard(CopyDestination),
SystemClipboardFailure,
InputReceived,
Visible(bool),
}
/// Describes the different input modes, which change the way that keystrokes will be interpreted.
#[derive(
Debug,
PartialEq,
Eq,
Hash,
Copy,
Clone,
EnumIter,
Serialize,
Deserialize,
ArgEnum,
PartialOrd,
Ord,
)]
pub enum InputMode {
/// In `Normal` mode, input is always written to the terminal, except for the shortcuts leading
/// to other modes
#[serde(alias = "normal")]
Normal,
/// In `Locked` mode, input is always written to the terminal and all shortcuts are disabled
/// except the one leading back to normal mode
#[serde(alias = "locked")]
Locked,
/// `Resize` mode allows resizing the different existing panes.
#[serde(alias = "resize")]
Resize,
/// `Pane` mode allows creating and closing panes, as well as moving between them.
#[serde(alias = "pane")]
Pane,
/// `Tab` mode allows creating and closing tabs, as well as moving between them.
#[serde(alias = "tab")]
Tab,
/// `Scroll` mode allows scrolling up and down within a pane.
#[serde(alias = "scroll")]
Scroll,
/// `EnterSearch` mode allows for typing in the needle for a search in the scroll buffer of a pane.
#[serde(alias = "entersearch")]
EnterSearch,
/// `Search` mode allows for searching a term in a pane (superset of `Scroll`).
#[serde(alias = "search")]
Search,
/// `RenameTab` mode allows assigning a new name to a tab.
#[serde(alias = "renametab")]
RenameTab,
/// `RenamePane` mode allows assigning a new name to a pane.
#[serde(alias = "renamepane")]
RenamePane,
/// `Session` mode allows detaching sessions
#[serde(alias = "session")]
Session,
/// `Move` mode allows moving the different existing panes within a tab
#[serde(alias = "move")]
Move,
/// `Prompt` mode allows interacting with active prompts.
#[serde(alias = "prompt")]
Prompt,
/// `Tmux` mode allows for basic tmux keybindings functionality
#[serde(alias = "tmux")]
Tmux,
}
// impl TryFrom<&str> for InputMode {
// type Error = String;
// fn try_from(mode: &str) -> Result<Self, String> {
// match mode {
// "normal" | "Normal" => Ok(InputMode::Normal),
// "locked" | "Locked" => Ok(InputMode::Locked),
// "resize" | "Resize" => Ok(InputMode::Resize),
// "pane" | "Pane" => Ok(InputMode::Pane),
// "tab" | "Tab" => Ok(InputMode::Tab),
// "search" | "Search" => Ok(InputMode::Search),
// "renametab" | "RenameTab" => Ok(InputMode::RenameTab),
// "renamepane" | "RenamePane" => Ok(InputMode::RenamePane),
// "session" | "Session" => Ok(InputMode::Session),
// "move" | "Move" => Ok(InputMode::Move),
// "prompt" | "Prompt" => Ok(InputMode::Prompt),
// "tmux" | "Tmux" => Ok(InputMode::Tmux),
// _ => Err(format!("Unrecognized mode: {}", mode)),
// }
// }
// }
impl Default for InputMode {
fn default() -> InputMode {
InputMode::Normal
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum ThemeHue {
Light,
Dark,
}
impl Default for ThemeHue {
fn default() -> ThemeHue {
ThemeHue::Dark
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum PaletteColor {
Rgb((u8, u8, u8)),
EightBit(u8),
}
impl Default for PaletteColor {
fn default() -> PaletteColor {
PaletteColor::EightBit(0)
}
}
impl FromStr for InputMode {
type Err = ConversionError;
fn from_str(s: &str) -> Result<Self, ConversionError> {
match s {
"normal" | "Normal" => Ok(InputMode::Normal),
"locked" | "Locked" => Ok(InputMode::Locked),
"resize" | "Resize" => Ok(InputMode::Resize),
"pane" | "Pane" => Ok(InputMode::Pane),
"tab" | "Tab" => Ok(InputMode::Tab),
"search" | "Search" => Ok(InputMode::Search),
"scroll" | "Scroll" => Ok(InputMode::Scroll),
"renametab" | "RenameTab" => Ok(InputMode::RenameTab),
"renamepane" | "RenamePane" => Ok(InputMode::RenamePane),
"session" | "Session" => Ok(InputMode::Session),
"move" | "Move" => Ok(InputMode::Move),
"prompt" | "Prompt" => Ok(InputMode::Prompt),
"tmux" | "Tmux" => Ok(InputMode::Tmux),
"entersearch" | "Entersearch" | "EnterSearch" => Ok(InputMode::EnterSearch),
e => Err(ConversionError::UnknownInputMode(e.into())),
}
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum PaletteSource {
Default,
Xresources,
}
impl Default for PaletteSource {
fn default() -> PaletteSource {
PaletteSource::Default
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
pub struct Palette {
pub source: PaletteSource,
pub theme_hue: ThemeHue,
pub fg: PaletteColor,
pub bg: PaletteColor,
pub black: PaletteColor,
pub red: PaletteColor,
pub green: PaletteColor,
pub yellow: PaletteColor,
pub blue: PaletteColor,
pub magenta: PaletteColor,
pub cyan: PaletteColor,
pub white: PaletteColor,
pub orange: PaletteColor,
pub gray: PaletteColor,
pub purple: PaletteColor,
pub gold: PaletteColor,
pub silver: PaletteColor,
pub pink: PaletteColor,
pub brown: PaletteColor,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
pub struct Style {
pub colors: Palette,
pub rounded_corners: bool,
}
// FIXME: Poor devs hashtable since HashTable can't derive `Default`...
pub type KeybindsVec = Vec<(InputMode, Vec<(Key, Vec<Action>)>)>;
/// Represents the contents of the help message that is printed in the status bar,
/// which indicates the current [`InputMode`] and what the keybinds for that mode
/// are. Related to the default `status-bar` plugin.
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ModeInfo {
pub mode: InputMode,
pub keybinds: KeybindsVec,
pub style: Style,
pub capabilities: PluginCapabilities,
pub session_name: Option<String>,
}
impl ModeInfo {
pub fn get_mode_keybinds(&self) -> Vec<(Key, Vec<Action>)> {
self.get_keybinds_for_mode(self.mode)
}
pub fn get_keybinds_for_mode(&self, mode: InputMode) -> Vec<(Key, Vec<Action>)> {
for (vec_mode, map) in &self.keybinds {
if mode == *vec_mode {
return map.to_vec();
}
}
vec![]
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct TabInfo {
/* subset of fields to publish to plugins */
pub position: usize,
pub name: String,
pub active: bool,
pub panes_to_hide: usize,
pub is_fullscreen_active: bool,
pub is_sync_panes_active: bool,
pub are_floating_panes_visible: bool,
pub other_focused_clients: Vec<ClientId>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct PluginIds {
pub plugin_id: u32,
pub zellij_pid: u32,
}
/// Tag used to identify the plugin in layout and config yaml files
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, PartialOrd, Ord)]
pub struct PluginTag(String);
impl PluginTag {
pub fn new(url: impl Into<String>) -> Self {
PluginTag(url.into())
}
}
impl From<PluginTag> for String {
fn from(tag: PluginTag) -> Self {
tag.0
}
}
impl fmt::Display for PluginTag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct PluginCapabilities {
pub arrow_fonts: bool,
}
impl Default for PluginCapabilities {
fn default() -> PluginCapabilities {
PluginCapabilities { arrow_fonts: true }
}
}
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
pub enum CopyDestination {
Command,
Primary,
System,
}