From db3e9aa0d883ed6ac1514d310e901bd2c0621996 Mon Sep 17 00:00:00 2001 From: amtoine Date: Sat, 9 Sep 2023 12:16:15 +0200 Subject: [PATCH 01/18] add empty tranpose and tests that should pass --- src/nu/value.rs | 84 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/src/nu/value.rs b/src/nu/value.rs index 86e2c8f..bf04cd6 100644 --- a/src/nu/value.rs +++ b/src/nu/value.rs @@ -120,10 +120,45 @@ pub(crate) fn is_table(value: &Value) -> bool { } } +/// this effectively implements the following idempotent `transpose` command written in Nushell +/// ```nushell +/// alias "core transpose" = transpose +/// +/// def transpose []: [table -> any, record -> table] { +/// let data = $in +/// +/// if ($data | columns) == (seq 1 ($data | columns | length) | into string) { +/// if ($data | columns | length) == 2 { +/// return ($data | core transpose --header-row | into record) +/// } else { +/// return ($data | core transpose --header-row) +/// } +/// } +/// +/// $data | core transpose | rename --block { +/// ($in | str replace "column" "" | into int) + 1 | into string +/// } +/// } +/// +/// #[test] +/// def transposition [] { +/// use std assert +/// +/// assert equal (ls | transpose explore | transpose) (ls) +/// assert equal (open Cargo.toml | transpose | transpose) (open Cargo.toml) +/// } +/// ``` +pub(crate) fn transpose(value: &Value) -> Value { + value.clone() +} + #[cfg(test)] mod tests { use super::{is_table, mutate_value_cell}; - use crate::nu::cell_path::{to_path_member_vec, PM}; + use crate::nu::{ + cell_path::{to_path_member_vec, PM}, + value::transpose, + }; use nu_protocol::{ast::CellPath, record, Config, Value}; fn default_value_repr(value: &Value) -> String { @@ -368,4 +403,51 @@ mod tests { assert_eq!(is_table(&Value::test_int(0)), false); } + + #[test] + fn transposition() { + let record = Value::test_record(record! { + "a" => Value::test_int(1), + "b" => Value::test_int(2), + }); + let expected = Value::test_list(vec![ + Value::test_record(record! { + "1" => Value::test_string("a"), + "2" => Value::test_int(1), + }), + Value::test_record(record! { + "1" => Value::test_string("b"), + "2" => Value::test_int(2), + }), + ]); + assert_eq!(transpose(&record), expected); + // make sure `transpose` is an *involution* + assert_eq!(transpose(&expected), record); + + let table = Value::test_list(vec![ + Value::test_record(record! { + "a" => Value::test_int(1), + "b" => Value::test_int(2), + }), + Value::test_record(record! { + "a" => Value::test_int(3), + "b" => Value::test_int(4), + }), + ]); + let expected = Value::test_list(vec![ + Value::test_record(record! { + "1" => Value::test_string("a"), + "2" => Value::test_int(1), + "3" => Value::test_int(3), + }), + Value::test_record(record! { + "1" => Value::test_string("b"), + "2" => Value::test_int(2), + "3" => Value::test_int(4), + }), + ]); + assert_eq!(transpose(&table), expected); + // make sure `transpose` is an *involution* + assert_eq!(transpose(&expected), table); + } } From feea6c2cdac0fe7f334e90ae811a2c336b3ec514 Mon Sep 17 00:00:00 2001 From: amtoine Date: Sat, 9 Sep 2023 12:52:21 +0200 Subject: [PATCH 02/18] transpose when pressing t in NORMAL --- examples/config/default.nuon | 1 + src/config/mod.rs | 7 +++++++ src/handler.rs | 3 +++ src/ui.rs | 3 ++- 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/examples/config/default.nuon b/examples/config/default.nuon index 4614916..e4d49b5 100644 --- a/examples/config/default.nuon +++ b/examples/config/default.nuon @@ -72,5 +72,6 @@ under: 'p', # peek only what's under the cursor view: 'v', # peek the current view, i.e. what is visible }, + tranpose: 't', # tranpose the data if it's a table or a record, this is an *involution* } } diff --git a/src/config/mod.rs b/src/config/mod.rs index f7b2cf6..39b76f4 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -104,6 +104,7 @@ pub struct KeyBindingsMap { /// go into PEEKING mode (see [crate::app::Mode::Peeking]) pub peek: KeyCode, pub peeking: PeekingBindingsMap, + pub transpose: KeyCode, } /// the layout of the application @@ -200,6 +201,7 @@ impl Default for Config { under: KeyCode::Char('p'), view: KeyCode::Char('v'), }, + transpose: KeyCode::Char('t'), }, } } @@ -558,6 +560,11 @@ impl Config { } } } + "transpose" => { + if let Some(val) = try_key(&value, &["keybindings", "tranpose"])? { + config.keybindings.transpose = val + } + } x => return Err(invalid_field(&["keybindings", x], Some(cell.span()))), } } diff --git a/src/handler.rs b/src/handler.rs index 27a3722..1f976a8 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -6,6 +6,7 @@ use crate::{ app::{App, Mode}, config::Config, navigation::{self, Direction}, + nu::value::transpose, }; /// the result of a state transition @@ -56,6 +57,8 @@ pub fn handle_key_events( } else if key_event.code == config.keybindings.navigation.left { navigation::go_back_in_data(app); return Ok(TransitionResult::Continue); + } else if key_event.code == config.keybindings.transpose { + return Ok(TransitionResult::Edit(transpose(&app.value))); } } Mode::Insert => { diff --git a/src/ui.rs b/src/ui.rs index c5c47db..50e48c9 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -538,7 +538,7 @@ fn render_status_bar(frame: &mut Frame<'_, B>, app: &App, config: &C let hints = match app.mode { Mode::Normal => format!( - "{} to {} | {}{}{}{} to move around | {} to peek | {} to quit", + "{} to {} | {}{}{}{} to move around | {} to peek | {} to transpose | {} to quit", repr_keycode(&config.keybindings.insert), Mode::Insert, repr_keycode(&config.keybindings.navigation.left), @@ -546,6 +546,7 @@ fn render_status_bar(frame: &mut Frame<'_, B>, app: &App, config: &C repr_keycode(&config.keybindings.navigation.up), repr_keycode(&config.keybindings.navigation.right), repr_keycode(&config.keybindings.peek), + repr_keycode(&config.keybindings.transpose), repr_keycode(&config.keybindings.quit), ), Mode::Insert => format!( From ef7982bc51ae4afef279f5eed37bc22e3f126441 Mon Sep 17 00:00:00 2001 From: amtoine Date: Sat, 9 Sep 2023 13:15:29 +0200 Subject: [PATCH 03/18] pass the cell path with `TransitionResult::Edit` this is to make sure *transpose* does not mutate the cell under the cursor, but rather the whole view. --- src/handler.rs | 10 ++++++---- src/lib.rs | 8 ++------ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index 1f976a8..8b19953 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,6 +1,6 @@ use crossterm::event::KeyEvent; -use nu_protocol::{ShellError, Span, Value}; +use nu_protocol::{ast::CellPath, ShellError, Span, Value}; use crate::{ app::{App, Mode}, @@ -15,7 +15,7 @@ pub enum TransitionResult { Quit, Continue, Return(Value), - Edit(Value), + Edit(Value, CellPath), Error(String), } @@ -58,7 +58,9 @@ pub fn handle_key_events( navigation::go_back_in_data(app); return Ok(TransitionResult::Continue); } else if key_event.code == config.keybindings.transpose { - return Ok(TransitionResult::Edit(transpose(&app.value))); + let mut path = app.position.clone(); + path.members.pop(); + return Ok(TransitionResult::Edit(transpose(&app.value), path)); } } Mode::Insert => { @@ -70,7 +72,7 @@ pub fn handle_key_events( match app.editor.handle_key(&key_event.code) { Some(Some(v)) => { app.mode = Mode::Normal; - return Ok(TransitionResult::Edit(v)); + return Ok(TransitionResult::Edit(v, app.position.clone())); } Some(None) => { app.mode = Mode::Normal; diff --git a/src/lib.rs b/src/lib.rs index 5aa8d58..66e7cab 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,12 +69,8 @@ pub fn explore(call: &EvaluatedCall, input: Value) -> Result { match handle_key_events(key_event, &mut app, &config)? { TransitionResult::Quit => break, TransitionResult::Continue => {} - TransitionResult::Edit(cell) => { - app.value = crate::nu::value::mutate_value_cell( - &app.value, - &app.position, - &cell, - ) + TransitionResult::Edit(cell, path) => { + app.value = crate::nu::value::mutate_value_cell(&app.value, &path, &cell) } TransitionResult::Error(error) => { tui.draw(&mut app, &config, Some(&error))?; From 5e0e741566ab27ac63a24e422d57743ada2bc257 Mon Sep 17 00:00:00 2001 From: amtoine Date: Sat, 9 Sep 2023 13:18:04 +0200 Subject: [PATCH 04/18] transpose the current view, not the whole value --- src/handler.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/handler.rs b/src/handler.rs index 8b19953..c04c97d 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -60,7 +60,8 @@ pub fn handle_key_events( } else if key_event.code == config.keybindings.transpose { let mut path = app.position.clone(); path.members.pop(); - return Ok(TransitionResult::Edit(transpose(&app.value), path)); + let view = app.value.clone().follow_cell_path(&path.members, false)?; + return Ok(TransitionResult::Edit(transpose(&view), path)); } } Mode::Insert => { From 012e63ebd20c783b21016ef8d068fec7d56b24d0 Mon Sep 17 00:00:00 2001 From: amtoine Date: Sat, 9 Sep 2023 13:18:58 +0200 Subject: [PATCH 05/18] rename `TransitionResult::Edit` to `Mutate` now, it is not only used with INSERT mode, so it makes more sense to make it more general. --- src/handler.rs | 6 +++--- src/lib.rs | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index c04c97d..7d90784 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -15,7 +15,7 @@ pub enum TransitionResult { Quit, Continue, Return(Value), - Edit(Value, CellPath), + Mutate(Value, CellPath), Error(String), } @@ -61,7 +61,7 @@ pub fn handle_key_events( let mut path = app.position.clone(); path.members.pop(); let view = app.value.clone().follow_cell_path(&path.members, false)?; - return Ok(TransitionResult::Edit(transpose(&view), path)); + return Ok(TransitionResult::Mutate(transpose(&view), path)); } } Mode::Insert => { @@ -73,7 +73,7 @@ pub fn handle_key_events( match app.editor.handle_key(&key_event.code) { Some(Some(v)) => { app.mode = Mode::Normal; - return Ok(TransitionResult::Edit(v, app.position.clone())); + return Ok(TransitionResult::Mutate(v, app.position.clone())); } Some(None) => { app.mode = Mode::Normal; diff --git a/src/lib.rs b/src/lib.rs index 66e7cab..38a5406 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,8 +69,9 @@ pub fn explore(call: &EvaluatedCall, input: Value) -> Result { match handle_key_events(key_event, &mut app, &config)? { TransitionResult::Quit => break, TransitionResult::Continue => {} - TransitionResult::Edit(cell, path) => { - app.value = crate::nu::value::mutate_value_cell(&app.value, &path, &cell) + TransitionResult::Mutate(cell, path) => { + app.value = + crate::nu::value::mutate_value_cell(&app.value, &path, &cell) } TransitionResult::Error(error) => { tui.draw(&mut app, &config, Some(&error))?; From 630da3e3793d36c9c5b624ee0f8ffd9bdac9c0a1 Mon Sep 17 00:00:00 2001 From: amtoine Date: Sat, 9 Sep 2023 13:20:49 +0200 Subject: [PATCH 06/18] TMP: give an easy to see fake value from transposition this will be fixed in later commits of course, but it's not really less correct than passing the input value directly and it allows to see the effect of the transposition. --- src/nu/value.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nu/value.rs b/src/nu/value.rs index bf04cd6..770a69f 100644 --- a/src/nu/value.rs +++ b/src/nu/value.rs @@ -149,7 +149,7 @@ pub(crate) fn is_table(value: &Value) -> bool { /// } /// ``` pub(crate) fn transpose(value: &Value) -> Value { - value.clone() + Value::string("this cell has been transposed", value.span()) } #[cfg(test)] From 4ec749c412222c94325bd75a73b5d66498386e94 Mon Sep 17 00:00:00 2001 From: amtoine Date: Sat, 9 Sep 2023 13:42:09 +0200 Subject: [PATCH 07/18] add the first pass for records --- src/nu/value.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/nu/value.rs b/src/nu/value.rs index 770a69f..032957e 100644 --- a/src/nu/value.rs +++ b/src/nu/value.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use nu_protocol::{ ast::{CellPath, PathMember}, - Record, Span, Type, Value, + record, Record, Span, Type, Value, }; pub(crate) fn mutate_value_cell(value: &Value, cell_path: &CellPath, cell: &Value) -> Value { @@ -149,7 +149,23 @@ pub(crate) fn is_table(value: &Value) -> bool { /// } /// ``` pub(crate) fn transpose(value: &Value) -> Value { - Value::string("this cell has been transposed", value.span()) + match value { + Value::Record { val: rec, .. } => { + let mut rows = vec![]; + for (col, val) in rec.iter() { + rows.push(Value::record( + record! { + "1" => Value::string(col, Span::unknown()), + "2" => val.clone(), + }, + Span::unknown(), + )); + } + + Value::list(rows, Span::unknown()) + } + _ => value.clone(), + } } #[cfg(test)] From 1b5d760e3c2af745d8913f7bfe6de64b67c0adde Mon Sep 17 00:00:00 2001 From: amtoine Date: Sat, 9 Sep 2023 13:49:10 +0200 Subject: [PATCH 08/18] add the skeleton for the rest of the transposition --- src/nu/value.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/nu/value.rs b/src/nu/value.rs index 032957e..1291fd9 100644 --- a/src/nu/value.rs +++ b/src/nu/value.rs @@ -149,6 +149,22 @@ pub(crate) fn is_table(value: &Value) -> bool { /// } /// ``` pub(crate) fn transpose(value: &Value) -> Value { + if value.columns() + == &(1..(value.columns().len())) + .map(|i| format!("{i}")) + .collect::>() + { + if value.columns().len() == 2 { + return value.clone(); + } else { + return value.clone(); + } + } + + if is_table(value) { + return value.clone(); + } + match value { Value::Record { val: rec, .. } => { let mut rows = vec![]; From 1d2130af0ce6fc21bd91995dceac48a85091164b Mon Sep 17 00:00:00 2001 From: amtoine Date: Sat, 9 Sep 2023 14:03:41 +0200 Subject: [PATCH 09/18] make Clippy happy --- src/nu/value.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nu/value.rs b/src/nu/value.rs index 1291fd9..94deefa 100644 --- a/src/nu/value.rs +++ b/src/nu/value.rs @@ -150,7 +150,7 @@ pub(crate) fn is_table(value: &Value) -> bool { /// ``` pub(crate) fn transpose(value: &Value) -> Value { if value.columns() - == &(1..(value.columns().len())) + == (1..(value.columns().len())) .map(|i| format!("{i}")) .collect::>() { From e08b67f256ac01cea5e2b8e9c98ea88042853abb Mon Sep 17 00:00:00 2001 From: amtoine Date: Sat, 9 Sep 2023 14:04:09 +0200 Subject: [PATCH 10/18] give better error message when a test fails --- src/nu/value.rs | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/src/nu/value.rs b/src/nu/value.rs index 94deefa..fd0b0ea 100644 --- a/src/nu/value.rs +++ b/src/nu/value.rs @@ -452,9 +452,25 @@ mod tests { "2" => Value::test_int(2), }), ]); - assert_eq!(transpose(&record), expected); + let result = transpose(&record); + assert_eq!( + result, + expected, + "transposing {} should give {}, found {}", + default_value_repr(&record), + default_value_repr(&expected), + default_value_repr(&result) + ); // make sure `transpose` is an *involution* - assert_eq!(transpose(&expected), record); + let result = transpose(&expected); + assert_eq!( + result, + record, + "transposing {} should give {}, found {}", + default_value_repr(&expected), + default_value_repr(&record), + default_value_repr(&result) + ); let table = Value::test_list(vec![ Value::test_record(record! { @@ -478,8 +494,24 @@ mod tests { "3" => Value::test_int(4), }), ]); - assert_eq!(transpose(&table), expected); + let result = transpose(&table); + assert_eq!( + result, + expected, + "transposing {} should give {}, found {}", + default_value_repr(&table), + default_value_repr(&expected), + default_value_repr(&result) + ); // make sure `transpose` is an *involution* - assert_eq!(transpose(&expected), table); + let result = transpose(&expected); + assert_eq!( + result, + table, + "transposing {} should give {}, found {}", + default_value_repr(&expected), + default_value_repr(&table), + default_value_repr(&result) + ); } } From 0ced81cf0c4c36455f4045820ca9c67ca4c86b91 Mon Sep 17 00:00:00 2001 From: amtoine Date: Sat, 9 Sep 2023 14:11:12 +0200 Subject: [PATCH 11/18] fix the skeleton --- src/nu/value.rs | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/src/nu/value.rs b/src/nu/value.rs index fd0b0ea..92d17da 100644 --- a/src/nu/value.rs +++ b/src/nu/value.rs @@ -149,19 +149,39 @@ pub(crate) fn is_table(value: &Value) -> bool { /// } /// ``` pub(crate) fn transpose(value: &Value) -> Value { - if value.columns() - == (1..(value.columns().len())) + if is_table(value) { + let rows = match value { + Value::List { vals, .. } => vals, + _ => return value.clone(), + }; + + let foo = (1..(rows[0].columns().len())) .map(|i| format!("{i}")) - .collect::>() - { - if value.columns().len() == 2 { - return value.clone(); - } else { - return value.clone(); + .collect::>(); + + if rows[0].columns() == foo { + if rows[0].columns().len() == 2 { + match value { + Value::List { vals: rows, .. } => { + let cols: Vec = rows + .iter() + .map(|row| row.get_data_by_key("1").unwrap().as_string().unwrap()) + .collect(); + + let vals: Vec = rows + .iter() + .map(|row| row.get_data_by_key("2").unwrap()) + .collect(); + + return Value::record(Record { cols, vals }, Span::unknown()); + } + _ => return value.clone(), + } + } else { + return value.clone(); + } } - } - if is_table(value) { return value.clone(); } From dc285af050bb3f208cfa8b7dd5427b06115c769c Mon Sep 17 00:00:00 2001 From: amtoine Date: Sat, 9 Sep 2023 14:12:39 +0200 Subject: [PATCH 12/18] include the last column index in the involution test this let the "record" tests pass. --- src/nu/value.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nu/value.rs b/src/nu/value.rs index 92d17da..a77d6cd 100644 --- a/src/nu/value.rs +++ b/src/nu/value.rs @@ -155,7 +155,7 @@ pub(crate) fn transpose(value: &Value) -> Value { _ => return value.clone(), }; - let foo = (1..(rows[0].columns().len())) + let foo = (1..=(rows[0].columns().len())) .map(|i| format!("{i}")) .collect::>(); From ee51ad070938669ee113d46563041e58c0cba9fc Mon Sep 17 00:00:00 2001 From: amtoine Date: Sat, 9 Sep 2023 14:13:39 +0200 Subject: [PATCH 13/18] rename the binding variable for the full columns --- src/nu/value.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nu/value.rs b/src/nu/value.rs index a77d6cd..1cc8bbd 100644 --- a/src/nu/value.rs +++ b/src/nu/value.rs @@ -155,11 +155,11 @@ pub(crate) fn transpose(value: &Value) -> Value { _ => return value.clone(), }; - let foo = (1..=(rows[0].columns().len())) + let full_columns = (1..=(rows[0].columns().len())) .map(|i| format!("{i}")) .collect::>(); - if rows[0].columns() == foo { + if rows[0].columns() == full_columns { if rows[0].columns().len() == 2 { match value { Value::List { vals: rows, .. } => { From 596fcd80c9ba32e4fadbb4b0328403cadf0b2e40 Mon Sep 17 00:00:00 2001 From: amtoine Date: Sat, 9 Sep 2023 15:55:27 +0200 Subject: [PATCH 14/18] implement the first transposition for tables --- src/nu/value.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/nu/value.rs b/src/nu/value.rs index 1cc8bbd..fc2ac67 100644 --- a/src/nu/value.rs +++ b/src/nu/value.rs @@ -182,7 +182,25 @@ pub(crate) fn transpose(value: &Value) -> Value { } } - return value.clone(); + match value { + Value::List { vals, .. } => { + let mut rows = vec![]; + for col in vals[0].columns() { + let mut cols = vec!["1".into()]; + let mut vs = vec![Value::string(col, Span::unknown())]; + + for (i, v) in vals.iter().enumerate() { + cols.push(format!("{}", i + 2)); + vs.push(v.get_data_by_key(col).unwrap()); + } + + rows.push(Value::record(Record { cols, vals: vs }, Span::unknown())); + } + + return Value::list(rows, Span::unknown()); + } + _ => return value.clone(), + } } match value { From 3c1ae3a1ecdd85b8066475537b68836a0d7985c6 Mon Sep 17 00:00:00 2001 From: amtoine Date: Sat, 9 Sep 2023 16:11:01 +0200 Subject: [PATCH 15/18] implement the inverse transposition for tables --- src/nu/value.rs | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/nu/value.rs b/src/nu/value.rs index fc2ac67..9c520b0 100644 --- a/src/nu/value.rs +++ b/src/nu/value.rs @@ -178,7 +178,31 @@ pub(crate) fn transpose(value: &Value) -> Value { _ => return value.clone(), } } else { - return value.clone(); + match value { + Value::List { vals, .. } => { + let mut rows = vec![]; + let cols: Vec = vals + .iter() + .map(|v| v.get_data_by_key("1").unwrap().as_string().unwrap()) + .collect(); + + for i in 0..(vals[0].columns().len() - 1) { + rows.push(Value::record( + Record { + cols: cols.clone(), + vals: vals + .iter() + .map(|v| v.get_data_by_key(&format!("{}", i + 2)).unwrap()) + .collect(), + }, + Span::unknown(), + )); + } + + return Value::list(rows, Span::unknown()); + } + _ => return value.clone(), + } } } From 700ffef58ec9f46271d35d5acea5e86895309970 Mon Sep 17 00:00:00 2001 From: amtoine Date: Sat, 9 Sep 2023 16:14:36 +0200 Subject: [PATCH 16/18] make sure simple values and lists do not transpose --- src/nu/value.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/nu/value.rs b/src/nu/value.rs index 9c520b0..c2cf890 100644 --- a/src/nu/value.rs +++ b/src/nu/value.rs @@ -575,5 +575,18 @@ mod tests { default_value_repr(&table), default_value_repr(&result) ); + + assert_eq!( + transpose(&Value::test_string("foo")), + Value::test_string("foo") + ); + + assert_eq!( + transpose(&Value::test_list(vec![ + Value::test_int(1), + Value::test_int(2) + ])), + Value::test_list(vec![Value::test_int(1), Value::test_int(2)]) + ); } } From b21e592722b9c3d5f08d85150211d2bfb242a0a7 Mon Sep 17 00:00:00 2001 From: amtoine Date: Sat, 9 Sep 2023 16:39:40 +0200 Subject: [PATCH 17/18] make sure the position in the data is valid after transpose --- src/handler.rs | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index 7d90784..2587fbb 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,6 +1,9 @@ use crossterm::event::KeyEvent; -use nu_protocol::{ast::CellPath, ShellError, Span, Value}; +use nu_protocol::{ + ast::{CellPath, PathMember}, + ShellError, Span, Value, +}; use crate::{ app::{App, Mode}, @@ -60,8 +63,31 @@ pub fn handle_key_events( } else if key_event.code == config.keybindings.transpose { let mut path = app.position.clone(); path.members.pop(); + let view = app.value.clone().follow_cell_path(&path.members, false)?; - return Ok(TransitionResult::Mutate(transpose(&view), path)); + let transpose = transpose(&view); + + if transpose != view { + match transpose.clone() { + Value::Record { val: rec, .. } => { + *app.position.members.last_mut().unwrap() = PathMember::String { + val: rec.cols.get(0).unwrap_or(&"".to_string()).to_string(), + span: Span::unknown(), + optional: rec.cols.is_empty(), + }; + } + _ => { + *app.position.members.last_mut().unwrap() = PathMember::Int { + val: 0, + span: Span::unknown(), + optional: false, + }; + } + } + return Ok(TransitionResult::Mutate(transpose, path)); + } + + return Ok(TransitionResult::Continue); } } Mode::Insert => { From 53f9139fed0842c2581cdfd17a13633ca9b5b7c1 Mon Sep 17 00:00:00 2001 From: amtoine Date: Sat, 9 Sep 2023 16:48:51 +0200 Subject: [PATCH 18/18] add navigation test to make sure the position is set --- src/handler.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/handler.rs b/src/handler.rs index 2587fbb..c471059 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -528,4 +528,53 @@ mod tests { ]; run_peeking_scenario(peek_at_the_bottom, &config, value); } + + #[test] + fn transpose_the_data() { + let config = Config::default(); + let kmap = config.clone().keybindings; + + let value = Value::test_record(record!( + "a" => Value::test_int(1), + "b" => Value::test_int(2), + "c" => Value::test_int(3), + )); + let mut app = App::from_value(value.clone()); + + assert!(!app.is_at_bottom()); + assert_eq!(app.position.members, to_path_member_vec(&[PM::S("a")])); + + let transitions = vec![ + (kmap.navigation.down, vec![PM::S("b")]), + (kmap.transpose, vec![PM::I(0)]), + (kmap.navigation.up, vec![PM::I(2)]), + (kmap.transpose, vec![PM::S("a")]), + ]; + + for (key, cell_path) in transitions { + let expected = to_path_member_vec(&cell_path); + match handle_key_events(KeyEvent::new(key, KeyModifiers::empty()), &mut app, &config) + .unwrap() + { + TransitionResult::Mutate(cell, path) => { + app.value = crate::nu::value::mutate_value_cell(&app.value, &path, &cell) + } + _ => {} + } + + assert!( + !app.is_at_bottom(), + "expected NOT to be at the bottom after pressing {}", + repr_keycode(&key) + ); + + assert_eq!( + app.position.members, + expected, + "expected to be at {:?}, found {:?}", + repr_path_member_vec(&expected), + repr_path_member_vec(&app.position.members) + ); + } + } }