mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-24 16:21:04 +00:00
feat: undo and redo for input
This commit is contained in:
parent
f6547dd051
commit
14639f64ac
6 changed files with 294 additions and 165 deletions
|
|
@ -1,24 +1,20 @@
|
|||
use std::ops::Range;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use ratatui::layout::Rect;
|
||||
use tokio::sync::oneshot::Sender;
|
||||
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use super::InputSnap;
|
||||
use super::{InputSnap, InputSnaps};
|
||||
use crate::{core::{external, Position}, misc::CharKind};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Input {
|
||||
title: String,
|
||||
pub(crate) value: String,
|
||||
position: (u16, u16),
|
||||
snaps: InputSnaps,
|
||||
|
||||
pub(crate) op: InputOp,
|
||||
pub(crate) start: Option<usize>,
|
||||
|
||||
pub(crate) mode: InputMode,
|
||||
pub(crate) offset: usize,
|
||||
pub(crate) cursor: usize,
|
||||
callback: Option<Sender<Result<String>>>,
|
||||
title: String,
|
||||
position: (u16, u16),
|
||||
callback: Option<Sender<Result<String>>>,
|
||||
|
||||
pub visible: bool,
|
||||
}
|
||||
|
|
@ -52,102 +48,110 @@ pub enum InputOp {
|
|||
impl Input {
|
||||
pub fn show(&mut self, opt: InputOpt, tx: Sender<Result<String>>) {
|
||||
self.close(false);
|
||||
self.snaps.reset(opt.value);
|
||||
|
||||
self.title = opt.title;
|
||||
self.value = opt.value;
|
||||
self.position = match opt.position {
|
||||
Position::Coords(x, y) => (x, y),
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
|
||||
self.cursor = self.count();
|
||||
self.offset = self.cursor.saturating_sub(50 - 2 /* Border width */);
|
||||
self.callback = Some(tx);
|
||||
self.visible = true;
|
||||
}
|
||||
|
||||
pub fn close(&mut self, submit: bool) -> bool {
|
||||
if let Some(cb) = self.callback.take() {
|
||||
let _ = cb.send(if submit { Ok(self.value.clone()) } else { Err(anyhow!("canceled")) });
|
||||
let _ =
|
||||
cb.send(if submit { Ok(self.snap_mut().value.clone()) } else { Err(anyhow!("canceled")) });
|
||||
}
|
||||
|
||||
self.op = InputOp::None;
|
||||
self.start = None;
|
||||
|
||||
self.mode = InputMode::Insert;
|
||||
self.visible = false;
|
||||
true
|
||||
}
|
||||
|
||||
pub fn escape(&mut self) -> bool {
|
||||
match self.mode {
|
||||
let snap = self.snap_mut();
|
||||
match snap.mode {
|
||||
InputMode::Normal => {
|
||||
self.op = InputOp::None;
|
||||
self.start = None;
|
||||
snap.op = InputOp::None;
|
||||
snap.start = None;
|
||||
}
|
||||
InputMode::Insert => {
|
||||
self.mode = InputMode::Normal;
|
||||
snap.mode = InputMode::Normal;
|
||||
self.move_(-1);
|
||||
}
|
||||
}
|
||||
self.snaps.tag();
|
||||
true
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, append: bool) -> bool {
|
||||
if self.mode != InputMode::Normal {
|
||||
if !self.snap_mut().insert() {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.op = InputOp::None;
|
||||
self.start = None;
|
||||
self.mode = InputMode::Insert;
|
||||
if append {
|
||||
self.move_(1);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn visual(&mut self) -> bool {
|
||||
if self.mode != InputMode::Normal {
|
||||
return false;
|
||||
} else if self.value.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.start = Some(self.cursor);
|
||||
true
|
||||
self.snap_mut().visual();
|
||||
self.escape()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn undo(&mut self) -> bool {
|
||||
self.snaps.undo();
|
||||
self.escape()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn redo(&mut self) -> bool { self.snaps.redo() }
|
||||
|
||||
pub fn move_(&mut self, step: isize) -> bool {
|
||||
let snap = self.snap();
|
||||
let b = self.handle_op(
|
||||
if step <= 0 {
|
||||
self.cursor.saturating_sub(step.abs() as usize)
|
||||
snap.cursor.saturating_sub(step.abs() as usize)
|
||||
} else {
|
||||
self.count().min(self.cursor + step as usize)
|
||||
snap.count().min(snap.cursor + step as usize)
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
if self.cursor < self.offset {
|
||||
self.offset = self.cursor;
|
||||
} else if self.cursor >= self.offset + 50 - 2 {
|
||||
self.offset = self.cursor.saturating_sub(50 - 2 - self.mode.delta());
|
||||
let snap = self.snap_mut();
|
||||
if snap.cursor < snap.offset {
|
||||
snap.offset = snap.cursor;
|
||||
} else if snap.value.is_empty() {
|
||||
snap.offset = 0;
|
||||
} else {
|
||||
let delta = snap.mode.delta();
|
||||
let s = snap.slice(snap.offset..snap.cursor + delta);
|
||||
if s.width() >= /*TODO: hardcode*/ 50 - 2 {
|
||||
let s = s.chars().rev().collect::<String>();
|
||||
snap.offset = snap.cursor - InputSnap::find_window(&s, 0).end.saturating_sub(delta);
|
||||
}
|
||||
}
|
||||
|
||||
b
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn move_in_operating(&mut self, step: isize) -> bool {
|
||||
if self.op == InputOp::None { false } else { self.move_(step) }
|
||||
if self.snap_mut().op == InputOp::None { false } else { self.move_(step) }
|
||||
}
|
||||
|
||||
pub fn backward(&mut self) -> bool {
|
||||
if self.cursor == 0 {
|
||||
let snap = self.snap();
|
||||
if snap.cursor == 0 {
|
||||
return self.move_(0);
|
||||
}
|
||||
|
||||
let idx = self.idx(self.cursor).unwrap_or(self.value.len());
|
||||
let mut it = self.value[..idx].chars().rev().enumerate();
|
||||
let idx = snap.idx(snap.cursor).unwrap_or(snap.len());
|
||||
let mut it = snap.value[..idx].chars().rev().enumerate();
|
||||
let mut prev = CharKind::new(it.next().unwrap().1);
|
||||
for (i, c) in it {
|
||||
let c = CharKind::new(c);
|
||||
|
|
@ -158,17 +162,18 @@ impl Input {
|
|||
}
|
||||
|
||||
if prev != CharKind::Space {
|
||||
return self.move_(-(self.value.len() as isize));
|
||||
return self.move_(-(snap.len() as isize));
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn forward(&mut self, end: bool) -> bool {
|
||||
if self.value.is_empty() {
|
||||
let snap = self.snap();
|
||||
if snap.value.is_empty() {
|
||||
return self.move_(0);
|
||||
}
|
||||
|
||||
let mut it = self.value.chars().skip(self.cursor).enumerate();
|
||||
let mut it = snap.value.chars().skip(snap.cursor).enumerate();
|
||||
let mut prev = CharKind::new(it.next().unwrap().1);
|
||||
for (i, c) in it {
|
||||
let c = CharKind::new(c);
|
||||
|
|
@ -177,7 +182,7 @@ impl Input {
|
|||
} else {
|
||||
c != CharKind::Space && c != prev
|
||||
};
|
||||
if b && self.op != InputOp::None {
|
||||
if b && snap.op != InputOp::None {
|
||||
return self.move_(i as isize);
|
||||
} else if b {
|
||||
return self.move_(if end { i - 1 } else { i } as isize);
|
||||
|
|
@ -185,46 +190,50 @@ impl Input {
|
|||
prev = c;
|
||||
}
|
||||
|
||||
self.move_(self.value.len() as isize)
|
||||
self.move_(snap.len() as isize)
|
||||
}
|
||||
|
||||
pub fn type_(&mut self, c: char) -> bool {
|
||||
if self.cursor < 1 {
|
||||
self.value.insert(0, c);
|
||||
} else if self.cursor == self.count() {
|
||||
self.value.push(c);
|
||||
let snap = self.snap_mut();
|
||||
if snap.cursor < 1 {
|
||||
snap.value.insert(0, c);
|
||||
} else if snap.cursor == snap.count() {
|
||||
snap.value.push(c);
|
||||
} else {
|
||||
self.value.insert(self.idx(self.cursor).unwrap(), c);
|
||||
snap.value.insert(snap.idx(snap.cursor).unwrap(), c);
|
||||
}
|
||||
self.move_(1)
|
||||
}
|
||||
|
||||
pub fn backspace(&mut self) -> bool {
|
||||
if self.cursor < 1 {
|
||||
let snap = self.snap_mut();
|
||||
if snap.cursor < 1 {
|
||||
return false;
|
||||
} else if self.cursor == self.count() {
|
||||
self.value.pop();
|
||||
} else if snap.cursor == snap.count() {
|
||||
snap.value.pop();
|
||||
} else {
|
||||
self.value.remove(self.idx(self.cursor - 1).unwrap());
|
||||
snap.value.remove(snap.idx(snap.cursor - 1).unwrap());
|
||||
}
|
||||
self.move_(-1)
|
||||
}
|
||||
|
||||
pub fn delete(&mut self, insert: bool) -> bool {
|
||||
match self.op {
|
||||
match self.snap().op {
|
||||
InputOp::None => {
|
||||
self.op = InputOp::Delete(insert);
|
||||
if self.start.is_some() {
|
||||
return self.handle_op(self.cursor, true).then(|| self.move_(0)).is_some();
|
||||
if self.snap().start.is_some() {
|
||||
self.snap_mut().op = InputOp::Delete(insert);
|
||||
return self.handle_op(self.snap().cursor, true).then(|| self.move_(0)).is_some();
|
||||
}
|
||||
|
||||
self.start = Some(self.cursor);
|
||||
let snap = self.snap_mut();
|
||||
snap.op = InputOp::Delete(insert);
|
||||
snap.start = Some(snap.cursor);
|
||||
false
|
||||
}
|
||||
InputOp::Delete(..) => {
|
||||
self.move_(-(self.value.len() as isize));
|
||||
self.value.clear();
|
||||
self.mode = if insert { InputMode::Insert } else { InputMode::Normal };
|
||||
self.move_(-(self.snap().len() as isize));
|
||||
self.snap_mut().value.clear();
|
||||
self.snap_mut().mode = if insert { InputMode::Insert } else { InputMode::Normal };
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
|
|
@ -232,19 +241,21 @@ impl Input {
|
|||
}
|
||||
|
||||
pub fn yank(&mut self) -> bool {
|
||||
match self.op {
|
||||
match self.snap().op {
|
||||
InputOp::None => {
|
||||
self.op = InputOp::Yank;
|
||||
if self.start.is_some() {
|
||||
return self.handle_op(self.cursor, true).then(|| self.move_(0)).is_some();
|
||||
if self.snap().start.is_some() {
|
||||
self.snap_mut().op = InputOp::Yank;
|
||||
return self.handle_op(self.snap().cursor, true).then(|| self.move_(0)).is_some();
|
||||
}
|
||||
|
||||
self.start = Some(self.cursor);
|
||||
let snap = self.snap_mut();
|
||||
snap.op = InputOp::Yank;
|
||||
snap.start = Some(snap.cursor);
|
||||
false
|
||||
}
|
||||
InputOp::Yank => {
|
||||
self.start = Some(0);
|
||||
self.move_(self.value.len() as isize);
|
||||
self.snap_mut().start = Some(0);
|
||||
self.move_(self.snap().len() as isize);
|
||||
false
|
||||
}
|
||||
_ => false,
|
||||
|
|
@ -252,9 +263,9 @@ impl Input {
|
|||
}
|
||||
|
||||
pub fn paste(&mut self, before: bool) -> bool {
|
||||
if self.start.is_some() {
|
||||
self.op = InputOp::Delete(false);
|
||||
self.handle_op(self.cursor, true);
|
||||
if self.snap().start.is_some() {
|
||||
self.snap_mut().op = InputOp::Delete(false);
|
||||
self.handle_op(self.snap().cursor, true);
|
||||
}
|
||||
|
||||
let str =
|
||||
|
|
@ -272,25 +283,26 @@ impl Input {
|
|||
}
|
||||
|
||||
fn handle_op(&mut self, cursor: usize, include: bool) -> bool {
|
||||
let snap = self.snap();
|
||||
let range = if self.op == InputOp::None { None } else { self.range(cursor, include) };
|
||||
let old = self.snap().clone();
|
||||
let snap = self.snap_mut();
|
||||
let range = if snap.op == InputOp::None { None } else { snap.range(cursor, include) };
|
||||
|
||||
match self.op {
|
||||
match snap.op {
|
||||
InputOp::None => {
|
||||
self.cursor = cursor;
|
||||
snap.cursor = cursor;
|
||||
}
|
||||
InputOp::Delete(insert) => {
|
||||
let range = range.unwrap();
|
||||
let (start, end) = (self.idx(range.0), self.idx(range.1));
|
||||
let Range { start, end } = snap.idx(range.start)..snap.idx(range.end);
|
||||
|
||||
self.value.drain(start.unwrap()..end.unwrap());
|
||||
self.mode = if insert { InputMode::Insert } else { InputMode::Normal };
|
||||
self.cursor = range.0;
|
||||
snap.value.drain(start.unwrap()..end.unwrap());
|
||||
snap.mode = if insert { InputMode::Insert } else { InputMode::Normal };
|
||||
snap.cursor = range.start;
|
||||
}
|
||||
InputOp::Yank => {
|
||||
let range = range.unwrap();
|
||||
let (start, end) = (self.idx(range.0), self.idx(range.1));
|
||||
let yanked = &self.value[start.unwrap()..end.unwrap()];
|
||||
let Range { start, end } = snap.idx(range.start)..snap.idx(range.end);
|
||||
let yanked = &snap.value[start.unwrap()..end.unwrap()];
|
||||
|
||||
futures::executor::block_on(async {
|
||||
external::clipboard_set(yanked).await.ok();
|
||||
|
|
@ -298,9 +310,16 @@ impl Input {
|
|||
}
|
||||
};
|
||||
|
||||
self.op = InputOp::None;
|
||||
self.cursor = self.count().saturating_sub(self.mode.delta()).min(self.cursor);
|
||||
snap != self.snap()
|
||||
snap.op = InputOp::None;
|
||||
snap.cursor = snap.count().saturating_sub(snap.mode.delta()).min(snap.cursor);
|
||||
if *snap == old {
|
||||
return false;
|
||||
}
|
||||
|
||||
if old.op != InputOp::None {
|
||||
self.snaps.tag();
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -309,87 +328,50 @@ impl Input {
|
|||
pub fn title(&self) -> String { self.title.clone() }
|
||||
|
||||
#[inline]
|
||||
pub fn value(&self) -> &str {
|
||||
let win = self.window();
|
||||
&self.value[self.idx(win.0).unwrap()..self.idx(win.1).unwrap()]
|
||||
}
|
||||
pub fn value(&self) -> &str { self.snap().slice(self.snap().window()) }
|
||||
|
||||
#[inline]
|
||||
pub fn mode(&self) -> InputMode { self.mode }
|
||||
pub fn mode(&self) -> InputMode { self.snap().mode }
|
||||
|
||||
#[inline]
|
||||
pub fn area(&self) -> Rect {
|
||||
// TODO: hardcode
|
||||
Rect { x: self.position.0, y: self.position.1 + 2, width: 50, height: 3 }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cursor(&self) -> (u16, u16) {
|
||||
let area = self.area();
|
||||
let width = self.value[self.offset..self.idx(self.cursor).unwrap()].width() as u16;
|
||||
let snap = self.snap();
|
||||
let width = snap.slice(snap.offset..snap.cursor).width() as u16;
|
||||
|
||||
let area = self.area();
|
||||
(area.x + width + 1, area.y + 1)
|
||||
}
|
||||
|
||||
pub fn selected(&self) -> Option<Rect> {
|
||||
if self.start.is_none() {
|
||||
let snap = self.snap();
|
||||
if snap.start.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let start = self.start.unwrap();
|
||||
let start = snap.start.unwrap();
|
||||
let (start, end) =
|
||||
if start < self.cursor { (start, self.cursor) } else { (self.cursor + 1, start + 1) };
|
||||
if start < snap.cursor { (start, snap.cursor) } else { (snap.cursor + 1, start + 1) };
|
||||
|
||||
let win = self.window();
|
||||
let (start, end) = (start.max(win.0), end.min(win.1));
|
||||
let (start, end) = (self.idx(start).unwrap(), self.idx(end).unwrap());
|
||||
let win = snap.window();
|
||||
let Range { start, end } = start.max(win.start)..end.min(win.end);
|
||||
|
||||
let offset = self.idx(self.offset).unwrap();
|
||||
Some(Rect {
|
||||
x: self.position.0 + 1 + self.value[offset..start].width() as u16,
|
||||
x: self.position.0 + 1 + snap.slice(snap.offset..start).width() as u16,
|
||||
y: self.position.1 + 3,
|
||||
width: self.value[start..end].width() as u16,
|
||||
width: snap.slice(start..end).width() as u16,
|
||||
height: 1,
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn count(&self) -> usize { self.value.chars().count() }
|
||||
fn snap(&self) -> &InputSnap { self.snaps.current() }
|
||||
|
||||
#[inline]
|
||||
fn idx(&self, n: usize) -> Option<usize> {
|
||||
self
|
||||
.value
|
||||
.char_indices()
|
||||
.nth(n)
|
||||
.map(|(i, _)| i)
|
||||
.or_else(|| if n == self.count() { Some(self.value.len()) } else { None })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn range(&mut self, cursor: usize, include: bool) -> Option<(usize, usize)> {
|
||||
self
|
||||
.start
|
||||
.take()
|
||||
.map(|s| if s <= cursor { (s, cursor) } else { (cursor, s) })
|
||||
.map(|(s, e)| (s, e + include as usize))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn window(&self) -> (usize, usize) {
|
||||
let mut len = 0;
|
||||
let v = self
|
||||
.value
|
||||
.chars()
|
||||
.enumerate()
|
||||
.skip(self.offset)
|
||||
.map_while(|(i, c)| {
|
||||
len += c.width().unwrap_or(0);
|
||||
if len > 50 - 2/*Border width*/ { None } else { Some(i) }
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
(v.first().copied().unwrap_or(0), v.last().map(|l| l + 1).unwrap_or(0))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn snap(&self) -> InputSnap { InputSnap::from(self) }
|
||||
fn snap_mut(&mut self) -> &mut InputSnap { self.snaps.current_mut() }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
mod input;
|
||||
mod snap;
|
||||
mod snaps;
|
||||
|
||||
pub use input::*;
|
||||
pub use snap::*;
|
||||
use snap::*;
|
||||
use snaps::*;
|
||||
|
|
|
|||
|
|
@ -1,28 +1,113 @@
|
|||
use super::{Input, InputMode, InputOp};
|
||||
use std::ops::Range;
|
||||
|
||||
#[derive(Default, PartialEq, Eq)]
|
||||
pub struct InputSnap {
|
||||
value: String,
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
|
||||
op: InputOp,
|
||||
start: Option<usize>,
|
||||
use super::{InputMode, InputOp};
|
||||
|
||||
mode: InputMode,
|
||||
offset: usize,
|
||||
cursor: usize,
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub(super) struct InputSnap {
|
||||
pub(super) value: String,
|
||||
|
||||
pub(super) op: InputOp,
|
||||
pub(super) start: Option<usize>,
|
||||
|
||||
pub(super) mode: InputMode,
|
||||
pub(super) offset: usize,
|
||||
pub(super) cursor: usize,
|
||||
}
|
||||
|
||||
impl From<&Input> for InputSnap {
|
||||
fn from(input: &Input) -> Self {
|
||||
impl InputSnap {
|
||||
pub(super) fn new(value: String) -> Self {
|
||||
let cursor = value.chars().count();
|
||||
let offset =
|
||||
cursor.saturating_sub(Self::find_window(&value.chars().rev().collect::<String>(), 0).end);
|
||||
|
||||
Self {
|
||||
value: input.value.clone(),
|
||||
value,
|
||||
|
||||
op: input.op,
|
||||
start: input.start,
|
||||
op: Default::default(),
|
||||
start: Default::default(),
|
||||
|
||||
mode: input.mode,
|
||||
offset: input.offset,
|
||||
cursor: input.cursor,
|
||||
mode: Default::default(),
|
||||
offset,
|
||||
cursor,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn insert(&mut self) -> bool {
|
||||
if self.mode != InputMode::Normal {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.op = InputOp::None;
|
||||
self.start = None;
|
||||
self.mode = InputMode::Insert;
|
||||
true
|
||||
}
|
||||
|
||||
pub fn visual(&mut self) -> bool {
|
||||
if self.mode != InputMode::Normal {
|
||||
return false;
|
||||
} else if self.value.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.start = Some(self.cursor);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl InputSnap {
|
||||
#[inline]
|
||||
pub(super) fn len(&self) -> usize { self.value.len() }
|
||||
|
||||
#[inline]
|
||||
pub(super) fn count(&self) -> usize { self.value.chars().count() }
|
||||
|
||||
#[inline]
|
||||
pub(super) fn idx(&self, n: usize) -> Option<usize> {
|
||||
self
|
||||
.value
|
||||
.char_indices()
|
||||
.nth(n)
|
||||
.map(|(i, _)| i)
|
||||
.or_else(|| if n == self.count() { Some(self.len()) } else { None })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn slice(&self, range: Range<usize>) -> &str {
|
||||
let (s, e) = (self.idx(range.start), self.idx(range.end));
|
||||
&self.value[s.unwrap()..e.unwrap()]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn range(&mut self, cursor: usize, include: bool) -> Option<Range<usize>> {
|
||||
self
|
||||
.start
|
||||
.take()
|
||||
.map(|s| if s <= cursor { (s, cursor) } else { (cursor, s) })
|
||||
.map(|(s, e)| s..e + include as usize)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn window(&self) -> Range<usize> { Self::find_window(&self.value, self.offset) }
|
||||
|
||||
#[inline]
|
||||
pub(super) fn find_window(s: &str, offset: usize) -> Range<usize> {
|
||||
let mut width = 0;
|
||||
let v = s
|
||||
.chars()
|
||||
.enumerate()
|
||||
.skip(offset)
|
||||
.map_while(|(i, c)| {
|
||||
width += c.width().unwrap_or(0);
|
||||
if width < /*TODO: hardcode*/ 50 - 2 { Some(i) } else { None }
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if v.is_empty() {
|
||||
return 0..0;
|
||||
}
|
||||
*v.first().unwrap()..v.last().unwrap() + 1
|
||||
}
|
||||
}
|
||||
|
|
|
|||
57
src/core/input/snaps.rs
Normal file
57
src/core/input/snaps.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
use super::{InputMode, InputSnap};
|
||||
|
||||
#[derive(Default, PartialEq, Eq)]
|
||||
pub(super) struct InputSnaps {
|
||||
idx: usize,
|
||||
versions: Vec<InputSnap>,
|
||||
current: InputSnap,
|
||||
}
|
||||
|
||||
impl InputSnaps {
|
||||
#[inline]
|
||||
pub(super) fn reset(&mut self, value: String) {
|
||||
self.idx = 0;
|
||||
self.versions.clear();
|
||||
self.versions.push(InputSnap::new(value));
|
||||
self.current = self.versions[0].clone();
|
||||
}
|
||||
|
||||
pub(super) fn undo(&mut self) -> bool {
|
||||
if self.idx == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.idx -= 1;
|
||||
self.current = self.versions[self.idx].clone();
|
||||
true
|
||||
}
|
||||
|
||||
pub(super) fn redo(&mut self) -> bool {
|
||||
if self.idx + 1 == self.versions.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.idx += 1;
|
||||
self.current = self.versions[self.idx].clone();
|
||||
true
|
||||
}
|
||||
|
||||
pub(super) fn tag(&mut self) -> bool {
|
||||
if self.current.value == self.versions[self.idx].value {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.versions.truncate(self.idx + 1);
|
||||
self.versions.push(self.current().clone());
|
||||
self.idx += 1;
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl InputSnaps {
|
||||
#[inline]
|
||||
pub(super) fn current(&self) -> &InputSnap { &self.current }
|
||||
|
||||
#[inline]
|
||||
pub(super) fn current_mut(&mut self) -> &mut InputSnap { &mut self.current }
|
||||
}
|
||||
|
|
@ -4,9 +4,9 @@ mod process;
|
|||
mod scheduler;
|
||||
mod tasks;
|
||||
|
||||
pub(crate) use file::*;
|
||||
use file::*;
|
||||
pub use precache::*;
|
||||
pub(crate) use process::*;
|
||||
use process::*;
|
||||
pub use scheduler::*;
|
||||
pub use tasks::*;
|
||||
|
||||
|
|
|
|||
|
|
@ -210,6 +210,9 @@ impl Executor {
|
|||
|
||||
"yank" => cx.input.yank(),
|
||||
"paste" => cx.input.paste(exec.named.contains_key("before")),
|
||||
|
||||
"undo" => cx.input.undo(),
|
||||
"redo" => cx.input.redo(),
|
||||
_ => false,
|
||||
},
|
||||
InputMode::Insert => match exec.cmd.as_str() {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue