diff --git a/src/keybindings.rs b/src/keybindings.rs index 15132b2..dee325a 100644 --- a/src/keybindings.rs +++ b/src/keybindings.rs @@ -3,13 +3,13 @@ //! The keybindings are set up here. We define some iamb-specific keybindings, but the default Vim //! keys come from [modalkit::env::vim::keybindings]. use modalkit::{ - actions::{MacroAction, WindowAction}, + actions::{InsertTextAction, MacroAction, WindowAction}, env::vim::keybindings::{InputStep, VimBindings}, env::vim::VimMode, env::CommonKeyClass, key::TerminalKey, keybindings::{EdgeEvent, EdgeRepeat, InputBindings}, - prelude::Count, + prelude::*, }; use crate::base::{IambAction, IambInfo, Keybindings, MATRIX_ID_WORD}; @@ -36,6 +36,7 @@ pub fn setup_keybindings() -> Keybindings { let ctrl_z = "".parse::().unwrap(); let key_m_lc = "m".parse::().unwrap(); let key_z_lc = "z".parse::().unwrap(); + let shift_enter = "".parse::().unwrap(); let cwz = vec![once(&ctrl_w), once(&key_z_lc)]; let cwcz = vec![once(&ctrl_w), once(&ctrl_z)]; @@ -57,6 +58,17 @@ pub fn setup_keybindings() -> Keybindings { ism.add_mapping(VimMode::Visual, &cwm, &stoggle); ism.add_mapping(VimMode::Normal, &cwcm, &stoggle); ism.add_mapping(VimMode::Visual, &cwcm, &stoggle); + + let shift_enter = vec![once(&shift_enter)]; + let newline = IambStep::new().actions(vec![InsertTextAction::Type( + Char::Single('\n').into(), + MoveDir1D::Previous, + 1.into(), + ) + .into()]); + ism.add_mapping(VimMode::Insert, &cwm, &newline); + ism.add_mapping(VimMode::Insert, &shift_enter, &newline); + ism } diff --git a/src/main.rs b/src/main.rs index 739c9aa..2311a3c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -48,6 +48,9 @@ use modalkit::crossterm::{ EnableFocusChange, Event, KeyEventKind, + KeyboardEnhancementFlags, + PopKeyboardEnhancementFlags, + PushKeyboardEnhancementFlags, }, execute, terminal::{EnterAlternateScreen, LeaveAlternateScreen, SetTitle}, @@ -250,16 +253,7 @@ impl Application { settings: ApplicationSettings, store: AsyncProgramStore, ) -> IambResult { - let mut stdout = stdout(); - crossterm::terminal::enable_raw_mode()?; - crossterm::execute!(stdout, EnterAlternateScreen)?; - crossterm::execute!(stdout, EnableBracketedPaste)?; - crossterm::execute!(stdout, EnableFocusChange)?; - - let title = format!("iamb ({})", settings.profile.user_id); - crossterm::execute!(stdout, SetTitle(title))?; - - let backend = CrosstermBackend::new(stdout); + let backend = CrosstermBackend::new(stdout()); let terminal = Terminal::new(backend)?; let mut bindings = crate::keybindings::setup_keybindings(); @@ -905,6 +899,70 @@ async fn login_normal( Ok(()) } +struct EnableModifyOtherKeys; +struct DisableModifyOtherKeys; + +impl crossterm::Command for EnableModifyOtherKeys { + fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result { + write!(f, "\x1B[>4;2m") + } + + #[cfg(windows)] + fn execute_winapi(&self) -> std::io::Result<()> { + Ok(()) + } +} + +impl crossterm::Command for DisableModifyOtherKeys { + fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result { + write!(f, "\x1B[>4;0m") + } + + #[cfg(windows)] + fn execute_winapi(&self) -> std::io::Result<()> { + Ok(()) + } +} + +/// Set up the terminal for drawing the TUI, and getting additional info. +fn setup_tty(title: &str, enable_enhanced_keys: bool) -> std::io::Result<()> { + let title = format!("iamb ({})", title); + + // Enable raw mode and enter the alternate screen. + crossterm::terminal::enable_raw_mode()?; + crossterm::execute!(stdout(), EnterAlternateScreen)?; + + if enable_enhanced_keys { + // Enable the Kitty keyboard enhancement protocol for improved keypresses. + crossterm::queue!( + stdout(), + PushKeyboardEnhancementFlags(KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES) + )?; + } else { + crossterm::queue!(stdout(), EnableModifyOtherKeys)?; + } + + crossterm::execute!(stdout(), EnableBracketedPaste, EnableFocusChange, SetTitle(title)) +} + +// Do our best to reverse what we did in setup_tty() when we exit or crash. +fn restore_tty(enable_enhanced_keys: bool) { + if enable_enhanced_keys { + let _ = crossterm::queue!(stdout(), PopKeyboardEnhancementFlags); + } + + let _ = crossterm::execute!( + stdout(), + DisableModifyOtherKeys, + DisableBracketedPaste, + DisableFocusChange, + LeaveAlternateScreen, + CursorShow, + ); + + let _ = crossterm::terminal::disable_raw_mode(); +} + async fn run(settings: ApplicationSettings) -> IambResult<()> { // Get old keys the first time we run w/ the upgraded SDK. let import_keys = check_import_keys(&settings).await?; @@ -938,27 +996,30 @@ async fn run(settings: ApplicationSettings) -> IambResult<()> { Ok(()) => (), } - fn restore_tty() { - let _ = crossterm::terminal::disable_raw_mode(); - let _ = crossterm::execute!(stdout(), DisableBracketedPaste); - let _ = crossterm::execute!(stdout(), DisableFocusChange); - let _ = crossterm::execute!(stdout(), LeaveAlternateScreen); - let _ = crossterm::execute!(stdout(), CursorShow); - } + // Set up the terminal for drawing, and cleanup properly on panics. + let enable_enhanced_keys = match crossterm::terminal::supports_keyboard_enhancement() { + Ok(supported) => supported, + Err(e) => { + tracing::warn!(err = %e, + "Failed to determine whether the terminal supports keyboard enhancements"); + false + }, + }; + setup_tty(settings.profile.user_id.as_str(), enable_enhanced_keys)?; - // Make sure panics clean up the terminal properly. let orig_hook = std::panic::take_hook(); std::panic::set_hook(Box::new(move |panic_info| { - restore_tty(); + restore_tty(enable_enhanced_keys); orig_hook(panic_info); process::exit(1); })); + // And finally, start running the terminal UI. let mut application = Application::new(settings, store).await?; - - // We can now run the application. application.run().await?; - restore_tty(); + + // Clean up the terminal on exit. + restore_tty(enable_enhanced_keys); Ok(()) }