mirror of
https://github.com/zaphar/sheetsui.git
synced 2025-07-22 13:00:22 -04:00
docs: help text, spelling
This commit is contained in:
parent
c62fd08043
commit
bb5d81106e
@ -76,6 +76,9 @@ 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-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
|
||||
@ -94,3 +97,15 @@ The currently supported commands are:
|
||||
* `quit` Quits the application. `q` is a shorthand alias for this command.
|
||||
|
||||
<aside>Note that in the case of `quit` and `edit` that we do not currently prompt you if the current spreadsheet has not been saved yet. So your changes will be discarded if you have not saved first.</aside>
|
||||
|
||||
### 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.
|
||||
|
||||
<aside>We only support continuous ranges for the moment. Planned for discontinuous ranges still needs the interaction interface to be determined.</aside>
|
||||
|
@ -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
|
||||
|
128
src/ui/mod.rs
128
src/ui/mod.rs
@ -30,7 +30,7 @@ pub enum Modality {
|
||||
CellEdit,
|
||||
Command,
|
||||
Dialog,
|
||||
RangeCopy,
|
||||
RangeSelect,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -40,6 +40,8 @@ pub struct AppState<'ws> {
|
||||
pub command_state: TextState<'ws>,
|
||||
pub numeric_prefix: Vec<char>,
|
||||
pub original_location: Option<Address>,
|
||||
pub original_sheet: Option<u32>,
|
||||
pub range_sheet: Option<u32>,
|
||||
pub start_range: Option<Address>,
|
||||
pub end_range: Option<Address>,
|
||||
dirty: bool,
|
||||
@ -54,6 +56,8 @@ impl<'ws> Default for AppState<'ws> {
|
||||
command_state: Default::default(),
|
||||
numeric_prefix: Default::default(),
|
||||
original_location: Default::default(),
|
||||
original_sheet: Default::default(),
|
||||
range_sheet: Default::default(),
|
||||
start_range: Default::default(),
|
||||
end_range: Default::default(),
|
||||
dirty: Default::default(),
|
||||
@ -104,6 +108,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 {
|
||||
@ -156,6 +173,33 @@ impl<'ws> Workspace<'ws> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn selected_range_to_string(&self) -> String {
|
||||
let state = &self.state;
|
||||
let start = state
|
||||
.start_range
|
||||
.as_ref()
|
||||
.map(|addr| addr.to_range_part())
|
||||
.unwrap_or_else(|| String::new());
|
||||
let end = state
|
||||
.end_range
|
||||
.as_ref()
|
||||
.map(|addr| format!(":{}", addr.to_range_part()))
|
||||
.unwrap_or_else(|| String::new());
|
||||
if let Some(range_sheet) = state.range_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"),
|
||||
start,
|
||||
end
|
||||
);
|
||||
}
|
||||
}
|
||||
format!("{}:{}", start, end)
|
||||
}
|
||||
|
||||
/// Move a row down in the current sheet.
|
||||
pub fn move_down(&mut self) -> Result<()> {
|
||||
let mut loc = self.book.location.clone();
|
||||
@ -204,7 +248,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::RangeCopy => self.handle_range_copy_input(key)?,
|
||||
Modality::RangeSelect => self.handle_range_select_input(key)?,
|
||||
};
|
||||
return Ok(result);
|
||||
}
|
||||
@ -225,13 +269,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(),
|
||||
],
|
||||
@ -241,6 +286,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()],
|
||||
}
|
||||
}
|
||||
@ -284,6 +337,15 @@ 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();
|
||||
return Ok(None);
|
||||
}
|
||||
KeyCode::Enter => self.exit_edit_mode(true)?,
|
||||
KeyCode::Esc => self.exit_edit_mode(false)?,
|
||||
_ => {
|
||||
@ -371,19 +433,22 @@ impl<'ws> Workspace<'ws> {
|
||||
self.state.numeric_prefix.push(digit);
|
||||
}
|
||||
|
||||
fn handle_range_copy_input(&mut self, key: event::KeyEvent) -> Result<Option<ExitCode>> {
|
||||
fn handle_range_select_input(&mut self, key: event::KeyEvent) -> Result<Option<ExitCode>> {
|
||||
if key.kind == KeyEventKind::Press {
|
||||
match key.code {
|
||||
KeyCode::Esc => {
|
||||
self.state.reset_n_prefix();
|
||||
}
|
||||
KeyCode::Char('h') if key.modifiers == KeyModifiers::ALT => {
|
||||
self.enter_dialog_mode(self.render_help_text());
|
||||
return Ok(None);
|
||||
}
|
||||
KeyCode::Char(d) if d.is_ascii_digit() => {
|
||||
self.handle_numeric_prefix(d);
|
||||
}
|
||||
KeyCode::Char('h') => {
|
||||
self.run_with_prefix(|ws: &mut Workspace<'_>| -> Result<()> {
|
||||
ws.move_left()?;
|
||||
dbg!(&ws.book.location);
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
@ -396,7 +461,6 @@ impl<'ws> Workspace<'ws> {
|
||||
KeyCode::Char('k') => {
|
||||
self.run_with_prefix(|ws: &mut Workspace<'_>| -> Result<()> {
|
||||
ws.move_up()?;
|
||||
dbg!(&ws.book.location);
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
@ -408,12 +472,26 @@ impl<'ws> Workspace<'ws> {
|
||||
}
|
||||
KeyCode::Char(' ') => {
|
||||
if self.state.start_range.is_none() {
|
||||
self.state.start_range = dbg!(Some(self.book.location.clone()));
|
||||
self.state.start_range = Some(self.book.location.clone());
|
||||
} else {
|
||||
self.state.end_range = dbg!(Some(self.book.location.clone()));
|
||||
self.exit_range_copy_mode()?;
|
||||
self.state.end_range = Some(self.book.location.clone());
|
||||
self.exit_range_select_mode()?;
|
||||
}
|
||||
}
|
||||
KeyCode::Char('n') if key.modifiers == KeyModifiers::CONTROL => {
|
||||
self.run_with_prefix(|ws: &mut Workspace<'_>| -> Result<()> {
|
||||
ws.book.select_next_sheet();
|
||||
Ok(())
|
||||
})?;
|
||||
self.state.range_sheet = Some(self.book.current_sheet);
|
||||
}
|
||||
KeyCode::Char('p') if key.modifiers == KeyModifiers::CONTROL => {
|
||||
self.run_with_prefix(|ws: &mut Workspace<'_>| -> Result<()> {
|
||||
ws.book.select_prev_sheet();
|
||||
Ok(())
|
||||
})?;
|
||||
self.state.range_sheet = Some(self.book.current_sheet);
|
||||
}
|
||||
_ => {
|
||||
// moop
|
||||
}
|
||||
@ -441,7 +519,7 @@ impl<'ws> Workspace<'ws> {
|
||||
self.save_file()?;
|
||||
}
|
||||
KeyCode::Char('r') if key.modifiers == KeyModifiers::CONTROL => {
|
||||
self.enter_range_copy_mode();
|
||||
self.enter_range_select_mode();
|
||||
}
|
||||
KeyCode::Char('h') if key.modifiers == KeyModifiers::ALT => {
|
||||
self.enter_dialog_mode(self.render_help_text());
|
||||
@ -549,7 +627,10 @@ impl<'ws> Workspace<'ws> {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
fn run_with_prefix(&mut self, action: impl Fn(&mut Workspace<'_>) -> std::result::Result<(), anyhow::Error>) -> Result<(), anyhow::Error> {
|
||||
fn run_with_prefix(
|
||||
&mut self,
|
||||
action: impl Fn(&mut Workspace<'_>) -> std::result::Result<(), anyhow::Error>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
for _ in 1..=self.state.get_n_prefix() {
|
||||
action(self)?;
|
||||
}
|
||||
@ -569,14 +650,15 @@ impl<'ws> Workspace<'ws> {
|
||||
self.state.modality_stack.push(Modality::Dialog);
|
||||
}
|
||||
|
||||
fn enter_range_copy_mode(&mut self) {
|
||||
fn enter_range_select_mode(&mut self) {
|
||||
self.state.range_sheet = Some(self.book.current_sheet);
|
||||
self.state.original_sheet = Some(self.book.current_sheet);
|
||||
self.state.original_location = Some(self.book.location.clone());
|
||||
self.state.start_range = None;
|
||||
self.state.end_range = None;
|
||||
self.state.modality_stack.push(Modality::RangeCopy);
|
||||
self.state.modality_stack.push(Modality::RangeSelect);
|
||||
}
|
||||
|
||||
|
||||
fn enter_edit_mode(&mut self) {
|
||||
self.state.modality_stack.push(Modality::CellEdit);
|
||||
self.text_area
|
||||
@ -601,14 +683,26 @@ impl<'ws> Workspace<'ws> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exit_range_copy_mode(&mut self) -> Result<()> {
|
||||
self.book.location = self.state.original_location.clone().expect("Missing original location after range copy");
|
||||
fn exit_range_select_mode(&mut self) -> Result<()> {
|
||||
self.book.current_sheet = self
|
||||
.state
|
||||
.original_sheet
|
||||
.clone()
|
||||
.expect("Missing original sheet");
|
||||
self.book.location = self
|
||||
.state
|
||||
.original_location
|
||||
.clone()
|
||||
.expect("Missing original location after range copy");
|
||||
self.state.original_location = None;
|
||||
self.state.pop_modality();
|
||||
if self.state.modality() == &Modality::CellEdit {
|
||||
self.text_area.set_yank_text(self.selected_range_to_string());
|
||||
self.text_area.paste();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
fn exit_edit_mode(&mut self, keep: bool) -> Result<()> {
|
||||
self.text_area.set_cursor_line_style(Style::default());
|
||||
self.text_area.set_cursor_style(Style::default());
|
||||
|
@ -95,7 +95,7 @@ impl<'widget, 'ws: 'widget> Widget for &'widget mut Workspace<'ws> {
|
||||
Modality::CellEdit => "edit",
|
||||
Modality::Command => "command",
|
||||
Modality::Dialog => "",
|
||||
Modality::RangeCopy => "range-copy",
|
||||
Modality::RangeSelect => "range-copy",
|
||||
})
|
||||
.title_bottom(
|
||||
Line::from(format!(
|
||||
|
@ -362,7 +362,7 @@ fn test_range_copy() {
|
||||
let original_loc = ws.book.location.clone();
|
||||
ws.handle_input(construct_modified_key_event(KeyCode::Char('r'), KeyModifiers::CONTROL))
|
||||
.expect("Failed to handle 'Ctrl-r' key event");
|
||||
assert_eq!(Some(&Modality::RangeCopy), ws.state.modality_stack.last());
|
||||
assert_eq!(Some(&Modality::RangeSelect), ws.state.modality_stack.last());
|
||||
assert_eq!(Some(original_loc.clone()), ws.state.original_location);
|
||||
assert!(ws.state.start_range.is_none());
|
||||
assert!(ws.state.end_range.is_none());
|
||||
@ -390,7 +390,7 @@ fn test_range_copy() {
|
||||
|
||||
ws.handle_input(construct_modified_key_event(KeyCode::Char('r'), KeyModifiers::CONTROL))
|
||||
.expect("Failed to handle 'Ctrl-r' key event");
|
||||
assert_eq!(Some(&Modality::RangeCopy), ws.state.modality_stack.last());
|
||||
assert_eq!(Some(&Modality::RangeSelect), ws.state.modality_stack.last());
|
||||
assert_eq!(Some(original_loc_2.clone()), ws.state.original_location);
|
||||
assert!(ws.state.start_range.is_none());
|
||||
assert!(ws.state.end_range.is_none());
|
||||
@ -412,3 +412,16 @@ fn test_range_copy() {
|
||||
assert_eq!(original_loc_2, ws.book.location);
|
||||
assert_eq!(Some(&Modality::Navigate), ws.state.modality_stack.last());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_range_copy_mode_from_edit_mode() {
|
||||
let mut ws =
|
||||
Workspace::new_empty("en", "America/New_York").expect("Failed to get empty workbook");
|
||||
assert_eq!(Some(&Modality::Navigate), ws.state.modality_stack.last());
|
||||
ws.handle_input(construct_key_event(KeyCode::Char('e')))
|
||||
.expect("Failed to handle 'e' key event");
|
||||
assert_eq!(Some(&Modality::CellEdit), ws.state.modality_stack.last());
|
||||
ws.handle_input(construct_modified_key_event(KeyCode::Char('r'), KeyModifiers::CONTROL))
|
||||
.expect("Failed to handle 'Ctrl-r' key event");
|
||||
assert_eq!(Some(&Modality::RangeSelect), ws.state.modality_stack.last());
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user