diff --git a/docs/index.md b/docs/index.md
index 83db44d..aea95d9 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -22,11 +22,14 @@ Options:
## User Interface
-The sheetui user interface is loosely inspired by vim. It is a modal interface that is entirely keyboard driven. At nearly any time you can type `Alt-h` to get some context sensitive help.
+The sheetui user interface is loosely inspired by vim. It is a modal interface
+that is entirely keyboard driven. At nearly any time you can type `Alt-h` to
+get some context sensitive help.
### Navigation Mode
-The interface will start out in navigation mode. You can navigate around the table and between the sheets using the following keybinds:
+The interface will start out in navigation mode. You can navigate around the
+table and between the sheets using the following keybinds:
**Cell Navigation**
@@ -44,7 +47,9 @@ Sheet navigation moving will loop around when you reach the ends.
**Numeric prefixes**
-You can prefix each of the keybinds above with a numeric prefix to do them that many times. So typing `123h` will move to the left 123 times. Hitting `Esc` will clear the numeric prefix if you want to cancel it.
+You can prefix each of the keybinds above with a numeric prefix to do them that
+many times. So typing `123h` will move to the left 123 times. Hitting `Esc`
+will clear the numeric prefix if you want to cancel it.
**Modifying the Sheet or Cells**
@@ -54,33 +59,51 @@ You can prefix each of the keybinds above with a numeric prefix to do them that
**Other Keybindings**
+* `Ctrl-r` will enter range selection mode
* `Ctrl-s` will save the sheet.
* `q` will exit the application.
* `:` will enter CommandMode.
-
-
+
+Range selections made from navigation mode will be available to paste into a Cell Edit.
+
+
### CellEdit Mode
-You enter CellEdit mode by hitting `e` or `i` while in navigation mode. Type what you want into the cell.
+You enter CellEdit mode by hitting `e` or `i` while in navigation mode. Type
+what you want into the cell.
Starting with:
* `=` will treat what you type as a formula.
* `$` will treat it as us currency.
-Typing a number will treat the contents as a number. While typing non-numeric text will treat it as text content.
+Typing a number will treat the contents as a number. While typing non-numeric
+text will treat it as text content.
-For the most part this should work the same way you expect a spreadsheet to work.
+
+
+For the most part this should work the same way you expect a spreadsheet to
+work.
* `Enter` will update the cell contents.
* `Esc` will cancel editing the cell and leave it unedited.
+* `Ctrl-p` will paste the range selection if it exists into the cell.
-You can find the functions we support documented here: [ironcalc docs](https://docs.ironcalc.com/functions/lookup-and-reference.html)
+`Ctrl-r` will enter range select mode when editing a formula. You can navigate
+around the sheet and hit space to select that cell in the sheet to set the
+start of the range. Navigate some more and hit space to set the end of the
+range.
+
+You can find the functions we support documented here:
+[ironcalc docs](https://docs.ironcalc.com/functions/lookup-and-reference.html)
### Command Mode
-You enter command mode by typing `:` while in navigation mode. You can then type a command and hit `Enter` to execute it or `Esc` to cancel.
+You enter command mode by typing `:` while in navigation mode. You can then
+type a command and hit `Enter` to execute it or `Esc` to cancel.
The currently supported commands are:
@@ -93,4 +116,22 @@ The currently supported commands are:
* `edit ` Edit a new spreadsheet at the current path. `e` is a shorthand alias for this command.
* `quit` Quits the application. `q` is a shorthand alias for this command.
-
+
+
+### Range Select Mode
+
+Range Select mode copies a range reference for use later. You can enter range
+select mode from CellEdit mode with `CTRL-r`.
+
+* `h`, `j`, `k`, `l` will navigate around the sheet.
+* `Ctrl-n`, `Ctrl-p` will navigate between sheets.
+* `The spacebar will select the start and end of the range respectively.
+
+When you have selected the end of the range you will exit range select mode and
+the range reference will be placed into the cell contents you are editing.
+
+
diff --git a/src/book/mod.rs b/src/book/mod.rs
index 5e481b7..8602ea2 100644
--- a/src/book/mod.rs
+++ b/src/book/mod.rs
@@ -287,6 +287,13 @@ impl Book {
.map_err(|s| anyhow!("Invalid Worksheet: {}", s))?)
}
+ pub(crate) fn get_sheet_name_by_idx(&self, idx: usize) -> Result<&str> {
+ Ok(&self
+ .model
+ .workbook
+ .worksheet(idx as u32)
+ .map_err(|s| anyhow!("Invalid Worksheet: {}", s))?.name)
+ }
pub(crate) fn get_sheet_by_idx_mut(&mut self, idx: usize) -> Result<&mut Worksheet> {
Ok(self
.model
diff --git a/src/ui/mod.rs b/src/ui/mod.rs
index c12b2a7..01ff5c1 100644
--- a/src/ui/mod.rs
+++ b/src/ui/mod.rs
@@ -30,6 +30,40 @@ pub enum Modality {
CellEdit,
Command,
Dialog,
+ RangeSelect,
+}
+
+#[derive(Debug, Default)]
+pub struct RangeSelection {
+ pub original_location: Option,
+ pub original_sheet: Option,
+ pub sheet: Option,
+ pub start: Option,
+ pub end: Option,
+}
+
+impl RangeSelection {
+ pub fn get_range(&self) -> Option<(Address, Address)> {
+ if let (Some(start), Some(end)) = (&self.start, &self.end) {
+ return Some((
+ Address {
+ row: std::cmp::min(start.row, end.row),
+ col: std::cmp::min(start.col, end.col),
+ },
+ Address {
+ row: std::cmp::max(start.row, end.row),
+ col: std::cmp::max(start.col, end.col),
+ },
+ ));
+ }
+ None
+ }
+
+ pub fn reset_range_selection(&mut self) {
+ self.start = None;
+ self.end = None;
+ self.sheet = None;
+ }
}
#[derive(Debug)]
@@ -38,6 +72,7 @@ pub struct AppState<'ws> {
pub viewport_state: ViewportState,
pub command_state: TextState<'ws>,
pub numeric_prefix: Vec,
+ pub range_select: RangeSelection,
dirty: bool,
popup: Vec,
}
@@ -49,6 +84,7 @@ impl<'ws> Default for AppState<'ws> {
viewport_state: Default::default(),
command_state: Default::default(),
numeric_prefix: Default::default(),
+ range_select: Default::default(),
dirty: Default::default(),
popup: Default::default(),
}
@@ -97,6 +133,19 @@ impl Address {
pub fn new(row: usize, col: usize) -> Self {
Self { row, col }
}
+
+ pub fn to_range_part(&self) -> String {
+ let count = if self.col == 26 {
+ 1
+ } else {
+ (self.col / 26) + 1
+ };
+ format!(
+ "{}{}",
+ render::viewport::COLNAMES[(self.col - 1) % 26].repeat(count),
+ self.row
+ )
+ }
}
impl Default for Address {
@@ -149,6 +198,26 @@ impl<'ws> Workspace<'ws> {
Ok(())
}
+ pub fn selected_range_to_string(&self) -> String {
+ let state = &self.state;
+ if let Some((start, end)) = state.range_select.get_range() {
+ let a1 = format!("{}{}", start.to_range_part(), format!(":{}", end.to_range_part()));
+ if let Some(range_sheet) = state.range_select.sheet {
+ if range_sheet != self.book.current_sheet {
+ return format!(
+ "{}!{}",
+ self.book
+ .get_sheet_name_by_idx(range_sheet as usize)
+ .expect("No such sheet index"),
+ a1
+ );
+ }
+ }
+ return a1;
+ }
+ return String::new()
+ }
+
/// Move a row down in the current sheet.
pub fn move_down(&mut self) -> Result<()> {
let mut loc = self.book.location.clone();
@@ -197,6 +266,7 @@ impl<'ws> Workspace<'ws> {
Modality::CellEdit => self.handle_edit_input(key)?,
Modality::Command => self.handle_command_input(key)?,
Modality::Dialog => self.handle_dialog_input(key)?,
+ Modality::RangeSelect => self.handle_range_select_input(key)?,
};
return Ok(result);
}
@@ -217,13 +287,14 @@ impl<'ws> Workspace<'ws> {
"* CTRl-h: Shrink column width by 1".to_string(),
"* CTRl-n: Next sheet. Starts over at beginning if at end.".to_string(),
"* CTRl-p: Previous sheet. Starts over at end if at beginning.".to_string(),
- "* CTRl-?: Previous sheet. Starts over at end if at beginning.".to_string(),
+ "* ALT-h: Previous sheet. Starts over at end if at beginning.".to_string(),
"* q exit".to_string(),
"* Ctrl-S Save sheet".to_string(),
],
Modality::CellEdit => vec![
"Edit Mode:".to_string(),
"* ENTER/RETURN: Exit edit mode and save changes".to_string(),
+ "* Ctrl-r: Enter Range Selection mode".to_string(),
"* ESC: Exit edit mode and discard changes".to_string(),
"Otherwise edit as normal".to_string(),
],
@@ -233,6 +304,14 @@ impl<'ws> Workspace<'ws> {
"* CTRL-?: Exit command mode".to_string(),
"* ENTER/RETURN: run command and exit command mode".to_string(),
],
+ Modality::RangeSelect => vec![
+ "Range Selection Mode:".to_string(),
+ "* ESC: Exit command mode".to_string(),
+ "* h,j,k,l: vim style navigation".to_string(),
+ "* Spacebar: Select start and end of range".to_string(),
+ "* CTRl-n: Next sheet. Starts over at beginning if at end.".to_string(),
+ "* CTRl-p: Previous sheet. Starts over at end if at beginning.".to_string(),
+ ],
_ => vec!["General help".to_string()],
}
}
@@ -276,6 +355,17 @@ impl<'ws> Workspace<'ws> {
self.enter_dialog_mode(self.render_help_text());
return Ok(None);
}
+ KeyCode::Char('r') if key.modifiers == KeyModifiers::CONTROL => {
+ self.enter_range_select_mode();
+ return Ok(None);
+ }
+ KeyCode::Char('p') if key.modifiers == KeyModifiers::CONTROL => {
+ self.text_area
+ .set_yank_text(self.selected_range_to_string());
+ self.text_area.paste();
+ self.state.dirty = true;
+ return Ok(None);
+ }
KeyCode::Enter => self.exit_edit_mode(true)?,
KeyCode::Esc => self.exit_edit_mode(false)?,
_ => {
@@ -363,6 +453,91 @@ impl<'ws> Workspace<'ws> {
self.state.numeric_prefix.push(digit);
}
+ fn handle_range_select_input(&mut self, key: event::KeyEvent) -> Result