Early implementation of Verbatim mode
This commit is contained in:
@@ -11,6 +11,7 @@ bitflags! {
|
||||
const EX = 0b0001000;
|
||||
const OP_PENDING = 0b0010000;
|
||||
const REPLACE = 0b0100000;
|
||||
const VERBATIM = 0b1000000;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(());
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,18 +515,20 @@ 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;
|
||||
}
|
||||
|
||||
} else {
|
||||
// Feed all bytes through vte parser
|
||||
self.parser.advance(&mut self.collector, bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PollReader {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"),
|
||||
}
|
||||
}
|
||||
|
||||
66
src/readline/vimode/verbatim.rs
Normal file
66
src/readline/vimode/verbatim.rs
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user