Early implementation of Verbatim mode

This commit is contained in:
2026-03-04 15:26:54 -05:00
parent 12267716be
commit 553330a858
10 changed files with 130 additions and 16 deletions

View File

@@ -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<str>),
Verbatim(Arc<str>), // For sequences that should be treated as literal input, not parsed into a KeyCode
Delete,
Down,
End,

View File

@@ -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::<ShResult<Vec<Tk>>>()
else {
log::error!("Failed to lex buffer for indent calculation");
return;
};
let mut level: usize = 0;
let mut last_keyword: Option<String> = 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

View File

@@ -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<dyn ViMode> = if self.mode.report_mode() == ModeReport::Ex {
let mut mode: Box<dyn ViMode> = if matches!(self.mode.report_mode(), ModeReport::Ex | ModeReport::Verbatim) {
if let Some(saved) = self.saved_mode.take() {
saved
} else {

View File

@@ -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);
}
}
}

View File

@@ -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,

View File

@@ -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

View File

@@ -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"),
}
}

View File

@@ -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<ViCmd>,
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<ViCmd> {
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<CmdReplay> {
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<String> {
None
}
fn move_cursor_on_undo(&self) -> bool {
true
}
fn clamp_cursor(&self) -> bool {
false
}
fn hist_scroll_start_pos(&self) -> Option<To> {
Some(To::End)
}
fn report_mode(&self) -> ModeReport {
ModeReport::Insert
}
}