Release v0.21.6 (#701)

- Snap build
- xplr.util.debug()
- `c` and `m` key bindings for quick copy and move.
- ScrollUpHalf fix
- Dependency updates
pull/702/head v0.21.6
Arijit Basu 2 months ago committed by GitHub
commit a6b19425ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -65,6 +65,9 @@ jobs:
run: | run: |
sudo apt-get update --fix-missing sudo apt-get update --fix-missing
sudo apt-get install -y --no-install-recommends liblua5.1-0-dev libluajit-5.1-dev gcc pkg-config curl git make ca-certificates sudo apt-get install -y --no-install-recommends liblua5.1-0-dev libluajit-5.1-dev gcc pkg-config curl git make ca-certificates
sudo apt-get install -y snapd
sudo snap install snapcraft --classic
sudo snap install multipass --classic --beta
- if: matrix.build == 'linux-musl' - if: matrix.build == 'linux-musl'
run: sudo apt-get install -y musl-tools run: sudo apt-get install -y musl-tools
@ -81,6 +84,13 @@ jobs:
- name: Running cargo build - name: Running cargo build
run: cargo build --locked --release --target ${{ matrix.target }} run: cargo build --locked --release --target ${{ matrix.target }}
- name: Running snapcraft build
run: |
snapcraft
printf ' [ INFO ] generated <snapcraft> files include:\n'
command ls -Al | grep "\.snap" | awk '{ print $9 }'
mv ./*.snap ./xplr.snap
- name: Install gpg secret key - name: Install gpg secret key
run: | run: |
cat <(echo -e "${{ secrets.GPG_SECRET }}") | gpg --batch --import cat <(echo -e "${{ secrets.GPG_SECRET }}") | gpg --batch --import
@ -103,6 +113,7 @@ jobs:
target/${{ matrix.target }}/release/xplr-${{ matrix.build }}.tar.gz target/${{ matrix.target }}/release/xplr-${{ matrix.build }}.tar.gz
target/${{ matrix.target }}/release/xplr-${{ matrix.build }}.sha256 target/${{ matrix.target }}/release/xplr-${{ matrix.build }}.sha256
target/${{ matrix.target }}/release/xplr-${{ matrix.build }}.tar.gz.asc target/${{ matrix.target }}/release/xplr-${{ matrix.build }}.tar.gz.asc
xplr.snap
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@ -130,6 +141,10 @@ jobs:
source.tar.gz.asc source.tar.gz.asc
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Cleaning snapcraft
run: |
command rm --verbose ./*.snap
snapcraft clean
publish-cargo: publish-cargo:
name: Publishing to Cargo name: Publishing to Cargo

468
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -8,7 +8,7 @@ path = './benches/criterion.rs'
[package] [package]
name = 'xplr' name = 'xplr'
version = '0.21.5' version = '0.21.6'
authors = ['Arijit Basu <hi@arijitbasu.in>'] authors = ['Arijit Basu <hi@arijitbasu.in>']
edition = '2021' edition = '2021'
description = 'A hackable, minimal, fast TUI file explorer' description = 'A hackable, minimal, fast TUI file explorer'
@ -22,29 +22,29 @@ categories = ['command-line-interface', 'command-line-utilities']
include = ['src/**/*', 'docs/en/src/**/*', 'LICENSE', 'README.md'] include = ['src/**/*', 'docs/en/src/**/*', 'LICENSE', 'README.md']
[dependencies] [dependencies]
libc = "0.2.151" libc = "0.2.153"
humansize = "2.1.3" humansize = "2.1.3"
natord = "1.0.9" natord = "1.0.9"
anyhow = "1.0.79" anyhow = "1.0.81"
serde_yaml = "0.9.30" serde_yaml = "0.9.33"
crossterm = { version = "0.27.0", features = [], default-features = false } crossterm = { version = "0.27.0", features = [], default-features = false }
ansi-to-tui = "3.1.0" ansi-to-tui = "4.0.1"
regex = "1.10.2" regex = "1.10.3"
gethostname = "0.4.3" gethostname = "0.4.3"
serde_json = "1.0.110" serde_json = "1.0.114"
path-absolutize = "3.1.1" path-absolutize = "3.1.1"
which = "5.0.0" which = "6.0.0"
nu-ansi-term = "0.49.0" nu-ansi-term = "0.50.0"
textwrap = "0.16" textwrap = "0.16"
snailquote = "0.3.1" snailquote = "0.3.1"
skim = { version = "0.10.4", default-features = false } skim = { version = "0.10.4", default-features = false }
time = { version = "0.3.31", features = ["serde", "local-offset", "formatting", "macros"] } time = { version = "0.3.34", features = ["serde", "local-offset", "formatting", "macros"] }
jf = "0.6.2" jf = "0.6.2"
xdg = "2.5.2" xdg = "2.5.2"
home = "0.5.9" home = "0.5.9"
[dependencies.lscolors] [dependencies.lscolors]
version = "0.16.0" version = "0.17.0"
default-features = false default-features = false
features = ["nu-ansi-term"] features = ["nu-ansi-term"]
@ -57,22 +57,22 @@ version = "2.0.4"
default-features = false default-features = false
[dependencies.tui] [dependencies.tui]
version = "0.25.0" version = "0.26.1"
default-features = false default-features = false
features = ['crossterm', 'serde', 'underline-color'] features = ['crossterm', 'serde']
package = 'ratatui' package = 'ratatui'
[dependencies.serde] [dependencies.serde]
version = "1.0.194" version = "1.0.197"
features = [] features = []
default-features = false default-features = false
[dependencies.indexmap] [dependencies.indexmap]
version = "2.1.0" version = "2.2.5"
features = ['serde'] features = ['serde']
[dependencies.mlua] [dependencies.mlua]
version = "0.9.2" version = "0.9.6"
features = ['luajit', 'vendored', 'serialize', 'send'] features = ['luajit', 'vendored', 'serialize', 'send']
[dependencies.tui-input] [dependencies.tui-input]
@ -81,7 +81,7 @@ features = ['serde']
[dev-dependencies] [dev-dependencies]
criterion = "0.5.1" criterion = "0.5.1"
assert_cmd = "2.0.12" assert_cmd = "2.0.14"
[profile.release] [profile.release]
lto = true lto = true

@ -24,6 +24,7 @@ of [modes][4] and the key mappings for each mode.
| ? | f1 | global help menu | | ? | f1 | global help menu |
| G | | go to bottom | | G | | go to bottom |
| V | ctrl-a | select/unselect all | | V | ctrl-a | select/unselect all |
| c | | copy to |
| ctrl-d | | duplicate as | | ctrl-d | | duplicate as |
| ctrl-i | tab | next visited path | | ctrl-i | tab | next visited path |
| ctrl-n | | next selection | | ctrl-n | | next selection |
@ -40,6 +41,7 @@ of [modes][4] and the key mappings for each mode.
| h | left | back | | h | left | back |
| k | up | up | | k | up | up |
| l | right | enter | | l | right | enter |
| m | | move to |
| page-down | | scroll down | | page-down | | scroll down |
| page-up | | scroll up | | page-up | | scroll up |
| q | | quit | | q | | quit |
@ -51,7 +53,7 @@ of [modes][4] and the key mappings for each mode.
| ~ | | go home | | ~ | | go home |
| [0-9] | | input | | [0-9] | | input |
### duplicate_as ### go_to_path
| key | remaps | action | | key | remaps | action |
| ----- | ------ | ---------------- | | ----- | ------ | ---------------- |
@ -59,16 +61,37 @@ of [modes][4] and the key mappings for each mode.
| f1 | | global help menu | | f1 | | global help menu |
| tab | | try complete | | tab | | try complete |
### filter ### rename
| key | remaps | action | | key | remaps | action |
| --------- | ------ | ---------------------------------- | | ----- | ------ | ---------------- |
| R | | relative path does not match regex | | enter | | submit |
| backspace | | remove last filter | | f1 | | global help menu |
| ctrl-r | | reset filters | | tab | | try complete |
| ctrl-u | | clear filters |
| f1 | | global help menu | ### recover
| r | | relative path does match regex |
| key | remaps | action |
| --- | ------ | ---------------- |
| f1 | | global help menu |
### go_to
| key | remaps | action |
| --- | ------ | ---------------- |
| f | | follow symlink |
| f1 | | global help menu |
| g | | top |
| i | | initial $PWD |
| p | | path |
| x | | open in gui |
### relative_path_does_match_regex
| key | remaps | action |
| ----- | ------ | ---------------- |
| enter | | submit |
| f1 | | global help menu |
### action ### action
@ -86,23 +109,62 @@ of [modes][4] and the key mappings for each mode.
| v | | vroot | | v | | vroot |
| [0-9] | | go to index | | [0-9] | | go to index |
### create ### default
| key | remaps | action | | key | remaps | action |
| --- | ------ | ---------------- | | --------- | ------ | ------------------- |
| d | | create directory | | ( | | prev deep branch |
| f | | create file | | ) | | next deep branch |
| f1 | | global help menu | | . | | show hidden |
| / | ctrl-f | search |
| : | | action |
| ? | f1 | global help menu |
| G | | go to bottom |
| V | ctrl-a | select/unselect all |
| c | | copy to |
| 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 |
| d | | delete |
| down | j | down |
| enter | | quit with result |
| f | | filter |
| g | | go to |
| h | left | back |
| k | up | up |
| l | right | enter |
| m | | move to |
| page-down | | scroll down |
| page-up | | scroll up |
| q | | quit |
| r | | rename |
| s | | sort |
| space | v | toggle selection |
| { | | scroll up half |
| } | | scroll down half |
| ~ | | go home |
| [0-9] | | input |
### switch_layout ### debug_error
| key | remaps | action | | key | remaps | action |
| --- | ------ | -------------------- | | ----- | ------ | ------------------- |
| 1 | | default | | enter | | open logs in editor |
| 2 | | no help menu | | f1 | | global help menu |
| 3 | | no selection panel | | q | | quit |
| 4 | | no help or selection |
| f1 | | global help menu | ### create_directory
| key | remaps | action |
| ----- | ------ | ---------------- |
| enter | | submit |
| f1 | | global help menu |
| tab | | try complete |
### selection_ops ### selection_ops
@ -117,25 +179,14 @@ of [modes][4] and the key mappings for each mode.
| s | | softlink here | | s | | softlink here |
| u | | clear selection | | u | | clear selection |
### delete ### relative_path_does_not_match_regex
| key | remaps | action |
| --- | ------ | ---------------- |
| D | | force delete |
| d | | delete |
| f1 | | global help menu |
### number
| key | remaps | action | | key | remaps | action |
| ----- | ------ | ---------------- | | ----- | ------ | ---------------- |
| down | j | to down | | enter | | submit |
| enter | | to index |
| f1 | | global help menu | | f1 | | global help menu |
| k | up | to up |
| [0-9] | | input |
### create_directory ### create_file
| key | remaps | action | | key | remaps | action |
| ----- | ------ | ---------------- | | ----- | ------ | ---------------- |
@ -143,20 +194,25 @@ of [modes][4] and the key mappings for each mode.
| f1 | | global help menu | | f1 | | global help menu |
| tab | | try complete | | tab | | try complete |
### recover ### quit
| key | remaps | action |
| ----- | ------ | ----------------------- |
| enter | | just quit |
| f | | quit printing focus |
| f1 | | global help menu |
| p | | quit printing pwd |
| r | | quit printing result |
| s | | quit printing selection |
### create
| key | remaps | action | | key | remaps | action |
| --- | ------ | ---------------- | | --- | ------ | ---------------- |
| d | | create directory |
| f | | create file |
| f1 | | global help menu | | f1 | | global help menu |
### rename
| key | remaps | action |
| ----- | ------ | ---------------- |
| enter | | submit |
| f1 | | global help menu |
| tab | | try complete |
### vroot ### vroot
| key | remaps | action | | key | remaps | action |
@ -169,43 +225,33 @@ of [modes][4] and the key mappings for each mode.
| v | | toggle vroot | | v | | toggle vroot |
| ~ | | vroot $HOME | | ~ | | vroot $HOME |
### relative_path_does_match_regex ### search
| key | remaps | action |
| ----- | ------ | ---------------- |
| enter | | submit |
| f1 | | global help menu |
### relative_path_does_not_match_regex
| key | remaps | action |
| ----- | ------ | ---------------- |
| enter | | submit |
| f1 | | global help menu |
### debug_error
| key | remaps | action | | key | remaps | action |
| ----- | ------ | ------------------- | | ------ | ------ | ----------------------- |
| enter | | open logs in editor | | ctrl-a | | toggle search algorithm |
| f1 | | global help menu | | ctrl-f | | fuzzy search |
| q | | quit | | ctrl-n | down | down |
| ctrl-p | up | up |
| ctrl-r | | regex search |
| ctrl-s | | sort (no search order) |
| ctrl-z | | toggle ordering |
| enter | | submit |
| esc | | cancel |
| f1 | | global help menu |
| left | | back |
| right | | enter |
| tab | | toggle selection |
### edit_permissions ### switch_layout
| key | remaps | action | | key | remaps | action |
| ------ | ------ | ---------------- | | --- | ------ | -------------------- |
| G | | -group | | 1 | | default |
| M | | min | | 2 | | no help menu |
| O | | -other | | 3 | | no selection panel |
| U | | -user | | 4 | | no help or selection |
| ctrl-r | | reset | | f1 | | global help menu |
| enter | | submit |
| f1 | | global help menu |
| g | | +group |
| m | | max |
| o | | +other |
| u | | +user |
### sort ### sort
@ -232,47 +278,49 @@ of [modes][4] and the key mappings for each mode.
| r | | by relative path | | r | | by relative path |
| s | | by size | | s | | by size |
### go_to ### number
| key | remaps | action | | key | remaps | action |
| --- | ------ | ---------------- | | ----- | ------ | ---------------- |
| f | | follow symlink | | down | j | to down |
| f1 | | global help menu | | enter | | to index |
| g | | top | | f1 | | global help menu |
| i | | initial $PWD | | k | up | to up |
| p | | path | | [0-9] | | input |
| x | | open in gui |
### quit ### copy_to
| key | remaps | action | | key | remaps | action |
| ----- | ------ | ----------------------- | | ----- | ------ | ---------------- |
| enter | | just quit | | enter | | submit |
| f | | quit printing focus | | f1 | | global help menu |
| f1 | | global help menu | | tab | | try complete |
| p | | quit printing pwd |
| r | | quit printing result |
| s | | quit printing selection |
### search ### edit_permissions
| key | remaps | action | | key | remaps | action |
| ------ | ------ | ----------------------- | | ------ | ------ | ---------------- |
| ctrl-a | | toggle search algorithm | | G | | -group |
| ctrl-f | | fuzzy search | | M | | min |
| ctrl-n | down | down | | O | | -other |
| ctrl-p | up | up | | U | | -user |
| ctrl-r | | regex search | | ctrl-r | | reset |
| ctrl-s | | sort (no search order) | | enter | | submit |
| ctrl-z | | toggle ordering | | f1 | | global help menu |
| enter | | submit | | g | | +group |
| esc | | cancel | | m | | max |
| f1 | | global help menu | | o | | +other |
| left | | back | | u | | +user |
| right | | enter |
| tab | | toggle selection |
### go_to_path ### delete
| key | remaps | action |
| --- | ------ | ---------------- |
| D | | force delete |
| d | | delete |
| f1 | | global help menu |
### move_to
| key | remaps | action | | key | remaps | action |
| ----- | ------ | ---------------- | | ----- | ------ | ---------------- |
@ -280,7 +328,18 @@ of [modes][4] and the key mappings for each mode.
| f1 | | global help menu | | f1 | | global help menu |
| tab | | try complete | | tab | | try complete |
### create_file ### filter
| key | remaps | action |
| --------- | ------ | ---------------------------------- |
| R | | relative path does not match regex |
| backspace | | remove last filter |
| ctrl-r | | reset filters |
| ctrl-u | | clear filters |
| f1 | | global help menu |
| r | | relative path does match regex |
### duplicate_as
| key | remaps | action | | key | remaps | action |
| ----- | ------ | ---------------- | | ----- | ------ | ---------------- |

@ -35,6 +35,18 @@ The builtin go to path mode.
Type: [Mode](https://xplr.dev/en/mode) Type: [Mode](https://xplr.dev/en/mode)
#### xplr.config.modes.builtin.move_to
The builtin move_to mode.
Type: [Mode](https://xplr.dev/en/mode)
#### xplr.config.modes.builtin.copy_to
The builtin copy_to mode.
Type: [Mode](https://xplr.dev/en/mode)
#### xplr.config.modes.builtin.selection_ops #### xplr.config.modes.builtin.selection_ops
The builtin selection ops mode. The builtin selection ops mode.

@ -45,7 +45,7 @@ compatibility.
### Instructions ### Instructions
#### [v0.20.2][48] -> [v0.21.5][49] #### [v0.20.2][48] -> [v0.21.6][49]
- Some plugins might stop rendering colors. Wait for them to update. - Some plugins might stop rendering colors. Wait for them to update.
- Rename `xplr.config.general.sort_and_filter_ui.search_identifier` to - Rename `xplr.config.general.sort_and_filter_ui.search_identifier` to
@ -123,6 +123,10 @@ compatibility.
messages: messages:
- NextVisitedDeepBranch (bound to `)` key) - NextVisitedDeepBranch (bound to `)` key)
- PreviousVisitedDeepBranch (bound to `(` key) - PreviousVisitedDeepBranch (bound to `(` key)
- Since v0.21.6:
- You can use `c` and `m` keys in default mode to quickly copy
and move focused or selected files, without having to change directory.
- Use `xplr.util.debug()` to debug lua values.
Thanks to @noahmayr for contributing to a major part of this release. Thanks to @noahmayr for contributing to a major part of this release.
@ -521,5 +525,5 @@ Else do the following:
[46]: https://github.com/sayanarijit/xplr/releases/tag/v0.18.0 [46]: https://github.com/sayanarijit/xplr/releases/tag/v0.18.0
[47]: https://github.com/sayanarijit/xplr/releases/tag/v0.19.4 [47]: https://github.com/sayanarijit/xplr/releases/tag/v0.19.4
[48]: https://github.com/sayanarijit/xplr/releases/tag/v0.20.2 [48]: https://github.com/sayanarijit/xplr/releases/tag/v0.20.2
[49]: https://github.com/sayanarijit/xplr/releases/tag/v0.21.5 [49]: https://github.com/sayanarijit/xplr/releases/tag/v0.21.6
[50]: https://github.com/lotabout/skim#search-syntax [50]: https://github.com/lotabout/skim#search-syntax

@ -11,6 +11,23 @@ xplr.util.version()
-- { major = 0, minor = 0, patch = 0 } -- { major = 0, minor = 0, patch = 0 }
``` ```
### xplr.util.debug
Print the given value to the console, and return it as a string.
Useful for debugging.
Type: function( value ) -> string
Example:
```lua
xplr.util.debug({ foo = "bar", bar = function() end })
-- {
-- ["bar"] = function: 0x55e5cebdeae0,
-- ["foo"] = "bar",
-- }
```
### xplr.util.clone ### xplr.util.clone
Clone/deepcopy a Lua value. Doesn't work with functions. Clone/deepcopy a Lua value. Doesn't work with functions.

@ -0,0 +1,26 @@
name: xplr
version: git
summary: A hackable, minimal, fast TUI file explorer
description: |
xplr is a terminal UI based file explorer
that aims to increase our terminal productivity by being a flexible,
interactive orchestrator for the ever growing awesome command-line
utilities that work with the file-system.
source-code: https://github.com/sayanarijit/xplr
issues: https://github.com/sayanarijit/xplr/issues
website: https://xplr.dev/
base: core20
grade: devel # must be 'stable' to release into candidate/stable channels
confinement: devmode # use 'strict' once you have the right plugs and slots
parts:
xplr:
plugin: rust
source: .
apps:
xplr:
command: bin/xplr

@ -414,13 +414,19 @@ impl App {
prompt: config.general.prompt.format.clone().unwrap_or_default(), prompt: config.general.prompt.format.clone().unwrap_or_default(),
}; };
let hist = if &pwd == "/" {
pwd.clone()
} else {
format!("{0}/", &pwd)
};
let mut app = Self { let mut app = Self {
bin, bin,
version: VERSION.to_string(), version: VERSION.to_string(),
config, config,
vroot, vroot,
initial_vroot, initial_vroot,
pwd: pwd.clone(), pwd,
initial_pwd, initial_pwd,
directory_buffer: Default::default(), directory_buffer: Default::default(),
last_focus: Default::default(), last_focus: Default::default(),
@ -435,7 +441,7 @@ impl App {
explorer_config, explorer_config,
logs: Default::default(), logs: Default::default(),
logs_hidden: Default::default(), logs_hidden: Default::default(),
history: History::default().push(format!("{pwd}/")), history: History::default().push(hist),
last_modes: Default::default(), last_modes: Default::default(),
hostname, hostname,
hooks, hooks,
@ -1295,7 +1301,7 @@ impl App {
} }
pub fn scroll_up_half(mut self) -> Result<Self> { pub fn scroll_up_half(mut self) -> Result<Self> {
self.msg_out.push_back(MsgOut::ScrollUp); self.msg_out.push_back(MsgOut::ScrollUpHalf);
Ok(self) Ok(self)
} }

@ -1296,6 +1296,22 @@ xplr.config.modes.builtin.default = {
"FocusPreviousSelection", "FocusPreviousSelection",
}, },
}, },
["m"] = {
help = "move to",
messages = {
"PopMode",
{ SwitchModeBuiltin = "move_to" },
{ SetInputBuffer = "" },
},
},
["c"] = {
help = "copy to",
messages = {
"PopMode",
{ SwitchModeBuiltin = "copy_to" },
{ SetInputBuffer = "" },
},
},
}, },
on_number = { on_number = {
help = "input", help = "input",
@ -1460,6 +1476,148 @@ xplr.config.modes.builtin.go_to_path = {
}, },
} }
-- The builtin move_to mode.
--
-- Type: [Mode](https://xplr.dev/en/mode)
xplr.config.modes.builtin.move_to = {
name = "move_to",
prompt = "ð ",
key_bindings = {
on_key = {
["enter"] = {
help = "submit",
messages = {
{
BashExec0 = [===[
DEST="$XPLR_INPUT_BUFFER"
[ -z "$DEST" ] && exit
if [ ! -d "$DEST" ] && ! mkdir -p -- "$DEST"; then
"$XPLR" -m 'LogError: %q' "could not create $DEST"
exit
fi
"$XPLR" -m "ChangeDirectory: %q" "$DEST"
! cd -- "$DEST" && exit
DEST="$(pwd)" && echo "PWD=$DEST"
while IFS= read -r -d '' PTH; do
PTH_ESC=$(printf %q "$PTH")
BASENAME=$(basename -- "$PTH")
BASENAME_ESC=$(printf %q "$BASENAME")
if [ -e "$BASENAME" ]; then
echo
echo "$BASENAME_ESC exists, do you want to overwrite it?"
read -p "[y]es, [n]o, [S]kip: " ANS < /dev/tty
case "$ANS" in
[yY]*)
;;
[nN]*)
read -p "Enter new name: " BASENAME < /dev/tty
BASENAME_ESC=$(printf %q "$BASENAME")
;;
*)
continue
;;
esac
fi
if mv -v -- "${PTH:?}" "./${BASENAME:?}"; then
"$XPLR" -m 'LogSuccess: %q' "$PTH_ESC moved to $BASENAME_ESC"
"$XPLR" -m 'FocusPath: %q' "$BASENAME"
else
"$XPLR" -m 'LogError: %q' "could not move $PTH_ESC to $BASENAME_ESC"
fi
done < "${XPLR_PIPE_RESULT_OUT:?}"
echo
read -p "[press enter to continue]"
]===],
},
"PopMode",
},
},
["tab"] = {
help = "try complete",
messages = {
{ CallLuaSilently = "builtin.try_complete_path" },
},
},
},
default = {
messages = {
"UpdateInputBufferFromKey",
},
},
},
}
-- The builtin copy_to mode.
--
-- Type: [Mode](https://xplr.dev/en/mode)
xplr.config.modes.builtin.copy_to = {
name = "copy_to",
prompt = "ð ",
key_bindings = {
on_key = {
["enter"] = {
help = "submit",
messages = {
{
BashExec0 = [===[
DEST="$XPLR_INPUT_BUFFER"
[ -z "$DEST" ] && exit
if [ ! -d "$DEST" ] && ! mkdir -p -- "$DEST"; then
"$XPLR" -m 'LogError: %q' "could not create $DEST"
exit
fi
"$XPLR" -m "ChangeDirectory: %q" "$DEST"
! cd -- "$DEST" && exit
DEST="$(pwd)" && echo "PWD=$DEST"
while IFS= read -r -d '' PTH; do
PTH_ESC=$(printf %q "$PTH")
BASENAME=$(basename -- "$PTH")
BASENAME_ESC=$(printf %q "$BASENAME")
if [ -e "$BASENAME" ]; then
echo
echo "$BASENAME_ESC exists, do you want to overwrite it?"
read -p "[y]es, [n]o, [S]kip: " ANS < /dev/tty
case "$ANS" in
[yY]*)
;;
[nN]*)
read -p "Enter new name: " BASENAME < /dev/tty
BASENAME_ESC=$(printf %q "$BASENAME")
;;
*)
continue
;;
esac
fi
if cp -vr -- "${PTH:?}" "./${BASENAME:?}"; then
"$XPLR" -m 'LogSuccess: %q' "$PTH_ESC copied to $BASENAME_ESC"
"$XPLR" -m 'FocusPath: %q' "$BASENAME"
else
"$XPLR" -m 'LogError: %q' "could not copy $PTH_ESC to $BASENAME_ESC"
fi
done < "${XPLR_PIPE_RESULT_OUT:?}"
echo
read -p "[press enter to continue]"
]===],
},
"PopMode",
},
},
["tab"] = {
help = "try complete",
messages = {
{ CallLuaSilently = "builtin.try_complete_path" },
},
},
},
default = {
messages = {
"UpdateInputBufferFromKey",
},
},
},
}
-- The builtin selection ops mode. -- The builtin selection ops mode.
-- --
-- Type: [Mode](https://xplr.dev/en/mode) -- Type: [Mode](https://xplr.dev/en/mode)
@ -1531,10 +1689,10 @@ xplr.config.modes.builtin.selection_ops = {
esac esac
fi fi
if cp -vr -- "${PTH:?}" "./${BASENAME:?}"; then if cp -vr -- "${PTH:?}" "./${BASENAME:?}"; then
"$XPLR" -m 'LogSuccess: %q' "$PTH_ESC copied to ./$BASENAME_ESC" "$XPLR" -m 'LogSuccess: %q' "$PTH_ESC copied to $BASENAME_ESC"
"$XPLR" -m 'FocusPath: %q' "$BASENAME" "$XPLR" -m 'FocusPath: %q' "$BASENAME"
else else
"$XPLR" -m 'LogError: %q' "could not copy $PTH_ESC to ./$BASENAME_ESC" "$XPLR" -m 'LogError: %q' "could not copy $PTH_ESC to $BASENAME_ESC"
fi fi
done < "${XPLR_PIPE_SELECTION_OUT:?}" done < "${XPLR_PIPE_SELECTION_OUT:?}"
echo echo
@ -1571,10 +1729,10 @@ xplr.config.modes.builtin.selection_ops = {
esac esac
fi fi
if mv -v -- "${PTH:?}" "./${BASENAME:?}"; then if mv -v -- "${PTH:?}" "./${BASENAME:?}"; then
"$XPLR" -m 'LogSuccess: %q' "$PTH_ESC moved to ./$BASENAME_ESC" "$XPLR" -m 'LogSuccess: %q' "$PTH_ESC moved to $BASENAME_ESC"
"$XPLR" -m 'FocusPath: %q' "$BASENAME" "$XPLR" -m 'FocusPath: %q' "$BASENAME"
else else
"$XPLR" -m 'LogError: %q' "could not move $PTH_ESC to ./$BASENAME_ESC" "$XPLR" -m 'LogError: %q' "could not move $PTH_ESC to $BASENAME_ESC"
fi fi
done < "${XPLR_PIPE_SELECTION_OUT:?}" done < "${XPLR_PIPE_SELECTION_OUT:?}"
echo echo
@ -1611,10 +1769,10 @@ xplr.config.modes.builtin.selection_ops = {
esac esac
fi fi
if ln -sv -- "${PTH:?}" "./${BASENAME:?}"; then if ln -sv -- "${PTH:?}" "./${BASENAME:?}"; then
"$XPLR" -m 'LogSuccess: %q' "$PTH_ESC softlinked as ./$BASENAME_ESC" "$XPLR" -m 'LogSuccess: %q' "$PTH_ESC softlinked as $BASENAME_ESC"
"$XPLR" -m 'FocusPath: %q' "$BASENAME" "$XPLR" -m 'FocusPath: %q' "$BASENAME"
else else
"$XPLR" -m 'LogError: %q' "could not softlink $PTH_ESC as ./$BASENAME_ESC" "$XPLR" -m 'LogError: %q' "could not softlink $PTH_ESC as $BASENAME_ESC"
fi fi
done < "${XPLR_PIPE_SELECTION_OUT:?}" done < "${XPLR_PIPE_SELECTION_OUT:?}"
echo echo
@ -1651,10 +1809,10 @@ xplr.config.modes.builtin.selection_ops = {
esac esac
fi fi
if ln -v -- "${PTH:?}" "./${BASENAME:?}"; then if ln -v -- "${PTH:?}" "./${BASENAME:?}"; then
"$XPLR" -m 'LogSuccess: %q' "$PTH_ESC hardlinked as ./$BASENAME_ESC" "$XPLR" -m 'LogSuccess: %q' "$PTH_ESC hardlinked as $BASENAME_ESC"
"$XPLR" -m 'FocusPath: %q' "$BASENAME" "$XPLR" -m 'FocusPath: %q' "$BASENAME"
else else
"$XPLR" -m 'LogError: %q' "could not hardlink $PTH_ESC as ./$BASENAME_ESC" "$XPLR" -m 'LogError: %q' "could not hardlink $PTH_ESC as $BASENAME_ESC"
fi fi
done < "${XPLR_PIPE_SELECTION_OUT:?}" done < "${XPLR_PIPE_SELECTION_OUT:?}"
echo echo

@ -160,24 +160,24 @@ mod tests {
assert!(check_version(VERSION, "foo path").is_ok()); assert!(check_version(VERSION, "foo path").is_ok());
// Current release if OK // Current release if OK
assert!(check_version("0.21.5", "foo path").is_ok()); assert!(check_version("0.21.6", "foo path").is_ok());
// Prev major release is ERR // Prev major release is ERR
// - Not yet // - Not yet
// Prev minor release is ERR (Change when we get to v1) // Prev minor release is ERR (Change when we get to v1)
assert!(check_version("0.20.5", "foo path").is_err()); assert!(check_version("0.20.6", "foo path").is_err());
// Prev bugfix release is OK // Prev bugfix release is OK
assert!(check_version("0.21.4", "foo path").is_ok()); assert!(check_version("0.21.5", "foo path").is_ok());
// Next major release is ERR // Next major release is ERR
assert!(check_version("1.20.5", "foo path").is_err()); assert!(check_version("1.20.6", "foo path").is_err());
// Next minor release is ERR // Next minor release is ERR
assert!(check_version("0.22.5", "foo path").is_err()); assert!(check_version("0.22.6", "foo path").is_err());
// Next bugfix release is ERR (Change when we get to v1) // Next bugfix release is ERR (Change when we get to v1)
assert!(check_version("0.21.6", "foo path").is_err()); assert!(check_version("0.21.7", "foo path").is_err());
} }
} }

@ -13,6 +13,7 @@ use crate::ui::Layout;
use crate::ui::Style; use crate::ui::Style;
use crate::ui::WrapOptions; use crate::ui::WrapOptions;
use anyhow::Result; use anyhow::Result;
use lazy_static::lazy_static;
use lscolors::LsColors; use lscolors::LsColors;
use mlua::Error as LuaError; use mlua::Error as LuaError;
use mlua::Lua; use mlua::Lua;
@ -28,6 +29,10 @@ use std::borrow::Cow;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
lazy_static! {
static ref LS_COLORS: LsColors = LsColors::from_env().unwrap_or_default();
}
/// Get the xplr version details. /// Get the xplr version details.
/// ///
/// Type: function() -> { major: number, minor: number, patch: number } /// Type: function() -> { major: number, minor: number, patch: number }
@ -64,6 +69,30 @@ pub fn version<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
Ok(util) Ok(util)
} }
/// Print the given value to the console, and return it as a string.
/// Useful for debugging.
///
/// Type: function( value ) -> string
///
/// Example:
///
/// ```lua
/// xplr.util.debug({ foo = "bar", bar = function() end })
/// -- {
/// -- ["bar"] = function: 0x55e5cebdeae0,
/// -- ["foo"] = "bar",
/// -- }
/// ```
pub fn debug<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
let func = lua.create_function(|_, value: Value| {
let log = format!("{:#?}", value);
println!("{}", log);
Ok(log)
})?;
util.set("debug", func)?;
Ok(util)
}
/// Clone/deepcopy a Lua value. Doesn't work with functions. /// Clone/deepcopy a Lua value. Doesn't work with functions.
/// ///
/// Type: function( value ) -> value /// Type: function( value ) -> value
@ -634,9 +663,8 @@ pub fn to_yaml<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
/// -- { fg = "Red", bg = nil, add_modifiers = {}, sub_modifiers = {} } /// -- { fg = "Red", bg = nil, add_modifiers = {}, sub_modifiers = {} }
/// ``` /// ```
pub fn lscolor<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> { pub fn lscolor<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
let lscolors = LsColors::from_env().unwrap_or_default();
let func = lua.create_function(move |lua, path: String| { let func = lua.create_function(move |lua, path: String| {
let style = lscolors.style_for_path(path).map(Style::from); let style = LS_COLORS.style_for_path(path).map(Style::from);
lua::serialize(lua, &style).map_err(LuaError::custom) lua::serialize(lua, &style).map_err(LuaError::custom)
})?; })?;
util.set("lscolor", func)?; util.set("lscolor", func)?;
@ -838,6 +866,7 @@ pub(crate) fn create_table(lua: &Lua) -> Result<Table> {
let mut util = lua.create_table()?; let mut util = lua.create_table()?;
util = version(util, lua)?; util = version(util, lua)?;
util = debug(util, lua)?;
util = clone(util, lua)?; util = clone(util, lua)?;
util = exists(util, lua)?; util = exists(util, lua)?;
util = is_dir(util, lua)?; util = is_dir(util, lua)?;

@ -18,7 +18,7 @@ use std::ops::BitXor;
use time::macros::format_description; use time::macros::format_description;
use tui::layout::Rect as TuiRect; use tui::layout::Rect as TuiRect;
use tui::layout::{Constraint as TuiConstraint, Direction, Layout as TuiLayout}; use tui::layout::{Constraint as TuiConstraint, Direction, Layout as TuiLayout};
use tui::style::{Color, Modifier as TuiModifier, Style as TuiStyle}; use tui::style::{Color as TuiColor, Modifier as TuiModifier, Style as TuiStyle};
use tui::text::{Line, Span, Text}; use tui::text::{Line, Span, Text};
use tui::widgets::{ use tui::widgets::{
Block, BorderType as TuiBorderType, Borders as TuiBorders, Cell, List, ListItem, Block, BorderType as TuiBorderType, Borders as TuiBorders, Cell, List, ListItem,
@ -26,9 +26,10 @@ use tui::widgets::{
}; };
use tui::Frame; use tui::Frame;
const DEFAULT_STYLE: TuiStyle = TuiStyle::new();
lazy_static! { lazy_static! {
pub static ref NO_COLOR: bool = env::var("NO_COLOR").is_ok(); pub static ref NO_COLOR: bool = env::var("NO_COLOR").is_ok();
pub static ref DEFAULT_STYLE: TuiStyle = TuiStyle::default();
} }
fn read_only_indicator(app: &app::App) -> &str { fn read_only_indicator(app: &app::App) -> &str {
@ -297,6 +298,59 @@ fn extend_optional_modifiers(
} }
} }
// raratui doesn't support directly serializing `Color::Rgb` and
// `Color::Indexed` anymore.
// See https://github.com/ratatui-org/ratatui/pull/934
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub enum Color {
#[default]
Reset,
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
Gray,
DarkGray,
LightRed,
LightGreen,
LightYellow,
LightBlue,
LightMagenta,
LightCyan,
White,
Rgb(u8, u8, u8),
Indexed(u8),
}
impl From<Color> for TuiColor {
fn from(value: Color) -> Self {
match value {
Color::Reset => TuiColor::Reset,
Color::Black => TuiColor::Black,
Color::Red => TuiColor::Red,
Color::Green => TuiColor::Green,
Color::Yellow => TuiColor::Yellow,
Color::Blue => TuiColor::Blue,
Color::Magenta => TuiColor::Magenta,
Color::Cyan => TuiColor::Cyan,
Color::Gray => TuiColor::Gray,
Color::DarkGray => TuiColor::DarkGray,
Color::LightRed => TuiColor::LightRed,
Color::LightGreen => TuiColor::LightGreen,
Color::LightYellow => TuiColor::LightYellow,
Color::LightBlue => TuiColor::LightBlue,
Color::LightMagenta => TuiColor::LightMagenta,
Color::LightCyan => TuiColor::LightCyan,
Color::White => TuiColor::White,
Color::Rgb(r, g, b) => TuiColor::Rgb(r, g, b),
Color::Indexed(index) => TuiColor::Indexed(index),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)] #[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct Style { pub struct Style {
@ -322,8 +376,8 @@ impl Style {
} }
} }
impl Into<TuiStyle> for Style { impl From<Style> for TuiStyle {
fn into(self) -> TuiStyle { fn from(val: Style) -> Self {
fn xor(modifiers: Option<IndexSet<Modifier>>) -> u16 { fn xor(modifiers: Option<IndexSet<Modifier>>) -> u16 {
modifiers modifiers
.unwrap_or_default() .unwrap_or_default()
@ -332,14 +386,13 @@ impl Into<TuiStyle> for Style {
.fold(0, BitXor::bitxor) .fold(0, BitXor::bitxor)
} }
if *NO_COLOR { if *NO_COLOR {
*DEFAULT_STYLE DEFAULT_STYLE
} else { } else {
TuiStyle { TuiStyle {
fg: self.fg, fg: val.fg.map(Into::into),
bg: self.bg, bg: val.bg.map(Into::into),
underline_color: None, add_modifier: TuiModifier::from_bits_truncate(xor(val.add_modifiers)),
add_modifier: TuiModifier::from_bits_truncate(xor(self.add_modifiers)), sub_modifier: TuiModifier::from_bits_truncate(xor(val.sub_modifiers)),
sub_modifier: TuiModifier::from_bits_truncate(xor(self.sub_modifiers)),
} }
} }
} }
@ -378,8 +431,8 @@ impl From<&LsColorsStyle> for Style {
} }
} }
impl Into<nu_ansi_term::Style> for Style { impl From<Style> for nu_ansi_term::Style {
fn into(self) -> nu_ansi_term::Style { fn from(val: Style) -> Self {
fn convert_color(color: Color) -> Option<nu_ansi_term::Color> { fn convert_color(color: Color) -> Option<nu_ansi_term::Color> {
match color { match color {
Color::Black => Some(nu_ansi_term::Color::Black), Color::Black => Some(nu_ansi_term::Color::Black),
@ -411,20 +464,20 @@ impl Into<nu_ansi_term::Style> for Style {
} }
let mut style = nu_ansi_term::Style::new(); let mut style = nu_ansi_term::Style::new();
style.foreground = self.fg.and_then(convert_color); style.foreground = val.fg.and_then(convert_color);
style.background = self.bg.and_then(convert_color); style.background = val.bg.and_then(convert_color);
style.is_bold = match_modifiers(&self, |m| m.contains(&Modifier::Bold)); style.is_bold = match_modifiers(&val, |m| m.contains(&Modifier::Bold));
style.is_dimmed = match_modifiers(&self, |m| m.contains(&Modifier::Dim)); style.is_dimmed = match_modifiers(&val, |m| m.contains(&Modifier::Dim));
style.is_italic = match_modifiers(&self, |m| m.contains(&Modifier::Italic)); style.is_italic = match_modifiers(&val, |m| m.contains(&Modifier::Italic));
style.is_underline = style.is_underline =
match_modifiers(&self, |m| m.contains(&Modifier::Underlined)); match_modifiers(&val, |m| m.contains(&Modifier::Underlined));
style.is_blink = match_modifiers(&self, |m| { style.is_blink = match_modifiers(&val, |m| {
m.contains(&Modifier::SlowBlink) || m.contains(&Modifier::RapidBlink) m.contains(&Modifier::SlowBlink) || m.contains(&Modifier::RapidBlink)
}); });
style.is_reverse = match_modifiers(&self, |m| m.contains(&Modifier::Reversed)); style.is_reverse = match_modifiers(&val, |m| m.contains(&Modifier::Reversed));
style.is_hidden = match_modifiers(&self, |m| m.contains(&Modifier::Hidden)); style.is_hidden = match_modifiers(&val, |m| m.contains(&Modifier::Hidden));
style.is_strikethrough = style.is_strikethrough =
match_modifiers(&self, |m| m.contains(&Modifier::CrossedOut)); match_modifiers(&val, |m| m.contains(&Modifier::CrossedOut));
style style
} }
} }
@ -658,11 +711,11 @@ pub fn block<'a>(config: PanelUiConfig, default_title: String) -> Block<'a> {
)) ))
.title(Span::styled( .title(Span::styled(
config.title.format.unwrap_or(default_title), config.title.format.unwrap_or(default_title),
config.title.style.into(), config.title.style,
)) ))
.style(config.style.into()) .style(config.style)
.border_type(config.border_type.unwrap_or_default().into()) .border_type(config.border_type.unwrap_or_default().into())
.border_style(config.border_style.into()) .border_style(config.border_style)
} }
fn draw_table( fn draw_table(
@ -784,10 +837,10 @@ fn draw_table(
}) })
.unwrap_or_default() .unwrap_or_default()
.into_iter() .into_iter()
.map(|(text, style)| Cell::from(text).style(style.into())) .map(|(text, style)| Cell::from(text).style(style))
.collect::<Vec<Cell>>(); .collect::<Vec<Cell>>();
Row::new(cols).style(row_style.to_owned().into()) Row::new(cols).style(row_style.to_owned())
}) })
.collect::<Vec<Row>>() .collect::<Vec<Row>>()
}) })
@ -822,8 +875,8 @@ fn draw_table(
}; };
let table = Table::new(rows, table_constraints) let table = Table::new(rows, table_constraints)
.style(app_config.general.table.style.to_owned().into()) .style(app_config.general.table.style.to_owned())
.highlight_style(app_config.general.focus_ui.style.to_owned().into()) .highlight_style(app_config.general.focus_ui.style.to_owned())
.column_spacing(app_config.general.table.col_spacing.unwrap_or_default()) .column_spacing(app_config.general.table.col_spacing.unwrap_or_default())
.block(block( .block(block(
config, config,
@ -842,12 +895,12 @@ fn draw_table(
.iter() .iter()
.map(|c| { .map(|c| {
Cell::from(c.format.to_owned().unwrap_or_default()) Cell::from(c.format.to_owned().unwrap_or_default())
.style(c.style.to_owned().into()) .style(c.style.to_owned())
}) })
.collect::<Vec<Cell>>(), .collect::<Vec<Cell>>(),
) )
.height(header_height) .height(header_height)
.style(app_config.general.table.header.style.to_owned().into()), .style(app_config.general.table.header.style.to_owned()),
); );
f.render_widget(table, layout_size); f.render_widget(table, layout_size);
@ -891,8 +944,7 @@ fn draw_selection(
string_to_text(out) string_to_text(out)
}) })
.map(|i| { .map(|i| {
ListItem::new(i) ListItem::new(i).style(app.config.general.selection.item.style.to_owned())
.style(app.config.general.selection.item.style.to_owned().into())
}) })
.collect(); .collect();
@ -990,7 +1042,7 @@ fn draw_input_buffer(
let input_buf = Paragraph::new(Line::from(vec![ let input_buf = Paragraph::new(Line::from(vec![
Span::styled( Span::styled(
app.input.prompt.to_owned(), app.input.prompt.to_owned(),
app.config.general.prompt.style.to_owned().into(), app.config.general.prompt.style.to_owned(),
), ),
Span::raw(input.value()), Span::raw(input.value()),
])) ]))
@ -1061,9 +1113,9 @@ fn draw_sort_n_filter(
( (
Span::styled( Span::styled(
ui.format.to_owned().unwrap_or_default(), ui.format.to_owned().unwrap_or_default(),
ui.style.to_owned().into(), ui.style.to_owned(),
), ),
Span::styled(f.input.to_owned(), ui.style.into()), Span::styled(f.input.to_owned(), ui.style),
) )
}) })
.unwrap_or((Span::raw("f"), Span::raw(""))) .unwrap_or((Span::raw("f"), Span::raw("")))
@ -1084,10 +1136,10 @@ fn draw_sort_n_filter(
.map(|f| format!("{f}{p}", p = &s.pattern)) .map(|f| format!("{f}{p}", p = &s.pattern))
.unwrap_or_else(|| s.pattern.clone()); .unwrap_or_else(|| s.pattern.clone());
( (
Span::styled(f, ui.style.into()), Span::styled(f, ui.style),
Span::styled( Span::styled(
direction.format.to_owned().unwrap_or_default(), direction.format.to_owned().unwrap_or_default(),
direction.style.to_owned().into(), direction.style.to_owned(),
), ),
) )
}) })
@ -1105,11 +1157,11 @@ fn draw_sort_n_filter(
( (
Span::styled( Span::styled(
ui.format.to_owned().unwrap_or_default(), ui.format.to_owned().unwrap_or_default(),
ui.style.into(), ui.style,
), ),
Span::styled( Span::styled(
direction.format.to_owned().unwrap_or_default(), direction.format.to_owned().unwrap_or_default(),
direction.style.to_owned().into(), direction.style.to_owned(),
), ),
) )
}) })
@ -1119,7 +1171,7 @@ fn draw_sort_n_filter(
) )
.zip(std::iter::repeat(Span::styled( .zip(std::iter::repeat(Span::styled(
ui.separator.format.to_owned().unwrap_or_default(), ui.separator.format.to_owned().unwrap_or_default(),
ui.separator.style.to_owned().into(), ui.separator.style.to_owned(),
))) )))
.flat_map(|((a, b), c)| vec![a, b, c]) .flat_map(|((a, b), c)| vec![a, b, c])
.collect::<Vec<Span>>(); .collect::<Vec<Span>>();
@ -1189,7 +1241,7 @@ fn draw_logs(
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("\n"); .join("\n");
ListItem::new(txt).style(cfg.style.to_owned().into()) ListItem::new(txt).style(cfg.style.to_owned())
}) })
.collect::<Vec<ListItem>>() .collect::<Vec<ListItem>>()
}; };
@ -1451,7 +1503,6 @@ pub fn draw(f: &mut Frame, app: &app::App, lua: &Lua) {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use tui::style::Color;
fn modifier(m: Modifier) -> Option<IndexSet<Modifier>> { fn modifier(m: Modifier) -> Option<IndexSet<Modifier>> {
let mut x = IndexSet::new(); let mut x = IndexSet::new();

Loading…
Cancel
Save