Skip to content

Commit c2b582a

Browse files
tui: Use Crossterm on Windows (#14454)
1 parent 0ae37dc commit c2b582a

File tree

11 files changed

+972
-25
lines changed

11 files changed

+972
-25
lines changed

Cargo.lock

Lines changed: 89 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

helix-term/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ anyhow = "1"
5454
once_cell = "1.21"
5555

5656
tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
57-
tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["termina"] }
57+
tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["termina", "crossterm"] }
5858
termina = { workspace = true, features = ["event-stream"] }
5959
signal-hook = "0.3"
6060
tokio-stream = "0.1"
@@ -93,6 +93,9 @@ grep-searcher = "0.1.14"
9393

9494
dashmap = "6.0"
9595

96+
[target.'cfg(windows)'.dependencies]
97+
crossterm = { version = "0.28", features = ["event-stream"] }
98+
9699
[target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100
97100
signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }
98101
libc = "0.2.175"

helix-term/src/application.rs

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,25 +36,35 @@ use std::{
3636
sync::Arc,
3737
};
3838

39+
#[cfg_attr(windows, allow(unused_imports))]
3940
use anyhow::{Context, Error};
4041

4142
#[cfg(not(windows))]
4243
use {signal_hook::consts::signal, signal_hook_tokio::Signals};
4344
#[cfg(windows)]
4445
type Signals = futures_util::stream::Empty<()>;
4546

46-
#[cfg(not(feature = "integration"))]
47+
#[cfg(all(not(windows), not(feature = "integration")))]
4748
use tui::backend::TerminaBackend;
4849

50+
#[cfg(all(windows, not(feature = "integration")))]
51+
use tui::backend::CrosstermBackend;
52+
4953
#[cfg(feature = "integration")]
5054
use tui::backend::TestBackend;
5155

52-
#[cfg(not(feature = "integration"))]
56+
#[cfg(all(not(windows), not(feature = "integration")))]
5357
type TerminalBackend = TerminaBackend;
54-
58+
#[cfg(all(windows, not(feature = "integration")))]
59+
type TerminalBackend = CrosstermBackend<std::io::Stdout>;
5560
#[cfg(feature = "integration")]
5661
type TerminalBackend = TestBackend;
5762

63+
#[cfg(not(windows))]
64+
type TerminalEvent = termina::Event;
65+
#[cfg(windows)]
66+
type TerminalEvent = crossterm::event::Event;
67+
5868
type Terminal = tui::terminal::Terminal<TerminalBackend>;
5969

6070
pub struct Application {
@@ -102,9 +112,11 @@ impl Application {
102112
theme_parent_dirs.extend(helix_loader::runtime_dirs().iter().cloned());
103113
let theme_loader = theme::Loader::new(&theme_parent_dirs);
104114

105-
#[cfg(not(feature = "integration"))]
115+
#[cfg(all(not(windows), not(feature = "integration")))]
106116
let backend = TerminaBackend::new((&config.editor).into())
107117
.context("failed to create terminal backend")?;
118+
#[cfg(all(windows, not(feature = "integration")))]
119+
let backend = CrosstermBackend::new(std::io::stdout(), (&config.editor).into());
108120

109121
#[cfg(feature = "integration")]
110122
let backend = TestBackend::new(120, 150);
@@ -286,7 +298,7 @@ impl Application {
286298

287299
pub async fn event_loop<S>(&mut self, input_stream: &mut S)
288300
where
289-
S: Stream<Item = std::io::Result<termina::Event>> + Unpin,
301+
S: Stream<Item = std::io::Result<TerminalEvent>> + Unpin,
290302
{
291303
self.render().await;
292304

@@ -299,7 +311,7 @@ impl Application {
299311

300312
pub async fn event_loop_until_idle<S>(&mut self, input_stream: &mut S) -> bool
301313
where
302-
S: Stream<Item = std::io::Result<termina::Event>> + Unpin,
314+
S: Stream<Item = std::io::Result<TerminalEvent>> + Unpin,
303315
{
304316
loop {
305317
if self.editor.should_close() {
@@ -659,14 +671,15 @@ impl Application {
659671
false
660672
}
661673

662-
pub async fn handle_terminal_events(&mut self, event: std::io::Result<termina::Event>) {
674+
pub async fn handle_terminal_events(&mut self, event: std::io::Result<TerminalEvent>) {
663675
let mut cx = crate::compositor::Context {
664676
editor: &mut self.editor,
665677
jobs: &mut self.jobs,
666678
scroll: None,
667679
};
668680
// Handle key events
669681
let should_redraw = match event.unwrap() {
682+
#[cfg(not(windows))]
670683
termina::Event::WindowResized(termina::WindowSize { rows, cols, .. }) => {
671684
self.terminal
672685
.resize(Rect::new(0, 0, cols, rows))
@@ -679,11 +692,31 @@ impl Application {
679692
self.compositor
680693
.handle_event(&Event::Resize(cols, rows), &mut cx)
681694
}
695+
#[cfg(not(windows))]
682696
// Ignore keyboard release events.
683697
termina::Event::Key(termina::event::KeyEvent {
684698
kind: termina::event::KeyEventKind::Release,
685699
..
686700
}) => false,
701+
#[cfg(windows)]
702+
TerminalEvent::Resize(width, height) => {
703+
self.terminal
704+
.resize(Rect::new(0, 0, width, height))
705+
.expect("Unable to resize terminal");
706+
707+
let area = self.terminal.size().expect("couldn't get terminal size");
708+
709+
self.compositor.resize(area);
710+
711+
self.compositor
712+
.handle_event(&Event::Resize(width, height), &mut cx)
713+
}
714+
#[cfg(windows)]
715+
// Ignore keyboard release events.
716+
crossterm::event::Event::Key(crossterm::event::KeyEvent {
717+
kind: crossterm::event::KeyEventKind::Release,
718+
..
719+
}) => false,
687720
event => self.compositor.handle_event(&event.into(), &mut cx),
688721
};
689722

@@ -1132,15 +1165,20 @@ impl Application {
11321165
self.terminal.restore()
11331166
}
11341167

1135-
#[cfg(not(feature = "integration"))]
1136-
pub fn event_stream(&self) -> impl Stream<Item = std::io::Result<termina::Event>> + Unpin {
1168+
#[cfg(all(not(feature = "integration"), not(windows)))]
1169+
pub fn event_stream(&self) -> impl Stream<Item = std::io::Result<TerminalEvent>> + Unpin {
11371170
use termina::Terminal as _;
11381171
let reader = self.terminal.backend().terminal().event_reader();
11391172
termina::EventStream::new(reader, |event| !event.is_escape())
11401173
}
11411174

1175+
#[cfg(all(not(feature = "integration"), windows))]
1176+
pub fn event_stream(&self) -> impl Stream<Item = std::io::Result<TerminalEvent>> + Unpin {
1177+
crossterm::event::EventStream::new()
1178+
}
1179+
11421180
#[cfg(feature = "integration")]
1143-
pub fn event_stream(&self) -> impl Stream<Item = std::io::Result<termina::Event>> + Unpin {
1181+
pub fn event_stream(&self) -> impl Stream<Item = std::io::Result<TerminalEvent>> + Unpin {
11441182
use std::{
11451183
pin::Pin,
11461184
task::{Context, Poll},
@@ -1150,7 +1188,7 @@ impl Application {
11501188
pub struct DummyEventStream;
11511189

11521190
impl Stream for DummyEventStream {
1153-
type Item = std::io::Result<termina::Event>;
1191+
type Item = std::io::Result<TerminalEvent>;
11541192

11551193
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
11561194
Poll::Pending
@@ -1162,7 +1200,7 @@ impl Application {
11621200

11631201
pub async fn run<S>(&mut self, input_stream: &mut S) -> Result<i32, Error>
11641202
where
1165-
S: Stream<Item = std::io::Result<termina::Event>> + Unpin,
1203+
S: Stream<Item = std::io::Result<TerminalEvent>> + Unpin,
11661204
{
11671205
self.terminal.claim()?;
11681206

0 commit comments

Comments
 (0)