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) => {
|
Ok(n) => {
|
||||||
let mut reader = PollReader::new();
|
let mut reader = PollReader::new();
|
||||||
reader.feed_bytes(&buf[..n], false);
|
reader.feed_bytes(&buf[..n]);
|
||||||
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(());
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ use crate::libsh::sys::TTY_FILENO;
|
|||||||
use crate::libsh::utils::AutoCmdVecUtils;
|
use crate::libsh::utils::AutoCmdVecUtils;
|
||||||
use crate::parse::execute::exec_input;
|
use crate::parse::execute::exec_input;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::procio::borrow_fd;
|
||||||
use crate::readline::term::{LineWriter, RawModeGuard, raw_mode};
|
use crate::readline::term::{LineWriter, RawModeGuard, raw_mode};
|
||||||
use crate::readline::{Prompt, ReadlineEvent, ShedVi};
|
use crate::readline::{Prompt, ReadlineEvent, ShedVi};
|
||||||
use crate::signal::{GOT_SIGWINCH, JOB_DONE, QUIT_CODE, check_signals, sig_setup, signals_pending};
|
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 {
|
} else if let Some(cmd) = args.command {
|
||||||
exec_input(cmd, None, false, None)
|
exec_input(cmd, None, false, None)
|
||||||
} else {
|
} 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();
|
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
|
// Main poll loop
|
||||||
loop {
|
loop {
|
||||||
write_meta(|m| {
|
write_meta(|m| {
|
||||||
|
|||||||
@@ -2184,7 +2184,7 @@ impl LineBuf {
|
|||||||
let Some(pos) = self.find_next_matching_delim() else {
|
let Some(pos) = self.find_next_matching_delim() else {
|
||||||
return MotionKind::Null;
|
return MotionKind::Null;
|
||||||
};
|
};
|
||||||
MotionKind::On(pos)
|
MotionKind::Onto(pos)
|
||||||
}
|
}
|
||||||
MotionCmd(_, Motion::ToBrace(direction))
|
MotionCmd(_, Motion::ToBrace(direction))
|
||||||
| MotionCmd(_, Motion::ToBracket(direction))
|
| MotionCmd(_, Motion::ToBracket(direction))
|
||||||
|
|||||||
@@ -300,8 +300,7 @@ 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]) {
|
||||||
let verbatim = self.mode.report_mode() == ModeReport::Verbatim;
|
self.reader.feed_bytes(bytes);
|
||||||
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)
|
||||||
@@ -405,6 +404,7 @@ impl ShedVi {
|
|||||||
|
|
||||||
// Process all available keys
|
// Process all available keys
|
||||||
while let Some(key) = self.reader.read_key()? {
|
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 completer or history search are active, delegate input to it
|
||||||
if self.history.fuzzy_finder.is_active() {
|
if self.history.fuzzy_finder.is_active() {
|
||||||
self.print_line(false)?;
|
self.print_line(false)?;
|
||||||
@@ -1048,7 +1048,7 @@ impl ShedVi {
|
|||||||
|
|
||||||
Verb::ExMode => Box::new(ViEx::new()),
|
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()),
|
Verb::NormalMode => Box::new(ViNormal::new()),
|
||||||
|
|
||||||
|
|||||||
@@ -444,6 +444,8 @@ impl Perform for KeyCollector {
|
|||||||
21 => KeyCode::F(10),
|
21 => KeyCode::F(10),
|
||||||
23 => KeyCode::F(11),
|
23 => KeyCode::F(11),
|
||||||
24 => KeyCode::F(12),
|
24 => KeyCode::F(12),
|
||||||
|
200 => KeyCode::BracketedPasteStart,
|
||||||
|
201 => KeyCode::BracketedPasteEnd,
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
KeyEvent(key, mods)
|
KeyEvent(key, mods)
|
||||||
@@ -496,6 +498,8 @@ impl Perform for KeyCollector {
|
|||||||
pub struct PollReader {
|
pub struct PollReader {
|
||||||
parser: Parser,
|
parser: Parser,
|
||||||
collector: KeyCollector,
|
collector: KeyCollector,
|
||||||
|
byte_buf: VecDeque<u8>,
|
||||||
|
pub verbatim: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PollReader {
|
impl PollReader {
|
||||||
@@ -503,25 +507,32 @@ impl PollReader {
|
|||||||
Self {
|
Self {
|
||||||
parser: Parser::new(),
|
parser: Parser::new(),
|
||||||
collector: KeyCollector::new(),
|
collector: KeyCollector::new(),
|
||||||
|
byte_buf: VecDeque::new(),
|
||||||
|
verbatim: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn feed_bytes(&mut self, bytes: &[u8], verbatim: bool) {
|
pub fn handle_bracket_paste(&mut self) -> Option<KeyEvent> {
|
||||||
if verbatim {
|
let end_marker = b"\x1b[201~";
|
||||||
let seq = String::from_utf8_lossy(bytes).to_string();
|
let mut raw = vec![];
|
||||||
self.collector.push(KeyEvent(
|
while let Some(byte) = self.byte_buf.pop_front() {
|
||||||
KeyCode::Verbatim(Arc::from(seq.as_str())),
|
raw.push(byte);
|
||||||
ModKeys::empty(),
|
if raw.ends_with(end_marker) {
|
||||||
));
|
// Strip the end marker from the raw sequence
|
||||||
} else if bytes == [b'\x1b'] {
|
raw.truncate(raw.len() - end_marker.len());
|
||||||
// Single escape byte - user pressed ESC key
|
let paste = String::from_utf8_lossy(&raw).to_string();
|
||||||
self
|
self.verbatim = false;
|
||||||
.collector
|
return Some(KeyEvent(KeyCode::Verbatim(paste.into()), ModKeys::empty()));
|
||||||
.push(KeyEvent(KeyCode::Esc, ModKeys::empty()));
|
}
|
||||||
} else {
|
}
|
||||||
// Feed all bytes through vte parser
|
|
||||||
self.parser.advance(&mut self.collector, bytes);
|
self.verbatim = true;
|
||||||
}
|
self.byte_buf.extend(raw);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn feed_bytes(&mut self, bytes: &[u8]) {
|
||||||
|
self.byte_buf.extend(bytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -533,7 +544,34 @@ impl Default for PollReader {
|
|||||||
|
|
||||||
impl KeyReader for PollReader {
|
impl KeyReader for PollReader {
|
||||||
fn read_key(&mut self) -> Result<Option<KeyEvent>, ShErr> {
|
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(),
|
raw_seq: String::new(),
|
||||||
flags: Default::default(),
|
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) => {
|
E(K::Char('W'), M::CTRL) => {
|
||||||
self.pending_cmd.set_verb(VerbCmd(1, Verb::Delete));
|
self.pending_cmd.set_verb(VerbCmd(1, Verb::Delete));
|
||||||
self.pending_cmd.set_motion(MotionCmd(
|
self.pending_cmd.set_motion(MotionCmd(
|
||||||
|
|||||||
@@ -4,11 +4,19 @@ use crate::readline::vicmd::{CmdFlags, RegisterName, To, Verb, VerbCmd, ViCmd};
|
|||||||
|
|
||||||
#[derive(Default, Clone, Debug)]
|
#[derive(Default, Clone, Debug)]
|
||||||
pub struct ViVerbatim {
|
pub struct ViVerbatim {
|
||||||
|
pending_seq: String,
|
||||||
sent_cmd: Vec<ViCmd>,
|
sent_cmd: Vec<ViCmd>,
|
||||||
repeat_count: u16,
|
repeat_count: u16,
|
||||||
|
read_one: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ViVerbatim {
|
impl ViVerbatim {
|
||||||
|
pub fn read_one() -> Self {
|
||||||
|
Self {
|
||||||
|
read_one: true,
|
||||||
|
..Self::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
@@ -23,7 +31,7 @@ impl ViVerbatim {
|
|||||||
impl ViMode for ViVerbatim {
|
impl ViMode for ViVerbatim {
|
||||||
fn handle_key(&mut self, key: E) -> Option<ViCmd> {
|
fn handle_key(&mut self, key: E) -> Option<ViCmd> {
|
||||||
match key {
|
match key {
|
||||||
E(K::Verbatim(seq), _mods) => {
|
E(K::Verbatim(seq), _mods) if self.read_one => {
|
||||||
log::debug!("Received verbatim key sequence: {:?}", seq);
|
log::debug!("Received verbatim key sequence: {:?}", seq);
|
||||||
let cmd = ViCmd {
|
let cmd = ViCmd {
|
||||||
register: RegisterName::default(),
|
register: RegisterName::default(),
|
||||||
@@ -35,6 +43,22 @@ impl ViMode for ViVerbatim {
|
|||||||
self.sent_cmd.push(cmd.clone());
|
self.sent_cmd.push(cmd.clone());
|
||||||
Some(cmd)
|
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),
|
_ => common_cmds(key),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user