diff --git a/docs/en/src/default-key-bindings.md b/docs/en/src/default-key-bindings.md index 5d9d394..dca8af9 100644 --- a/docs/en/src/default-key-bindings.md +++ b/docs/en/src/default-key-bindings.md @@ -24,7 +24,9 @@ of [modes][4] and the key mappings for each mode. | V | ctrl-a | select/unselect all | | ctrl-d | | duplicate as | | ctrl-i | tab | next visited path | +| ctrl-n | | next selection | | ctrl-o | | last visited path | +| ctrl-p | | prev selection | | ctrl-r | | refresh screen | | ctrl-u | | clear selection | | ctrl-w | | switch layout | @@ -47,47 +49,17 @@ of [modes][4] and the key mappings for each mode. | ~ | | go home | | [0-9] | | input | -### duplicate_as - -| key | remaps | action | -| ----- | ------ | ------------ | -| enter | | submit | -| tab | | try complete | - -### vroot - -| key | remaps | action | -| ------ | ------ | ------------ | -| . | | vroot $PWD | -| / | | vroot / | -| ctrl-r | | reset vroot | -| ctrl-u | | unset vroot | -| v | | toggle vroot | -| ~ | | vroot $HOME | - -### quit - -| key | remaps | action | -| ----- | ------ | ----------------------- | -| enter | | just quit | -| f | | quit printing focus | -| p | | quit printing pwd | -| r | | quit printing result | -| s | | quit printing selection | - -### action +### search -| key | remaps | action | -| ----- | ------ | -------------------- | -| ! | | shell | -| c | | create | -| e | | open in editor | -| l | | logs | -| m | | toggle mouse | -| q | | quit options | -| s | | selection operations | -| v | | vroot | -| [0-9] | | go to index | +| key | remaps | action | +| ------ | ------ | ---------------- | +| ctrl-n | down | down | +| ctrl-p | up | up | +| enter | | submit | +| esc | | cancel | +| left | | back | +| right | | enter | +| tab | | toggle selection | ### go_to @@ -99,6 +71,44 @@ of [modes][4] and the key mappings for each mode. | p | | path | | x | | open in gui | +### go_to_path + +| key | remaps | action | +| ----- | ------ | ------------ | +| enter | | submit | +| tab | | try complete | + +### duplicate_as + +| key | remaps | action | +| ----- | ------ | ------------ | +| enter | | submit | +| tab | | try complete | + +### sort + +| key | remaps | action | +| --------- | ------ | --------------------------------- | +| ! | | reverse sorters | +| C | | by created reverse | +| E | | by canonical extension reverse | +| L | | by last modified reverse | +| M | | by canonical mime essence reverse | +| N | | by node type reverse | +| R | | by relative path reverse | +| S | | by size reverse | +| backspace | | remove last sorter | +| c | | by created | +| ctrl-r | | reset sorters | +| ctrl-u | | clear sorters | +| e | | by canonical extension | +| enter | | submit | +| l | | by last modified | +| m | | by canonical mime essence | +| n | | by node type | +| r | | by relative path | +| s | | by size | + ### debug_error | key | remaps | action | @@ -106,39 +116,53 @@ of [modes][4] and the key mappings for each mode. | enter | | open logs in editor | | q | | quit | -### recover +### delete -| key | remaps | action | -| --- | ------ | ------ | +| key | remaps | action | +| --- | ------ | ------------ | +| D | | force delete | +| d | | delete | -### relative_path_does_not_match_regex +### create -| key | remaps | action | -| ----- | ------ | ------ | -| enter | | submit | +| key | remaps | action | +| --- | ------ | ---------------- | +| d | | create directory | +| f | | create file | -### rename +### create_directory | key | remaps | action | | ----- | ------ | ------------ | | enter | | submit | | tab | | try complete | -### switch_layout +### vroot -| key | remaps | action | -| --- | ------ | -------------------- | -| 1 | | default | -| 2 | | no help menu | -| 3 | | no selection panel | -| 4 | | no help or selection | +| key | remaps | action | +| ------ | ------ | ------------ | +| . | | vroot $PWD | +| / | | vroot / | +| ctrl-r | | reset vroot | +| ctrl-u | | unset vroot | +| v | | toggle vroot | +| ~ | | vroot $HOME | -### create_file +### relative_path_does_match_regex -| key | remaps | action | -| ----- | ------ | ------------ | -| enter | | submit | -| tab | | try complete | +| key | remaps | action | +| ----- | ------ | ------ | +| enter | | submit | + +### quit + +| key | remaps | action | +| ----- | ------ | ----------------------- | +| enter | | just quit | +| f | | quit printing focus | +| p | | quit printing pwd | +| r | | quit printing result | +| s | | quit printing selection | ### filter @@ -150,7 +174,7 @@ of [modes][4] and the key mappings for each mode. | ctrl-u | | clear filters | | r | | relative path does match regex | -### create_directory +### create_file | key | remaps | action | | ----- | ------ | ------------ | @@ -169,74 +193,52 @@ of [modes][4] and the key mappings for each mode. | s | | softlink here | | u | | clear selection | -### search - -| key | remaps | action | -| ------ | ------ | ---------------- | -| ctrl-n | down | down | -| ctrl-p | up | up | -| enter | | submit | -| esc | | cancel | -| left | | back | -| right | | enter | -| tab | | toggle selection | - -### number - -| key | remaps | action | -| ----- | ------ | -------- | -| down | j | to down | -| enter | | to index | -| k | up | to up | -| [0-9] | | input | - -### delete +### switch_layout -| key | remaps | action | -| --- | ------ | ------------ | -| D | | force delete | -| d | | delete | +| key | remaps | action | +| --- | ------ | -------------------- | +| 1 | | default | +| 2 | | no help menu | +| 3 | | no selection panel | +| 4 | | no help or selection | -### go_to_path +### rename | key | remaps | action | | ----- | ------ | ------------ | | enter | | submit | | tab | | try complete | -### relative_path_does_match_regex +### relative_path_does_not_match_regex | key | remaps | action | | ----- | ------ | ------ | | enter | | submit | -### create +### number -| key | remaps | action | -| --- | ------ | ---------------- | -| d | | create directory | -| f | | create file | +| key | remaps | action | +| ----- | ------ | -------- | +| down | j | to down | +| enter | | to index | +| k | up | to up | +| [0-9] | | input | -### sort +### recover -| key | remaps | action | -| --------- | ------ | --------------------------------- | -| ! | | reverse sorters | -| C | | by created reverse | -| E | | by canonical extension reverse | -| L | | by last modified reverse | -| M | | by canonical mime essence reverse | -| N | | by node type reverse | -| R | | by relative path reverse | -| S | | by size reverse | -| backspace | | remove last sorter | -| c | | by created | -| ctrl-r | | reset sorters | -| ctrl-u | | clear sorters | -| e | | by canonical extension | -| enter | | submit | -| l | | by last modified | -| m | | by canonical mime essence | -| n | | by node type | -| r | | by relative path | -| s | | by size | +| key | remaps | action | +| --- | ------ | ------ | + +### action + +| key | remaps | action | +| ----- | ------ | -------------------- | +| ! | | shell | +| c | | create | +| e | | open in editor | +| l | | logs | +| m | | toggle mouse | +| q | | quit options | +| s | | selection operations | +| v | | vroot | +| [0-9] | | go to index | diff --git a/docs/en/src/messages.md b/docs/en/src/messages.md index fb52de6..34c40ae 100644 --- a/docs/en/src/messages.md +++ b/docs/en/src/messages.md @@ -97,6 +97,15 @@ Example: - Lua: `"FocusNext"` - YAML: `FocusNext` +#### FocusNextSelection + +Focus on the next selected node. + +Example: + +- Lua: `"FocusNextSelection"` +- YAML: `FocusNextSelection` + #### FocusNextByRelativeIndex Focus on the `n`th node relative to the current focus where `n` is a @@ -128,6 +137,15 @@ Example: - Lua: `"FocusPrevious"` - YAML: `FocusPrevious` +#### FocusPreviousSelection + +Focus on the previous item. + +Example: + +- Lua: `"FocusPreviousSelection"` +- YAML: `FocusPreviousSelection` + #### FocusPreviousByRelativeIndex Focus on the `-n`th node relative to the current focus where `n` is a diff --git a/docs/en/src/xplr.util.md b/docs/en/src/xplr.util.md index 208b3bf..213f0df 100644 --- a/docs/en/src/xplr.util.md +++ b/docs/en/src/xplr.util.md @@ -406,7 +406,7 @@ xplr.util.paint("Desktop", { fg = "Red", bg = nil, add_modifiers = {}, sub_modif Mix multiple [Style][3] objects into one. -Type: function( { [Style][3], [Style][3], ... } ) -> Style[3] +Type: function( { [Style][3], [Style][3], ... } ) -> [Style][3] Example: @@ -475,4 +475,4 @@ xplr.util.layout_replace(layout, "Table", "Selection") [4]: https://xplr.dev/en/layout [5]: https://xplr.dev/en/lua-function-calls#node [6]: https://xplr.dev/en/node-type -[7]: https://xplr.dev/en/node-types +[7]: https://xplr.dev/en/node_types diff --git a/src/app.rs b/src/app.rs index d3bdfe2..17709cb 100644 --- a/src/app.rs +++ b/src/app.rs @@ -427,6 +427,7 @@ impl App { FocusFirst => self.focus_first(true), FocusLast => self.focus_last(), FocusPrevious => self.focus_previous(), + FocusPreviousSelection => self.focus_previous_selection(), FocusPreviousByRelativeIndex(i) => { self.focus_previous_by_relative_index(i) } @@ -435,6 +436,7 @@ impl App { self.focus_previous_by_relative_index_from_input() } FocusNext => self.focus_next(), + FocusNextSelection => self.focus_next_selection(), FocusNextByRelativeIndex(i) => self.focus_next_by_relative_index(i), FocusNextByRelativeIndexFromInput => { self.focus_next_by_relative_index_from_input() @@ -689,6 +691,46 @@ impl App { Ok(self) } + fn focus_previous_selection(mut self) -> Result { + let total = self.selection.len(); + if total == 0 { + return Ok(self); + } + + let bounded = self.config.general.enforce_bounded_index_navigation; + + if let Some(n) = self + .directory_buffer + .as_ref() + .and_then(|d| d.focused_node()) + { + if let Some(idx) = self.selection.get_index_of(n) { + let idx = if idx == 0 { + if bounded { + idx + } else { + total.max(1) - 1 + } + } else { + idx.max(1) - 1 + }; + if let Some(p) = self + .selection + .get_index(idx) + .map(|n| n.absolute_path.clone()) + { + self = self.focus_path(&p, true)?; + } + } else if let Some(p) = + self.selection.last().map(|n| n.absolute_path.clone()) + { + self = self.focus_path(&p, true)?; + } + } + + Ok(self) + } + pub fn focus_previous_by_relative_index(mut self, index: usize) -> Result { let mut history = self.history.clone(); if let Some(dir) = self.directory_buffer_mut() { @@ -734,6 +776,46 @@ impl App { Ok(self) } + fn focus_next_selection(mut self) -> Result { + let total = self.selection.len(); + if total == 0 { + return Ok(self); + } + + let bounded = self.config.general.enforce_bounded_index_navigation; + + if let Some(n) = self + .directory_buffer + .as_ref() + .and_then(|d| d.focused_node()) + { + if let Some(idx) = self.selection.get_index_of(n) { + let idx = if idx + 1 == total { + if bounded { + idx + } else { + 0 + } + } else { + idx + 1 + }; + if let Some(p) = self + .selection + .get_index(idx) + .map(|n| n.absolute_path.clone()) + { + self = self.focus_path(&p, true)?; + } + } else if let Some(p) = + self.selection.first().map(|n| n.absolute_path.clone()) + { + self = self.focus_path(&p, true)?; + } + } + + Ok(self) + } + pub fn focus_next_by_relative_index(mut self, index: usize) -> Result { let mut history = self.history.clone(); if let Some(dir) = self.directory_buffer_mut() { diff --git a/src/init.lua b/src/init.lua index 1c24e0d..3d7ccbf 100644 --- a/src/init.lua +++ b/src/init.lua @@ -1242,6 +1242,18 @@ xplr.config.modes.builtin.default = { "ScrollDownHalf", }, }, + ["ctrl-n"] = { + help = "next selection", + messages = { + "FocusNextSelection", + }, + }, + ["ctrl-p"] = { + help = "prev selection", + messages = { + "FocusPreviousSelection", + }, + }, }, on_number = { help = "input", @@ -1468,6 +1480,7 @@ xplr.config.modes.builtin.selection_ops = { if cp -vr -- "${PTH:?}" "./${BASENAME:?}"; then "$XPLR" -m 'LogSuccess: %q' "$PTH_ESC copied to ./$BASENAME_ESC" "$XPLR" -m 'UnSelectPath: %q' "$PTH" + "$XPLR" -m 'FocusPath: %q' "$BASENAME" else "$XPLR" -m 'LogError: %q' "Failed to copy $PTH_ESC to ./$BASENAME_ESC" fi @@ -1494,7 +1507,7 @@ xplr.config.modes.builtin.selection_ops = { done if mv -v -- "${PTH:?}" "./${BASENAME:?}"; then "$XPLR" -m 'LogSuccess: %q' "$PTH_ESC moved to ./$BASENAME_ESC" - "$XPLR" -m 'UnSelectPath: %q' "$PTH" + "$XPLR" -m 'FocusPath: %q' "$BASENAME" else "$XPLR" -m 'LogError: %q' "Failed to move $PTH_ESC to ./$BASENAME_ESC" fi @@ -1522,6 +1535,7 @@ xplr.config.modes.builtin.selection_ops = { if ln -sv -- "${PTH:?}" "./${BASENAME:?}"; then "$XPLR" -m 'LogSuccess: %q' "$PTH_ESC softlinked as ./$BASENAME_ESC" "$XPLR" -m 'UnSelectPath: %q' "$PTH" + "$XPLR" -m 'FocusPath: %q' "$BASENAME" else "$XPLR" -m 'LogError: %q' "Failed to softlink $PTH_ESC as ./$BASENAME_ESC" fi @@ -1549,6 +1563,7 @@ xplr.config.modes.builtin.selection_ops = { if ln -v -- "${PTH:?}" "./${BASENAME:?}"; then "$XPLR" -m 'LogSuccess: %q' "$PTH_ESC hardlinked as ./$BASENAME_ESC" "$XPLR" -m 'UnSelectPath: %q' "$PTH" + "$XPLR" -m 'FocusPath: %q' "$BASENAME" else "$XPLR" -m 'LogError: %q' "Failed to hardlink $PTH_ESC as ./$BASENAME_ESC" fi diff --git a/src/lua/util.rs b/src/lua/util.rs index b98e971..a9c3ed8 100644 --- a/src/lua/util.rs +++ b/src/lua/util.rs @@ -673,7 +673,7 @@ pub fn paint<'a>(util: Table<'a>, lua: &Lua) -> Result> { /// Mix multiple [Style][3] objects into one. /// -/// Type: function( { [Style][3], [Style][3], ... } ) -> Style[3] +/// Type: function( { [Style][3], [Style][3], ... } ) -> [Style][3] /// /// Example: /// @@ -783,7 +783,7 @@ pub fn layout_replace<'a>(util: Table<'a>, lua: &Lua) -> Result> { /// [4]: https://xplr.dev/en/layout /// [5]: https://xplr.dev/en/lua-function-calls#node /// [6]: https://xplr.dev/en/node-type -/// [7]: https://xplr.dev/en/node-types +/// [7]: https://xplr.dev/en/node_types pub(crate) fn create_table(lua: &Lua) -> Result { let mut util = lua.create_table()?; diff --git a/src/msg/in_/external.rs b/src/msg/in_/external.rs index 6c91346..13ff06f 100644 --- a/src/msg/in_/external.rs +++ b/src/msg/in_/external.rs @@ -74,6 +74,14 @@ pub enum ExternalMsg { /// - YAML: `FocusNext` FocusNext, + /// Focus on the next selected node. + /// + /// Example: + /// + /// - Lua: `"FocusNextSelection"` + /// - YAML: `FocusNextSelection` + FocusNextSelection, + /// Focus on the `n`th node relative to the current focus where `n` is a /// given value. /// @@ -102,6 +110,14 @@ pub enum ExternalMsg { /// - YAML: `FocusPrevious` FocusPrevious, + /// Focus on the previous item. + /// + /// Example: + /// + /// - Lua: `"FocusPreviousSelection"` + /// - YAML: `FocusPreviousSelection` + FocusPreviousSelection, + /// Focus on the `-n`th node relative to the current focus where `n` is a /// given value. ///