Add bracketed paste mode support for handling pasted text as verbatim input
This commit is contained in:
@@ -297,7 +297,7 @@ pub fn read_key(node: Node) -> ShResult<()> {
|
||||
}
|
||||
Ok(n) => {
|
||||
let mut reader = PollReader::new();
|
||||
reader.feed_bytes(&buf[..n], false);
|
||||
reader.feed_bytes(&buf[..n]);
|
||||
let Some(key) = reader.read_key()? else {
|
||||
state::set_status(1);
|
||||
return Ok(());
|
||||
|
||||
@@ -32,6 +32,7 @@ use crate::libsh::sys::TTY_FILENO;
|
||||
use crate::libsh::utils::AutoCmdVecUtils;
|
||||
use crate::parse::execute::exec_input;
|
||||
use crate::prelude::*;
|
||||
use crate::procio::borrow_fd;
|
||||
use crate::readline::term::{LineWriter, RawModeGuard, raw_mode};
|
||||
use crate::readline::{Prompt, ReadlineEvent, ShedVi};
|
||||
use crate::signal::{GOT_SIGWINCH, JOB_DONE, QUIT_CODE, check_signals, sig_setup, signals_pending};
|
||||
@@ -131,7 +132,9 @@ fn main() -> ExitCode {
|
||||
} else if let Some(cmd) = args.command {
|
||||
exec_input(cmd, None, false, None)
|
||||
} else {
|
||||
shed_interactive(args)
|
||||
let res = shed_interactive(args);
|
||||
write(borrow_fd(*TTY_FILENO), b"\x1b[?2004l").ok(); // disable bracketed paste mode on exit
|
||||
res
|
||||
} {
|
||||
e.print_error();
|
||||
};
|
||||
@@ -201,6 +204,8 @@ fn shed_interactive(args: ShedArgs) -> ShResult<()> {
|
||||
}
|
||||
};
|
||||
|
||||
readline.writer.flush_write("\x1b[?2004h")?; // enable bracketed paste mode
|
||||
|
||||
// Main poll loop
|
||||
loop {
|
||||
write_meta(|m| {
|
||||
|
||||
@@ -2184,7 +2184,7 @@ impl LineBuf {
|
||||
let Some(pos) = self.find_next_matching_delim() else {
|
||||
return MotionKind::Null;
|
||||
};
|
||||
MotionKind::On(pos)
|
||||
MotionKind::Onto(pos)
|
||||
}
|
||||
MotionCmd(_, Motion::ToBrace(direction))
|
||||
| MotionCmd(_, Motion::ToBracket(direction))
|
||||
|
||||
@@ -300,8 +300,7 @@ impl ShedVi {
|
||||
|
||||
/// Feed raw bytes from stdin into the reader's buffer
|
||||
pub fn feed_bytes(&mut self, bytes: &[u8]) {
|
||||
let verbatim = self.mode.report_mode() == ModeReport::Verbatim;
|
||||
self.reader.feed_bytes(bytes, verbatim);
|
||||
self.reader.feed_bytes(bytes);
|
||||
}
|
||||
|
||||
/// Mark that the display needs to be redrawn (e.g., after SIGWINCH)
|
||||
@@ -405,6 +404,7 @@ impl ShedVi {
|
||||
|
||||
// Process all available keys
|
||||
while let Some(key) = self.reader.read_key()? {
|
||||
log::debug!("Read key: {key:?} in mode {:?}, self.reader.verbatim = {}", self.mode.report_mode(), self.reader.verbatim);
|
||||
// If completer or history search are active, delegate input to it
|
||||
if self.history.fuzzy_finder.is_active() {
|
||||
self.print_line(false)?;
|
||||
@@ -1048,7 +1048,7 @@ impl ShedVi {
|
||||
|
||||
Verb::ExMode => Box::new(ViEx::new()),
|
||||
|
||||
Verb::VerbatimMode => Box::new(ViVerbatim::new().with_count(count as u16)),
|
||||
Verb::VerbatimMode => Box::new(ViVerbatim::read_one().with_count(count as u16)),
|
||||
|
||||
Verb::NormalMode => Box::new(ViNormal::new()),
|
||||
|
||||
|
||||
@@ -444,6 +444,8 @@ impl Perform for KeyCollector {
|
||||
21 => KeyCode::F(10),
|
||||
23 => KeyCode::F(11),
|
||||
24 => KeyCode::F(12),
|
||||
200 => KeyCode::BracketedPasteStart,
|
||||
201 => KeyCode::BracketedPasteEnd,
|
||||
_ => return,
|
||||
};
|
||||
KeyEvent(key, mods)
|
||||
@@ -496,6 +498,8 @@ impl Perform for KeyCollector {
|
||||
pub struct PollReader {
|
||||
parser: Parser,
|
||||
collector: KeyCollector,
|
||||
byte_buf: VecDeque<u8>,
|
||||
pub verbatim: bool,
|
||||
}
|
||||
|
||||
impl PollReader {
|
||||
@@ -503,26 +507,33 @@ impl PollReader {
|
||||
Self {
|
||||
parser: Parser::new(),
|
||||
collector: KeyCollector::new(),
|
||||
byte_buf: VecDeque::new(),
|
||||
verbatim: false,
|
||||
}
|
||||
}
|
||||
|
||||
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()));
|
||||
} else {
|
||||
// Feed all bytes through vte parser
|
||||
self.parser.advance(&mut self.collector, bytes);
|
||||
pub fn handle_bracket_paste(&mut self) -> Option<KeyEvent> {
|
||||
let end_marker = b"\x1b[201~";
|
||||
let mut raw = vec![];
|
||||
while let Some(byte) = self.byte_buf.pop_front() {
|
||||
raw.push(byte);
|
||||
if raw.ends_with(end_marker) {
|
||||
// Strip the end marker from the raw sequence
|
||||
raw.truncate(raw.len() - end_marker.len());
|
||||
let paste = String::from_utf8_lossy(&raw).to_string();
|
||||
self.verbatim = false;
|
||||
return Some(KeyEvent(KeyCode::Verbatim(paste.into()), ModKeys::empty()));
|
||||
}
|
||||
}
|
||||
|
||||
self.verbatim = true;
|
||||
self.byte_buf.extend(raw);
|
||||
None
|
||||
}
|
||||
|
||||
pub fn feed_bytes(&mut self, bytes: &[u8]) {
|
||||
self.byte_buf.extend(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PollReader {
|
||||
@@ -533,7 +544,34 @@ impl Default for PollReader {
|
||||
|
||||
impl KeyReader for PollReader {
|
||||
fn read_key(&mut self) -> Result<Option<KeyEvent>, ShErr> {
|
||||
Ok(self.collector.pop())
|
||||
if self.verbatim {
|
||||
if let Some(paste) = self.handle_bracket_paste() {
|
||||
return Ok(Some(paste));
|
||||
}
|
||||
// If we're in verbatim mode but haven't seen the end marker yet, don't attempt to parse keys
|
||||
return Ok(None);
|
||||
} else if self.byte_buf.len() == 1
|
||||
&& self.byte_buf.front() == Some(&b'\x1b') {
|
||||
// User pressed escape
|
||||
self.byte_buf.pop_front(); // Consume the escape byte
|
||||
return Ok(Some(KeyEvent(KeyCode::Esc, ModKeys::empty())));
|
||||
}
|
||||
while let Some(byte) = self.byte_buf.pop_front() {
|
||||
self.parser.advance(&mut self.collector, &[byte]);
|
||||
if let Some(key) = self.collector.pop() {
|
||||
match key {
|
||||
KeyEvent(KeyCode::BracketedPasteStart, _) => {
|
||||
if let Some(paste) = self.handle_bracket_paste() {
|
||||
return Ok(Some(paste));
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
_ => return Ok(Some(key))
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,10 @@ impl ViMode for ViInsert {
|
||||
raw_seq: String::new(),
|
||||
flags: Default::default(),
|
||||
}),
|
||||
E(K::Verbatim(seq), _) => {
|
||||
self.pending_cmd.set_verb(VerbCmd(1, Verb::Insert(seq.to_string())));
|
||||
self.register_and_return()
|
||||
}
|
||||
E(K::Char('W'), M::CTRL) => {
|
||||
self.pending_cmd.set_verb(VerbCmd(1, Verb::Delete));
|
||||
self.pending_cmd.set_motion(MotionCmd(
|
||||
|
||||
@@ -4,11 +4,19 @@ use crate::readline::vicmd::{CmdFlags, RegisterName, To, Verb, VerbCmd, ViCmd};
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct ViVerbatim {
|
||||
pending_seq: String,
|
||||
sent_cmd: Vec<ViCmd>,
|
||||
repeat_count: u16,
|
||||
read_one: bool
|
||||
}
|
||||
|
||||
impl ViVerbatim {
|
||||
pub fn read_one() -> Self {
|
||||
Self {
|
||||
read_one: true,
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
@@ -23,7 +31,7 @@ impl ViVerbatim {
|
||||
impl ViMode for ViVerbatim {
|
||||
fn handle_key(&mut self, key: E) -> Option<ViCmd> {
|
||||
match key {
|
||||
E(K::Verbatim(seq), _mods) => {
|
||||
E(K::Verbatim(seq), _mods) if self.read_one => {
|
||||
log::debug!("Received verbatim key sequence: {:?}", seq);
|
||||
let cmd = ViCmd {
|
||||
register: RegisterName::default(),
|
||||
@@ -35,6 +43,22 @@ impl ViMode for ViVerbatim {
|
||||
self.sent_cmd.push(cmd.clone());
|
||||
Some(cmd)
|
||||
}
|
||||
E(K::Verbatim(seq), _mods) => {
|
||||
self.pending_seq.push_str(&seq);
|
||||
None
|
||||
}
|
||||
E(K::BracketedPasteEnd, _mods) => {
|
||||
log::debug!("Received verbatim paste: {:?}", self.pending_seq);
|
||||
let cmd = ViCmd {
|
||||
register: RegisterName::default(),
|
||||
verb: Some(VerbCmd(1, Verb::Insert(self.pending_seq.clone()))),
|
||||
motion: None,
|
||||
raw_seq: std::mem::take(&mut self.pending_seq),
|
||||
flags: CmdFlags::EXIT_CUR_MODE,
|
||||
};
|
||||
self.sent_cmd.push(cmd.clone());
|
||||
Some(cmd)
|
||||
}
|
||||
_ => common_cmds(key),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user