mirror of
https://github.com/zaphar/sheetsui.git
synced 2025-07-22 21:09:48 -04:00
Compare commits
2 Commits
81af54b6f4
...
f0a82ed2b0
Author | SHA1 | Date | |
---|---|---|---|
f0a82ed2b0 | |||
8a76a031cb |
@ -18,6 +18,7 @@ The currently supported commands are:
|
|||||||
* `help [topic]` Display help for a given topic.
|
* `help [topic]` Display help for a given topic.
|
||||||
* `export-csv <path>` Export the current sheet to a csv file at `<path>`.
|
* `export-csv <path>` Export the current sheet to a csv file at `<path>`.
|
||||||
* `quit` Quits the application. `q` is a shorthand alias for this command.
|
* `quit` Quits the application. `q` is a shorthand alias for this command.
|
||||||
|
* `system-paste` Paste from the system clipboard
|
||||||
|
|
||||||
<aside>Note that in the case of `quit` and `edit` that we do not currently
|
<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
|
prompt you if the current spreadsheet has not been saved yet. So your changes
|
||||||
|
@ -49,3 +49,5 @@ Range selections made from navigation mode will be available to paste into a Cel
|
|||||||
<aside>Note that for `q` this will not currently prompt you if the sheet is not
|
<aside>Note that for `q` this will not currently prompt you if the sheet is not
|
||||||
saved.</aside>
|
saved.</aside>
|
||||||
|
|
||||||
|
Note also that copy paste works with the system clipboard.
|
||||||
|
|
||||||
|
@ -5,8 +5,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`, `y` Copy the cell or range contents.
|
* `Ctrl-c`, `y` Copy the cell or range formatted contents.
|
||||||
* `Ctrl-Shift-C`, 'Y' Copy the cell or range formatted content.
|
* `Ctrl-Shift-C`, 'Y' Copy the cell or range 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
|
||||||
|
@ -16,6 +16,7 @@ pub enum Cmd<'a> {
|
|||||||
Edit(&'a str),
|
Edit(&'a str),
|
||||||
Help(Option<&'a str>),
|
Help(Option<&'a str>),
|
||||||
ExportCsv(&'a str),
|
ExportCsv(&'a str),
|
||||||
|
SystemPaste,
|
||||||
Quit,
|
Quit,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,6 +69,9 @@ pub fn parse<'cmd, 'i: 'cmd>(input: &'i str) -> Result<Option<Cmd<'cmd>>, &'stat
|
|||||||
if let Some(cmd) = try_consume_color_cell(cursor.clone())? {
|
if let Some(cmd) = try_consume_color_cell(cursor.clone())? {
|
||||||
return Ok(Some(cmd));
|
return Ok(Some(cmd));
|
||||||
}
|
}
|
||||||
|
if let Some(cmd) = try_consume_system_paste(cursor.clone())? {
|
||||||
|
return Ok(Some(cmd));
|
||||||
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,6 +320,22 @@ fn try_consume_quit<'cmd, 'i: 'cmd>(
|
|||||||
return Ok(Some(Cmd::Quit));
|
return Ok(Some(Cmd::Quit));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn try_consume_system_paste<'cmd, 'i: 'cmd>(
|
||||||
|
mut input: StrCursor<'i>,
|
||||||
|
) -> Result<Option<Cmd<'cmd>>, &'static str> {
|
||||||
|
const LONG: &'static str = "system-paste";
|
||||||
|
|
||||||
|
if compare(input.clone(), LONG) {
|
||||||
|
input.seek(LONG.len());
|
||||||
|
} else {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
if input.remaining() > 0 {
|
||||||
|
return Err("Invalid command: system-paste does not take an argument");
|
||||||
|
}
|
||||||
|
return Ok(Some(Cmd::SystemPaste));
|
||||||
|
}
|
||||||
|
|
||||||
fn try_consume_rename_sheet<'cmd, 'i: 'cmd>(
|
fn try_consume_rename_sheet<'cmd, 'i: 'cmd>(
|
||||||
mut input: StrCursor<'i>,
|
mut input: StrCursor<'i>,
|
||||||
) -> Result<Option<Cmd<'cmd>>, &'static str> {
|
) -> Result<Option<Cmd<'cmd>>, &'static str> {
|
||||||
|
@ -5,6 +5,7 @@ use crate::book::{self, AddressRange, Book};
|
|||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use crossterm::event::{self, Event, KeyCode, KeyEventKind, KeyModifiers};
|
use crossterm::event::{self, Event, KeyCode, KeyEventKind, KeyModifiers};
|
||||||
|
use csv::StringRecord;
|
||||||
use ironcalc::base::{expressions::types::Area, Model};
|
use ironcalc::base::{expressions::types::Area, Model};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
buffer::Buffer,
|
buffer::Buffer,
|
||||||
@ -513,6 +514,12 @@ impl<'ws> Workspace<'ws> {
|
|||||||
.set_cell_style(&[("fill.bg_color", &color)], &area)?;
|
.set_cell_style(&[("fill.bg_color", &color)], &area)?;
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
Ok(Some(Cmd::SystemPaste)) => {
|
||||||
|
let rows = self.get_rows_from_system_cipboard()?;
|
||||||
|
self.state.clipboard = Some(ClipboardContents::Range(rows));
|
||||||
|
self.paste_range()?;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
self.enter_dialog_mode(Markdown::from_str(&format!(
|
self.enter_dialog_mode(Markdown::from_str(&format!(
|
||||||
"Unrecognized commmand {}",
|
"Unrecognized commmand {}",
|
||||||
@ -607,19 +614,19 @@ impl<'ws> Workspace<'ws> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
KeyCode::Char('C') if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||||
self.copy_range(true)?;
|
self.copy_range(true)?;
|
||||||
self.exit_range_select_mode()?;
|
self.exit_range_select_mode()?;
|
||||||
}
|
}
|
||||||
KeyCode::Char('Y') => {
|
|
||||||
self.copy_range(true)?;
|
|
||||||
self.exit_range_select_mode()?;
|
|
||||||
}
|
|
||||||
KeyCode::Char('c') if key.modifiers == KeyModifiers::CONTROL => {
|
|
||||||
self.copy_range(false)?;
|
|
||||||
self.exit_range_select_mode()?;
|
|
||||||
}
|
|
||||||
KeyCode::Char('y') => {
|
KeyCode::Char('y') => {
|
||||||
|
self.copy_range(true)?;
|
||||||
|
self.exit_range_select_mode()?;
|
||||||
|
}
|
||||||
|
KeyCode::Char('C') if key.modifiers == KeyModifiers::CONTROL => {
|
||||||
|
self.copy_range(false)?;
|
||||||
|
self.exit_range_select_mode()?;
|
||||||
|
}
|
||||||
|
KeyCode::Char('Y') => {
|
||||||
self.copy_range(false)?;
|
self.copy_range(false)?;
|
||||||
self.exit_range_select_mode()?;
|
self.exit_range_select_mode()?;
|
||||||
}
|
}
|
||||||
@ -662,7 +669,9 @@ impl<'ws> Workspace<'ws> {
|
|||||||
}
|
}
|
||||||
// TODO(zaphar): Rethink this a bit perhaps?
|
// TODO(zaphar): Rethink this a bit perhaps?
|
||||||
let mut cb = Clipboard::new()?;
|
let mut cb = Clipboard::new()?;
|
||||||
let (html, csv) = self.book.range_to_clipboard_content(AddressRange { start, end })?;
|
let (html, csv) = self
|
||||||
|
.book
|
||||||
|
.range_to_clipboard_content(AddressRange { start, end })?;
|
||||||
cb.set_html(html, Some(csv))?;
|
cb.set_html(html, Some(csv))?;
|
||||||
self.state.clipboard = Some(ClipboardContents::Range(rows));
|
self.state.clipboard = Some(ClipboardContents::Range(rows));
|
||||||
}
|
}
|
||||||
@ -677,6 +686,31 @@ impl<'ws> Workspace<'ws> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_rows_from_system_cipboard(&mut self) -> Result<Vec<Vec<String>>, anyhow::Error> {
|
||||||
|
use arboard::Clipboard;
|
||||||
|
let mut cb = Clipboard::new()?;
|
||||||
|
let txt = match cb.get_text() {
|
||||||
|
Ok(txt) => txt,
|
||||||
|
Err(e) => return Err(anyhow!(e)),
|
||||||
|
};
|
||||||
|
let reader = csv::Reader::from_reader(txt.as_bytes());
|
||||||
|
let mut rows = Vec::new();
|
||||||
|
for rec in reader.into_byte_records() {
|
||||||
|
let record = rec?;
|
||||||
|
let mut row = Vec::with_capacity(record.len());
|
||||||
|
for i in 0..record.len() {
|
||||||
|
row.push(
|
||||||
|
String::from_utf8_lossy(
|
||||||
|
record.get(i).expect("Unexpected failure to get cell row"),
|
||||||
|
)
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
rows.push(row);
|
||||||
|
}
|
||||||
|
Ok(rows)
|
||||||
|
}
|
||||||
|
|
||||||
fn update_range_selection(&mut self) -> Result<bool, anyhow::Error> {
|
fn update_range_selection(&mut self) -> Result<bool, 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());
|
||||||
@ -944,7 +978,34 @@ impl<'ws> Workspace<'ws> {
|
|||||||
self.book.evaluate();
|
self.book.evaluate();
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// NOOP
|
// Try to get from system clipboard
|
||||||
|
use arboard::Clipboard;
|
||||||
|
let mut cb = Clipboard::new()?;
|
||||||
|
let csv = cb.get_text()?;
|
||||||
|
let rdr = csv::Reader::from_reader(csv.as_bytes());
|
||||||
|
let records: Vec<Result<csv::StringRecord, csv::Error>> =
|
||||||
|
rdr.into_records().collect();
|
||||||
|
let Address { sheet, row, col } = self.book.location.clone();
|
||||||
|
let row_len = records.len();
|
||||||
|
for ri in 0..row_len {
|
||||||
|
let columns = &records[ri];
|
||||||
|
if let Ok(columns) = columns {
|
||||||
|
let col_len = columns.len();
|
||||||
|
for ci in 0..col_len {
|
||||||
|
self.book.update_cell(
|
||||||
|
&Address {
|
||||||
|
sheet,
|
||||||
|
row: ri + row,
|
||||||
|
col: ci + col,
|
||||||
|
},
|
||||||
|
columns
|
||||||
|
.get(ci)
|
||||||
|
.expect("Failed to get column value from csv")
|
||||||
|
.to_string(),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.state.clipboard = None;
|
self.state.clipboard = None;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user