mirror of https://github.com/helix-editor/helix
Merge branch 'master' of github.com:helix-editor/helix into line_ending_detection
Rebasing was making me manually fix conflicts on every commit, so merging instead.pull/224/head
commit
e686c3e462
@ -0,0 +1,4 @@
|
||||
---
|
||||
name: Blank Issue
|
||||
about: Create a blank issue.
|
||||
---
|
@ -0,0 +1,13 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest a new feature or improvement
|
||||
title: ''
|
||||
labels: C-enchancement
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
<!-- Your feature may already be reported!
|
||||
Please search on the issue tracker before creating one. -->
|
||||
|
||||
#### Describe your feature request
|
||||
|
@ -0,0 +1,94 @@
|
||||
# Themes
|
||||
|
||||
First you'll need to place selected themes in your `themes` directory (i.e `~/.config/helix/themes`), the directory might have to be created beforehand.
|
||||
|
||||
To use a custom theme add `theme = <name>` to your [`config.toml`](./configuration.md) or override it during runtime using `:theme <name>`.
|
||||
|
||||
The default theme.toml can be found [here](https://github.com/helix-editor/helix/blob/master/theme.toml), and user submitted themes [here](https://github.com/helix-editor/helix/blob/master/runtime/themes).
|
||||
|
||||
## Creating a theme
|
||||
|
||||
First create a file with the name of your theme as file name (i.e `mytheme.toml`) and place it in your `themes` directory (i.e `~/.config/helix/themes`).
|
||||
|
||||
Each line in the theme file is specified as below:
|
||||
|
||||
```toml
|
||||
key = { fg = "#ffffff", bg = "#000000", modifiers = ["bold", "italic"] }
|
||||
```
|
||||
|
||||
where `key` represents what you want to style, `fg` specifies the foreground color, `bg` the background color, and `modifiers` is a list of style modifiers. `bg` and `modifiers` can be omitted to defer to the defaults.
|
||||
|
||||
To specify only the foreground color:
|
||||
|
||||
```toml
|
||||
key = "#ffffff"
|
||||
```
|
||||
|
||||
if the key contains a dot `'.'`, it must be quoted to prevent it being parsed as a [dotted key](https://toml.io/en/v1.0.0#keys).
|
||||
|
||||
```toml
|
||||
"key.key" = "#ffffff"
|
||||
```
|
||||
|
||||
Possible modifiers:
|
||||
|
||||
| Modifier |
|
||||
| --- |
|
||||
| `bold` |
|
||||
| `dim` |
|
||||
| `italic` |
|
||||
| `underlined` |
|
||||
| `slow\_blink` |
|
||||
| `rapid\_blink` |
|
||||
| `reversed` |
|
||||
| `hidden` |
|
||||
| `crossed\_out` |
|
||||
|
||||
Possible keys:
|
||||
|
||||
| Key | Notes |
|
||||
| --- | --- |
|
||||
| `attribute` | |
|
||||
| `keyword` | |
|
||||
| `keyword.directive` | Preprocessor directives (\#if in C) |
|
||||
| `namespace` | |
|
||||
| `punctuation` | |
|
||||
| `punctuation.delimiter` | |
|
||||
| `operator` | |
|
||||
| `special` | |
|
||||
| `property` | |
|
||||
| `variable` | |
|
||||
| `variable.parameter` | |
|
||||
| `type` | |
|
||||
| `type.builtin` | |
|
||||
| `constructor` | |
|
||||
| `function` | |
|
||||
| `function.macro` | |
|
||||
| `function.builtin` | |
|
||||
| `comment` | |
|
||||
| `variable.builtin` | |
|
||||
| `constant` | |
|
||||
| `constant.builtin` | |
|
||||
| `string` | |
|
||||
| `number` | |
|
||||
| `escape` | Escaped characters |
|
||||
| `label` | For lifetimes |
|
||||
| `module` | |
|
||||
| `ui.background` | |
|
||||
| `ui.linenr` | |
|
||||
| `ui.statusline` | |
|
||||
| `ui.popup` | |
|
||||
| `ui.window` | |
|
||||
| `ui.help` | |
|
||||
| `ui.text` | |
|
||||
| `ui.text.focus` | |
|
||||
| `ui.menu.selected` | |
|
||||
| `ui.selection` | For selections in the editing area |
|
||||
| `warning` | LSP warning |
|
||||
| `error` | LSP error |
|
||||
| `info` | LSP info |
|
||||
| `hint` | LSP hint |
|
||||
|
||||
These keys match [tree-sitter scopes](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#theme). We half-follow the common scopes from [macromates language grammars](https://macromates.com/manual/en/language_grammars) with some differences.
|
||||
|
||||
For a given highlight produced, styling will be determined based on the longest matching theme key. So it's enough to provide function to highlight `function.macro` and `function.builtin` as well, but you can use more specific scopes to highlight specific cases differently.
|
@ -0,0 +1 @@
|
||||
../runtime/themes
|
@ -1,63 +1,55 @@
|
||||
use serde::Deserialize;
|
||||
use anyhow::{Error, Result};
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
use crate::commands::Command;
|
||||
use crate::keymap::Keymaps;
|
||||
use serde::{de::Error as SerdeError, Deserialize, Serialize};
|
||||
|
||||
use crate::keymap::{parse_keymaps, Keymaps};
|
||||
|
||||
#[derive(Debug, PartialEq, Deserialize)]
|
||||
pub struct GlobalConfig {
|
||||
pub theme: Option<String>,
|
||||
pub lsp_progress: bool,
|
||||
}
|
||||
|
||||
impl Default for GlobalConfig {
|
||||
fn default() -> Self {
|
||||
Self { lsp_progress: true }
|
||||
Self {
|
||||
lsp_progress: true,
|
||||
theme: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Deserialize)]
|
||||
#[serde(default)]
|
||||
#[derive(Default)]
|
||||
pub struct Config {
|
||||
pub global: GlobalConfig,
|
||||
pub keys: Keymaps,
|
||||
pub keymaps: Keymaps,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_keymaps_config_file() {
|
||||
use helix_core::hashmap;
|
||||
use helix_view::document::Mode;
|
||||
use helix_view::input::{KeyCode, KeyEvent, KeyModifiers};
|
||||
|
||||
let sample_keymaps = r#"
|
||||
[keys.insert]
|
||||
y = "move_line_down"
|
||||
S-C-a = "delete_selection"
|
||||
|
||||
[keys.normal]
|
||||
A-F12 = "move_next_word_end"
|
||||
"#;
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
struct TomlConfig {
|
||||
theme: Option<String>,
|
||||
lsp_progress: Option<bool>,
|
||||
keys: Option<HashMap<String, HashMap<String, String>>>,
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
toml::from_str::<Config>(sample_keymaps).unwrap(),
|
||||
Config {
|
||||
global: Default::default(),
|
||||
keys: Keymaps(hashmap! {
|
||||
Mode::Insert => hashmap! {
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('y'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
} => Command::move_line_down,
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('a'),
|
||||
modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL,
|
||||
} => Command::delete_selection,
|
||||
},
|
||||
Mode::Normal => hashmap! {
|
||||
KeyEvent {
|
||||
code: KeyCode::F(12),
|
||||
modifiers: KeyModifiers::ALT,
|
||||
} => Command::move_next_word_end,
|
||||
impl<'de> Deserialize<'de> for Config {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let config = TomlConfig::deserialize(deserializer)?;
|
||||
Ok(Self {
|
||||
global: GlobalConfig {
|
||||
lsp_progress: config.lsp_progress.unwrap_or(true),
|
||||
theme: config.theme,
|
||||
},
|
||||
keymaps: config
|
||||
.keys
|
||||
.map(|r| parse_keymaps(&r))
|
||||
.transpose()
|
||||
.map_err(|e| D::Error::custom(format!("Error deserializing keymap: {}", e)))?
|
||||
.unwrap_or_else(Keymaps::default),
|
||||
})
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,193 @@
|
||||
// Implementation reference: https://github.com/neovim/neovim/blob/f2906a4669a2eef6d7bf86a29648793d63c98949/runtime/autoload/provider/clipboard.vim#L68-L152
|
||||
|
||||
use anyhow::Result;
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub trait ClipboardProvider: std::fmt::Debug {
|
||||
fn name(&self) -> Cow<str>;
|
||||
fn get_contents(&self) -> Result<String>;
|
||||
fn set_contents(&self, contents: String) -> Result<()>;
|
||||
}
|
||||
|
||||
macro_rules! command_provider {
|
||||
(paste => $get_prg:literal $( , $get_arg:literal )* ; copy => $set_prg:literal $( , $set_arg:literal )* ; ) => {{
|
||||
Box::new(provider::CommandProvider {
|
||||
get_cmd: provider::CommandConfig {
|
||||
prg: $get_prg,
|
||||
args: &[ $( $get_arg ),* ],
|
||||
},
|
||||
set_cmd: provider::CommandConfig {
|
||||
prg: $set_prg,
|
||||
args: &[ $( $set_arg ),* ],
|
||||
},
|
||||
})
|
||||
}};
|
||||
}
|
||||
|
||||
pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
|
||||
// TODO: support for user-defined provider, probably when we have plugin support by setting a
|
||||
// variable?
|
||||
|
||||
if exists("pbcopy") && exists("pbpaste") {
|
||||
command_provider! {
|
||||
paste => "pbpaste";
|
||||
copy => "pbcopy";
|
||||
}
|
||||
} else if env_var_is_set("WAYLAND_DISPLAY") && exists("wl-copy") && exists("wl-paste") {
|
||||
command_provider! {
|
||||
paste => "wl-paste", "--no-newline";
|
||||
copy => "wl-copy", "--foreground", "--type", "text/plain";
|
||||
}
|
||||
} else if env_var_is_set("DISPLAY") && exists("xclip") {
|
||||
command_provider! {
|
||||
paste => "xclip", "-o", "-selection", "clipboard";
|
||||
copy => "xclip", "-i", "-selection", "clipboard";
|
||||
}
|
||||
} else if env_var_is_set("DISPLAY") && exists("xsel") && is_exit_success("xsel", &["-o", "-b"])
|
||||
{
|
||||
// FIXME: check performance of is_exit_success
|
||||
command_provider! {
|
||||
paste => "xsel", "-o", "-b";
|
||||
copy => "xsel", "--nodetach", "-i", "-b";
|
||||
}
|
||||
} else if exists("lemonade") {
|
||||
command_provider! {
|
||||
paste => "lemonade", "paste";
|
||||
copy => "lemonade", "copy";
|
||||
}
|
||||
} else if exists("doitclient") {
|
||||
command_provider! {
|
||||
paste => "doitclient", "wclip", "-r";
|
||||
copy => "doitclient", "wclip";
|
||||
}
|
||||
} else if exists("win32yank.exe") {
|
||||
// FIXME: does it work within WSL?
|
||||
command_provider! {
|
||||
paste => "win32yank.exe", "-o", "--lf";
|
||||
copy => "win32yank.exe", "-i", "--crlf";
|
||||
}
|
||||
} else if exists("termux-clipboard-set") && exists("termux-clipboard-get") {
|
||||
command_provider! {
|
||||
paste => "termux-clipboard-get";
|
||||
copy => "termux-clipboard-set";
|
||||
}
|
||||
} else if env_var_is_set("TMUX") && exists("tmux") {
|
||||
command_provider! {
|
||||
paste => "tmux", "save-buffer", "-";
|
||||
copy => "tmux", "load-buffer", "-";
|
||||
}
|
||||
} else {
|
||||
Box::new(provider::NopProvider)
|
||||
}
|
||||
}
|
||||
|
||||
fn exists(executable_name: &str) -> bool {
|
||||
which::which(executable_name).is_ok()
|
||||
}
|
||||
|
||||
fn env_var_is_set(env_var_name: &str) -> bool {
|
||||
std::env::var_os(env_var_name).is_some()
|
||||
}
|
||||
|
||||
fn is_exit_success(program: &str, args: &[&str]) -> bool {
|
||||
std::process::Command::new(program)
|
||||
.args(args)
|
||||
.output()
|
||||
.ok()
|
||||
.and_then(|out| out.status.success().then(|| ())) // TODO: use then_some when stabilized
|
||||
.is_some()
|
||||
}
|
||||
|
||||
mod provider {
|
||||
use super::ClipboardProvider;
|
||||
use anyhow::{bail, Context as _, Result};
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NopProvider;
|
||||
|
||||
impl ClipboardProvider for NopProvider {
|
||||
fn name(&self) -> Cow<str> {
|
||||
Cow::Borrowed("none")
|
||||
}
|
||||
|
||||
fn get_contents(&self) -> Result<String> {
|
||||
Ok(String::new())
|
||||
}
|
||||
|
||||
fn set_contents(&self, _: String) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CommandConfig {
|
||||
pub prg: &'static str,
|
||||
pub args: &'static [&'static str],
|
||||
}
|
||||
|
||||
impl CommandConfig {
|
||||
fn execute(&self, input: Option<&str>, pipe_output: bool) -> Result<Option<String>> {
|
||||
use std::io::Write;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
let stdin = input.map(|_| Stdio::piped()).unwrap_or_else(Stdio::null);
|
||||
let stdout = pipe_output.then(Stdio::piped).unwrap_or_else(Stdio::null);
|
||||
|
||||
let mut child = Command::new(self.prg)
|
||||
.args(self.args)
|
||||
.stdin(stdin)
|
||||
.stdout(stdout)
|
||||
.stderr(Stdio::null())
|
||||
.spawn()?;
|
||||
|
||||
if let Some(input) = input {
|
||||
let mut stdin = child.stdin.take().context("stdin is missing")?;
|
||||
stdin
|
||||
.write_all(input.as_bytes())
|
||||
.context("couldn't write in stdin")?;
|
||||
}
|
||||
|
||||
// TODO: add timer?
|
||||
let output = child.wait_with_output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
bail!("clipboard provider {} failed", self.prg);
|
||||
}
|
||||
|
||||
if pipe_output {
|
||||
Ok(Some(String::from_utf8(output.stdout)?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CommandProvider {
|
||||
pub get_cmd: CommandConfig,
|
||||
pub set_cmd: CommandConfig,
|
||||
}
|
||||
|
||||
impl ClipboardProvider for CommandProvider {
|
||||
fn name(&self) -> Cow<str> {
|
||||
if self.get_cmd.prg != self.set_cmd.prg {
|
||||
Cow::Owned(format!("{}+{}", self.get_cmd.prg, self.set_cmd.prg))
|
||||
} else {
|
||||
Cow::Borrowed(self.get_cmd.prg)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_contents(&self) -> Result<String> {
|
||||
let output = self
|
||||
.get_cmd
|
||||
.execute(None, true)?
|
||||
.context("output is missing")?;
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn set_contents(&self, value: String) -> Result<()> {
|
||||
self.set_cmd.execute(Some(&value), false).map(|_| ())
|
||||
}
|
||||
}
|
||||
}
|
@ -1,226 +0,0 @@
|
||||
//! Input event handling, currently backed by crossterm.
|
||||
use anyhow::{anyhow, Error};
|
||||
use crossterm::event;
|
||||
use serde::de::{self, Deserialize, Deserializer};
|
||||
use std::fmt;
|
||||
|
||||
pub use crossterm::event::{KeyCode, KeyModifiers};
|
||||
|
||||
/// Represents a key event.
|
||||
// We use a newtype here because we want to customize Deserialize and Display.
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Copy, Hash)]
|
||||
pub struct KeyEvent {
|
||||
pub code: KeyCode,
|
||||
pub modifiers: KeyModifiers,
|
||||
}
|
||||
|
||||
impl fmt::Display for KeyEvent {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_fmt(format_args!(
|
||||
"{}{}{}",
|
||||
if self.modifiers.contains(KeyModifiers::SHIFT) {
|
||||
"S-"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
if self.modifiers.contains(KeyModifiers::ALT) {
|
||||
"A-"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
if self.modifiers.contains(KeyModifiers::CONTROL) {
|
||||
"C-"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
))?;
|
||||
match self.code {
|
||||
KeyCode::Backspace => f.write_str("backspace")?,
|
||||
KeyCode::Enter => f.write_str("ret")?,
|
||||
KeyCode::Left => f.write_str("left")?,
|
||||
KeyCode::Right => f.write_str("right")?,
|
||||
KeyCode::Up => f.write_str("up")?,
|
||||
KeyCode::Down => f.write_str("down")?,
|
||||
KeyCode::Home => f.write_str("home")?,
|
||||
KeyCode::End => f.write_str("end")?,
|
||||
KeyCode::PageUp => f.write_str("pageup")?,
|
||||
KeyCode::PageDown => f.write_str("pagedown")?,
|
||||
KeyCode::Tab => f.write_str("tab")?,
|
||||
KeyCode::BackTab => f.write_str("backtab")?,
|
||||
KeyCode::Delete => f.write_str("del")?,
|
||||
KeyCode::Insert => f.write_str("ins")?,
|
||||
KeyCode::Null => f.write_str("null")?,
|
||||
KeyCode::Esc => f.write_str("esc")?,
|
||||
KeyCode::Char('<') => f.write_str("lt")?,
|
||||
KeyCode::Char('>') => f.write_str("gt")?,
|
||||
KeyCode::Char('+') => f.write_str("plus")?,
|
||||
KeyCode::Char('-') => f.write_str("minus")?,
|
||||
KeyCode::Char(';') => f.write_str("semicolon")?,
|
||||
KeyCode::Char('%') => f.write_str("percent")?,
|
||||
KeyCode::F(i) => f.write_fmt(format_args!("F{}", i))?,
|
||||
KeyCode::Char(c) => f.write_fmt(format_args!("{}", c))?,
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for KeyEvent {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut tokens: Vec<_> = s.split('-').collect();
|
||||
let code = match tokens.pop().ok_or_else(|| anyhow!("Missing key code"))? {
|
||||
"backspace" => KeyCode::Backspace,
|
||||
"space" => KeyCode::Char(' '),
|
||||
"ret" => KeyCode::Enter,
|
||||
"lt" => KeyCode::Char('<'),
|
||||
"gt" => KeyCode::Char('>'),
|
||||
"plus" => KeyCode::Char('+'),
|
||||
"minus" => KeyCode::Char('-'),
|
||||
"semicolon" => KeyCode::Char(';'),
|
||||
"percent" => KeyCode::Char('%'),
|
||||
"left" => KeyCode::Left,
|
||||
"right" => KeyCode::Right,
|
||||
"up" => KeyCode::Down,
|
||||
"home" => KeyCode::Home,
|
||||
"end" => KeyCode::End,
|
||||
"pageup" => KeyCode::PageUp,
|
||||
"pagedown" => KeyCode::PageDown,
|
||||
"tab" => KeyCode::Tab,
|
||||
"backtab" => KeyCode::BackTab,
|
||||
"del" => KeyCode::Delete,
|
||||
"ins" => KeyCode::Insert,
|
||||
"null" => KeyCode::Null,
|
||||
"esc" => KeyCode::Esc,
|
||||
single if single.len() == 1 => KeyCode::Char(single.chars().next().unwrap()),
|
||||
function if function.len() > 1 && function.starts_with('F') => {
|
||||
let function: String = function.chars().skip(1).collect();
|
||||
let function = str::parse::<u8>(&function)?;
|
||||
(function > 0 && function < 13)
|
||||
.then(|| KeyCode::F(function))
|
||||
.ok_or_else(|| anyhow!("Invalid function key '{}'", function))?
|
||||
}
|
||||
invalid => return Err(anyhow!("Invalid key code '{}'", invalid)),
|
||||
};
|
||||
|
||||
let mut modifiers = KeyModifiers::empty();
|
||||
for token in tokens {
|
||||
let flag = match token {
|
||||
"S" => KeyModifiers::SHIFT,
|
||||
"A" => KeyModifiers::ALT,
|
||||
"C" => KeyModifiers::CONTROL,
|
||||
_ => return Err(anyhow!("Invalid key modifier '{}-'", token)),
|
||||
};
|
||||
|
||||
if modifiers.contains(flag) {
|
||||
return Err(anyhow!("Repeated key modifier '{}-'", token));
|
||||
}
|
||||
modifiers.insert(flag);
|
||||
}
|
||||
|
||||
Ok(KeyEvent { code, modifiers })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for KeyEvent {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
s.parse().map_err(de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<event::KeyEvent> for KeyEvent {
|
||||
fn from(event::KeyEvent { code, modifiers }: event::KeyEvent) -> KeyEvent {
|
||||
KeyEvent { code, modifiers }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parsing_unmodified_keys() {
|
||||
assert_eq!(
|
||||
str::parse::<KeyEvent>("backspace").unwrap(),
|
||||
KeyEvent {
|
||||
code: KeyCode::Backspace,
|
||||
modifiers: KeyModifiers::NONE
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
str::parse::<KeyEvent>("left").unwrap(),
|
||||
KeyEvent {
|
||||
code: KeyCode::Left,
|
||||
modifiers: KeyModifiers::NONE
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
str::parse::<KeyEvent>(",").unwrap(),
|
||||
KeyEvent {
|
||||
code: KeyCode::Char(','),
|
||||
modifiers: KeyModifiers::NONE
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
str::parse::<KeyEvent>("w").unwrap(),
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('w'),
|
||||
modifiers: KeyModifiers::NONE
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
str::parse::<KeyEvent>("F12").unwrap(),
|
||||
KeyEvent {
|
||||
code: KeyCode::F(12),
|
||||
modifiers: KeyModifiers::NONE
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_modified_keys() {
|
||||
assert_eq!(
|
||||
str::parse::<KeyEvent>("S-minus").unwrap(),
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('-'),
|
||||
modifiers: KeyModifiers::SHIFT
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
str::parse::<KeyEvent>("C-A-S-F12").unwrap(),
|
||||
KeyEvent {
|
||||
code: KeyCode::F(12),
|
||||
modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL | KeyModifiers::ALT
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
str::parse::<KeyEvent>("S-C-2").unwrap(),
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('2'),
|
||||
modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_nonsensical_keys_fails() {
|
||||
assert!(str::parse::<KeyEvent>("F13").is_err());
|
||||
assert!(str::parse::<KeyEvent>("F0").is_err());
|
||||
assert!(str::parse::<KeyEvent>("aaa").is_err());
|
||||
assert!(str::parse::<KeyEvent>("S-S-a").is_err());
|
||||
assert!(str::parse::<KeyEvent>("C-A-S-C-1").is_err());
|
||||
assert!(str::parse::<KeyEvent>("FU").is_err());
|
||||
assert!(str::parse::<KeyEvent>("123").is_err());
|
||||
assert!(str::parse::<KeyEvent>("S--").is_err());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue