reimplemented ex-mode widget/function execution
This commit is contained in:
12
Cargo.lock
generated
12
Cargo.lock
generated
@@ -224,6 +224,17 @@ version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "fnmatch-regex"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f319c7da34eac5f0b8c7220a4afb2e1ddde0c24ae87c7435a8e36dcd62a43a3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
@@ -568,6 +579,7 @@ dependencies = [
|
||||
"bitflags",
|
||||
"clap",
|
||||
"env_logger",
|
||||
"fnmatch-regex",
|
||||
"glob",
|
||||
"itertools",
|
||||
"log",
|
||||
|
||||
@@ -14,6 +14,7 @@ ariadne = "0.6.0"
|
||||
bitflags = "2.8.0"
|
||||
clap = { version = "4.5.38", features = ["derive"] }
|
||||
env_logger = "0.11.9"
|
||||
fnmatch-regex = "0.3.0"
|
||||
glob = "0.3.2"
|
||||
itertools = "0.14.0"
|
||||
log = "0.4.29"
|
||||
|
||||
@@ -865,15 +865,15 @@ impl QueryEditor {
|
||||
self.available_width = width;
|
||||
}
|
||||
pub fn update_scroll_offset(&mut self) {
|
||||
let cursor_pos = self.linebuf.cursor.get();
|
||||
let cursor_pos = self.linebuf.cursor_to_flat();
|
||||
if cursor_pos < self.scroll_offset + 1 {
|
||||
self.scroll_offset = self.linebuf.cursor.ret_sub(1);
|
||||
self.scroll_offset = self.linebuf.cursor_to_flat().saturating_sub(1)
|
||||
}
|
||||
if cursor_pos >= self.scroll_offset + self.available_width.saturating_sub(1) {
|
||||
self.scroll_offset = self
|
||||
.linebuf
|
||||
.cursor
|
||||
.ret_sub(self.available_width.saturating_sub(1));
|
||||
.cursor_to_flat()
|
||||
.saturating_sub(self.available_width.saturating_sub(1));
|
||||
}
|
||||
let max_offset = self
|
||||
.linebuf
|
||||
@@ -1257,8 +1257,7 @@ impl FuzzySelector {
|
||||
let cursor_in_window = self
|
||||
.query
|
||||
.linebuf
|
||||
.cursor
|
||||
.get()
|
||||
.cursor_to_flat()
|
||||
.saturating_sub(self.query.scroll_offset);
|
||||
let cursor_col = (cursor_in_window + 4) as u16;
|
||||
write!(buf, "\x1b[{}A\r\x1b[{}C", lines_below_prompt, cursor_col).unwrap();
|
||||
|
||||
@@ -317,7 +317,7 @@ impl History {
|
||||
|
||||
pub fn update_pending_cmd(&mut self, buf: (&str, usize)) {
|
||||
let cursor_pos = if let Some(pending) = &self.pending {
|
||||
pending.cursor.get()
|
||||
pending.cursor_to_flat()
|
||||
} else {
|
||||
buf.1
|
||||
};
|
||||
@@ -329,7 +329,7 @@ impl History {
|
||||
|
||||
if let Some(pending) = &mut self.pending {
|
||||
pending.set_buffer(cmd);
|
||||
pending.cursor.set(cursor_pos);
|
||||
pending.set_cursor_from_flat(cursor_pos);
|
||||
} else {
|
||||
self.pending = Some(LineBuf::new().with_initial(&cmd, cursor_pos));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use std::{
|
||||
fmt::Display,
|
||||
ops::{Index, IndexMut},
|
||||
slice::SliceIndex,
|
||||
collections::HashSet, fmt::Display, ops::{Index, IndexMut}, slice::SliceIndex
|
||||
};
|
||||
|
||||
use smallvec::SmallVec;
|
||||
@@ -14,11 +12,11 @@ use super::vicmd::{
|
||||
};
|
||||
use crate::{
|
||||
expand::expand_cmd_sub,
|
||||
libsh::error::ShResult,
|
||||
libsh::{error::ShResult, guards::{RawModeGuard, var_ctx_guard}},
|
||||
parse::{
|
||||
Redir, RedirType,
|
||||
execute::exec_input,
|
||||
lex::{LexFlags, LexStream, Tk, TkFlags, TkRule},
|
||||
lex::{LexFlags, LexStream, Tk, TkFlags},
|
||||
},
|
||||
prelude::*,
|
||||
procio::{IoFrame, IoMode, IoStack},
|
||||
@@ -26,7 +24,7 @@ use crate::{
|
||||
markers,
|
||||
register::RegisterContent, vicmd::{ReadSrc, VerbCmd, WriteDest},
|
||||
},
|
||||
state::{read_vars, write_meta},
|
||||
state::{VarFlags, VarKind, read_vars, write_meta, write_vars},
|
||||
};
|
||||
|
||||
const PUNCTUATION: [&str; 3] = ["?", "!", "."];
|
||||
@@ -298,32 +296,6 @@ impl From<&Grapheme> for CharClass {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_whitespace(a: &Grapheme) -> bool {
|
||||
CharClass::from(a) == CharClass::Whitespace
|
||||
}
|
||||
|
||||
fn is_other_class(a: &Grapheme, b: &Grapheme) -> bool {
|
||||
let a = CharClass::from(a);
|
||||
let b = CharClass::from(b);
|
||||
a != b
|
||||
}
|
||||
|
||||
fn is_other_class_not_ws(a: &Grapheme, b: &Grapheme) -> bool {
|
||||
if is_whitespace(a) || is_whitespace(b) {
|
||||
false
|
||||
} else {
|
||||
is_other_class(a, b)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_other_class_or_is_ws(a: &Grapheme, b: &Grapheme) -> bool {
|
||||
if is_whitespace(a) || is_whitespace(b) {
|
||||
true
|
||||
} else {
|
||||
is_other_class(a, b)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum SelectMode {
|
||||
Char(Pos),
|
||||
@@ -343,7 +315,10 @@ impl Pos {
|
||||
row: usize::MAX,
|
||||
col: usize::MAX,
|
||||
};
|
||||
pub const MIN: Self = Pos { row: 0, col: 0 };
|
||||
pub const MIN: Self = Pos {
|
||||
row: usize::MIN, // just in case we discover something smaller than '0'
|
||||
col: usize::MIN,
|
||||
};
|
||||
|
||||
pub fn row_col_add(&self, row: isize, col: isize) -> Self {
|
||||
Self {
|
||||
@@ -412,21 +387,6 @@ pub struct Cursor {
|
||||
pub exclusive: bool,
|
||||
}
|
||||
|
||||
impl Cursor {
|
||||
/// Compat shim: returns the flat column position (col on row 0 in single-line mode)
|
||||
pub fn get(&self) -> usize {
|
||||
self.pos.col
|
||||
}
|
||||
/// Compat shim: sets the flat column position
|
||||
pub fn set(&mut self, col: usize) {
|
||||
self.pos.col = col;
|
||||
}
|
||||
/// Compat shim: returns cursor.col - n without mutating, clamped to 0
|
||||
pub fn ret_sub(&self, n: usize) -> usize {
|
||||
self.pos.col.saturating_sub(n)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct Edit {
|
||||
pub old_cursor: Pos,
|
||||
@@ -715,7 +675,66 @@ impl LineBuf {
|
||||
col,
|
||||
};
|
||||
}
|
||||
fn verb_shell_cmd(&self, cmd: &str) -> ShResult<()> {
|
||||
fn verb_shell_cmd(&mut self, cmd: &str) -> ShResult<()> {
|
||||
let mut vars = HashSet::new();
|
||||
vars.insert("_BUFFER".into());
|
||||
vars.insert("_CURSOR".into());
|
||||
vars.insert("_ANCHOR".into());
|
||||
let _guard = var_ctx_guard(vars);
|
||||
|
||||
let mut buf = self.joined();
|
||||
let mut cursor = self.cursor_to_flat();
|
||||
let mut anchor = self.select_mode.map(|r| {
|
||||
match r {
|
||||
SelectMode::Char(pos) |
|
||||
SelectMode::Block(pos) |
|
||||
SelectMode::Line(pos) => {
|
||||
self.pos_to_flat(pos).to_string()
|
||||
}
|
||||
}
|
||||
}).unwrap_or_default();
|
||||
|
||||
write_vars(|v| {
|
||||
v.set_var("_BUFFER", VarKind::Str(buf.clone()), VarFlags::EXPORT)?;
|
||||
v.set_var(
|
||||
"_CURSOR",
|
||||
VarKind::Str(cursor.to_string()),
|
||||
VarFlags::EXPORT,
|
||||
)?;
|
||||
v.set_var(
|
||||
"_ANCHOR",
|
||||
VarKind::Str(anchor.clone()),
|
||||
VarFlags::EXPORT,
|
||||
)
|
||||
})?;
|
||||
|
||||
RawModeGuard::with_cooked_mode(|| exec_input(cmd.to_string(), None, true, Some("<ex-mode-cmd>".into())))?;
|
||||
|
||||
let keys = write_vars(|v| {
|
||||
buf = v.take_var("_BUFFER");
|
||||
cursor = v.take_var("_CURSOR").parse().unwrap_or(cursor);
|
||||
anchor = v.take_var("_ANCHOR");
|
||||
v.take_var("_KEYS")
|
||||
});
|
||||
|
||||
self.set_buffer(buf);
|
||||
self.set_cursor_from_flat(cursor);
|
||||
if let Ok(pos) = anchor.parse()
|
||||
&& pos != cursor
|
||||
&& self.select_mode.is_some() {
|
||||
let new_pos = self.pos_from_flat(pos);
|
||||
match self.select_mode.as_mut() {
|
||||
Some(SelectMode::Line(pos)) |
|
||||
Some(SelectMode::Block(pos)) |
|
||||
Some(SelectMode::Char(pos)) => {
|
||||
*pos = new_pos
|
||||
}
|
||||
None => unreachable!()
|
||||
}
|
||||
}
|
||||
if !keys.is_empty() {
|
||||
write_meta(|m| m.set_pending_widget_keys(&keys))
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn insert_at(&mut self, pos: Pos, gr: Grapheme) {
|
||||
@@ -1064,10 +1083,15 @@ impl LineBuf {
|
||||
match obj {
|
||||
// text structures
|
||||
TextObj::Word(word, bound) => self.text_obj_word(count, word, obj, bound),
|
||||
TextObj::Sentence(direction) => todo!(),
|
||||
TextObj::Paragraph(direction) => todo!(),
|
||||
TextObj::WholeSentence(bound) => todo!(),
|
||||
TextObj::WholeParagraph(bound) => todo!(),
|
||||
TextObj::Sentence(_) |
|
||||
TextObj::Paragraph(_) |
|
||||
TextObj::WholeSentence(_) |
|
||||
TextObj::Tag(_) |
|
||||
TextObj::Custom(_) |
|
||||
TextObj::WholeParagraph(_) => {
|
||||
log::warn!("{:?} text objects are not implemented yet", obj);
|
||||
None
|
||||
}
|
||||
|
||||
// quote stuff
|
||||
TextObj::DoubleQuote(bound) |
|
||||
@@ -1081,9 +1105,6 @@ impl LineBuf {
|
||||
| TextObj::Bracket(bound)
|
||||
| TextObj::Brace(bound)
|
||||
| TextObj::Angle(bound) => self.text_obj_delim(count, obj, bound),
|
||||
|
||||
TextObj::Tag(bound) => todo!(),
|
||||
TextObj::Custom(_) => todo!(),
|
||||
}
|
||||
}
|
||||
fn text_obj_word(
|
||||
@@ -1736,10 +1757,13 @@ impl LineBuf {
|
||||
let (s, e) = ordered(*s, *e);
|
||||
Some(MotionKind::Block { start: s, end: e })
|
||||
}
|
||||
Motion::RepeatMotion => todo!(),
|
||||
Motion::RepeatMotionRev => todo!(),
|
||||
Motion::Global(val) => todo!(),
|
||||
Motion::NotGlobal(val) => todo!(),
|
||||
Motion::RepeatMotion |
|
||||
Motion::RepeatMotionRev => unreachable!("Repeat motions should have been resolved in readline/mod.rs"),
|
||||
Motion::Global(val) |
|
||||
Motion::NotGlobal(val) => {
|
||||
log::warn!("Global motions are not implemented yet (val: {:?})", val);
|
||||
None
|
||||
}
|
||||
Motion::Null => None,
|
||||
};
|
||||
|
||||
@@ -1748,11 +1772,11 @@ impl LineBuf {
|
||||
}
|
||||
fn move_to_start(&mut self, motion: MotionKind) {
|
||||
match motion {
|
||||
MotionKind::Char { start, end, inclusive } => {
|
||||
MotionKind::Char { start, end, .. } => {
|
||||
let (s,_) = ordered(start, end);
|
||||
self.set_cursor(s);
|
||||
}
|
||||
MotionKind::Line { start, end, inclusive } => {
|
||||
MotionKind::Line { start, end, .. } => {
|
||||
let (s,_) = ordered(start, end);
|
||||
self.set_cursor(Pos { row: s, col: 0 });
|
||||
}
|
||||
@@ -2250,7 +2274,7 @@ impl LineBuf {
|
||||
let line_len = self.line(row).len();
|
||||
|
||||
// we are going to calculate the level twice, once at column = 0 and once at column = line.len()
|
||||
// "b-b-b-b-but the performance" i dont care. open a pull request genius
|
||||
// "b-b-b-b-but the performance" i dont care
|
||||
// the number of tabs we use for the line is the lesser of these two calculations
|
||||
// if level_start > level_end, the line has an closer
|
||||
// if level_end > level_start, the line has a opener
|
||||
@@ -2758,10 +2782,28 @@ impl LineBuf {
|
||||
offset + pos.col.min(self.lines[row].len())
|
||||
}
|
||||
|
||||
fn pos_from_flat(&self, mut flat: usize) -> Pos {
|
||||
for (i, line) in self.lines.iter().enumerate() {
|
||||
if flat <= line.len() {
|
||||
return Pos { row: i, col: flat };
|
||||
}
|
||||
flat = flat.saturating_sub(line.len() + 1); // +1 for '\n'
|
||||
}
|
||||
// If we exceed the total length, clamp to end
|
||||
let last_row = self.lines.len().saturating_sub(1);
|
||||
let last_col = self.lines[last_row].len();
|
||||
Pos { row: last_row, col: last_col }
|
||||
}
|
||||
|
||||
pub fn cursor_to_flat(&self) -> usize {
|
||||
self.pos_to_flat(self.cursor.pos)
|
||||
}
|
||||
|
||||
pub fn set_cursor_from_flat(&mut self, flat: usize) {
|
||||
self.cursor.pos = self.pos_from_flat(flat);
|
||||
self.fix_cursor();
|
||||
}
|
||||
|
||||
/// Compat shim: attempt history expansion. Stub that returns false.
|
||||
pub fn attempt_history_expansion(&mut self, _history: &super::history::History) -> bool {
|
||||
// TODO: implement history expansion for 2D buffer
|
||||
|
||||
@@ -343,7 +343,7 @@ impl ShedVi {
|
||||
self.editor = LineBuf::new().with_initial(initial, 0);
|
||||
{
|
||||
let s = self.editor.joined();
|
||||
let c = self.editor.cursor.get();
|
||||
let c = self.editor.cursor_to_flat();
|
||||
self.history.update_pending_cmd((&s, c));
|
||||
}
|
||||
self
|
||||
@@ -486,7 +486,7 @@ impl ShedVi {
|
||||
|
||||
self
|
||||
.history
|
||||
.update_pending_cmd((&self.editor.joined(), self.editor.cursor.get()));
|
||||
.update_pending_cmd((&self.editor.joined(), self.editor.cursor_to_flat()));
|
||||
self.editor.set_hint(None);
|
||||
{
|
||||
let mut writer = std::mem::take(&mut self.writer);
|
||||
@@ -548,7 +548,7 @@ impl ShedVi {
|
||||
let new_cursor = span_start + candidate.len();
|
||||
let line = self.completer.get_completed_line(&candidate);
|
||||
self.focused_editor().set_buffer(line);
|
||||
self.focused_editor().cursor.set(new_cursor);
|
||||
self.focused_editor().set_cursor_from_flat(new_cursor);
|
||||
// Don't reset yet — clear() needs old_layout to erase the selector.
|
||||
|
||||
if !self.history.at_pending() {
|
||||
@@ -556,7 +556,7 @@ impl ShedVi {
|
||||
}
|
||||
self
|
||||
.history
|
||||
.update_pending_cmd((&self.editor.joined(), self.editor.cursor.get()));
|
||||
.update_pending_cmd((&self.editor.joined(), self.editor.cursor_to_flat()));
|
||||
let hint = self.history.get_hint();
|
||||
self.editor.set_hint(hint);
|
||||
self.completer.clear(&mut self.writer)?;
|
||||
@@ -684,7 +684,7 @@ impl ShedVi {
|
||||
}
|
||||
self
|
||||
.history
|
||||
.update_pending_cmd((&self.editor.joined(), self.editor.cursor.get()));
|
||||
.update_pending_cmd((&self.editor.joined(), self.editor.cursor_to_flat()));
|
||||
self.needs_redraw = true;
|
||||
return Ok(None);
|
||||
}
|
||||
@@ -728,14 +728,14 @@ impl ShedVi {
|
||||
.unwrap_or_default();
|
||||
|
||||
self.focused_editor().set_buffer(line.clone());
|
||||
self.focused_editor().cursor.set(new_cursor);
|
||||
self.focused_editor().set_cursor_from_flat(new_cursor);
|
||||
|
||||
if !self.history.at_pending() {
|
||||
self.history.reset_to_pending();
|
||||
}
|
||||
self
|
||||
.history
|
||||
.update_pending_cmd((&self.editor.joined(), self.editor.cursor.get()));
|
||||
.update_pending_cmd((&self.editor.joined(), self.editor.cursor_to_flat()));
|
||||
let hint = self.history.get_hint();
|
||||
self.editor.set_hint(hint);
|
||||
write_vars(|v| {
|
||||
@@ -804,7 +804,7 @@ impl ShedVi {
|
||||
self.focused_editor().move_cursor_to_end();
|
||||
self
|
||||
.history
|
||||
.update_pending_cmd((&self.editor.joined(), self.editor.cursor.get()));
|
||||
.update_pending_cmd((&self.editor.joined(), self.editor.cursor_to_flat()));
|
||||
self.editor.set_hint(None);
|
||||
}
|
||||
None => {
|
||||
@@ -880,7 +880,7 @@ impl ShedVi {
|
||||
}
|
||||
|
||||
self.editor.set_hint(None);
|
||||
self.editor.cursor.set(self.editor.cursor_max());
|
||||
self.editor.set_cursor_from_flat(self.editor.cursor_max());
|
||||
self.print_line(true)?;
|
||||
self.writer.flush_write("\n")?;
|
||||
let buf = self.editor.take_buf();
|
||||
@@ -931,7 +931,7 @@ impl ShedVi {
|
||||
if before != after {
|
||||
self
|
||||
.history
|
||||
.update_pending_cmd((&self.editor.joined(), self.editor.cursor.get()));
|
||||
.update_pending_cmd((&self.editor.joined(), self.editor.cursor_to_flat()));
|
||||
} else if before == after && has_edit_verb {
|
||||
self.writer.send_bell().ok();
|
||||
}
|
||||
|
||||
@@ -42,11 +42,10 @@ struct ExEditor {
|
||||
|
||||
impl ExEditor {
|
||||
pub fn new(history: History) -> Self {
|
||||
let mut new = Self {
|
||||
Self {
|
||||
history,
|
||||
..Default::default()
|
||||
};
|
||||
new
|
||||
}
|
||||
}
|
||||
pub fn clear(&mut self) {
|
||||
*self = Self::default()
|
||||
@@ -169,7 +168,7 @@ impl ViMode for ViEx {
|
||||
}
|
||||
|
||||
fn pending_cursor(&self) -> Option<usize> {
|
||||
Some(self.pending_cmd.buf.cursor.get())
|
||||
Some(self.pending_cmd.buf.cursor_to_flat())
|
||||
}
|
||||
|
||||
fn move_cursor_on_undo(&self) -> bool {
|
||||
@@ -229,9 +228,6 @@ fn parse_ex_cmd(raw: &str) -> Result<Option<ViCmd>, Option<String>> {
|
||||
|
||||
/// Unescape shell command arguments
|
||||
fn unescape_shell_cmd(cmd: &str) -> String {
|
||||
// The pest grammar uses double quotes for vicut commands
|
||||
// So shell commands need to escape double quotes
|
||||
// We will be removing a single layer of escaping from double quotes
|
||||
let mut result = String::new();
|
||||
let mut chars = cmd.chars().peekable();
|
||||
while let Some(ch) = chars.next() {
|
||||
|
||||
Reference in New Issue
Block a user