mirror of
https://github.com/zaphar/sheetsui.git
synced 2025-07-25 06:19:51 -04:00
Compare commits
2 Commits
3f79c94b12
...
f8faeacf39
Author | SHA1 | Date | |
---|---|---|---|
f8faeacf39 | |||
982622859b |
@ -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
|
||||||
|
188
src/ui/mod.rs
188
src/ui/mod.rs
@ -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>,
|
||||||
|
@ -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);
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
@ -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");
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user