Compare commits

..

2 Commits

5 changed files with 129 additions and 112 deletions

View File

@ -63,8 +63,8 @@ will clear the numeric prefix if you want to cancel it.
* `Ctrl-r` will enter range selection mode * `Ctrl-r` will enter range selection mode
* `Ctrl-s` will save the sheet. * `Ctrl-s` will save the sheet.
* `Ctrl-c` Copy the cell or range contents. * `Ctrl-c`, `y` Copy the cell or range contents.
* `Ctrl-v` Paste into the sheet. * `Ctrl-v`, `p` Paste into the sheet.
* `Ctrl-Shift-C` Copy the cell or range formatted content. * `Ctrl-Shift-C` Copy the cell or range formatted content.
* `q` will exit the application. * `q` will exit the application.
* `:` will enter CommandMode. * `:` will enter CommandMode.
@ -132,8 +132,8 @@ select mode from CellEdit mode with `CTRL-r`.
* `h`, `j`, `k`, `l` will navigate around the sheet. * `h`, `j`, `k`, `l` will navigate around the sheet.
* `Ctrl-n`, `Ctrl-p` will navigate between sheets. * `Ctrl-n`, `Ctrl-p` will navigate between sheets.
* `Ctrl-c` Copy the cell or range contents. * `Ctrl-c`, `y` Copy the cell or range contents.
* `Ctrl-Shift-C` Copy the cell or range formatted content. * `Ctrl-Shift-C`, 'Y' Copy the cell or range formatted content.
* `The spacebar will select the start and end of the range respectively. * `The spacebar will select the start and end of the range respectively.
* `d` will delete the contents of the range leaving any style untouched * `d` will delete the contents of the range leaving any style untouched
* `D` will delete the contents of the range including any style * `D` will delete the contents of the range including any style

View File

@ -565,77 +565,13 @@ impl<'ws> Workspace<'ws> {
.contains(KeyModifiers::CONTROL | KeyModifiers::SHIFT) => .contains(KeyModifiers::CONTROL | KeyModifiers::SHIFT) =>
{ {
// TODO(zaphar): Share the algorithm below between both copies // TODO(zaphar): Share the algorithm below between both copies
self.update_range_selection()?; self.copy_range(true)?;
match &self.state.range_select.get_range() {
Some((
Address {
row: row_start,
col: col_start,
},
Address {
row: row_end,
col: col_end,
},
)) => {
let mut rows = Vec::new();
for ri in (*row_start)..=(*row_end) {
let mut cols = Vec::new();
for ci in (*col_start)..=(*col_end) {
cols.push(
self.book.get_cell_addr_rendered(&Address {
row: ri,
col: ci,
})?,
);
}
rows.push(cols);
}
self.state.clipboard = Some(ClipboardContents::Range(rows));
}
None => {
self.state.clipboard = Some(ClipboardContents::Cell(
self.book.get_current_cell_rendered()?,
));
}
}
self.exit_range_select_mode()?;
} }
KeyCode::Char('Y') => self.copy_range(true)?,
KeyCode::Char('c') if key.modifiers == KeyModifiers::CONTROL => { KeyCode::Char('c') if key.modifiers == KeyModifiers::CONTROL => {
self.update_range_selection()?; self.copy_range(false)?;
match &self.state.range_select.get_range() {
Some((
Address {
row: row_start,
col: col_start,
},
Address {
row: row_end,
col: col_end,
},
)) => {
let mut rows = Vec::new();
for ri in (*row_start)..=(*row_end) {
let mut cols = Vec::new();
for ci in (*col_start)..=(*col_end) {
cols.push(
self.book.get_cell_addr_contents(&Address {
row: ri,
col: ci,
})?,
);
}
rows.push(cols);
}
self.state.clipboard = Some(ClipboardContents::Range(rows));
}
None => {
self.state.clipboard = Some(ClipboardContents::Cell(
self.book.get_current_cell_contents()?,
));
}
}
self.exit_range_select_mode()?;
} }
KeyCode::Char('y') => self.copy_range(false)?,
_ => { _ => {
// moop // moop
} }
@ -644,6 +580,49 @@ impl<'ws> Workspace<'ws> {
Ok(None) Ok(None)
} }
fn copy_range(&mut self, formatted: bool) -> Result<(), anyhow::Error> {
self.update_range_selection()?;
match &self.state.range_select.get_range() {
Some((
Address {
row: row_start,
col: col_start,
},
Address {
row: row_end,
col: col_end,
},
)) => {
let mut rows = Vec::new();
for ri in (*row_start)..=(*row_end) {
let mut cols = Vec::new();
for ci in (*col_start)..=(*col_end) {
cols.push(if formatted {
self.book
.get_cell_addr_rendered(&Address { row: ri, col: ci })?
} else {
self.book
.get_cell_addr_contents(&Address { row: ri, col: ci })?
});
}
rows.push(cols);
}
self.state.clipboard = Some(ClipboardContents::Range(rows));
}
None => {
self.state.clipboard = Some(ClipboardContents::Cell(if formatted {
self.book
.get_current_cell_rendered()?
} else {
self.book
.get_current_cell_contents()?
}));
}
}
self.exit_range_select_mode()?;
Ok(())
}
fn update_range_selection(&mut self) -> Result<(), anyhow::Error> { fn update_range_selection(&mut self) -> Result<(), anyhow::Error> {
Ok(if self.state.range_select.start.is_none() { Ok(if self.state.range_select.start.is_none() {
self.state.range_select.start = Some(self.book.location.clone()); self.state.range_select.start = Some(self.book.location.clone());
@ -686,6 +665,16 @@ impl<'ws> Workspace<'ws> {
self.book.get_current_cell_contents()?, self.book.get_current_cell_contents()?,
)); ));
} }
KeyCode::Char('y') => {
self.state.clipboard = Some(ClipboardContents::Cell(
self.book.get_current_cell_contents()?,
));
}
KeyCode::Char('Y') => {
self.state.clipboard = Some(ClipboardContents::Cell(
self.book.get_current_cell_rendered()?,
));
}
KeyCode::Char('C') KeyCode::Char('C')
if key if key
.modifiers .modifiers
@ -695,33 +684,14 @@ impl<'ws> Workspace<'ws> {
self.book.get_current_cell_rendered()?, self.book.get_current_cell_rendered()?,
)); ));
} }
KeyCode::Char('v') if key.modifiers != KeyModifiers::CONTROL => {
self.enter_range_select_mode()
}
KeyCode::Char('p') if key.modifiers != KeyModifiers::CONTROL => {
self.paste_range()?;
}
KeyCode::Char('v') if key.modifiers == KeyModifiers::CONTROL => { KeyCode::Char('v') if key.modifiers == KeyModifiers::CONTROL => {
match &self.state.clipboard { self.paste_range()?;
Some(ClipboardContents::Cell(contents)) => {
self.book.edit_current_cell(contents)?;
}
Some(ClipboardContents::Range(ref rows)) => {
let Address { row, col } = self.book.location.clone();
let row_len = rows.len();
for ri in 0..row_len {
let columns = &rows[ri];
let col_len = columns.len();
for ci in 0..col_len {
self.book.update_cell(
&Address {
row: ri + row,
col: ci + col,
},
columns[ci].clone(),
)?;
}
}
}
None => {
// NOOP
}
}
self.state.clipboard = None;
} }
KeyCode::Char('h') if key.modifiers == KeyModifiers::ALT => { KeyCode::Char('h') if key.modifiers == KeyModifiers::ALT => {
self.enter_dialog_mode(self.render_help_text()); self.enter_dialog_mode(self.render_help_text());
@ -835,6 +805,36 @@ impl<'ws> Workspace<'ws> {
return Ok(None); return Ok(None);
} }
fn paste_range(&mut self) -> Result<(), anyhow::Error> {
match &self.state.clipboard {
Some(ClipboardContents::Cell(contents)) => {
self.book.edit_current_cell(contents)?;
}
Some(ClipboardContents::Range(ref rows)) => {
let Address { row, col } = self.book.location.clone();
let row_len = rows.len();
for ri in 0..row_len {
let columns = &rows[ri];
let col_len = columns.len();
for ci in 0..col_len {
self.book.update_cell(
&Address {
row: ri + row,
col: ci + col,
},
columns[ci].clone(),
)?;
}
}
}
None => {
// NOOP
}
}
self.state.clipboard = None;
Ok(())
}
fn run_with_prefix( fn run_with_prefix(
&mut self, &mut self,
action: impl Fn(&mut Workspace<'_>) -> std::result::Result<(), anyhow::Error>, action: impl Fn(&mut Workspace<'_>) -> std::result::Result<(), anyhow::Error>,

View File

@ -28,25 +28,40 @@ impl<'ws> Workspace<'ws> {
]; ];
let mut rs: Vec<Box<dyn Fn(Rect, &mut Buffer, &mut Self)>> = vec![ let mut rs: Vec<Box<dyn Fn(Rect, &mut Buffer, &mut Self)>> = vec![
Box::new(|rect: Rect, buf: &mut Buffer, ws: &mut Self| { Box::new(|rect: Rect, buf: &mut Buffer, ws: &mut Self| {
let tabs = Tabs::new(ws.book.get_sheet_names().iter().enumerate().map(|(idx, name)| format!("{} {}", name, idx)).collect::<Vec<String>>()) let tabs = Tabs::new(
.select(Some(ws.book.current_sheet as usize)); ws.book
.get_sheet_names()
.iter()
.enumerate()
.map(|(idx, name)| format!("{} {}", name, idx))
.collect::<Vec<String>>(),
)
.select(Some(ws.book.current_sheet as usize));
tabs.render(rect, buf); tabs.render(rect, buf);
}), }),
Box::new(|rect: Rect, buf: &mut Buffer, ws: &mut Self| { Box::new(|rect: Rect, buf: &mut Buffer, ws: &mut Self| {
let [text_rect, info_rect] = Layout::horizontal(vec![Constraint::Fill(1),Constraint::Fill(1)]).areas(rect); let [text_rect, info_rect] =
Layout::horizontal(vec![Constraint::Fill(1), Constraint::Fill(1)]).areas(rect);
ws.text_area.render(text_rect, buf); ws.text_area.render(text_rect, buf);
let hint = Paragraph::new(vec![ let hint = Paragraph::new(vec![
Line::from(""), Line::from(""),
Line::from("ALT-h to toggle help dialog").centered() Line::from("ALT-h to toggle help dialog").centered(),
]); ]);
hint.render(info_rect, buf); hint.render(info_rect, buf);
}), }),
Box::new(move |rect: Rect, buf: &mut Buffer, ws: &mut Self| { Box::new(move |rect: Rect, buf: &mut Buffer, ws: &mut Self| {
let sheet_name = ws.book.get_sheet_name().unwrap_or("Unknown"); let sheet_name = ws.book.get_sheet_name().unwrap_or("Unknown");
let table_block = Block::bordered().title_top(sheet_name); let table_block = Block::bordered().title_top(sheet_name);
let viewport = Viewport::new(&ws.book, &ws.state.range_select) let viewport = Viewport::new(
.with_selected(ws.book.location.clone()) &ws.book,
.block(table_block); if ws.state.modality() == &Modality::RangeSelect {
Some(&ws.state.range_select)
} else {
None
},
)
.with_selected(ws.book.location.clone())
.block(table_block);
StatefulWidget::render(viewport, rect, buf, &mut ws.state.viewport_state); StatefulWidget::render(viewport, rect, buf, &mut ws.state.viewport_state);
}), }),
]; ];

View File

@ -14,7 +14,7 @@ fn test_viewport_get_visible_columns() {
let width = dbg!(dbg!(default_size) * 12 / 2); let width = dbg!(dbg!(default_size) * 12 / 2);
let app_state = AppState::default(); let app_state = AppState::default();
let viewport = let viewport =
Viewport::new(&book, &app_state.range_select).with_selected(Address { row: 1, col: 17 }); Viewport::new(&book, Some(&app_state.range_select)).with_selected(Address { row: 1, col: 17 });
let cols = viewport let cols = viewport
.get_visible_columns((width + 5) as u16, &mut state) .get_visible_columns((width + 5) as u16, &mut state)
.expect("Failed to get visible columns"); .expect("Failed to get visible columns");
@ -31,7 +31,7 @@ fn test_viewport_get_visible_rows() {
let height = 6; let height = 6;
let app_state = AppState::default(); let app_state = AppState::default();
let viewport = let viewport =
Viewport::new(&book, &app_state.range_select).with_selected(Address { row: 17, col: 1 }); Viewport::new(&book, Some(&app_state.range_select)).with_selected(Address { row: 17, col: 1 });
let rows = dbg!(viewport.get_visible_rows(height as u16, &mut state)); let rows = dbg!(viewport.get_visible_rows(height as u16, &mut state));
assert_eq!(height - 1, rows.len()); assert_eq!(height - 1, rows.len());
assert_eq!( assert_eq!(
@ -51,7 +51,7 @@ fn test_viewport_visible_columns_after_length_change() {
let width = dbg!(dbg!(default_size) * 12 / 2); let width = dbg!(dbg!(default_size) * 12 / 2);
{ {
let app_state = AppState::default(); let app_state = AppState::default();
let viewport = Viewport::new(&book, &app_state.range_select) let viewport = Viewport::new(&book, Some(&app_state.range_select))
.with_selected(Address { row: 1, col: 17 }); .with_selected(Address { row: 1, col: 17 });
let cols = viewport let cols = viewport
.get_visible_columns((width + 5) as u16, &mut state) .get_visible_columns((width + 5) as u16, &mut state)
@ -65,7 +65,7 @@ fn test_viewport_visible_columns_after_length_change() {
{ {
let app_state = AppState::default(); let app_state = AppState::default();
let viewport = let viewport =
Viewport::new(&book, &app_state.range_select).with_selected(Address { row: 1, col: 1 }); Viewport::new(&book, Some(&app_state.range_select)).with_selected(Address { row: 1, col: 1 });
let cols = viewport let cols = viewport
.get_visible_columns((width + 5) as u16, &mut state) .get_visible_columns((width + 5) as u16, &mut state)
.expect("Failed to get visible columns"); .expect("Failed to get visible columns");

View File

@ -37,7 +37,7 @@ pub struct ViewportState {
pub struct Viewport<'ws> { pub struct Viewport<'ws> {
pub(crate) selected: Address, pub(crate) selected: Address,
book: &'ws Book, book: &'ws Book,
range_selection: &'ws RangeSelection, range_selection: Option<&'ws RangeSelection>,
block: Option<Block<'ws>>, block: Option<Block<'ws>>,
} }
@ -47,7 +47,7 @@ pub(crate) const COLNAMES: [&'static str; 26] = [
]; ];
impl<'ws> Viewport<'ws> { impl<'ws> Viewport<'ws> {
pub fn new(book: &'ws Book, app_state: &'ws RangeSelection) -> Self { pub fn new(book: &'ws Book, app_state: Option<&'ws RangeSelection>) -> Self {
Self { Self {
book, book,
range_selection: app_state, range_selection: app_state,
@ -161,7 +161,9 @@ impl<'ws> Viewport<'ws> {
.get_cell_addr_rendered(&Address { row: ri, col: *ci }) .get_cell_addr_rendered(&Address { row: ri, col: *ci })
.unwrap(); .unwrap();
let mut cell = Cell::new(Text::raw(content)); let mut cell = Cell::new(Text::raw(content));
if let Some((start, end)) = &self.range_selection.get_range() { if let Some((start, end)) =
&self.range_selection.map_or(None, |r| r.get_range())
{
if ri >= start.row if ri >= start.row
&& ri <= end.row && ri <= end.row
&& *ci >= start.col && *ci >= start.col