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

@@ -11,6 +11,7 @@ bitflags! {
const EX = 0b0001000; const EX = 0b0001000;
const OP_PENDING = 0b0010000; const OP_PENDING = 0b0010000;
const REPLACE = 0b0100000; const REPLACE = 0b0100000;
const VERBATIM = 0b1000000;
} }
} }

View File

@@ -284,7 +284,7 @@ pub fn read_key(node: Node) -> ShResult<()> {
} }
Ok(n) => { Ok(n) => {
let mut reader = PollReader::new(); let mut reader = PollReader::new();
reader.feed_bytes(&buf[..n]); reader.feed_bytes(&buf[..n], false);
let Some(key) = reader.read_key()? else { let Some(key) = reader.read_key()? else {
state::set_status(1); state::set_status(1);
return Ok(()); return Ok(());

View File

@@ -21,7 +21,7 @@ impl KeyEvent {
// If more than one grapheme, it's not a single key event // If more than one grapheme, it's not a single key event
if graphemes.next().is_some() { 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(); let mut chars = first.chars();
@@ -184,6 +184,7 @@ impl KeyEvent {
seq.push(*ch); seq.push(*ch);
} }
KeyCode::Grapheme(gr) => seq.push_str(gr), KeyCode::Grapheme(gr) => seq.push_str(gr),
KeyCode::Verbatim(s) => seq.push_str(s),
} }
if needs_angle_bracket { if needs_angle_bracket {
@@ -203,6 +204,7 @@ pub enum KeyCode {
BracketedPasteEnd, BracketedPasteEnd,
Char(char), Char(char),
Grapheme(Arc<str>), Grapheme(Arc<str>),
Verbatim(Arc<str>), // For sequences that should be treated as literal input, not parsed into a KeyCode
Delete, Delete,
Down, Down,
End, End,

View File

@@ -1991,13 +1991,19 @@ impl LineBuf {
.slice_to_cursor() .slice_to_cursor()
.map(|s| s.to_string()) .map(|s| s.to_string())
.unwrap_or(self.buffer.clone()); .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 input = Arc::new(to_cursor);
let Ok(tokens) = LexStream::new(input, LexFlags::LEX_UNFINISHED).collect::<ShResult<Vec<Tk>>>() let Ok(tokens) = LexStream::new(input, LexFlags::LEX_UNFINISHED).collect::<ShResult<Vec<Tk>>>()
else { else {
log::error!("Failed to lex buffer for indent calculation"); log::error!("Failed to lex buffer for indent calculation");
return; return;
}; };
let mut level: usize = 0;
let mut last_keyword: Option<String> = None; let mut last_keyword: Option<String> = None;
for tk in tokens { for tk in tokens {
if tk.flags.contains(TkFlags::KEYWORD) { if tk.flags.contains(TkFlags::KEYWORD) {
@@ -3095,6 +3101,7 @@ impl LineBuf {
| Verb::InsertMode | Verb::InsertMode
| Verb::NormalMode | Verb::NormalMode
| Verb::VisualMode | Verb::VisualMode
| Verb::VerbatimMode
| Verb::ReplaceMode | Verb::ReplaceMode
| Verb::VisualModeLine | Verb::VisualModeLine
| Verb::VisualModeBlock | Verb::VisualModeBlock

View File

@@ -15,7 +15,7 @@ use crate::parse::lex::{LexStream, QuoteState};
use crate::{prelude::*, state}; use crate::{prelude::*, state};
use crate::readline::complete::FuzzyCompleter; use crate::readline::complete::FuzzyCompleter;
use crate::readline::term::{Pos, TermReader, calc_str_width}; 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::state::{AutoCmdKind, ShellParam, VarFlags, VarKind, read_logic, read_shopts, with_vars, write_meta, write_vars};
use crate::{ use crate::{
libsh::error::ShResult, libsh::error::ShResult,
@@ -269,7 +269,8 @@ impl ShedVi {
/// Feed raw bytes from stdin into the reader's buffer /// Feed raw bytes from stdin into the reader's buffer
pub fn feed_bytes(&mut self, bytes: &[u8]) { 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) /// Mark that the display needs to be redrawn (e.g., after SIGWINCH)
@@ -323,6 +324,7 @@ impl ShedVi {
ModeReport::Ex => flags |= KeyMapFlags::EX, ModeReport::Ex => flags |= KeyMapFlags::EX,
ModeReport::Visual => flags |= KeyMapFlags::VISUAL, ModeReport::Visual => flags |= KeyMapFlags::VISUAL,
ModeReport::Replace => flags |= KeyMapFlags::REPLACE, ModeReport::Replace => flags |= KeyMapFlags::REPLACE,
ModeReport::Verbatim => flags |= KeyMapFlags::VERBATIM,
ModeReport::Unknown => todo!(), ModeReport::Unknown => todo!(),
} }
@@ -835,6 +837,10 @@ impl ShedVi {
Box::new(ViEx::new()) Box::new(ViEx::new())
} }
Verb::VerbatimMode => {
Box::new(ViVerbatim::new().with_count(count as u16))
}
Verb::NormalMode => Box::new(ViNormal::new()), Verb::NormalMode => Box::new(ViNormal::new()),
Verb::ReplaceMode => Box::new(ViReplace::new()), Verb::ReplaceMode => Box::new(ViReplace::new()),
@@ -863,10 +869,9 @@ impl ShedVi {
} }
}; };
self.swap_mode(&mut mode); 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); self.saved_mode = Some(mode);
write_vars(|v| v.set_var("SHED_VI_MODE", VarKind::Str(self.mode.report_mode().to_string()), VarFlags::NONE))?; write_vars(|v| v.set_var("SHED_VI_MODE", VarKind::Str(self.mode.report_mode().to_string()), VarFlags::NONE))?;
self.prompt.refresh()?; self.prompt.refresh()?;
@@ -1007,7 +1012,7 @@ impl ShedVi {
} }
if cmd.flags.contains(CmdFlags::EXIT_CUR_MODE) { 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() { if let Some(saved) = self.saved_mode.take() {
saved saved
} else { } else {

View File

@@ -3,7 +3,7 @@ use std::{
env, env,
fmt::{Debug, Write}, fmt::{Debug, Write},
io::{BufRead, BufReader, Read}, io::{BufRead, BufReader, Read},
os::fd::{AsFd, BorrowedFd, RawFd}, os::fd::{AsFd, BorrowedFd, RawFd}, sync::Arc,
}; };
use nix::{ use nix::{
@@ -457,6 +457,27 @@ impl Perform for KeyCollector {
}; };
KeyEvent(key, mods) 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) // SGR mouse: CSI < button;x;y M/m (ignore mouse events for now)
([b'<'], 'M') | ([b'<'], 'm') => { ([b'<'], 'M') | ([b'<'], 'm') => {
return; return;
@@ -494,17 +515,19 @@ impl PollReader {
} }
} }
pub fn feed_bytes(&mut self, bytes: &[u8]) { pub fn feed_bytes(&mut self, bytes: &[u8], verbatim: bool) {
if bytes == [b'\x1b'] { 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 // Single escape byte - user pressed ESC key
self self
.collector .collector
.push(KeyEvent(KeyCode::Esc, ModKeys::empty())); .push(KeyEvent(KeyCode::Esc, ModKeys::empty()));
return; } else {
} // Feed all bytes through vte parser
self.parser.advance(&mut self.collector, bytes);
// Feed all bytes through vte parser }
self.parser.advance(&mut self.collector, bytes);
} }
} }

View File

@@ -178,6 +178,7 @@ impl ViCmd {
matches!( matches!(
v.1, v.1,
Verb::Change Verb::Change
| Verb::VerbatimMode
| Verb::ExMode | Verb::ExMode
| Verb::InsertMode | Verb::InsertMode
| Verb::InsertModeLineBreak(_) | Verb::InsertModeLineBreak(_)
@@ -231,6 +232,7 @@ pub enum Verb {
RepeatLast, RepeatLast,
Put(Anchor), Put(Anchor),
ReplaceMode, ReplaceMode,
VerbatimMode,
InsertMode, InsertMode,
InsertModeLineBreak(Anchor), InsertModeLineBreak(Anchor),
NormalMode, NormalMode,

View File

@@ -64,6 +64,10 @@ impl ViMode for ViInsert {
)); ));
self.register_and_return() 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) => { E(K::Char('H'), M::CTRL) | E(K::Backspace, M::NONE) => {
self.pending_cmd.set_verb(VerbCmd(1, Verb::Delete)); self.pending_cmd.set_verb(VerbCmd(1, Verb::Delete));
self self

View File

@@ -13,12 +13,14 @@ pub mod normal;
pub mod replace; pub mod replace;
pub mod visual; pub mod visual;
pub mod ex; pub mod ex;
pub mod verbatim;
pub use ex::ViEx; pub use ex::ViEx;
pub use insert::ViInsert; pub use insert::ViInsert;
pub use normal::ViNormal; pub use normal::ViNormal;
pub use replace::ViReplace; pub use replace::ViReplace;
pub use visual::ViVisual; pub use visual::ViVisual;
pub use verbatim::ViVerbatim;
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ModeReport { pub enum ModeReport {
@@ -27,6 +29,7 @@ pub enum ModeReport {
Ex, Ex,
Visual, Visual,
Replace, Replace,
Verbatim,
Unknown, Unknown,
} }
@@ -38,6 +41,7 @@ impl Display for ModeReport {
ModeReport::Ex => write!(f, "COMMAND"), ModeReport::Ex => write!(f, "COMMAND"),
ModeReport::Visual => write!(f, "VISUAL"), ModeReport::Visual => write!(f, "VISUAL"),
ModeReport::Replace => write!(f, "REPLACE"), ModeReport::Replace => write!(f, "REPLACE"),
ModeReport::Verbatim => write!(f, "VERBATIM"),
ModeReport::Unknown => write!(f, "UNKNOWN"), 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
}
}