Skip to content

Commit

Permalink
add support from table / record transposition (#28)
Browse files Browse the repository at this point in the history
this PR adds the ability to tranpose the current view, if it's a table
or a record, just as the built-in `transpose` command does.

the default binding is `t`.

the transposition is an *involution*, i.e. it is its self inverse,
meaning that applying the tranposition twice give you back the original
data.
  • Loading branch information
amtoine authored Sep 9, 2023
1 parent fb30f33 commit d1b2643
Show file tree
Hide file tree
Showing 6 changed files with 320 additions and 12 deletions.
1 change: 1 addition & 0 deletions examples/config/default.nuon
Original file line number Diff line number Diff line change
Expand Up @@ -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*
}
}
7 changes: 7 additions & 0 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -200,6 +201,7 @@ impl Default for Config {
under: KeyCode::Char('p'),
view: KeyCode::Char('v'),
},
transpose: KeyCode::Char('t'),
},
}
}
Expand Down Expand Up @@ -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()))),
}
}
Expand Down
87 changes: 84 additions & 3 deletions src/handler.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
use crossterm::event::KeyEvent;

use nu_protocol::{ShellError, Span, Value};
use nu_protocol::{
ast::{CellPath, PathMember},
ShellError, Span, Value,
};

use crate::{
app::{App, Mode},
config::Config,
navigation::{self, Direction},
nu::value::transpose,
};

/// the result of a state transition
Expand All @@ -14,7 +18,7 @@ pub enum TransitionResult {
Quit,
Continue,
Return(Value),
Edit(Value),
Mutate(Value, CellPath),
Error(String),
}

Expand Down Expand Up @@ -55,6 +59,34 @@ pub fn handle_key_events(
return Ok(TransitionResult::Continue);
} 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 {
let mut path = app.position.clone();
path.members.pop();

let view = app.value.clone().follow_cell_path(&path.members, false)?;
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);
}
}
Expand All @@ -67,7 +99,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::Mutate(v, app.position.clone()));
}
Some(None) => {
app.mode = Mode::Normal;
Expand Down Expand Up @@ -496,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)
);
}
}
}
9 changes: 3 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,9 @@ pub fn explore(call: &EvaluatedCall, input: Value) -> Result<Value> {
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::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))?;
Expand Down
Loading

0 comments on commit d1b2643

Please sign in to comment.