mirror of
https://github.com/helix-editor/helix.git
synced 2025-10-06 00:13:28 +02:00
Compare commits
1 Commits
d0218f7e78
...
crossterm-
Author | SHA1 | Date | |
---|---|---|---|
|
8e5455b28f |
96
Cargo.lock
generated
96
Cargo.lock
generated
@@ -248,6 +248,32 @@ version = "0.8.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.28.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crossterm_winapi",
|
||||
"futures-core",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"rustix 0.38.44",
|
||||
"signal-hook",
|
||||
"signal-hook-mio",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm_winapi"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
@@ -874,7 +900,7 @@ dependencies = [
|
||||
"itoa",
|
||||
"libc",
|
||||
"memmap2",
|
||||
"rustix",
|
||||
"rustix 1.0.8",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
]
|
||||
@@ -1500,7 +1526,7 @@ dependencies = [
|
||||
"regex-automata",
|
||||
"regex-cursor",
|
||||
"ropey",
|
||||
"rustix",
|
||||
"rustix 1.0.8",
|
||||
"tempfile",
|
||||
"unicode-segmentation",
|
||||
"which",
|
||||
@@ -1515,6 +1541,7 @@ dependencies = [
|
||||
"arc-swap",
|
||||
"chrono",
|
||||
"content_inspector",
|
||||
"crossterm",
|
||||
"dashmap",
|
||||
"fern",
|
||||
"futures-util",
|
||||
@@ -1560,6 +1587,7 @@ version = "25.7.1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cassowary",
|
||||
"crossterm",
|
||||
"helix-core",
|
||||
"helix-view",
|
||||
"log",
|
||||
@@ -1594,6 +1622,7 @@ dependencies = [
|
||||
"bitflags",
|
||||
"chardetng",
|
||||
"clipboard-win",
|
||||
"crossterm",
|
||||
"futures-util",
|
||||
"helix-core",
|
||||
"helix-dap",
|
||||
@@ -1608,7 +1637,7 @@ dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"rustix",
|
||||
"rustix 1.0.8",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"slotmap",
|
||||
@@ -1980,6 +2009,12 @@ dependencies = [
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.9.2"
|
||||
@@ -2060,6 +2095,7 @@ checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
@@ -2344,6 +2380,19 @@ version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.15",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.8"
|
||||
@@ -2353,7 +2402,7 @@ dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"linux-raw-sys 0.9.2",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
@@ -2462,6 +2511,17 @@ dependencies = [
|
||||
"signal-hook-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-mio"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mio",
|
||||
"signal-hook",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.2"
|
||||
@@ -2583,7 +2643,7 @@ dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.3.1",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"rustix 1.0.8",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
@@ -2596,7 +2656,7 @@ dependencies = [
|
||||
"bitflags",
|
||||
"futures-core",
|
||||
"parking_lot",
|
||||
"rustix",
|
||||
"rustix 1.0.8",
|
||||
"signal-hook",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
@@ -2969,10 +3029,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d"
|
||||
dependencies = [
|
||||
"env_home",
|
||||
"rustix",
|
||||
"rustix 1.0.8",
|
||||
"winsafe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.9"
|
||||
@@ -2982,6 +3058,12 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
|
@@ -54,7 +54,7 @@ anyhow = "1"
|
||||
once_cell = "1.21"
|
||||
|
||||
tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
|
||||
tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["termina"] }
|
||||
tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["termina", "crossterm"] }
|
||||
termina = { workspace = true, features = ["event-stream"] }
|
||||
signal-hook = "0.3"
|
||||
tokio-stream = "0.1"
|
||||
@@ -93,6 +93,9 @@ grep-searcher = "0.1.14"
|
||||
|
||||
dashmap = "6.0"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
crossterm = { version = "0.28", features = ["event-stream"] }
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100
|
||||
signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }
|
||||
libc = "0.2.175"
|
||||
|
@@ -36,6 +36,7 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
#[cfg_attr(windows, allow(unused_imports))]
|
||||
use anyhow::{Context, Error};
|
||||
|
||||
#[cfg(not(windows))]
|
||||
@@ -43,18 +44,27 @@ use {signal_hook::consts::signal, signal_hook_tokio::Signals};
|
||||
#[cfg(windows)]
|
||||
type Signals = futures_util::stream::Empty<()>;
|
||||
|
||||
#[cfg(not(feature = "integration"))]
|
||||
#[cfg(all(not(windows), not(feature = "integration")))]
|
||||
use tui::backend::TerminaBackend;
|
||||
|
||||
#[cfg(all(windows, not(feature = "integration")))]
|
||||
use tui::backend::CrosstermBackend;
|
||||
|
||||
#[cfg(feature = "integration")]
|
||||
use tui::backend::TestBackend;
|
||||
|
||||
#[cfg(not(feature = "integration"))]
|
||||
#[cfg(all(not(windows), not(feature = "integration")))]
|
||||
type TerminalBackend = TerminaBackend;
|
||||
|
||||
#[cfg(all(windows, not(feature = "integration")))]
|
||||
type TerminalBackend = CrosstermBackend<std::io::Stdout>;
|
||||
#[cfg(feature = "integration")]
|
||||
type TerminalBackend = TestBackend;
|
||||
|
||||
#[cfg(not(windows))]
|
||||
type TerminalEvent = termina::Event;
|
||||
#[cfg(windows)]
|
||||
type TerminalEvent = crossterm::event::Event;
|
||||
|
||||
type Terminal = tui::terminal::Terminal<TerminalBackend>;
|
||||
|
||||
pub struct Application {
|
||||
@@ -102,9 +112,11 @@ impl Application {
|
||||
theme_parent_dirs.extend(helix_loader::runtime_dirs().iter().cloned());
|
||||
let theme_loader = theme::Loader::new(&theme_parent_dirs);
|
||||
|
||||
#[cfg(not(feature = "integration"))]
|
||||
#[cfg(all(not(windows), not(feature = "integration")))]
|
||||
let backend = TerminaBackend::new((&config.editor).into())
|
||||
.context("failed to create terminal backend")?;
|
||||
#[cfg(all(windows, not(feature = "integration")))]
|
||||
let backend = CrosstermBackend::new(std::io::stdout(), (&config.editor).into());
|
||||
|
||||
#[cfg(feature = "integration")]
|
||||
let backend = TestBackend::new(120, 150);
|
||||
@@ -286,7 +298,7 @@ impl Application {
|
||||
|
||||
pub async fn event_loop<S>(&mut self, input_stream: &mut S)
|
||||
where
|
||||
S: Stream<Item = std::io::Result<termina::Event>> + Unpin,
|
||||
S: Stream<Item = std::io::Result<TerminalEvent>> + Unpin,
|
||||
{
|
||||
self.render().await;
|
||||
|
||||
@@ -299,7 +311,7 @@ impl Application {
|
||||
|
||||
pub async fn event_loop_until_idle<S>(&mut self, input_stream: &mut S) -> bool
|
||||
where
|
||||
S: Stream<Item = std::io::Result<termina::Event>> + Unpin,
|
||||
S: Stream<Item = std::io::Result<TerminalEvent>> + Unpin,
|
||||
{
|
||||
loop {
|
||||
if self.editor.should_close() {
|
||||
@@ -659,7 +671,7 @@ impl Application {
|
||||
false
|
||||
}
|
||||
|
||||
pub async fn handle_terminal_events(&mut self, event: std::io::Result<termina::Event>) {
|
||||
pub async fn handle_terminal_events(&mut self, event: std::io::Result<TerminalEvent>) {
|
||||
let mut cx = crate::compositor::Context {
|
||||
editor: &mut self.editor,
|
||||
jobs: &mut self.jobs,
|
||||
@@ -667,6 +679,7 @@ impl Application {
|
||||
};
|
||||
// Handle key events
|
||||
let should_redraw = match event.unwrap() {
|
||||
#[cfg(not(windows))]
|
||||
termina::Event::WindowResized(termina::WindowSize { rows, cols, .. }) => {
|
||||
self.terminal
|
||||
.resize(Rect::new(0, 0, cols, rows))
|
||||
@@ -679,11 +692,31 @@ impl Application {
|
||||
self.compositor
|
||||
.handle_event(&Event::Resize(cols, rows), &mut cx)
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
// Ignore keyboard release events.
|
||||
termina::Event::Key(termina::event::KeyEvent {
|
||||
kind: termina::event::KeyEventKind::Release,
|
||||
..
|
||||
}) => false,
|
||||
#[cfg(windows)]
|
||||
TerminalEvent::Resize(width, height) => {
|
||||
self.terminal
|
||||
.resize(Rect::new(0, 0, width, height))
|
||||
.expect("Unable to resize terminal");
|
||||
|
||||
let area = self.terminal.size().expect("couldn't get terminal size");
|
||||
|
||||
self.compositor.resize(area);
|
||||
|
||||
self.compositor
|
||||
.handle_event(&Event::Resize(width, height), &mut cx)
|
||||
}
|
||||
#[cfg(windows)]
|
||||
// Ignore keyboard release events.
|
||||
crossterm::event::Event::Key(crossterm::event::KeyEvent {
|
||||
kind: crossterm::event::KeyEventKind::Release,
|
||||
..
|
||||
}) => false,
|
||||
event => self.compositor.handle_event(&event.into(), &mut cx),
|
||||
};
|
||||
|
||||
@@ -1132,15 +1165,20 @@ impl Application {
|
||||
self.terminal.restore()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "integration"))]
|
||||
pub fn event_stream(&self) -> impl Stream<Item = std::io::Result<termina::Event>> + Unpin {
|
||||
#[cfg(all(not(feature = "integration"), not(windows)))]
|
||||
pub fn event_stream(&self) -> impl Stream<Item = std::io::Result<TerminalEvent>> + Unpin {
|
||||
use termina::Terminal as _;
|
||||
let reader = self.terminal.backend().terminal().event_reader();
|
||||
termina::EventStream::new(reader, |event| !event.is_escape())
|
||||
}
|
||||
|
||||
#[cfg(all(not(feature = "integration"), windows))]
|
||||
pub fn event_stream(&self) -> impl Stream<Item = std::io::Result<TerminalEvent>> + Unpin {
|
||||
crossterm::event::EventStream::new()
|
||||
}
|
||||
|
||||
#[cfg(feature = "integration")]
|
||||
pub fn event_stream(&self) -> impl Stream<Item = std::io::Result<termina::Event>> + Unpin {
|
||||
pub fn event_stream(&self) -> impl Stream<Item = std::io::Result<TerminalEvent>> + Unpin {
|
||||
use std::{
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
@@ -1150,7 +1188,7 @@ impl Application {
|
||||
pub struct DummyEventStream;
|
||||
|
||||
impl Stream for DummyEventStream {
|
||||
type Item = std::io::Result<termina::Event>;
|
||||
type Item = std::io::Result<TerminalEvent>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
Poll::Pending
|
||||
@@ -1162,7 +1200,7 @@ impl Application {
|
||||
|
||||
pub async fn run<S>(&mut self, input_stream: &mut S) -> Result<i32, Error>
|
||||
where
|
||||
S: Stream<Item = std::io::Result<termina::Event>> + Unpin,
|
||||
S: Stream<Item = std::io::Result<TerminalEvent>> + Unpin,
|
||||
{
|
||||
self.terminal.claim()?;
|
||||
|
||||
|
@@ -10,9 +10,13 @@ use helix_core::{diagnostic::Severity, test, Selection, Transaction};
|
||||
use helix_term::{application::Application, args::Args, config::Config, keymap::merge_keys};
|
||||
use helix_view::{current_ref, doc, editor::LspConfig, input::parse_macro, Editor};
|
||||
use tempfile::NamedTempFile;
|
||||
use termina::event::{Event, KeyEvent};
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
|
||||
#[cfg(windows)]
|
||||
use crossterm::event::{Event, KeyEvent};
|
||||
#[cfg(not(windows))]
|
||||
use termina::event::{Event, KeyEvent};
|
||||
|
||||
/// Specify how to set up the input text with line feeds
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum LineFeedHandling {
|
||||
|
@@ -12,7 +12,7 @@ repository.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["termina"]
|
||||
default = ["termina", "crossterm"]
|
||||
|
||||
[dependencies]
|
||||
helix-view = { path = "../helix-view", features = ["term"] }
|
||||
@@ -25,3 +25,6 @@ termina = { workspace = true, optional = true }
|
||||
termini = "1.0"
|
||||
once_cell = "1.21"
|
||||
log = "~0.4"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
crossterm = { version = "0.28", optional = true }
|
||||
|
450
helix-tui/src/backend/crossterm.rs
Normal file
450
helix-tui/src/backend/crossterm.rs
Normal file
@@ -0,0 +1,450 @@
|
||||
use crate::{backend::Backend, buffer::Cell, terminal::Config};
|
||||
use crossterm::{
|
||||
cursor::{Hide, MoveTo, SetCursorStyle, Show},
|
||||
event::{
|
||||
DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste,
|
||||
EnableFocusChange, EnableMouseCapture, KeyboardEnhancementFlags,
|
||||
PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags,
|
||||
},
|
||||
execute, queue,
|
||||
style::{
|
||||
Attribute as CAttribute, Color as CColor, Colors, Print, SetAttribute, SetBackgroundColor,
|
||||
SetColors, SetForegroundColor,
|
||||
},
|
||||
terminal::{self, Clear, ClearType},
|
||||
Command,
|
||||
};
|
||||
use helix_view::graphics::{Color, CursorKind, Modifier, Rect, UnderlineStyle};
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::{
|
||||
fmt,
|
||||
io::{self, Write},
|
||||
};
|
||||
use termini::TermInfo;
|
||||
|
||||
fn term_program() -> Option<String> {
|
||||
// Some terminals don't set $TERM_PROGRAM
|
||||
match std::env::var("TERM_PROGRAM") {
|
||||
Err(_) => std::env::var("TERM").ok(),
|
||||
Ok(term_program) => Some(term_program),
|
||||
}
|
||||
}
|
||||
fn vte_version() -> Option<usize> {
|
||||
std::env::var("VTE_VERSION").ok()?.parse().ok()
|
||||
}
|
||||
fn reset_cursor_approach(terminfo: TermInfo) -> String {
|
||||
let mut reset_str = "\x1B[0 q".to_string();
|
||||
|
||||
if let Some(termini::Value::Utf8String(se_str)) = terminfo.extended_cap("Se") {
|
||||
reset_str.push_str(se_str);
|
||||
};
|
||||
|
||||
reset_str.push_str(
|
||||
terminfo
|
||||
.utf8_string_cap(termini::StringCapability::CursorNormal)
|
||||
.unwrap_or(""),
|
||||
);
|
||||
|
||||
reset_str
|
||||
}
|
||||
|
||||
/// Describes terminal capabilities like extended underline, truecolor, etc.
|
||||
#[derive(Clone, Debug)]
|
||||
struct Capabilities {
|
||||
/// Support for undercurled, underdashed, etc.
|
||||
has_extended_underlines: bool,
|
||||
/// Support for resetting the cursor style back to normal.
|
||||
reset_cursor_command: String,
|
||||
}
|
||||
|
||||
impl Default for Capabilities {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
has_extended_underlines: false,
|
||||
reset_cursor_command: "\x1B[0 q".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Capabilities {
|
||||
/// Detect capabilities from the terminfo database located based
|
||||
/// on the $TERM environment variable. If detection fails, returns
|
||||
/// a default value where no capability is supported, or just undercurl
|
||||
/// if config.undercurl is set.
|
||||
pub fn from_env_or_default(config: &Config) -> Self {
|
||||
match termini::TermInfo::from_env() {
|
||||
Err(_) => Capabilities {
|
||||
has_extended_underlines: config.force_enable_extended_underlines,
|
||||
..Capabilities::default()
|
||||
},
|
||||
Ok(t) => Capabilities {
|
||||
// Smulx, VTE: https://unix.stackexchange.com/a/696253/246284
|
||||
// Su (used by kitty): https://sw.kovidgoyal.net/kitty/underlines
|
||||
// WezTerm supports underlines but a lot of distros don't properly install its terminfo
|
||||
has_extended_underlines: config.force_enable_extended_underlines
|
||||
|| t.extended_cap("Smulx").is_some()
|
||||
|| t.extended_cap("Su").is_some()
|
||||
|| vte_version() >= Some(5102)
|
||||
|| matches!(term_program().as_deref(), Some("WezTerm")),
|
||||
reset_cursor_command: reset_cursor_approach(t),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Terminal backend supporting a wide variety of terminals
|
||||
pub struct CrosstermBackend<W: Write> {
|
||||
buffer: W,
|
||||
config: Config,
|
||||
capabilities: Capabilities,
|
||||
supports_keyboard_enhancement_protocol: OnceCell<bool>,
|
||||
mouse_capture_enabled: bool,
|
||||
supports_bracketed_paste: bool,
|
||||
}
|
||||
|
||||
impl<W> CrosstermBackend<W>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
pub fn new(buffer: W, config: Config) -> CrosstermBackend<W> {
|
||||
// helix is not usable without colors, but crossterm will disable
|
||||
// them by default if NO_COLOR is set in the environment. Override
|
||||
// this behaviour.
|
||||
crossterm::style::force_color_output(true);
|
||||
CrosstermBackend {
|
||||
buffer,
|
||||
capabilities: Capabilities::from_env_or_default(&config),
|
||||
config,
|
||||
supports_keyboard_enhancement_protocol: OnceCell::new(),
|
||||
mouse_capture_enabled: false,
|
||||
supports_bracketed_paste: true,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn supports_keyboard_enhancement_protocol(&self) -> bool {
|
||||
*self.supports_keyboard_enhancement_protocol
|
||||
.get_or_init(|| {
|
||||
use std::time::Instant;
|
||||
|
||||
let now = Instant::now();
|
||||
let supported = matches!(terminal::supports_keyboard_enhancement(), Ok(true));
|
||||
log::debug!(
|
||||
"The keyboard enhancement protocol is {}supported in this terminal (checked in {:?})",
|
||||
if supported { "" } else { "not " },
|
||||
Instant::now().duration_since(now)
|
||||
);
|
||||
supported
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W> Write for CrosstermBackend<W>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.buffer.write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.buffer.flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl<W> Backend for CrosstermBackend<W>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
fn claim(&mut self) -> io::Result<()> {
|
||||
terminal::enable_raw_mode()?;
|
||||
execute!(
|
||||
self.buffer,
|
||||
terminal::EnterAlternateScreen,
|
||||
EnableFocusChange
|
||||
)?;
|
||||
match execute!(self.buffer, EnableBracketedPaste,) {
|
||||
Err(err) if err.kind() == io::ErrorKind::Unsupported => {
|
||||
log::warn!("Bracketed paste is not supported on this terminal.");
|
||||
self.supports_bracketed_paste = false;
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
Ok(_) => (),
|
||||
};
|
||||
execute!(self.buffer, terminal::Clear(terminal::ClearType::All))?;
|
||||
if self.config.enable_mouse_capture {
|
||||
execute!(self.buffer, EnableMouseCapture)?;
|
||||
self.mouse_capture_enabled = true;
|
||||
}
|
||||
if self.supports_keyboard_enhancement_protocol() {
|
||||
execute!(
|
||||
self.buffer,
|
||||
PushKeyboardEnhancementFlags(
|
||||
KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
|
||||
| KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS
|
||||
)
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reconfigure(&mut self, config: Config) -> io::Result<()> {
|
||||
if self.mouse_capture_enabled != config.enable_mouse_capture {
|
||||
if config.enable_mouse_capture {
|
||||
execute!(self.buffer, EnableMouseCapture)?;
|
||||
} else {
|
||||
execute!(self.buffer, DisableMouseCapture)?;
|
||||
}
|
||||
self.mouse_capture_enabled = config.enable_mouse_capture;
|
||||
}
|
||||
self.config = config;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn restore(&mut self) -> io::Result<()> {
|
||||
// reset cursor shape
|
||||
self.buffer
|
||||
.write_all(self.capabilities.reset_cursor_command.as_bytes())?;
|
||||
if self.config.enable_mouse_capture {
|
||||
execute!(self.buffer, DisableMouseCapture)?;
|
||||
}
|
||||
if self.supports_keyboard_enhancement_protocol() {
|
||||
execute!(self.buffer, PopKeyboardEnhancementFlags)?;
|
||||
}
|
||||
if self.supports_bracketed_paste {
|
||||
execute!(self.buffer, DisableBracketedPaste,)?;
|
||||
}
|
||||
execute!(
|
||||
self.buffer,
|
||||
DisableFocusChange,
|
||||
terminal::LeaveAlternateScreen
|
||||
)?;
|
||||
terminal::disable_raw_mode()
|
||||
}
|
||||
|
||||
fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
|
||||
where
|
||||
I: Iterator<Item = (u16, u16, &'a Cell)>,
|
||||
{
|
||||
let mut fg = Color::Reset;
|
||||
let mut bg = Color::Reset;
|
||||
let mut underline_color = Color::Reset;
|
||||
let mut underline_style = UnderlineStyle::Reset;
|
||||
let mut modifier = Modifier::empty();
|
||||
let mut last_pos: Option<(u16, u16)> = None;
|
||||
for (x, y, cell) in content {
|
||||
// Move the cursor if the previous location was not (x - 1, y)
|
||||
if !matches!(last_pos, Some(p) if x == p.0 + 1 && y == p.1) {
|
||||
queue!(self.buffer, MoveTo(x, y))?;
|
||||
}
|
||||
last_pos = Some((x, y));
|
||||
if cell.modifier != modifier {
|
||||
let diff = ModifierDiff {
|
||||
from: modifier,
|
||||
to: cell.modifier,
|
||||
};
|
||||
diff.queue(&mut self.buffer)?;
|
||||
modifier = cell.modifier;
|
||||
}
|
||||
if cell.fg != fg || cell.bg != bg {
|
||||
queue!(
|
||||
self.buffer,
|
||||
SetColors(Colors::new(cell.fg.into(), cell.bg.into()))
|
||||
)?;
|
||||
fg = cell.fg;
|
||||
bg = cell.bg;
|
||||
}
|
||||
|
||||
let mut new_underline_style = cell.underline_style;
|
||||
if self.capabilities.has_extended_underlines {
|
||||
if cell.underline_color != underline_color {
|
||||
let color = CColor::from(cell.underline_color);
|
||||
queue!(self.buffer, SetUnderlineColor(color))?;
|
||||
underline_color = cell.underline_color;
|
||||
}
|
||||
} else {
|
||||
match new_underline_style {
|
||||
UnderlineStyle::Reset | UnderlineStyle::Line => (),
|
||||
_ => new_underline_style = UnderlineStyle::Line,
|
||||
}
|
||||
}
|
||||
|
||||
if new_underline_style != underline_style {
|
||||
let attr = CAttribute::from(new_underline_style);
|
||||
queue!(self.buffer, SetAttribute(attr))?;
|
||||
underline_style = new_underline_style;
|
||||
}
|
||||
|
||||
queue!(self.buffer, Print(&cell.symbol))?;
|
||||
}
|
||||
|
||||
queue!(
|
||||
self.buffer,
|
||||
SetUnderlineColor(CColor::Reset),
|
||||
SetForegroundColor(CColor::Reset),
|
||||
SetBackgroundColor(CColor::Reset),
|
||||
SetAttribute(CAttribute::Reset)
|
||||
)
|
||||
}
|
||||
|
||||
fn hide_cursor(&mut self) -> io::Result<()> {
|
||||
execute!(self.buffer, Hide)
|
||||
}
|
||||
|
||||
fn show_cursor(&mut self, kind: CursorKind) -> io::Result<()> {
|
||||
let shape = match kind {
|
||||
CursorKind::Block => SetCursorStyle::SteadyBlock,
|
||||
CursorKind::Bar => SetCursorStyle::SteadyBar,
|
||||
CursorKind::Underline => SetCursorStyle::SteadyUnderScore,
|
||||
CursorKind::Hidden => unreachable!(),
|
||||
};
|
||||
execute!(self.buffer, Show, shape)
|
||||
}
|
||||
|
||||
fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
|
||||
execute!(self.buffer, MoveTo(x, y))
|
||||
}
|
||||
|
||||
fn clear(&mut self) -> io::Result<()> {
|
||||
execute!(self.buffer, Clear(ClearType::All))
|
||||
}
|
||||
|
||||
fn size(&self) -> io::Result<Rect> {
|
||||
let (width, height) =
|
||||
terminal::size().map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
|
||||
|
||||
Ok(Rect::new(0, 0, width, height))
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.buffer.flush()
|
||||
}
|
||||
|
||||
fn supports_true_color(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ModifierDiff {
|
||||
pub from: Modifier,
|
||||
pub to: Modifier,
|
||||
}
|
||||
|
||||
impl ModifierDiff {
|
||||
fn queue<W>(&self, mut w: W) -> io::Result<()>
|
||||
where
|
||||
W: io::Write,
|
||||
{
|
||||
//use crossterm::Attribute;
|
||||
let removed = self.from - self.to;
|
||||
if removed.contains(Modifier::REVERSED) {
|
||||
queue!(w, SetAttribute(CAttribute::NoReverse))?;
|
||||
}
|
||||
if removed.contains(Modifier::BOLD) {
|
||||
queue!(w, SetAttribute(CAttribute::NormalIntensity))?;
|
||||
if self.to.contains(Modifier::DIM) {
|
||||
queue!(w, SetAttribute(CAttribute::Dim))?;
|
||||
}
|
||||
}
|
||||
if removed.contains(Modifier::ITALIC) {
|
||||
queue!(w, SetAttribute(CAttribute::NoItalic))?;
|
||||
}
|
||||
if removed.contains(Modifier::DIM) {
|
||||
queue!(w, SetAttribute(CAttribute::NormalIntensity))?;
|
||||
}
|
||||
if removed.contains(Modifier::CROSSED_OUT) {
|
||||
queue!(w, SetAttribute(CAttribute::NotCrossedOut))?;
|
||||
}
|
||||
if removed.contains(Modifier::SLOW_BLINK) || removed.contains(Modifier::RAPID_BLINK) {
|
||||
queue!(w, SetAttribute(CAttribute::NoBlink))?;
|
||||
}
|
||||
if removed.contains(Modifier::HIDDEN) {
|
||||
queue!(w, SetAttribute(CAttribute::NoHidden))?;
|
||||
}
|
||||
|
||||
let added = self.to - self.from;
|
||||
if added.contains(Modifier::REVERSED) {
|
||||
queue!(w, SetAttribute(CAttribute::Reverse))?;
|
||||
}
|
||||
if added.contains(Modifier::BOLD) {
|
||||
queue!(w, SetAttribute(CAttribute::Bold))?;
|
||||
}
|
||||
if added.contains(Modifier::ITALIC) {
|
||||
queue!(w, SetAttribute(CAttribute::Italic))?;
|
||||
}
|
||||
if added.contains(Modifier::DIM) {
|
||||
queue!(w, SetAttribute(CAttribute::Dim))?;
|
||||
}
|
||||
if added.contains(Modifier::CROSSED_OUT) {
|
||||
queue!(w, SetAttribute(CAttribute::CrossedOut))?;
|
||||
}
|
||||
if added.contains(Modifier::SLOW_BLINK) {
|
||||
queue!(w, SetAttribute(CAttribute::SlowBlink))?;
|
||||
}
|
||||
if added.contains(Modifier::RAPID_BLINK) {
|
||||
queue!(w, SetAttribute(CAttribute::RapidBlink))?;
|
||||
}
|
||||
if added.contains(Modifier::HIDDEN) {
|
||||
queue!(w, SetAttribute(CAttribute::Hidden))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Crossterm uses semicolon as a separator for colors
|
||||
/// this is actually not spec compliant (although commonly supported)
|
||||
/// However the correct approach is to use colons as a separator.
|
||||
/// This usually doesn't make a difference for emulators that do support colored underlines.
|
||||
/// However terminals that do not support colored underlines will ignore underlines colors with colons
|
||||
/// while escape sequences with semicolons are always processed which leads to weird visual artifacts.
|
||||
/// See [this nvim issue](https://github.com/neovim/neovim/issues/9270) for details
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct SetUnderlineColor(pub CColor);
|
||||
|
||||
impl Command for SetUnderlineColor {
|
||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
||||
let color = self.0;
|
||||
|
||||
if color == CColor::Reset {
|
||||
write!(f, "\x1b[59m")?;
|
||||
return Ok(());
|
||||
}
|
||||
f.write_str("\x1b[58:")?;
|
||||
|
||||
let res = match color {
|
||||
CColor::Black => f.write_str("5:0"),
|
||||
CColor::DarkGrey => f.write_str("5:8"),
|
||||
CColor::Red => f.write_str("5:9"),
|
||||
CColor::DarkRed => f.write_str("5:1"),
|
||||
CColor::Green => f.write_str("5:10"),
|
||||
CColor::DarkGreen => f.write_str("5:2"),
|
||||
CColor::Yellow => f.write_str("5:11"),
|
||||
CColor::DarkYellow => f.write_str("5:3"),
|
||||
CColor::Blue => f.write_str("5:12"),
|
||||
CColor::DarkBlue => f.write_str("5:4"),
|
||||
CColor::Magenta => f.write_str("5:13"),
|
||||
CColor::DarkMagenta => f.write_str("5:5"),
|
||||
CColor::Cyan => f.write_str("5:14"),
|
||||
CColor::DarkCyan => f.write_str("5:6"),
|
||||
CColor::White => f.write_str("5:15"),
|
||||
CColor::Grey => f.write_str("5:7"),
|
||||
CColor::Rgb { r, g, b } => write!(f, "2::{}:{}:{}", r, g, b),
|
||||
CColor::AnsiValue(val) => write!(f, "5:{}", val),
|
||||
_ => Ok(()),
|
||||
};
|
||||
res?;
|
||||
write!(f, "m")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn execute_winapi(&self) -> io::Result<()> {
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"SetUnderlineColor not supported by winapi.",
|
||||
))
|
||||
}
|
||||
}
|
@@ -6,11 +6,16 @@ use crate::{buffer::Cell, terminal::Config};
|
||||
|
||||
use helix_view::graphics::{CursorKind, Rect};
|
||||
|
||||
#[cfg(feature = "termina")]
|
||||
#[cfg(all(feature = "termina", not(windows)))]
|
||||
mod termina;
|
||||
#[cfg(feature = "termina")]
|
||||
#[cfg(all(feature = "termina", not(windows)))]
|
||||
pub use self::termina::TerminaBackend;
|
||||
|
||||
#[cfg(all(feature = "termina", windows))]
|
||||
mod crossterm;
|
||||
#[cfg(all(feature = "termina", windows))]
|
||||
pub use self::crossterm::CrosstermBackend;
|
||||
|
||||
mod test;
|
||||
pub use self::test::TestBackend;
|
||||
|
||||
|
@@ -12,7 +12,7 @@ homepage.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
term = ["termina"]
|
||||
term = ["termina", "crossterm"]
|
||||
unicode-lines = []
|
||||
|
||||
[dependencies]
|
||||
@@ -56,6 +56,7 @@ kstring = "2.0"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
clipboard-win = { version = "5.4", features = ["std"] }
|
||||
crossterm = { version = "0.28", optional = true }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2"
|
||||
|
@@ -315,6 +315,34 @@ impl From<Color> for termina::style::ColorSpec {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "term", windows))]
|
||||
impl From<Color> for crossterm::style::Color {
|
||||
fn from(color: Color) -> Self {
|
||||
use crossterm::style::Color as CColor;
|
||||
|
||||
match color {
|
||||
Color::Reset => CColor::Reset,
|
||||
Color::Black => CColor::Black,
|
||||
Color::Red => CColor::DarkRed,
|
||||
Color::Green => CColor::DarkGreen,
|
||||
Color::Yellow => CColor::DarkYellow,
|
||||
Color::Blue => CColor::DarkBlue,
|
||||
Color::Magenta => CColor::DarkMagenta,
|
||||
Color::Cyan => CColor::DarkCyan,
|
||||
Color::Gray => CColor::DarkGrey,
|
||||
Color::LightRed => CColor::Red,
|
||||
Color::LightGreen => CColor::Green,
|
||||
Color::LightBlue => CColor::Blue,
|
||||
Color::LightYellow => CColor::Yellow,
|
||||
Color::LightMagenta => CColor::Magenta,
|
||||
Color::LightCyan => CColor::Cyan,
|
||||
Color::LightGray => CColor::Grey,
|
||||
Color::White => CColor::White,
|
||||
Color::Indexed(i) => CColor::AnsiValue(i),
|
||||
Color::Rgb(r, g, b) => CColor::Rgb { r, g, b },
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum UnderlineStyle {
|
||||
Reset,
|
||||
@@ -354,6 +382,20 @@ impl From<UnderlineStyle> for termina::style::Underline {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "term", windows))]
|
||||
impl From<UnderlineStyle> for crossterm::style::Attribute {
|
||||
fn from(style: UnderlineStyle) -> Self {
|
||||
match style {
|
||||
UnderlineStyle::Line => crossterm::style::Attribute::Underlined,
|
||||
UnderlineStyle::Curl => crossterm::style::Attribute::Undercurled,
|
||||
UnderlineStyle::Dotted => crossterm::style::Attribute::Underdotted,
|
||||
UnderlineStyle::Dashed => crossterm::style::Attribute::Underdashed,
|
||||
UnderlineStyle::DoubleLine => crossterm::style::Attribute::DoubleUnderlined,
|
||||
UnderlineStyle::Reset => crossterm::style::Attribute::NoUnderline,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Modifier changes the way a piece of text is displayed.
|
||||
///
|
||||
|
@@ -569,6 +569,113 @@ impl From<KeyEvent> for termina::event::KeyEvent {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "term", windows))]
|
||||
impl From<crossterm::event::Event> for Event {
|
||||
fn from(event: crossterm::event::Event) -> Self {
|
||||
match event {
|
||||
crossterm::event::Event::Key(key) => Self::Key(key.into()),
|
||||
crossterm::event::Event::Mouse(mouse) => Self::Mouse(mouse.into()),
|
||||
crossterm::event::Event::Resize(w, h) => Self::Resize(w, h),
|
||||
crossterm::event::Event::FocusGained => Self::FocusGained,
|
||||
crossterm::event::Event::FocusLost => Self::FocusLost,
|
||||
crossterm::event::Event::Paste(s) => Self::Paste(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "term", windows))]
|
||||
impl From<crossterm::event::MouseEvent> for MouseEvent {
|
||||
fn from(
|
||||
crossterm::event::MouseEvent {
|
||||
kind,
|
||||
column,
|
||||
row,
|
||||
modifiers,
|
||||
}: crossterm::event::MouseEvent,
|
||||
) -> Self {
|
||||
Self {
|
||||
kind: kind.into(),
|
||||
column,
|
||||
row,
|
||||
modifiers: modifiers.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "term", windows))]
|
||||
impl From<crossterm::event::MouseEventKind> for MouseEventKind {
|
||||
fn from(kind: crossterm::event::MouseEventKind) -> Self {
|
||||
match kind {
|
||||
crossterm::event::MouseEventKind::Down(button) => Self::Down(button.into()),
|
||||
crossterm::event::MouseEventKind::Up(button) => Self::Up(button.into()),
|
||||
crossterm::event::MouseEventKind::Drag(button) => Self::Drag(button.into()),
|
||||
crossterm::event::MouseEventKind::Moved => Self::Moved,
|
||||
crossterm::event::MouseEventKind::ScrollDown => Self::ScrollDown,
|
||||
crossterm::event::MouseEventKind::ScrollUp => Self::ScrollUp,
|
||||
crossterm::event::MouseEventKind::ScrollLeft => Self::ScrollLeft,
|
||||
crossterm::event::MouseEventKind::ScrollRight => Self::ScrollRight,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "term", windows))]
|
||||
impl From<crossterm::event::MouseButton> for MouseButton {
|
||||
fn from(button: crossterm::event::MouseButton) -> Self {
|
||||
match button {
|
||||
crossterm::event::MouseButton::Left => MouseButton::Left,
|
||||
crossterm::event::MouseButton::Right => MouseButton::Right,
|
||||
crossterm::event::MouseButton::Middle => MouseButton::Middle,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "term", windows))]
|
||||
impl From<crossterm::event::KeyEvent> for KeyEvent {
|
||||
fn from(
|
||||
crossterm::event::KeyEvent {
|
||||
code, modifiers, ..
|
||||
}: crossterm::event::KeyEvent,
|
||||
) -> Self {
|
||||
if code == crossterm::event::KeyCode::BackTab {
|
||||
// special case for BackTab -> Shift-Tab
|
||||
let mut modifiers: KeyModifiers = modifiers.into();
|
||||
modifiers.insert(KeyModifiers::SHIFT);
|
||||
Self {
|
||||
code: KeyCode::Tab,
|
||||
modifiers,
|
||||
}
|
||||
} else {
|
||||
Self {
|
||||
code: code.into(),
|
||||
modifiers: modifiers.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "term", windows))]
|
||||
impl From<KeyEvent> for crossterm::event::KeyEvent {
|
||||
fn from(KeyEvent { code, modifiers }: KeyEvent) -> Self {
|
||||
if code == KeyCode::Tab && modifiers.contains(KeyModifiers::SHIFT) {
|
||||
// special case for Shift-Tab -> BackTab
|
||||
let mut modifiers = modifiers;
|
||||
modifiers.remove(KeyModifiers::SHIFT);
|
||||
crossterm::event::KeyEvent {
|
||||
code: crossterm::event::KeyCode::BackTab,
|
||||
modifiers: modifiers.into(),
|
||||
kind: crossterm::event::KeyEventKind::Press,
|
||||
state: crossterm::event::KeyEventState::NONE,
|
||||
}
|
||||
} else {
|
||||
crossterm::event::KeyEvent {
|
||||
code: code.into(),
|
||||
modifiers: modifiers.into(),
|
||||
kind: crossterm::event::KeyEventKind::Press,
|
||||
state: crossterm::event::KeyEventState::NONE,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn parse_macro(keys_str: &str) -> anyhow::Result<Vec<KeyEvent>> {
|
||||
use anyhow::Context;
|
||||
let mut keys_res: anyhow::Result<_> = Ok(Vec::new());
|
||||
|
@@ -60,6 +60,53 @@ impl From<termina::event::Modifiers> for KeyModifiers {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "term", windows))]
|
||||
impl From<KeyModifiers> for crossterm::event::KeyModifiers {
|
||||
fn from(key_modifiers: KeyModifiers) -> Self {
|
||||
use crossterm::event::KeyModifiers as CKeyModifiers;
|
||||
|
||||
let mut result = CKeyModifiers::NONE;
|
||||
|
||||
if key_modifiers.contains(KeyModifiers::SHIFT) {
|
||||
result.insert(CKeyModifiers::SHIFT);
|
||||
}
|
||||
if key_modifiers.contains(KeyModifiers::CONTROL) {
|
||||
result.insert(CKeyModifiers::CONTROL);
|
||||
}
|
||||
if key_modifiers.contains(KeyModifiers::ALT) {
|
||||
result.insert(CKeyModifiers::ALT);
|
||||
}
|
||||
if key_modifiers.contains(KeyModifiers::SUPER) {
|
||||
result.insert(CKeyModifiers::SUPER);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "term", windows))]
|
||||
impl From<crossterm::event::KeyModifiers> for KeyModifiers {
|
||||
fn from(val: crossterm::event::KeyModifiers) -> Self {
|
||||
use crossterm::event::KeyModifiers as CKeyModifiers;
|
||||
|
||||
let mut result = KeyModifiers::NONE;
|
||||
|
||||
if val.contains(CKeyModifiers::SHIFT) {
|
||||
result.insert(KeyModifiers::SHIFT);
|
||||
}
|
||||
if val.contains(CKeyModifiers::CONTROL) {
|
||||
result.insert(KeyModifiers::CONTROL);
|
||||
}
|
||||
if val.contains(CKeyModifiers::ALT) {
|
||||
result.insert(KeyModifiers::ALT);
|
||||
}
|
||||
if val.contains(CKeyModifiers::SUPER) {
|
||||
result.insert(KeyModifiers::SUPER);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
/// Represents a media key (as part of [`KeyCode::Media`]).
|
||||
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)]
|
||||
pub enum MediaKeyCode {
|
||||
@@ -137,6 +184,51 @@ impl From<termina::event::MediaKeyCode> for MediaKeyCode {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "term", windows))]
|
||||
impl From<MediaKeyCode> for crossterm::event::MediaKeyCode {
|
||||
fn from(media_key_code: MediaKeyCode) -> Self {
|
||||
use crossterm::event::MediaKeyCode as CMediaKeyCode;
|
||||
|
||||
match media_key_code {
|
||||
MediaKeyCode::Play => CMediaKeyCode::Play,
|
||||
MediaKeyCode::Pause => CMediaKeyCode::Pause,
|
||||
MediaKeyCode::PlayPause => CMediaKeyCode::PlayPause,
|
||||
MediaKeyCode::Reverse => CMediaKeyCode::Reverse,
|
||||
MediaKeyCode::Stop => CMediaKeyCode::Stop,
|
||||
MediaKeyCode::FastForward => CMediaKeyCode::FastForward,
|
||||
MediaKeyCode::Rewind => CMediaKeyCode::Rewind,
|
||||
MediaKeyCode::TrackNext => CMediaKeyCode::TrackNext,
|
||||
MediaKeyCode::TrackPrevious => CMediaKeyCode::TrackPrevious,
|
||||
MediaKeyCode::Record => CMediaKeyCode::Record,
|
||||
MediaKeyCode::LowerVolume => CMediaKeyCode::LowerVolume,
|
||||
MediaKeyCode::RaiseVolume => CMediaKeyCode::RaiseVolume,
|
||||
MediaKeyCode::MuteVolume => CMediaKeyCode::MuteVolume,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "term", windows))]
|
||||
impl From<crossterm::event::MediaKeyCode> for MediaKeyCode {
|
||||
fn from(val: crossterm::event::MediaKeyCode) -> Self {
|
||||
use crossterm::event::MediaKeyCode as CMediaKeyCode;
|
||||
|
||||
match val {
|
||||
CMediaKeyCode::Play => MediaKeyCode::Play,
|
||||
CMediaKeyCode::Pause => MediaKeyCode::Pause,
|
||||
CMediaKeyCode::PlayPause => MediaKeyCode::PlayPause,
|
||||
CMediaKeyCode::Reverse => MediaKeyCode::Reverse,
|
||||
CMediaKeyCode::Stop => MediaKeyCode::Stop,
|
||||
CMediaKeyCode::FastForward => MediaKeyCode::FastForward,
|
||||
CMediaKeyCode::Rewind => MediaKeyCode::Rewind,
|
||||
CMediaKeyCode::TrackNext => MediaKeyCode::TrackNext,
|
||||
CMediaKeyCode::TrackPrevious => MediaKeyCode::TrackPrevious,
|
||||
CMediaKeyCode::Record => MediaKeyCode::Record,
|
||||
CMediaKeyCode::LowerVolume => MediaKeyCode::LowerVolume,
|
||||
CMediaKeyCode::RaiseVolume => MediaKeyCode::RaiseVolume,
|
||||
CMediaKeyCode::MuteVolume => MediaKeyCode::MuteVolume,
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Represents a media key (as part of [`KeyCode::Modifier`]).
|
||||
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)]
|
||||
pub enum ModifierKeyCode {
|
||||
@@ -218,6 +310,53 @@ impl From<termina::event::ModifierKeyCode> for ModifierKeyCode {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "term", windows))]
|
||||
impl From<ModifierKeyCode> for crossterm::event::ModifierKeyCode {
|
||||
fn from(modifier_key_code: ModifierKeyCode) -> Self {
|
||||
use crossterm::event::ModifierKeyCode as CModifierKeyCode;
|
||||
|
||||
match modifier_key_code {
|
||||
ModifierKeyCode::LeftShift => CModifierKeyCode::LeftShift,
|
||||
ModifierKeyCode::LeftControl => CModifierKeyCode::LeftControl,
|
||||
ModifierKeyCode::LeftAlt => CModifierKeyCode::LeftAlt,
|
||||
ModifierKeyCode::LeftSuper => CModifierKeyCode::LeftSuper,
|
||||
ModifierKeyCode::LeftHyper => CModifierKeyCode::LeftHyper,
|
||||
ModifierKeyCode::LeftMeta => CModifierKeyCode::LeftMeta,
|
||||
ModifierKeyCode::RightShift => CModifierKeyCode::RightShift,
|
||||
ModifierKeyCode::RightControl => CModifierKeyCode::RightControl,
|
||||
ModifierKeyCode::RightAlt => CModifierKeyCode::RightAlt,
|
||||
ModifierKeyCode::RightSuper => CModifierKeyCode::RightSuper,
|
||||
ModifierKeyCode::RightHyper => CModifierKeyCode::RightHyper,
|
||||
ModifierKeyCode::RightMeta => CModifierKeyCode::RightMeta,
|
||||
ModifierKeyCode::IsoLevel3Shift => CModifierKeyCode::IsoLevel3Shift,
|
||||
ModifierKeyCode::IsoLevel5Shift => CModifierKeyCode::IsoLevel5Shift,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "term", windows))]
|
||||
impl From<crossterm::event::ModifierKeyCode> for ModifierKeyCode {
|
||||
fn from(val: crossterm::event::ModifierKeyCode) -> Self {
|
||||
use crossterm::event::ModifierKeyCode as CModifierKeyCode;
|
||||
|
||||
match val {
|
||||
CModifierKeyCode::LeftShift => ModifierKeyCode::LeftShift,
|
||||
CModifierKeyCode::LeftControl => ModifierKeyCode::LeftControl,
|
||||
CModifierKeyCode::LeftAlt => ModifierKeyCode::LeftAlt,
|
||||
CModifierKeyCode::LeftSuper => ModifierKeyCode::LeftSuper,
|
||||
CModifierKeyCode::LeftHyper => ModifierKeyCode::LeftHyper,
|
||||
CModifierKeyCode::LeftMeta => ModifierKeyCode::LeftMeta,
|
||||
CModifierKeyCode::RightShift => ModifierKeyCode::RightShift,
|
||||
CModifierKeyCode::RightControl => ModifierKeyCode::RightControl,
|
||||
CModifierKeyCode::RightAlt => ModifierKeyCode::RightAlt,
|
||||
CModifierKeyCode::RightSuper => ModifierKeyCode::RightSuper,
|
||||
CModifierKeyCode::RightHyper => ModifierKeyCode::RightHyper,
|
||||
CModifierKeyCode::RightMeta => ModifierKeyCode::RightMeta,
|
||||
CModifierKeyCode::IsoLevel3Shift => ModifierKeyCode::IsoLevel3Shift,
|
||||
CModifierKeyCode::IsoLevel5Shift => ModifierKeyCode::IsoLevel5Shift,
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Represents a key.
|
||||
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)]
|
||||
pub enum KeyCode {
|
||||
@@ -351,3 +490,76 @@ impl From<termina::event::KeyCode> for KeyCode {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "term", windows))]
|
||||
impl From<KeyCode> for crossterm::event::KeyCode {
|
||||
fn from(key_code: KeyCode) -> Self {
|
||||
use crossterm::event::KeyCode as CKeyCode;
|
||||
|
||||
match key_code {
|
||||
KeyCode::Backspace => CKeyCode::Backspace,
|
||||
KeyCode::Enter => CKeyCode::Enter,
|
||||
KeyCode::Left => CKeyCode::Left,
|
||||
KeyCode::Right => CKeyCode::Right,
|
||||
KeyCode::Up => CKeyCode::Up,
|
||||
KeyCode::Down => CKeyCode::Down,
|
||||
KeyCode::Home => CKeyCode::Home,
|
||||
KeyCode::End => CKeyCode::End,
|
||||
KeyCode::PageUp => CKeyCode::PageUp,
|
||||
KeyCode::PageDown => CKeyCode::PageDown,
|
||||
KeyCode::Tab => CKeyCode::Tab,
|
||||
KeyCode::Delete => CKeyCode::Delete,
|
||||
KeyCode::Insert => CKeyCode::Insert,
|
||||
KeyCode::F(f_number) => CKeyCode::F(f_number),
|
||||
KeyCode::Char(character) => CKeyCode::Char(character),
|
||||
KeyCode::Null => CKeyCode::Null,
|
||||
KeyCode::Esc => CKeyCode::Esc,
|
||||
KeyCode::CapsLock => CKeyCode::CapsLock,
|
||||
KeyCode::ScrollLock => CKeyCode::ScrollLock,
|
||||
KeyCode::NumLock => CKeyCode::NumLock,
|
||||
KeyCode::PrintScreen => CKeyCode::PrintScreen,
|
||||
KeyCode::Pause => CKeyCode::Pause,
|
||||
KeyCode::Menu => CKeyCode::Menu,
|
||||
KeyCode::KeypadBegin => CKeyCode::KeypadBegin,
|
||||
KeyCode::Media(media_key_code) => CKeyCode::Media(media_key_code.into()),
|
||||
KeyCode::Modifier(modifier_key_code) => CKeyCode::Modifier(modifier_key_code.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "term", windows))]
|
||||
impl From<crossterm::event::KeyCode> for KeyCode {
|
||||
fn from(val: crossterm::event::KeyCode) -> Self {
|
||||
use crossterm::event::KeyCode as CKeyCode;
|
||||
|
||||
match val {
|
||||
CKeyCode::Backspace => KeyCode::Backspace,
|
||||
CKeyCode::Enter => KeyCode::Enter,
|
||||
CKeyCode::Left => KeyCode::Left,
|
||||
CKeyCode::Right => KeyCode::Right,
|
||||
CKeyCode::Up => KeyCode::Up,
|
||||
CKeyCode::Down => KeyCode::Down,
|
||||
CKeyCode::Home => KeyCode::Home,
|
||||
CKeyCode::End => KeyCode::End,
|
||||
CKeyCode::PageUp => KeyCode::PageUp,
|
||||
CKeyCode::PageDown => KeyCode::PageDown,
|
||||
CKeyCode::Tab => KeyCode::Tab,
|
||||
CKeyCode::BackTab => unreachable!("BackTab should have been handled on KeyEvent level"),
|
||||
CKeyCode::Delete => KeyCode::Delete,
|
||||
CKeyCode::Insert => KeyCode::Insert,
|
||||
CKeyCode::F(f_number) => KeyCode::F(f_number),
|
||||
CKeyCode::Char(character) => KeyCode::Char(character),
|
||||
CKeyCode::Null => KeyCode::Null,
|
||||
CKeyCode::Esc => KeyCode::Esc,
|
||||
CKeyCode::CapsLock => KeyCode::CapsLock,
|
||||
CKeyCode::ScrollLock => KeyCode::ScrollLock,
|
||||
CKeyCode::NumLock => KeyCode::NumLock,
|
||||
CKeyCode::PrintScreen => KeyCode::PrintScreen,
|
||||
CKeyCode::Pause => KeyCode::Pause,
|
||||
CKeyCode::Menu => KeyCode::Menu,
|
||||
CKeyCode::KeypadBegin => KeyCode::KeypadBegin,
|
||||
CKeyCode::Media(media_key_code) => KeyCode::Media(media_key_code.into()),
|
||||
CKeyCode::Modifier(modifier_key_code) => KeyCode::Modifier(modifier_key_code.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user