fix: quote CSV fields only when required
This commit is contained in:
@@ -8,4 +8,5 @@
|
||||
- `yc` should copy to csv format
|
||||
- should be able to copy without row names (`Y` keybinding)
|
||||
- `Ym` should copy to markdown format
|
||||
- `Yc` should copy to csv format
|
||||
- `Yc` should copy to csv format
|
||||
- [ ] add ability to update values from the row view
|
||||
+21
-6
@@ -677,6 +677,10 @@ fn append_record<'a>(
|
||||
if index > 0 {
|
||||
output.push(',');
|
||||
}
|
||||
if !value.contains([',', '"', '\r', '\n']) {
|
||||
output.push_str(value);
|
||||
continue;
|
||||
}
|
||||
output.push('"');
|
||||
for character in value.chars() {
|
||||
if character == '"' {
|
||||
@@ -755,6 +759,17 @@ fn moved_index(selected: Option<usize>, count: usize, delta: isize) -> Option<us
|
||||
mod tests {
|
||||
use super::moved_index;
|
||||
|
||||
#[test]
|
||||
fn csv_quotes_only_fields_with_delimiters_quotes_or_line_breaks() {
|
||||
let mut output = String::new();
|
||||
super::append_record(
|
||||
&mut output,
|
||||
["42", "00123", "界", "", "a,b", "a\"b", "a\rb", "a\nb"].into_iter(),
|
||||
false,
|
||||
);
|
||||
assert_eq!(output, "42,00123,界,,\"a,b\",\"a\"\"b\",\"a\rb\",\"a\nb\"\r\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rectangular_selection_copies_full_cells_and_resets_on_data_changes() -> crate::Result<()> {
|
||||
use super::{App, Focus};
|
||||
@@ -799,14 +814,14 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
app.selection_text(true, false).as_deref(),
|
||||
Some("\"b\",\"c\"\r\n\"a|b\",\"say \"\"hi\"\",界\"\r\n\"NULL\",\"<&>\\tail\"\r\n")
|
||||
Some("b,c\r\na|b,\"say \"\"hi\"\",界\"\r\nNULL,<&>\\tail\r\n")
|
||||
);
|
||||
assert_eq!(
|
||||
app.selection_text(false, false).as_deref(),
|
||||
Some("\"a|b\",\"say \"\"hi\"\",界\"\r\n\"NULL\",\"<&>\\tail\"\r\n")
|
||||
Some("a|b,\"say \"\"hi\"\",界\"\r\nNULL,<&>\\tail\r\n")
|
||||
);
|
||||
assert!(app.handle_rows_command(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE)));
|
||||
assert_eq!(app.selection_text(false, false).as_deref(), Some("\"a|b\"\r\n"));
|
||||
assert_eq!(app.selection_text(false, false).as_deref(), Some("a|b\r\n"));
|
||||
// A prefix cancels on focus changes rather than capturing a later key.
|
||||
app.handle_rows_command(key('y'));
|
||||
assert!(!app.handle_rows_command(KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE)));
|
||||
@@ -814,7 +829,7 @@ mod tests {
|
||||
app.handle_rows_command(key('v'));
|
||||
app.handle_key(key('n'));
|
||||
assert!(app.selection_anchor.is_none());
|
||||
assert_eq!(app.selection_text(false, false).as_deref(), Some("\"101\"\r\n"));
|
||||
assert_eq!(app.selection_text(false, false).as_deref(), Some("101\r\n"));
|
||||
app.handle_rows_command(key('v'));
|
||||
app.handle_key(key('s'));
|
||||
assert!(app.selection_anchor.is_none());
|
||||
@@ -824,11 +839,11 @@ mod tests {
|
||||
app.query.value = "SELECT 42 AS answer".into();
|
||||
app.execute_query();
|
||||
assert!(app.selection_anchor.is_none());
|
||||
assert_eq!(app.selection_text(true, false).as_deref(), Some("\"answer\"\r\n\"42\"\r\n"));
|
||||
assert_eq!(app.selection_text(true, false).as_deref(), Some("answer\r\n42\r\n"));
|
||||
app.handle_rows_command(key('v'));
|
||||
app.query.value = "SELECT missing FROM nowhere".into();
|
||||
app.execute_query();
|
||||
assert_eq!(app.selection_text(false, false).as_deref(), Some("\"42\"\r\n"));
|
||||
assert_eq!(app.selection_text(false, false).as_deref(), Some("42\r\n"));
|
||||
assert!(app.selection_anchor.is_some());
|
||||
app.open_relation(Some(0));
|
||||
assert!(app.selection_anchor.is_none());
|
||||
|
||||
+2
-1
@@ -433,7 +433,8 @@ fn draw_help(frame: &mut Frame, screen: Rect, offset: &mut u16) {
|
||||
With no block, copy uses the current cell. Blocks stay within the loaded page.\n\
|
||||
Paging, refresh, schema/relation changes, and successful queries clear blocks.\n\
|
||||
Copy uses full displayed values; NULL/control escapes are preserved.\n\
|
||||
Headerless Markdown has blank headings. CSV quotes fields with CRLF endings.\n\
|
||||
Headerless Markdown has blank headings. CSV uses CRLF and quotes only fields\n\
|
||||
containing commas, double quotes, or line breaks.\n\
|
||||
Clipboard: macOS pbcopy; Windows PowerShell; Wayland wl-copy; X11 xclip.\n\n\
|
||||
Query editor\n\
|
||||
i/a/I/A Enter Insert mode (at/after/start/end)\n\
|
||||
|
||||
+2
-1
@@ -79,7 +79,8 @@ Navigation:
|
||||
Blocks cover the current loaded page, including cells scrolled out of view.
|
||||
Paging, refreshing, opening a relation, schema changes, and successful queries clear blocks.
|
||||
Copies use full displayed cell values (NULL and escaped control characters included).
|
||||
Headerless Markdown uses blank headings; CSV quotes fields and uses CRLF line endings.
|
||||
Headerless Markdown uses blank headings; CSV quotes only fields containing commas,
|
||||
double quotes, or line breaks, and uses CRLF line endings.
|
||||
Clipboard tools: pbcopy on macOS, PowerShell on Windows, wl-copy on Wayland,
|
||||
or xclip on X11. A missing/failing tool reports an error without clearing the block.
|
||||
c Add a configured database in a floating window
|
||||
|
||||
Reference in New Issue
Block a user