diff --git a/crates/term/src/backends/process.rs b/crates/term/src/backends/process.rs index 8ccb092c..648e47d7 100644 --- a/crates/term/src/backends/process.rs +++ b/crates/term/src/backends/process.rs @@ -113,6 +113,7 @@ impl super::Backend for Process { } fn send(&mut self, msg: alacritty_terminal::event_loop::Msg) { + println!("{:?}", msg); let _ = self.event_loop_sender.send(msg); } diff --git a/crates/term/src/widget/keys.rs b/crates/term/src/widget/keys.rs new file mode 100644 index 00000000..d5247668 --- /dev/null +++ b/crates/term/src/widget/keys.rs @@ -0,0 +1,234 @@ +// Copyright C 2024 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +// +// Additional permission under GNU GPL version 3 section 7 +// +// If you modify this Program, or any covered work, by linking or combining +// it with Steamworks API by Valve Corporation, containing parts covered by +// terms of the Steamworks API by Valve Corporation, the licensors of this +// Program grant you additional permission to convey the resulting work. +use alacritty_terminal::term::TermMode; + +macro_rules! key_binding { + ( + $arg_key:ident, $modifiers:ident, $term_mode:ident; + $( + $key:ident + $(,$input_modifiers:expr)? + $(,+$terminal_mode_include:expr)? + $(,~$terminal_mode_exclude:expr)? + ;$action:literal + );* + $(;)* + ) => {{ + match $arg_key { + $( + egui::Key::$key if true + $( && $input_modifiers == $modifiers )? + $( && $term_mode.contains($terminal_mode_include) )? + $( && !$term_mode.contains($terminal_mode_exclude))? => Some($action), + )* + _ => None, + } + }}; +} + +// Adapted from https://github.com/Harzu/iced_term/blob/master/src/bindings.rs +pub fn key_to_codes( + key: egui::Key, + modifiers: egui::Modifiers, + term_mode: TermMode, +) -> Option<&'static [u8]> { + println!("{key:?} {modifiers:?}"); + key_binding! { + key, modifiers, term_mode; + // ANY + Space; b" "; + Enter; b"\x0d"; + Backspace; b"\x7f"; + Escape; b"\x1b"; + Tab; b"\x09"; + Insert; b"\x1b[2~"; + Delete; b"\x1b[3~"; + PageUp; b"\x1b[5~"; + PageDown; b"\x1b[6~"; + F1; b"\x1bOP"; + F2; b"\x1bOQ"; + F3; b"\x1bOR"; + F4; b"\x1bOS"; + F5; b"\x1b[15~"; + F6; b"\x1b[17~"; + F7; b"\x1b[18~"; + F8; b"\x1b[19~"; + F9; b"\x1b[20~"; + F10; b"\x1b[21~"; + F11; b"\x1b[23~"; + F12; b"\x1b[24~"; + F13; b"\x1b[25~"; + F14; b"\x1b[26~"; + F15; b"\x1b[28~"; + F16; b"\x1b[29~"; + F17; b"\x1b[31~"; + F18; b"\x1b[32~"; + F19; b"\x1b[33~"; + F20; b"\x1b[34~"; + // APP_CURSOR Excluding + End, ~TermMode::APP_CURSOR; b"\x1b[F"; + Home, ~TermMode::APP_CURSOR; b"\x1b[H"; + ArrowUp, ~TermMode::APP_CURSOR; b"\x1b[A"; + ArrowDown, ~TermMode::APP_CURSOR; b"\x1b[B"; + ArrowLeft, ~TermMode::APP_CURSOR; b"\x1b[D"; + ArrowRight, ~TermMode::APP_CURSOR; b"\x1b[C"; + // APP_CURSOR Including + End, +TermMode::APP_CURSOR; b"\x1BOF"; + Home, +TermMode::APP_CURSOR; b"\x1BOH"; + ArrowUp, +TermMode::APP_CURSOR; b"\x1bOA"; + ArrowDown, +TermMode::APP_CURSOR; b"\x1bOB"; + ArrowLeft, +TermMode::APP_CURSOR; b"\x1bOD"; + ArrowRight, +TermMode::APP_CURSOR; b"\x1bOC"; + // CTRL + ArrowUp, egui::Modifiers::COMMAND; b"\x1b[1;5A"; + ArrowDown, egui::Modifiers::COMMAND; b"\x1b[1;5B"; + ArrowLeft, egui::Modifiers::COMMAND; b"\x1b[1;5D"; + ArrowRight, egui::Modifiers::COMMAND; b"\x1b[1;5C"; + End, egui::Modifiers::CTRL; b"\x1b[1;5F"; + Home, egui::Modifiers::CTRL; b"\x1b[1;5H"; + Delete, egui::Modifiers::CTRL; b"\x1b[3;5~"; + PageUp, egui::Modifiers::CTRL; b"\x1b[5;5~"; + PageDown, egui::Modifiers::CTRL; b"\x1b[6;5~"; + F1, egui::Modifiers::CTRL; b"\x1bO;5P"; + F2, egui::Modifiers::CTRL; b"\x1bO;5Q"; + F3, egui::Modifiers::CTRL; b"\x1bO;5R"; + F4, egui::Modifiers::CTRL; b"\x1bO;5S"; + F5, egui::Modifiers::CTRL; b"\x1b[15;5~"; + F6, egui::Modifiers::CTRL; b"\x1b[17;5~"; + F7, egui::Modifiers::CTRL; b"\x1b[18;5~"; + F8, egui::Modifiers::CTRL; b"\x1b[19;5~"; + F9, egui::Modifiers::CTRL; b"\x1b[20;5~"; + F10, egui::Modifiers::CTRL; b"\x1b[21;5~"; + F11, egui::Modifiers::CTRL; b"\x1b[23;5~"; + F12, egui::Modifiers::CTRL; b"\x1b[24;5~"; + A, egui::Modifiers::CTRL; b"\x01"; + B, egui::Modifiers::CTRL; b"\x02"; + C, egui::Modifiers::CTRL; b"\x03"; + D, egui::Modifiers::CTRL; b"\x04"; + E, egui::Modifiers::CTRL; b"\x05"; // ENQ vt100 + F, egui::Modifiers::CTRL; b"\x06"; + G, egui::Modifiers::CTRL; b"\x07"; // Bell vt100 + H, egui::Modifiers::CTRL; b"\x08"; // Backspace vt100 + I, egui::Modifiers::CTRL; b"\x09"; // Tab vt100 + J, egui::Modifiers::CTRL; b"\x0a"; // LF new line vt100 + K, egui::Modifiers::CTRL; b"\x0b"; // VT vertical tab vt100 + L, egui::Modifiers::CTRL; b"\x0c"; // FF new page vt100 + M, egui::Modifiers::CTRL; b"\x0d"; // CR vt100 + N, egui::Modifiers::CTRL; b"\x0e"; // SO shift out vt100 + O, egui::Modifiers::CTRL; b"\x0f"; // SI shift in vt100 + P, egui::Modifiers::CTRL; b"\x10"; + Q, egui::Modifiers::CTRL; b"\x11"; + R, egui::Modifiers::CTRL; b"\x12"; + S, egui::Modifiers::CTRL; b"\x13"; + T, egui::Modifiers::CTRL; b"\x14"; + U, egui::Modifiers::CTRL; b"\x51"; + V, egui::Modifiers::CTRL; b"\x16"; + W, egui::Modifiers::CTRL; b"\x17"; + X, egui::Modifiers::CTRL; b"\x18"; + W, egui::Modifiers::CTRL; b"\x19"; + Z, egui::Modifiers::CTRL; b"\x1a"; + // SHIFT + Enter, egui::Modifiers::SHIFT; b"\x0d"; + Backspace, egui::Modifiers::SHIFT; b"\x7f"; + Tab, egui::Modifiers::SHIFT; b"\x1b[Z"; + End, egui::Modifiers::SHIFT, +TermMode::ALT_SCREEN; b"\x1b[1;2F"; + Home, egui::Modifiers::SHIFT, +TermMode::ALT_SCREEN; b"\x1b[1;2H"; + PageUp, egui::Modifiers::SHIFT, +TermMode::ALT_SCREEN; b"\x1b[5;2~"; + PageDown, egui::Modifiers::SHIFT, +TermMode::ALT_SCREEN; b"\x1b[6;2~"; + ArrowUp, egui::Modifiers::SHIFT; b"\x1b[1;2A"; + ArrowDown, egui::Modifiers::SHIFT; b"\x1b[1;2B"; + ArrowLeft, egui::Modifiers::SHIFT; b"\x1b[1;2D"; + ArrowRight, egui::Modifiers::SHIFT; b"\x1b[1;2C"; + // ALT + Backspace, egui::Modifiers::ALT; b"\x1b\x7f"; + End, egui::Modifiers::ALT; b"\x1b[1;3F"; + Home, egui::Modifiers::ALT; b"\x1b[1;3H"; + Insert, egui::Modifiers::ALT; b"\x1b[3;2~"; + Delete, egui::Modifiers::ALT; b"\x1b[3;3~"; + PageUp, egui::Modifiers::ALT; b"\x1b[5;3~"; + PageDown, egui::Modifiers::ALT; b"\x1b[6;3~"; + ArrowUp, egui::Modifiers::ALT; b"\x1b[1;3A"; + ArrowDown, egui::Modifiers::ALT; b"\x1b[1;3B"; + ArrowLeft, egui::Modifiers::ALT; b"\x1b[1;3D"; + ArrowRight, egui::Modifiers::ALT; b"\x1b[1;3C"; + // SHIFT + ALT + End, egui::Modifiers::SHIFT | egui::Modifiers::ALT; b"\x1b[1;4F"; + Home, egui::Modifiers::SHIFT | egui::Modifiers::ALT; b"\x1b[1;4H"; + ArrowUp, egui::Modifiers::SHIFT | egui::Modifiers::ALT; b"\x1b[1;4A"; + ArrowDown, egui::Modifiers::SHIFT | egui::Modifiers::ALT; b"\x1b[1;4B"; + ArrowLeft, egui::Modifiers::SHIFT | egui::Modifiers::ALT; b"\x1b[1;4D"; + ArrowRight, egui::Modifiers::SHIFT | egui::Modifiers::ALT; b"\x1b[1;4C"; + // SHIFT + CTRL + End, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x1b[1;6F"; + Home, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x1b[1;6H"; + ArrowUp, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x1b[1;6A"; + ArrowDown, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x1b[1;6B"; + ArrowLeft, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x1b[1;6D"; + ArrowRight, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x1b[1;6C"; + A, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x01"; + B, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x02"; + C, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x03"; + D, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x04"; + E, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x05"; + F, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x06"; + G, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x07"; + H, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x08"; + I, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x09"; + J, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x0a"; + K, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x0b"; + L, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x0c"; + M, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x0d"; + N, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x0e"; + O, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x0f"; + P, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x10"; + Q, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x11"; + R, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x12"; + S, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x13"; + T, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x14"; + U, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x51"; + V, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x16"; + W, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x17"; + X, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x18"; + W, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x19"; + Z, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x1a"; + Num2, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x00"; // Null vt100 + Num6, egui::Modifiers::SHIFT | egui::Modifiers::CTRL; b"\x1e"; + // CTRL + ALT + End, egui::Modifiers::CTRL | egui::Modifiers::ALT; b"\x1b[1;7F"; + Home, egui::Modifiers::CTRL | egui::Modifiers::ALT; b"\x1b[1;7H"; + PageUp, egui::Modifiers::CTRL | egui::Modifiers::ALT; b"\x1b[5;7~"; + PageDown, egui::Modifiers::CTRL | egui::Modifiers::ALT; b"\x1b[6;7~"; + ArrowUp, egui::Modifiers::CTRL | egui::Modifiers::ALT; b"\x1b[1;7A"; + ArrowDown, egui::Modifiers::CTRL | egui::Modifiers::ALT; b"\x1b[1;7B"; + ArrowLeft, egui::Modifiers::CTRL | egui::Modifiers::ALT; b"\x1b[1;7D"; + ArrowRight, egui::Modifiers::CTRL | egui::Modifiers::ALT; b"\x1b[1;7C"; + // SHIFT + CTRL + ALT + End, egui::Modifiers::SHIFT | egui::Modifiers::CTRL | egui::Modifiers::ALT; b"\x1b[1;8F"; + Home, egui::Modifiers::SHIFT | egui::Modifiers::CTRL | egui::Modifiers::ALT; b"\x1b[1;8H"; + ArrowUp, egui::Modifiers::SHIFT | egui::Modifiers::CTRL | egui::Modifiers::ALT; b"\x1b[1;8A"; + ArrowDown, egui::Modifiers::SHIFT | egui::Modifiers::CTRL | egui::Modifiers::ALT; b"\x1b[1;8B"; + ArrowLeft, egui::Modifiers::SHIFT | egui::Modifiers::CTRL | egui::Modifiers::ALT; b"\x1b[1;8D"; + ArrowRight, egui::Modifiers::SHIFT | egui::Modifiers::CTRL | egui::Modifiers::ALT; b"\x1b[1;8C"; + } +} diff --git a/crates/term/src/widget/mod.rs b/crates/term/src/widget/mod.rs index ecd0f2d9..2c93b34d 100644 --- a/crates/term/src/widget/mod.rs +++ b/crates/term/src/widget/mod.rs @@ -22,17 +22,22 @@ // terms of the Steamworks API by Valve Corporation, the licensors of this // Program grant you additional permission to convey the resulting work. -use alacritty_terminal::grid::Dimensions; +use alacritty_terminal::event::Event; +use alacritty_terminal::term::TermMode; +use alacritty_terminal::{event_loop::Msg, grid::Dimensions}; use crate::backends::Backend; +mod keys; + mod theme; pub use theme::Theme; pub struct Terminal { backend: T, theme: Theme, // TODO convert into shared config (possibly do this in luminol-preferences) - title: String, + pub id: egui::Id, + pub title: String, } pub type ProcessTerminal = Terminal; @@ -42,6 +47,7 @@ impl Terminal { fn new(backend: T) -> Self { Self { backend, + id: egui::Id::new("luminol_term_terminal"), theme: Theme::default(), title: "Luminol Terminal".to_string(), } @@ -65,14 +71,6 @@ impl Terminal where T: Backend, { - pub fn title(&self) -> String { - self.title.to_string() - } - - pub fn id(&self) -> egui::Id { - egui::Id::new("luminol_term_terminal").with(&self.title) - } - pub fn set_size(&mut self, cols: usize, lines: usize) { self.backend.resize(lines, cols) } @@ -116,8 +114,20 @@ where pub fn ui(&mut self, ui: &mut egui::Ui) -> color_eyre::Result<()> { self.backend.update(); + self.backend.with_event_recv(|recv| { + // + for event in recv.try_iter() { + match event { + Event::Title(title) => self.title = title, + Event::ResetTitle => "Luminol Terminal".clone_into(&mut self.title), + + _ => {} + } + } + }); + // TODO cache render jobs - let job = self.backend.with_term(|term| { + let (job, term_mode) = self.backend.with_term(|term| { let content = term.renderable_content(); let mut job = egui::text::LayoutJob::default(); @@ -125,10 +135,19 @@ where let mut buf = [0; 4]; let text = cell.c.encode_utf8(&mut buf); + let (color, background) = if cell.point == term.grid().cursor.point { + (egui::Color32::BLACK, egui::Color32::WHITE) + } else { + ( + self.theme.get_ansi_color(cell.fg), + self.theme.get_ansi_color(cell.bg), + ) + }; + let format = egui::TextFormat { font_id: egui::FontId::monospace(12.), - color: self.theme.get_ansi_color(cell.fg), - background: self.theme.get_ansi_color(cell.bg), + color, + background, ..Default::default() }; @@ -139,7 +158,7 @@ where } } - job + (job, *term.mode()) }); let galley = ui.fonts(|f| f.layout_job(job)); @@ -152,13 +171,71 @@ where egui::Color32::from_rgb(40, 39, 39), ); - painter.galley(response.rect.min, galley, egui::Color32::WHITE); + painter.galley(response.rect.min, galley.clone(), egui::Color32::WHITE); + + let id = response.id; + let event_filter = egui::EventFilter { + tab: true, + horizontal_arrows: true, + vertical_arrows: true, + escape: true, + }; if response.hovered() { ui.output_mut(|o| o.mutable_text_under_cursor = true); ui.ctx().set_cursor_icon(egui::CursorIcon::Text); } + if response.clicked() && !response.lost_focus() { + ui.memory_mut(|mem| mem.request_focus(id)) + } + + if ui.memory(|mem| mem.has_focus(id)) { + ui.memory_mut(|mem| mem.set_focus_lock_filter(id, event_filter)); + + let (events, modifiers) = ui.input(|i| (i.filtered_events(&event_filter), i.modifiers)); + + for event in events { + match event { + egui::Event::Paste(text) | egui::Event::Text(text) => { + let bytes = text.into_bytes(); + let cow = std::borrow::Cow::Owned(bytes); + self.backend.send(Msg::Input(cow)); + } + egui::Event::Key { + key, pressed: true, .. + } => { + if let Some(bytes) = keys::key_to_codes(key, modifiers, term_mode) { + let cow = std::borrow::Cow::Borrowed(bytes); + self.backend.send(Msg::Input(cow)); + } + } + egui::Event::Scroll(scroll_delta) => { + let delta = scroll_delta.y as i32; + if term_mode.contains(TermMode::ALT_SCREEN | TermMode::ALTERNATE_SCROLL) { + let line_cmd = if delta.is_positive() { b'A' } else { b'B' }; + let mut bytes = vec![]; + + for _ in 0..delta.abs() { + bytes.push(0x1b); + bytes.push(b'O'); + bytes.push(line_cmd); + } + + let cow = std::borrow::Cow::Owned(bytes); + self.backend.send(Msg::Input(cow)); + } else { + self.backend.with_term(|term| { + term.grid_mut() + .scroll_display(alacritty_terminal::grid::Scroll::Delta(delta)); + }); + } + } + _ => {} + } + } + } + Ok(()) } diff --git a/crates/ui/src/windows/console.rs b/crates/ui/src/windows/console.rs index 6c44bebe..5e090169 100644 --- a/crates/ui/src/windows/console.rs +++ b/crates/ui/src/windows/console.rs @@ -37,11 +37,11 @@ impl Window { impl luminol_core::Window for Window { fn name(&self) -> String { - self.term.title() + self.term.title.clone() } fn id(&self) -> egui::Id { - self.term.id() + self.term.id } fn requires_filesystem(&self) -> bool { @@ -55,7 +55,7 @@ impl luminol_core::Window for Window { update_state: &mut luminol_core::UpdateState<'_>, ) { egui::Window::new(self.name()) - .id(self.term.id()) + .id(self.term.id) .open(open) .resizable(false) .show(ctx, |ui| { diff --git a/src/app/log_window.rs b/src/app/log_window.rs index 8029d035..d69df25d 100644 --- a/src/app/log_window.rs +++ b/src/app/log_window.rs @@ -43,7 +43,7 @@ impl LogWindow { self.term.update(); egui::Window::new("Log") - .id(self.term.id()) + .id(self.term.id) .open(&mut self.term_shown) .resizable(false) .show(ui.ctx(), |ui| {