diff --git a/src/builtin/keymap.rs b/src/builtin/keymap.rs index a5ddb35..7642f7a 100644 --- a/src/builtin/keymap.rs +++ b/src/builtin/keymap.rs @@ -11,6 +11,7 @@ bitflags! { const EX = 0b0001000; const OP_PENDING = 0b0010000; const REPLACE = 0b0100000; + const VERBATIM = 0b1000000; } } diff --git a/src/builtin/read.rs b/src/builtin/read.rs index 956ac61..9d7875f 100644 --- a/src/builtin/read.rs +++ b/src/builtin/read.rs @@ -284,7 +284,7 @@ pub fn read_key(node: Node) -> ShResult<()> { } Ok(n) => { let mut reader = PollReader::new(); - reader.feed_bytes(&buf[..n]); + reader.feed_bytes(&buf[..n], false); let Some(key) = reader.read_key()? else { state::set_status(1); return Ok(()); diff --git a/src/readline/keys.rs b/src/readline/keys.rs index 6c3a776..9f66579 100644 --- a/src/readline/keys.rs +++ b/src/readline/keys.rs @@ -21,7 +21,7 @@ impl KeyEvent { // If more than one grapheme, it's not a single key event if graphemes.next().is_some() { - return E(K::Null, mods); // Or panic, or wrap in Grapheme if desired + return E(K::Null, mods); } let mut chars = first.chars(); @@ -184,6 +184,7 @@ impl KeyEvent { seq.push(*ch); } KeyCode::Grapheme(gr) => seq.push_str(gr), + KeyCode::Verbatim(s) => seq.push_str(s), } if needs_angle_bracket { @@ -203,6 +204,7 @@ pub enum KeyCode { BracketedPasteEnd, Char(char), Grapheme(Arc), + Verbatim(Arc), // For sequences that should be treated as literal input, not parsed into a KeyCode Delete, Down, End, diff --git a/src/readline/linebuf.rs b/src/readline/linebuf.rs index 12d215f..e1b4e16 100644 --- a/src/readline/linebuf.rs +++ b/src/readline/linebuf.rs @@ -1991,13 +1991,19 @@ impl LineBuf { .slice_to_cursor() .map(|s| s.to_string()) .unwrap_or(self.buffer.clone()); + + let mut level: usize = 0; + + if to_cursor.ends_with("\\\n") { + level += 1; // Line continuation, so we need to add an extra level + } + let input = Arc::new(to_cursor); let Ok(tokens) = LexStream::new(input, LexFlags::LEX_UNFINISHED).collect::>>() else { log::error!("Failed to lex buffer for indent calculation"); return; }; - let mut level: usize = 0; let mut last_keyword: Option = None; for tk in tokens { if tk.flags.contains(TkFlags::KEYWORD) { @@ -3095,6 +3101,7 @@ impl LineBuf { | Verb::InsertMode | Verb::NormalMode | Verb::VisualMode + | Verb::VerbatimMode | Verb::ReplaceMode | Verb::VisualModeLine | Verb::VisualModeBlock diff --git a/src/readline/mod.rs b/src/readline/mod.rs index cb0f197..6bf285a 100644 --- a/src/readline/mod.rs +++ b/src/readline/mod.rs @@ -15,7 +15,7 @@ use crate::parse::lex::{LexStream, QuoteState}; use crate::{prelude::*, state}; use crate::readline::complete::FuzzyCompleter; use crate::readline::term::{Pos, TermReader, calc_str_width}; -use crate::readline::vimode::ViEx; +use crate::readline::vimode::{ViEx, ViVerbatim}; use crate::state::{AutoCmdKind, ShellParam, VarFlags, VarKind, read_logic, read_shopts, with_vars, write_meta, write_vars}; use crate::{ libsh::error::ShResult, @@ -269,7 +269,8 @@ impl ShedVi { /// Feed raw bytes from stdin into the reader's buffer pub fn feed_bytes(&mut self, bytes: &[u8]) { - self.reader.feed_bytes(bytes); + let verbatim = self.mode.report_mode() == ModeReport::Verbatim; + self.reader.feed_bytes(bytes, verbatim); } /// Mark that the display needs to be redrawn (e.g., after SIGWINCH) @@ -323,6 +324,7 @@ impl ShedVi { ModeReport::Ex => flags |= KeyMapFlags::EX, ModeReport::Visual => flags |= KeyMapFlags::VISUAL, ModeReport::Replace => flags |= KeyMapFlags::REPLACE, + ModeReport::Verbatim => flags |= KeyMapFlags::VERBATIM, ModeReport::Unknown => todo!(), } @@ -835,6 +837,10 @@ impl ShedVi { Box::new(ViEx::new()) } + Verb::VerbatimMode => { + Box::new(ViVerbatim::new().with_count(count as u16)) + } + Verb::NormalMode => Box::new(ViNormal::new()), Verb::ReplaceMode => Box::new(ViReplace::new()), @@ -863,10 +869,9 @@ impl ShedVi { } }; - self.swap_mode(&mut mode); - if self.mode.report_mode() == ModeReport::Ex { + if matches!(self.mode.report_mode(), ModeReport::Ex | ModeReport::Verbatim) { self.saved_mode = Some(mode); write_vars(|v| v.set_var("SHED_VI_MODE", VarKind::Str(self.mode.report_mode().to_string()), VarFlags::NONE))?; self.prompt.refresh()?; @@ -1007,7 +1012,7 @@ impl ShedVi { } if cmd.flags.contains(CmdFlags::EXIT_CUR_MODE) { - let mut mode: Box = if self.mode.report_mode() == ModeReport::Ex { + let mut mode: Box = if matches!(self.mode.report_mode(), ModeReport::Ex | ModeReport::Verbatim) { if let Some(saved) = self.saved_mode.take() { saved } else { diff --git a/src/readline/term.rs b/src/readline/term.rs index f3a2504..b4131b3 100644 --- a/src/readline/term.rs +++ b/src/readline/term.rs @@ -3,7 +3,7 @@ use std::{ env, fmt::{Debug, Write}, io::{BufRead, BufReader, Read}, - os::fd::{AsFd, BorrowedFd, RawFd}, + os::fd::{AsFd, BorrowedFd, RawFd}, sync::Arc, }; use nix::{ @@ -457,6 +457,27 @@ impl Perform for KeyCollector { }; KeyEvent(key, mods) } + ([],'u') => { + let codepoint = params.first().copied().unwrap_or(0); + let mods = params + .get(1) + .map(|&m| Self::parse_modifiers(m)) + .unwrap_or(ModKeys::empty()); + let key = match codepoint { + 9 => KeyCode::Tab, + 13 => KeyCode::Enter, + 27 => KeyCode::Esc, + 127 => KeyCode::Backspace, + _ => { + if let Some(ch) = char::from_u32(codepoint as u32) { + KeyCode::Char(ch) + } else { + return + } + } + }; + KeyEvent(key, mods) + } // SGR mouse: CSI < button;x;y M/m (ignore mouse events for now) ([b'<'], 'M') | ([b'<'], 'm') => { return; @@ -494,17 +515,19 @@ impl PollReader { } } - pub fn feed_bytes(&mut self, bytes: &[u8]) { - if bytes == [b'\x1b'] { + pub fn feed_bytes(&mut self, bytes: &[u8], verbatim: bool) { + if verbatim { + let seq = String::from_utf8_lossy(bytes).to_string(); + self.collector.push(KeyEvent(KeyCode::Verbatim(Arc::from(seq.as_str())), ModKeys::empty())); + } else if bytes == [b'\x1b'] { // Single escape byte - user pressed ESC key self .collector .push(KeyEvent(KeyCode::Esc, ModKeys::empty())); - return; - } - - // Feed all bytes through vte parser - self.parser.advance(&mut self.collector, bytes); + } else { + // Feed all bytes through vte parser + self.parser.advance(&mut self.collector, bytes); + } } } diff --git a/src/readline/vicmd.rs b/src/readline/vicmd.rs index a9ca6fa..bce7756 100644 --- a/src/readline/vicmd.rs +++ b/src/readline/vicmd.rs @@ -178,6 +178,7 @@ impl ViCmd { matches!( v.1, Verb::Change + | Verb::VerbatimMode | Verb::ExMode | Verb::InsertMode | Verb::InsertModeLineBreak(_) @@ -231,6 +232,7 @@ pub enum Verb { RepeatLast, Put(Anchor), ReplaceMode, + VerbatimMode, InsertMode, InsertModeLineBreak(Anchor), NormalMode, diff --git a/src/readline/vimode/insert.rs b/src/readline/vimode/insert.rs index 4338435..e3c3670 100644 --- a/src/readline/vimode/insert.rs +++ b/src/readline/vimode/insert.rs @@ -64,6 +64,10 @@ impl ViMode for ViInsert { )); self.register_and_return() } + E(K::Char('V'), M::CTRL) => { + self.pending_cmd.set_verb(VerbCmd(1, Verb::VerbatimMode)); + self.register_and_return() + } E(K::Char('H'), M::CTRL) | E(K::Backspace, M::NONE) => { self.pending_cmd.set_verb(VerbCmd(1, Verb::Delete)); self diff --git a/src/readline/vimode/mod.rs b/src/readline/vimode/mod.rs index 959a31b..b77929a 100644 --- a/src/readline/vimode/mod.rs +++ b/src/readline/vimode/mod.rs @@ -13,12 +13,14 @@ pub mod normal; pub mod replace; pub mod visual; pub mod ex; +pub mod verbatim; pub use ex::ViEx; pub use insert::ViInsert; pub use normal::ViNormal; pub use replace::ViReplace; pub use visual::ViVisual; +pub use verbatim::ViVerbatim; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ModeReport { @@ -27,6 +29,7 @@ pub enum ModeReport { Ex, Visual, Replace, + Verbatim, Unknown, } @@ -38,6 +41,7 @@ impl Display for ModeReport { ModeReport::Ex => write!(f, "COMMAND"), ModeReport::Visual => write!(f, "VISUAL"), ModeReport::Replace => write!(f, "REPLACE"), + ModeReport::Verbatim => write!(f, "VERBATIM"), ModeReport::Unknown => write!(f, "UNKNOWN"), } } diff --git a/src/readline/vimode/verbatim.rs b/src/readline/vimode/verbatim.rs new file mode 100644 index 0000000..5f21ed9 --- /dev/null +++ b/src/readline/vimode/verbatim.rs @@ -0,0 +1,66 @@ +use super::{common_cmds, CmdReplay, ModeReport, ViMode}; +use crate::readline::keys::{KeyCode as K, KeyEvent as E, ModKeys as M}; +use crate::readline::register::Register; +use crate::readline::vicmd::{ + CmdFlags, Direction, Motion, MotionCmd, RegisterName, To, Verb, VerbCmd, ViCmd, Word +}; + +#[derive(Default, Clone, Debug)] +pub struct ViVerbatim { + sent_cmd: Vec, + repeat_count: u16 +} + +impl ViVerbatim { + pub fn new() -> Self { + Self::default() + } + pub fn with_count(self, repeat_count: u16) -> Self { + Self { repeat_count, ..self } + } +} + +impl ViMode for ViVerbatim { + fn handle_key(&mut self, key: E) -> Option { + match key { + E(K::Verbatim(seq),_mods) => { + let cmd = ViCmd { register: RegisterName::default(), + verb: Some(VerbCmd(1,Verb::Insert(seq.to_string()))), + motion: None, + raw_seq: seq.to_string(), + flags: CmdFlags::EXIT_CUR_MODE + }; + self.sent_cmd.push(cmd.clone()); + Some(cmd) + } + _ => common_cmds(key), + } + } + + fn is_repeatable(&self) -> bool { + true + } + + fn as_replay(&self) -> Option { + Some(CmdReplay::mode(self.sent_cmd.clone(), self.repeat_count)) + } + + fn cursor_style(&self) -> String { + "\x1b[6 q".to_string() + } + fn pending_seq(&self) -> Option { + None + } + fn move_cursor_on_undo(&self) -> bool { + true + } + fn clamp_cursor(&self) -> bool { + false + } + fn hist_scroll_start_pos(&self) -> Option { + Some(To::End) + } + fn report_mode(&self) -> ModeReport { + ModeReport::Insert + } +}