good start to #16
This commit is contained in:
@@ -1,24 +1,13 @@
|
|||||||
use std::{
|
use std::{
|
||||||
cmp::{max, min},
|
cmp::{max, min}, collections::HashMap, io, path::PathBuf
|
||||||
collections::HashMap,
|
|
||||||
io,
|
|
||||||
path::PathBuf,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
DefaultTerminal, Frame,
|
DefaultTerminal, Frame, crossterm::event, layout::{self, Constraint, Layout, Rect}, prelude, style::{Color, Modifier, Style}, widgets::{Paragraph, Widget}
|
||||||
crossterm::event,
|
|
||||||
layout::{self, Constraint, Layout, Rect},
|
|
||||||
prelude,
|
|
||||||
style::{Color, Modifier, Style},
|
|
||||||
widgets::{Paragraph, Widget},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::app::{
|
use crate::app::{
|
||||||
error_msg::ErrorMessage,
|
clipboard::Clipboard, error_msg::ErrorMessage, logic::calc::{CellType, Grid}, mode::Mode, screen::ScreenSpace
|
||||||
logic::calc::{CellType, Grid},
|
|
||||||
mode::Mode,
|
|
||||||
screen::ScreenSpace,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
@@ -31,6 +20,7 @@ pub struct App {
|
|||||||
pub screen: ScreenSpace,
|
pub screen: ScreenSpace,
|
||||||
// this could probably be a normal array
|
// this could probably be a normal array
|
||||||
pub marks: HashMap<char, (usize, usize)>,
|
pub marks: HashMap<char, (usize, usize)>,
|
||||||
|
pub clipboard: Clipboard,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for &App {
|
impl Widget for &App {
|
||||||
@@ -39,7 +29,7 @@ impl Widget for &App {
|
|||||||
|
|
||||||
let is_selected = |x: usize, y: usize| -> bool {
|
let is_selected = |x: usize, y: usize| -> bool {
|
||||||
if let Mode::Visual((mut x1, mut y1)) = self.mode {
|
if let Mode::Visual((mut x1, mut y1)) = self.mode {
|
||||||
let (mut x2, mut y2) = self.grid.selected_cell;
|
let (mut x2, mut y2) = self.grid.cursor();
|
||||||
x1 += 1;
|
x1 += 1;
|
||||||
y1 += 1;
|
y1 += 1;
|
||||||
x2 += 1;
|
x2 += 1;
|
||||||
@@ -96,7 +86,7 @@ impl Widget for &App {
|
|||||||
(true, false) => {
|
(true, false) => {
|
||||||
display = y_idx.to_string();
|
display = y_idx.to_string();
|
||||||
|
|
||||||
let bg = if y_idx == self.grid.selected_cell.1 {
|
let bg = if y_idx == self.grid.cursor().1 {
|
||||||
Color::DarkGray
|
Color::DarkGray
|
||||||
} else if y_idx % 2 == 0 {
|
} else if y_idx % 2 == 0 {
|
||||||
ORANGE1
|
ORANGE1
|
||||||
@@ -109,7 +99,7 @@ impl Widget for &App {
|
|||||||
(false, true) => {
|
(false, true) => {
|
||||||
display = Grid::num_to_char(x_idx);
|
display = Grid::num_to_char(x_idx);
|
||||||
|
|
||||||
let bg = if x_idx == self.grid.selected_cell.0 {
|
let bg = if x_idx == self.grid.cursor().0 {
|
||||||
Color::DarkGray
|
Color::DarkGray
|
||||||
} else if x_idx % 2 == 0 {
|
} else if x_idx % 2 == 0 {
|
||||||
ORANGE1
|
ORANGE1
|
||||||
@@ -157,7 +147,7 @@ impl Widget for &App {
|
|||||||
}
|
}
|
||||||
None => should_render = false,
|
None => should_render = false,
|
||||||
}
|
}
|
||||||
if (x_idx, y_idx) == self.grid.selected_cell {
|
if (x_idx, y_idx) == self.grid.cursor() {
|
||||||
should_render = true;
|
should_render = true;
|
||||||
style = Style::new().fg(Color::Black).bg(Color::White);
|
style = Style::new().fg(Color::Black).bg(Color::White);
|
||||||
// modify the style of the cell you are editing
|
// modify the style of the cell you are editing
|
||||||
@@ -201,6 +191,7 @@ impl App {
|
|||||||
vars: HashMap::new(),
|
vars: HashMap::new(),
|
||||||
screen: ScreenSpace::new(),
|
screen: ScreenSpace::new(),
|
||||||
marks: HashMap::new(),
|
marks: HashMap::new(),
|
||||||
|
clipboard: Clipboard::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,7 +222,7 @@ impl App {
|
|||||||
let len = match &self.mode {
|
let len = match &self.mode {
|
||||||
Mode::Insert(edit) | Mode::Command(edit) | Mode::Chord(edit) => edit.len(),
|
Mode::Insert(edit) | Mode::Command(edit) | Mode::Chord(edit) => edit.len(),
|
||||||
Mode::Normal => {
|
Mode::Normal => {
|
||||||
let (x, y) = self.grid.selected_cell;
|
let (x, y) = self.grid.cursor();
|
||||||
let cell = self.grid.get_cell_raw(x, y).as_ref().map(|f| f.to_string().len()).unwrap_or_default();
|
let cell = self.grid.get_cell_raw(x, y).as_ref().map(|f| f.to_string().len()).unwrap_or_default();
|
||||||
cell
|
cell
|
||||||
}
|
}
|
||||||
@@ -259,7 +250,7 @@ impl App {
|
|||||||
Mode::Chord(chord) => frame.render_widget(chord, cmd_line_left),
|
Mode::Chord(chord) => frame.render_widget(chord, cmd_line_left),
|
||||||
Mode::Normal => frame.render_widget(
|
Mode::Normal => frame.render_widget(
|
||||||
Paragraph::new({
|
Paragraph::new({
|
||||||
let (x, y) = self.grid.selected_cell;
|
let (x, y) = self.grid.cursor();
|
||||||
let cell = self.grid.get_cell_raw(x, y).as_ref().map(|f| f.to_string()).unwrap_or_default();
|
let cell = self.grid.get_cell_raw(x, y).as_ref().map(|f| f.to_string()).unwrap_or_default();
|
||||||
cell
|
cell
|
||||||
}),
|
}),
|
||||||
@@ -274,7 +265,7 @@ impl App {
|
|||||||
frame.render_widget(
|
frame.render_widget(
|
||||||
Paragraph::new(format!(
|
Paragraph::new(format!(
|
||||||
"x/w y/h: cursor{:?} scroll({}, {}) cell({}, {}) screen({}, {}) len{len}",
|
"x/w y/h: cursor{:?} scroll({}, {}) cell({}, {}) screen({}, {}) len{len}",
|
||||||
self.grid.selected_cell,
|
self.grid.cursor(),
|
||||||
self.screen.scroll_x(),
|
self.screen.scroll_x(),
|
||||||
self.screen.scroll_y(),
|
self.screen.scroll_y(),
|
||||||
self.screen.get_cell_width(&self.vars),
|
self.screen.get_cell_width(&self.vars),
|
||||||
@@ -312,13 +303,13 @@ impl App {
|
|||||||
|
|
||||||
// try to insert as a float
|
// try to insert as a float
|
||||||
if let Ok(v) = v.parse::<f64>() {
|
if let Ok(v) = v.parse::<f64>() {
|
||||||
self.grid.set_cell_raw(self.grid.selected_cell, Some(v));
|
self.grid.set_cell_raw(self.grid.cursor(), Some(v));
|
||||||
} else {
|
} else {
|
||||||
// if you can't, then insert as a string
|
// if you can't, then insert as a string
|
||||||
if !v.is_empty() {
|
if !v.is_empty() {
|
||||||
self.grid.set_cell_raw(self.grid.selected_cell, Some(v));
|
self.grid.set_cell_raw(self.grid.cursor(), Some(v));
|
||||||
} else {
|
} else {
|
||||||
self.grid.set_cell_raw::<CellType>(self.grid.selected_cell, None);
|
self.grid.set_cell_raw::<CellType>(self.grid.cursor(), None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,7 +369,7 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// make sure cursor is inside window
|
// make sure cursor is inside window
|
||||||
self.screen.scroll_based_on_cursor_location(self.grid.selected_cell, &self.vars);
|
self.screen.scroll_based_on_cursor_location(self.grid.cursor(), &self.vars);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
174
src/app/clipboard.rs
Normal file
174
src/app/clipboard.rs
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
use std::cmp::{max, min};
|
||||||
|
|
||||||
|
use crate::app::logic::calc::{CellType, Grid};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
use crate::app::{
|
||||||
|
app::App, mode::{Chord, Mode}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Clipboard {
|
||||||
|
// could this just be a grid?
|
||||||
|
clipboard: Vec<Vec<Option<CellType>>>,
|
||||||
|
// top_left_cell: (usize, usize),
|
||||||
|
last_paste_cell: (usize, usize),
|
||||||
|
momentum: (i32, i32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clipboard {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
clipboard: Vec::new(),
|
||||||
|
last_paste_cell: (0, 0),
|
||||||
|
momentum: (0, 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// After pasting you gain momentum which can be used to
|
||||||
|
/// to move the cursor in the same direction for the next
|
||||||
|
/// paste.
|
||||||
|
pub fn momentum(&self) -> (i32, i32) {
|
||||||
|
// normalize to (-1,-1) to (1,1)
|
||||||
|
let (mx, my) = self.momentum;
|
||||||
|
let x = min(mx, 1);
|
||||||
|
let x = max(x, -1);
|
||||||
|
|
||||||
|
let y = min(my, 1);
|
||||||
|
let y = max(y, -1);
|
||||||
|
|
||||||
|
// prevent diagonal momentum
|
||||||
|
if y != 0 {
|
||||||
|
(0, y)
|
||||||
|
} else {
|
||||||
|
(x,0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn paste(&mut self, into: &mut Grid) {
|
||||||
|
// cursor
|
||||||
|
let (cx, cy) = into.cursor();
|
||||||
|
|
||||||
|
for (x, row) in self.clipboard.iter().enumerate() {
|
||||||
|
for (y, cell) in row.iter().enumerate() {
|
||||||
|
into.set_cell_raw((x + cx, y + cy), cell.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (lx, ly) = self.last_paste_cell;
|
||||||
|
self.momentum = (cx as i32 - lx as i32, cy as i32 - ly as i32);
|
||||||
|
self.last_paste_cell = (cx, cy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clones data from Grid into self.
|
||||||
|
/// Start and end don't have to be sorted in any sort of way. The function works with
|
||||||
|
/// any two points.
|
||||||
|
pub fn clipboard_copy(&mut self, start: (usize, usize), end: (usize, usize), from: &Grid) {
|
||||||
|
let (x1, y1) = start;
|
||||||
|
let (x2, y2) = end;
|
||||||
|
|
||||||
|
let (low_x, hi_x) = if x1 < x2 { (x1, x2) } else { (x2, x1) };
|
||||||
|
let (low_y, hi_y) = if y1 < y2 { (y1, y2) } else { (y2, y1) };
|
||||||
|
|
||||||
|
// size the clipboard appropriately
|
||||||
|
self.clipboard.clear();
|
||||||
|
// clone data into clipboard
|
||||||
|
for x in low_x..=hi_x {
|
||||||
|
let mut col = Vec::new();
|
||||||
|
for y in low_y..=hi_y {
|
||||||
|
let a = from.get_cell_raw(x, y);
|
||||||
|
col.push(a.clone());
|
||||||
|
}
|
||||||
|
self.clipboard.push(col);
|
||||||
|
}
|
||||||
|
self.last_paste_cell = (low_x, low_y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn copy_paste() {
|
||||||
|
let mut app = App::new();
|
||||||
|
|
||||||
|
app.grid.set_cell("A0", "hello".to_string());
|
||||||
|
app.grid.mv_cursor_to(0, 0);
|
||||||
|
|
||||||
|
app.mode = super::mode::Mode::Chord(Chord::new('y'));
|
||||||
|
Mode::process_key(&mut app, 'y');
|
||||||
|
// yy will have set mode back to normal at this point
|
||||||
|
|
||||||
|
assert_eq!(app.clipboard.clipboard.len(), 1);
|
||||||
|
assert!(app.clipboard.clipboard[0][0].as_ref().is_some_and(|c| c.to_string() == "hello"));
|
||||||
|
|
||||||
|
app.grid.mv_cursor_to(1, 1);
|
||||||
|
Mode::process_key(&mut app, 'p');
|
||||||
|
|
||||||
|
let a = app.grid.get_cell("B1").as_ref().expect("Should've been set by paste");
|
||||||
|
assert_eq!(a.to_string(), "hello");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn momentum_y_pos() {
|
||||||
|
let mut app = App::new();
|
||||||
|
|
||||||
|
app.grid.set_cell("A0", "hello".to_string());
|
||||||
|
app.grid.mv_cursor_to(0, 0);
|
||||||
|
|
||||||
|
app.mode = super::mode::Mode::Chord(Chord::new('y'));
|
||||||
|
Mode::process_key(&mut app, 'y');
|
||||||
|
// yy will have set mode back to normal at this point
|
||||||
|
|
||||||
|
app.grid.mv_cursor_to(0, 1);
|
||||||
|
Mode::process_key(&mut app, 'p');
|
||||||
|
|
||||||
|
assert_eq!(app.clipboard.momentum(), (0,1));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn momentum_y_neg() {
|
||||||
|
let mut app = App::new();
|
||||||
|
|
||||||
|
app.grid.set_cell("A1", "hello".to_string());
|
||||||
|
app.grid.mv_cursor_to(0, 1);
|
||||||
|
|
||||||
|
app.mode = super::mode::Mode::Chord(Chord::new('y'));
|
||||||
|
Mode::process_key(&mut app, 'y');
|
||||||
|
// yy will have set mode back to normal at this point
|
||||||
|
|
||||||
|
app.grid.mv_cursor_to(0, 0);
|
||||||
|
Mode::process_key(&mut app, 'p');
|
||||||
|
|
||||||
|
assert_eq!(app.clipboard.momentum(), (0,-1));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn momentum_x_pos() {
|
||||||
|
let mut app = App::new();
|
||||||
|
|
||||||
|
app.grid.set_cell("A0", "hello".to_string());
|
||||||
|
app.grid.mv_cursor_to(0, 0);
|
||||||
|
|
||||||
|
app.mode = super::mode::Mode::Chord(Chord::new('y'));
|
||||||
|
Mode::process_key(&mut app, 'y');
|
||||||
|
// yy will have set mode back to normal at this point
|
||||||
|
|
||||||
|
app.grid.mv_cursor_to(1, 0);
|
||||||
|
Mode::process_key(&mut app, 'p');
|
||||||
|
|
||||||
|
assert_eq!(app.clipboard.momentum(), (1,0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn momentum_x_neg() {
|
||||||
|
let mut app = App::new();
|
||||||
|
|
||||||
|
app.grid.set_cell("B0", "hello".to_string());
|
||||||
|
app.grid.mv_cursor_to(1, 0);
|
||||||
|
|
||||||
|
app.mode = super::mode::Mode::Chord(Chord::new('y'));
|
||||||
|
Mode::process_key(&mut app, 'y');
|
||||||
|
// yy will have set mode back to normal at this point
|
||||||
|
|
||||||
|
app.grid.mv_cursor_to(0, 0);
|
||||||
|
Mode::process_key(&mut app, 'p');
|
||||||
|
|
||||||
|
assert_eq!(app.clipboard.momentum(), (-1,0));
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ use std::{
|
|||||||
|
|
||||||
use evalexpr::*;
|
use evalexpr::*;
|
||||||
|
|
||||||
use crate::app::logic::ctx;
|
use crate::app::{app::App, logic::ctx};
|
||||||
|
|
||||||
pub const LEN: usize = 1000;
|
pub const LEN: usize = 1000;
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ pub struct Grid {
|
|||||||
// ...
|
// ...
|
||||||
cells: Vec<Vec<Option<CellType>>>,
|
cells: Vec<Vec<Option<CellType>>>,
|
||||||
/// (X, Y)
|
/// (X, Y)
|
||||||
pub selected_cell: (usize, usize),
|
selected_cell: (usize, usize),
|
||||||
/// Have unsaved modifications been made?
|
/// Have unsaved modifications been made?
|
||||||
dirty: bool,
|
dirty: bool,
|
||||||
}
|
}
|
||||||
@@ -34,6 +34,14 @@ const CSV_DELIMITER: char = ',';
|
|||||||
const CSV_ESCAPE: char = '"';
|
const CSV_ESCAPE: char = '"';
|
||||||
|
|
||||||
impl Grid {
|
impl Grid {
|
||||||
|
pub fn cursor(&self) -> (usize, usize) {
|
||||||
|
self.selected_cell
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mv_cursor_to(&mut self, x: usize, y: usize) {
|
||||||
|
self.selected_cell = (x,y)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn needs_to_be_saved(&self) -> bool {
|
pub fn needs_to_be_saved(&self) -> bool {
|
||||||
self.dirty
|
self.dirty
|
||||||
}
|
}
|
||||||
@@ -311,7 +319,7 @@ impl Grid {
|
|||||||
|
|
||||||
/// Helper for tests
|
/// Helper for tests
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn set_cell<T: Into<CellType>>(&mut self, cell_id: &str, val: T) {
|
pub fn set_cell<T: Into<CellType>>(&mut self, cell_id: &str, val: T) {
|
||||||
if let Some(loc) = Self::parse_to_idx(cell_id) {
|
if let Some(loc) = Self::parse_to_idx(cell_id) {
|
||||||
self.set_cell_raw(loc, Some(val));
|
self.set_cell_raw(loc, Some(val));
|
||||||
}
|
}
|
||||||
@@ -365,7 +373,7 @@ impl Default for Grid {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum CellType {
|
pub enum CellType {
|
||||||
Number(f64),
|
Number(f64),
|
||||||
String(String),
|
String(String),
|
||||||
@@ -710,5 +718,16 @@ fn ranges() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn recursive_ranges() {
|
fn recursive_ranges() {
|
||||||
// recursive ranges causes weird behavior
|
// recursive ranges causes weird behavior
|
||||||
todo!();
|
// todo!();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cursor_fns() {
|
||||||
|
// surprisingly, this test was needed
|
||||||
|
let mut app = App::new();
|
||||||
|
let c = app.grid.cursor();
|
||||||
|
assert_eq!(c, (0,0));
|
||||||
|
|
||||||
|
app.grid.mv_cursor_to(1, 0);
|
||||||
|
assert_eq!(app.grid.cursor(), (1,0));
|
||||||
}
|
}
|
||||||
@@ -3,3 +3,4 @@ mod mode;
|
|||||||
mod error_msg;
|
mod error_msg;
|
||||||
mod screen;
|
mod screen;
|
||||||
mod logic;
|
mod logic;
|
||||||
|
mod clipboard;
|
||||||
@@ -116,31 +116,36 @@ impl Mode {
|
|||||||
match key {
|
match key {
|
||||||
// <
|
// <
|
||||||
'h' => {
|
'h' => {
|
||||||
app.grid.selected_cell.0 = app.grid.selected_cell.0.saturating_sub(1);
|
let (x, y) = app.grid.cursor();
|
||||||
|
app.grid.mv_cursor_to(x.saturating_sub(1), y);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// v
|
// v
|
||||||
'j' => {
|
'j' => {
|
||||||
app.grid.selected_cell.1 = min(app.grid.selected_cell.1.saturating_add(1), LEN - 1);
|
let (x, y) = app.grid.cursor();
|
||||||
|
app.grid.mv_cursor_to(x, min(y.saturating_add(1), LEN - 1));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// ^
|
// ^
|
||||||
'k' => {
|
'k' => {
|
||||||
app.grid.selected_cell.1 = app.grid.selected_cell.1.saturating_sub(1);
|
let (x, y) = app.grid.cursor();
|
||||||
|
app.grid.mv_cursor_to(x, y.saturating_sub(1));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// >
|
// >
|
||||||
'l' => {
|
'l' => {
|
||||||
app.grid.selected_cell.0 = min(app.grid.selected_cell.0.saturating_add(1), LEN - 1);
|
let (x, y) = app.grid.cursor();
|
||||||
|
app.grid.mv_cursor_to(min(x.saturating_add(1), LEN - 1), y);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
'0' => {
|
'0' => {
|
||||||
app.grid.selected_cell.0 = 0;
|
let (_, y) = app.grid.cursor();
|
||||||
|
app.grid.mv_cursor_to(0, y);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// edit cell
|
// edit cell
|
||||||
'i' | 'a' => {
|
'i' | 'a' => {
|
||||||
let (x, y) = app.grid.selected_cell;
|
let (x, y) = app.grid.cursor();
|
||||||
|
|
||||||
let val = app.grid.get_cell_raw(x, y).as_ref().map(|f| f.to_string()).unwrap_or(String::new());
|
let val = app.grid.get_cell_raw(x, y).as_ref().map(|f| f.to_string()).unwrap_or(String::new());
|
||||||
|
|
||||||
@@ -154,8 +159,15 @@ impl Mode {
|
|||||||
'A' => { /* insert col after */ }
|
'A' => { /* insert col after */ }
|
||||||
'o' => { /* insert row below */ }
|
'o' => { /* insert row below */ }
|
||||||
'O' => { /* insert row above */ }
|
'O' => { /* insert row above */ }
|
||||||
'v' => app.mode = Mode::Visual(app.grid.selected_cell),
|
'v' => app.mode = Mode::Visual(app.grid.cursor()),
|
||||||
':' => app.mode = Mode::Command(Chord::new(':')),
|
':' => app.mode = Mode::Command(Chord::new(':')),
|
||||||
|
'p' => {
|
||||||
|
app.clipboard.paste(&mut app.grid);
|
||||||
|
let (cx, cy) = app.grid.cursor();
|
||||||
|
let (mx, my) = app.clipboard.momentum();
|
||||||
|
app.grid.mv_cursor_to((cx as i32 + mx) as usize, (cy as i32 + my) as usize);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// loose chars will put you into chord mode
|
// loose chars will put you into chord mode
|
||||||
c => {
|
c => {
|
||||||
if let Mode::Normal = app.mode {
|
if let Mode::Normal = app.mode {
|
||||||
@@ -165,12 +177,13 @@ impl Mode {
|
|||||||
}
|
}
|
||||||
if let Mode::Visual((x1, y1)) = app.mode {
|
if let Mode::Visual((x1, y1)) = app.mode {
|
||||||
// TODO visual copy, paste, etc
|
// TODO visual copy, paste, etc
|
||||||
let (x2, y2) = app.grid.selected_cell;
|
let (x2, y2) = app.grid.cursor();
|
||||||
|
|
||||||
let (low_x, hi_x) = if x1 < x2 { (x1, x2) } else { (x2, x1) };
|
let (low_x, hi_x) = if x1 < x2 { (x1, x2) } else { (x2, x1) };
|
||||||
let (low_y, hi_y) = if y1 < y2 { (y1, y2) } else { (y2, y1) };
|
let (low_y, hi_y) = if y1 < y2 { (y1, y2) } else { (y2, y1) };
|
||||||
|
|
||||||
if key == 'd' {
|
match key {
|
||||||
|
'd' => {
|
||||||
for x in low_x..=hi_x {
|
for x in low_x..=hi_x {
|
||||||
for y in low_y..=hi_y {
|
for y in low_y..=hi_y {
|
||||||
app.grid.set_cell_raw::<CellType>((x, y), None);
|
app.grid.set_cell_raw::<CellType>((x, y), None);
|
||||||
@@ -178,6 +191,11 @@ impl Mode {
|
|||||||
}
|
}
|
||||||
app.mode = Mode::Normal
|
app.mode = Mode::Normal
|
||||||
}
|
}
|
||||||
|
'y' => {
|
||||||
|
app.clipboard.clipboard_copy((x1, y1), (x2, y2), &app.grid);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Mode::Chord(chord) => {
|
Mode::Chord(chord) => {
|
||||||
@@ -194,8 +212,8 @@ impl Mode {
|
|||||||
// For chords that can take a numeric input
|
// For chords that can take a numeric input
|
||||||
Ok(num) => match key {
|
Ok(num) => match key {
|
||||||
'G' => {
|
'G' => {
|
||||||
let sel = app.grid.selected_cell;
|
let (x, _) = app.grid.cursor();
|
||||||
app.grid.selected_cell = (sel.0, num);
|
app.grid.mv_cursor_to(x, num);
|
||||||
app.mode = Mode::Normal;
|
app.mode = Mode::Normal;
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
@@ -213,33 +231,40 @@ impl Mode {
|
|||||||
match (&c[..c.len() - 1], key) {
|
match (&c[..c.len() - 1], key) {
|
||||||
// delete cell under cursor
|
// delete cell under cursor
|
||||||
("d", ' ') | ("d", 'w') => {
|
("d", ' ') | ("d", 'w') => {
|
||||||
let loc = app.grid.selected_cell;
|
let loc = app.grid.cursor();
|
||||||
app.grid.set_cell_raw::<CellType>(loc, None);
|
app.grid.set_cell_raw::<CellType>(loc, None);
|
||||||
app.mode = Mode::Normal;
|
app.mode = Mode::Normal;
|
||||||
}
|
}
|
||||||
// go to top of row
|
// go to top of row
|
||||||
("g", 'g') => {
|
("g", 'g') => {
|
||||||
app.grid.selected_cell.1 = 0;
|
let (x, _) = app.grid.cursor();
|
||||||
|
app.grid.mv_cursor_to(x, 0);
|
||||||
app.mode = Mode::Normal;
|
app.mode = Mode::Normal;
|
||||||
}
|
}
|
||||||
// center screen to cursor
|
// center screen to cursor
|
||||||
("z", 'z') => {
|
("z", 'z') => {
|
||||||
app.screen.center_x(app.grid.selected_cell, &app.vars);
|
app.screen.center_x(app.grid.cursor(), &app.vars);
|
||||||
app.screen.center_y(app.grid.selected_cell, &app.vars);
|
app.screen.center_y(app.grid.cursor(), &app.vars);
|
||||||
app.mode = Mode::Normal;
|
app.mode = Mode::Normal;
|
||||||
}
|
}
|
||||||
// mark cell
|
// mark cell
|
||||||
("m", i) => {
|
("m", i) => {
|
||||||
app.marks.insert(i, app.grid.selected_cell);
|
app.marks.insert(i, app.grid.cursor());
|
||||||
app.mode = Mode::Normal;
|
app.mode = Mode::Normal;
|
||||||
}
|
}
|
||||||
// goto marked cell
|
// goto marked cell
|
||||||
("'", i) => {
|
("'", i) => {
|
||||||
if let Some(coords) = app.marks.get(&i) {
|
if let Some((cx, cy)) = app.marks.get(&i) {
|
||||||
app.grid.selected_cell = *coords;
|
app.grid.mv_cursor_to(*cx, *cy);
|
||||||
}
|
}
|
||||||
app.mode = Mode::Normal;
|
app.mode = Mode::Normal;
|
||||||
}
|
}
|
||||||
|
// copy 1 cell
|
||||||
|
("y", 'y') => {
|
||||||
|
let point = app.grid.cursor();
|
||||||
|
app.clipboard.clipboard_copy(point, point, &app.grid);
|
||||||
|
app.mode = Mode::Normal;
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -295,31 +320,47 @@ impl Widget for &Chord {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn movement_keybinds() {
|
||||||
|
let mut app = App::new();
|
||||||
|
|
||||||
|
assert_eq!(app.grid.cursor(), (0, 0));
|
||||||
|
Mode::process_key(&mut app, 'j');
|
||||||
|
assert_eq!(app.grid.cursor(), (0, 1));
|
||||||
|
|
||||||
|
Mode::process_key(&mut app, 'l');
|
||||||
|
assert_eq!(app.grid.cursor(), (1, 1));
|
||||||
|
|
||||||
|
Mode::process_key(&mut app, 'k');
|
||||||
|
assert_eq!(app.grid.cursor(), (1, 0));
|
||||||
|
|
||||||
|
Mode::process_key(&mut app, 'h');
|
||||||
|
assert_eq!(app.grid.cursor(), (0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn keybinds() {
|
fn keybinds() {
|
||||||
let mut app = App::new();
|
let mut app = App::new();
|
||||||
|
|
||||||
assert_eq!(app.grid.selected_cell, (0,0));
|
|
||||||
|
|
||||||
// start at B1
|
// start at B1
|
||||||
app.grid.selected_cell = (1,1);
|
app.grid.mv_cursor_to(1, 1);
|
||||||
assert_eq!(app.grid.selected_cell, (1,1));
|
assert_eq!(app.grid.cursor(), (1, 1));
|
||||||
|
|
||||||
// gg
|
// gg
|
||||||
app.mode = Mode::Chord(Chord::new('g'));
|
app.mode = Mode::Chord(Chord::new('g'));
|
||||||
Mode::process_key(&mut app, 'g');
|
Mode::process_key(&mut app, 'g');
|
||||||
assert_eq!(app.grid.selected_cell, (1,0));
|
assert_eq!(app.grid.cursor(), (1, 0));
|
||||||
|
|
||||||
// 0
|
// 0
|
||||||
app.mode = Mode::Normal;
|
app.mode = Mode::Normal;
|
||||||
Mode::process_key(&mut app, '0');
|
Mode::process_key(&mut app, '0');
|
||||||
assert_eq!(app.grid.selected_cell, (0,0));
|
assert_eq!(app.grid.cursor(), (0, 0));
|
||||||
|
|
||||||
// 10l
|
// 10l
|
||||||
// this should mean all the directions work
|
// this should mean all the directions work
|
||||||
app.grid.selected_cell = (0,0);
|
app.grid.mv_cursor_to(0, 0);
|
||||||
app.mode = Mode::Chord(Chord::new('1'));
|
app.mode = Mode::Chord(Chord::new('1'));
|
||||||
Mode::process_key(&mut app, '0');
|
Mode::process_key(&mut app, '0');
|
||||||
Mode::process_key(&mut app, 'l');
|
Mode::process_key(&mut app, 'l');
|
||||||
assert_eq!(app.grid.selected_cell, (10,0));
|
assert_eq!(app.grid.cursor(), (10, 0));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user