Skip to content

Commit 5264435

Browse files
authored
Implement yank (ctrl-y), which pastes from a previous deletion (#41)
In shells and whatnot, the multi-character deletion operations are actually the cut part of a cut-and-paste. The cut text can later be pasted with a yank command. This applies to four deletion operations.
1 parent c1d60f2 commit 5264435

File tree

3 files changed

+234
-15
lines changed

3 files changed

+234
-15
lines changed

src/backend/crossterm.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ pub fn to_input_request(evt: &CrosstermEvent) -> Option<InputRequest> {
5050

5151
(Delete, KeyModifiers::CONTROL) => Some(DeleteNextWord),
5252
(Char('k'), KeyModifiers::CONTROL) => Some(DeleteTillEnd),
53+
(Char('y'), KeyModifiers::CONTROL) => Some(Yank),
5354
(Char('a'), KeyModifiers::CONTROL) | (Home, KeyModifiers::NONE) => {
5455
Some(GoToStart)
5556
}

src/backend/termion.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub fn to_input_request(evt: &Event) -> Option<InputRequest> {
2323
Event::Key(Key::Ctrl('u')) => Some(DeleteLine),
2424
Event::Key(Key::Ctrl('w')) => Some(DeletePrevWord),
2525
// Event::Key(Key::Ctrl(Key::Delete)) => Some(DeleteNextWord),
26+
Event::Key(Key::Ctrl('y')) => Some(Yank),
2627
Event::Key(Key::Ctrl('a')) | Event::Key(Key::Home) => Some(GoToStart),
2728
Event::Key(Key::Ctrl('e')) | Event::Key(Key::End) => Some(GoToEnd),
2829
Event::Key(Key::Char('\t')) => None,

src/input.rs

Lines changed: 232 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
//! assert_eq!(input.to_string(), "Hello World");
1616
//! ```
1717
18+
enum Side {
19+
Left,
20+
Right,
21+
}
22+
1823
/// Input requests are used to change the input state.
1924
///
2025
/// Different backends can be used to convert events into requests.
@@ -35,6 +40,7 @@ pub enum InputRequest {
3540
DeleteNextWord,
3641
DeleteLine,
3742
DeleteTillEnd,
43+
Yank,
3844
}
3945

4046
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
@@ -63,14 +69,21 @@ pub type InputResponse = Option<StateChanged>;
6369
pub struct Input {
6470
value: String,
6571
cursor: usize,
72+
yank: String,
73+
last_was_cut: bool,
6674
}
6775

6876
impl Input {
6977
/// Initialize a new instance with a given value
7078
/// Cursor will be set to the given value's length.
7179
pub fn new(value: String) -> Self {
7280
let len = value.chars().count();
73-
Self { value, cursor: len }
81+
Self {
82+
value,
83+
cursor: len,
84+
yank: String::new(),
85+
last_was_cut: false,
86+
}
7487
}
7588

7689
/// Set the value manually.
@@ -101,10 +114,29 @@ impl Input {
101114
val
102115
}
103116

117+
fn add_to_yank(&mut self, deleted: String, side: Side) {
118+
if self.last_was_cut {
119+
match side {
120+
Side::Left => self.yank.insert_str(0, &deleted),
121+
Side::Right => self.yank.push_str(&deleted),
122+
}
123+
} else {
124+
self.yank = deleted;
125+
}
126+
}
127+
128+
fn set_last_was_cut(&mut self, req: InputRequest) {
129+
use InputRequest::*;
130+
self.last_was_cut = matches!(
131+
req,
132+
DeleteLine | DeletePrevWord | DeleteNextWord | DeleteTillEnd
133+
);
134+
}
135+
104136
/// Handle request and emit response.
105137
pub fn handle(&mut self, req: InputRequest) -> InputResponse {
106138
use InputRequest::*;
107-
match req {
139+
let result = match req {
108140
SetCursor(pos) => {
109141
let pos = pos.min(self.value.chars().count());
110142
if self.cursor == pos {
@@ -244,12 +276,17 @@ impl Input {
244276
if self.value.is_empty() {
245277
None
246278
} else {
247-
let cursor = self.cursor;
279+
let side = if self.cursor == self.value.chars().count() {
280+
Side::Left
281+
} else {
282+
Side::Right
283+
};
284+
self.add_to_yank(self.value.clone(), side);
248285
self.value = "".into();
249286
self.cursor = 0;
250287
Some(StateChanged {
251288
value: true,
252-
cursor: self.cursor == cursor,
289+
cursor: true,
253290
})
254291
}
255292
}
@@ -258,7 +295,6 @@ impl Input {
258295
if self.cursor == 0 {
259296
None
260297
} else {
261-
let remaining = self.value.chars().skip(self.cursor);
262298
let rev = self
263299
.value
264300
.chars()
@@ -268,6 +304,14 @@ impl Input {
268304
.skip_while(|c| c.is_alphanumeric())
269305
.collect::<Vec<char>>();
270306
let rev_len = rev.len();
307+
let deleted: String = self
308+
.value
309+
.chars()
310+
.skip(rev_len)
311+
.take(self.cursor - rev_len)
312+
.collect();
313+
self.add_to_yank(deleted, Side::Left);
314+
let remaining = self.value.chars().skip(self.cursor);
271315
self.value = rev.into_iter().rev().chain(remaining).collect();
272316
self.cursor = rev_len;
273317
Some(StateChanged {
@@ -281,18 +325,24 @@ impl Input {
281325
if self.cursor == self.value.chars().count() {
282326
None
283327
} else {
284-
self.value = self
328+
let after: Vec<_> = self
285329
.value
286330
.chars()
287-
.take(self.cursor)
288-
.chain(
289-
self.value
290-
.chars()
291-
.skip(self.cursor)
292-
.skip_while(|c| c.is_alphanumeric())
293-
.skip_while(|c| !c.is_alphanumeric()),
294-
)
331+
.skip(self.cursor)
332+
.skip_while(|c| c.is_alphanumeric())
333+
.skip_while(|c| !c.is_alphanumeric())
295334
.collect();
335+
let deleted_count =
336+
self.value.chars().count() - self.cursor - after.len();
337+
let deleted: String = self
338+
.value
339+
.chars()
340+
.skip(self.cursor)
341+
.take(deleted_count)
342+
.collect();
343+
self.add_to_yank(deleted, Side::Right);
344+
self.value =
345+
self.value.chars().take(self.cursor).chain(after).collect();
296346

297347
Some(StateChanged {
298348
value: true,
@@ -327,13 +377,43 @@ impl Input {
327377
}
328378

329379
DeleteTillEnd => {
380+
let deleted: String = self.value.chars().skip(self.cursor).collect();
381+
self.add_to_yank(deleted, Side::Right);
330382
self.value = self.value.chars().take(self.cursor).collect();
331383
Some(StateChanged {
332384
value: true,
333385
cursor: false,
334386
})
335387
}
336-
}
388+
389+
Yank => {
390+
if self.yank.is_empty() {
391+
None
392+
} else if self.cursor == self.value.chars().count() {
393+
self.value.push_str(&self.yank);
394+
self.cursor += self.yank.chars().count();
395+
Some(StateChanged {
396+
value: true,
397+
cursor: true,
398+
})
399+
} else {
400+
self.value = self
401+
.value
402+
.chars()
403+
.take(self.cursor)
404+
.chain(self.yank.chars())
405+
.chain(self.value.chars().skip(self.cursor))
406+
.collect();
407+
self.cursor += self.yank.chars().count();
408+
Some(StateChanged {
409+
value: true,
410+
cursor: true,
411+
})
412+
}
413+
}
414+
};
415+
self.set_last_was_cut(req);
416+
result
337417
}
338418

339419
/// Get a reference to the current value.
@@ -594,4 +674,141 @@ mod tests {
594674
assert_eq!(input.visual_cursor(), 23);
595675
assert_eq!(input.visual_scroll(6), 18);
596676
}
677+
678+
#[test]
679+
fn yank_delete_line() {
680+
let mut input: Input = TEXT.into();
681+
input.handle(InputRequest::DeleteLine);
682+
assert_eq!(input.value(), "");
683+
assert_eq!(input.cursor(), 0);
684+
assert_eq!(input.yank, TEXT);
685+
686+
input.handle(InputRequest::Yank);
687+
assert_eq!(input.value(), TEXT);
688+
assert_eq!(input.cursor(), TEXT.chars().count());
689+
assert_eq!(input.yank, TEXT);
690+
}
691+
692+
#[test]
693+
fn yank_delete_till_end() {
694+
let mut input = Input::from(TEXT).with_cursor(6);
695+
input.handle(InputRequest::DeleteTillEnd);
696+
assert_eq!(input.value(), "first ");
697+
assert_eq!(input.cursor(), 6);
698+
assert_eq!(input.yank, "second, third.");
699+
700+
input.handle(InputRequest::Yank);
701+
assert_eq!(input.value(), "first second, third.");
702+
assert_eq!(input.cursor(), TEXT.chars().count());
703+
assert_eq!(input.yank, "second, third.");
704+
}
705+
706+
#[test]
707+
fn yank_delete_prev_word() {
708+
let mut input = Input::from(TEXT).with_cursor(12);
709+
input.handle(InputRequest::DeletePrevWord);
710+
assert_eq!(input.value(), "first , third.");
711+
assert_eq!(input.yank, "second");
712+
713+
input.handle(InputRequest::Yank);
714+
assert_eq!(input.value(), "first second, third.");
715+
assert_eq!(input.yank, "second");
716+
}
717+
718+
#[test]
719+
fn yank_delete_next_word() {
720+
let mut input = Input::from(TEXT).with_cursor(6);
721+
input.handle(InputRequest::DeleteNextWord);
722+
assert_eq!(input.value(), "first third.");
723+
assert_eq!(input.yank, "second, ");
724+
725+
input.handle(InputRequest::Yank);
726+
assert_eq!(input.value(), "first second, third.");
727+
assert_eq!(input.yank, "second, ");
728+
}
729+
730+
#[test]
731+
fn yank_empty() {
732+
let mut input: Input = TEXT.into();
733+
let result = input.handle(InputRequest::Yank);
734+
assert_eq!(result, None);
735+
assert_eq!(input.value(), TEXT);
736+
assert_eq!(input.yank, "");
737+
}
738+
739+
#[test]
740+
fn yank_at_middle() {
741+
let mut input = Input::from(TEXT).with_cursor(6);
742+
input.handle(InputRequest::DeleteTillEnd);
743+
assert_eq!(input.value(), "first ");
744+
assert_eq!(input.yank, "second, third.");
745+
input.handle(InputRequest::GoToStart);
746+
input.handle(InputRequest::Yank);
747+
assert_eq!(input.value(), "second, third.first ");
748+
assert_eq!(input.cursor(), 14);
749+
assert_eq!(input.yank, "second, third.");
750+
}
751+
752+
#[test]
753+
fn yank_consecutive_delete_prev_word() {
754+
let mut input = Input::from(TEXT).with_cursor(TEXT.chars().count());
755+
input.handle(InputRequest::DeletePrevWord);
756+
assert_eq!(input.value(), "first second, ");
757+
assert_eq!(input.yank, "third.");
758+
input.handle(InputRequest::DeletePrevWord);
759+
assert_eq!(input.value(), "first ");
760+
assert_eq!(input.yank, "second, third.");
761+
input.handle(InputRequest::Yank);
762+
assert_eq!(input.value(), "first second, third.");
763+
}
764+
765+
#[test]
766+
fn yank_consecutive_delete_next_word() {
767+
let mut input = Input::from(TEXT).with_cursor(0);
768+
input.handle(InputRequest::DeleteNextWord);
769+
assert_eq!(input.value(), "second, third.");
770+
assert_eq!(input.yank, "first ");
771+
input.handle(InputRequest::DeleteNextWord);
772+
assert_eq!(input.value(), "third.");
773+
assert_eq!(input.yank, "first second, ");
774+
input.handle(InputRequest::Yank);
775+
assert_eq!(input.value(), "first second, third.");
776+
}
777+
778+
#[test]
779+
fn yank_insert_breaks_cut_sequence() {
780+
let mut input = Input::from(TEXT).with_cursor(TEXT.chars().count());
781+
input.handle(InputRequest::DeletePrevWord);
782+
assert_eq!(input.yank, "third.");
783+
input.handle(InputRequest::InsertChar('x'));
784+
input.handle(InputRequest::DeletePrevChar);
785+
input.handle(InputRequest::DeletePrevWord);
786+
assert_eq!(input.yank, "second, ");
787+
}
788+
789+
#[test]
790+
fn yank_mixed_delete_word_and_line() {
791+
let mut input = Input::from(TEXT).with_cursor(6);
792+
input.handle(InputRequest::DeletePrevWord);
793+
assert_eq!(input.value(), "second, third.");
794+
assert_eq!(input.yank, "first ");
795+
input.handle(InputRequest::DeleteLine);
796+
assert_eq!(input.value(), "");
797+
assert_eq!(input.yank, "first second, third.");
798+
input.handle(InputRequest::Yank);
799+
assert_eq!(input.value(), "first second, third.");
800+
}
801+
802+
#[test]
803+
fn yank_mixed_delete_word_and_line_from_end() {
804+
let mut input = Input::from(TEXT).with_cursor(TEXT.chars().count());
805+
input.handle(InputRequest::DeletePrevWord);
806+
assert_eq!(input.value(), "first second, ");
807+
assert_eq!(input.yank, "third.");
808+
input.handle(InputRequest::DeleteLine);
809+
assert_eq!(input.value(), "");
810+
assert_eq!(input.yank, "first second, third.");
811+
input.handle(InputRequest::Yank);
812+
assert_eq!(input.value(), "first second, third.");
813+
}
597814
}

0 commit comments

Comments
 (0)