Release v0.21.6 (#701)

- Snap build
- xplr.util.debug()
- `c` and `m` key bindings for quick copy and move.
- ScrollUpHalf fix
- Dependency updates
main 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: |
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 snapd
sudo snap install snapcraft --classic
sudo snap install multipass --classic --beta
- if: matrix.build == 'linux-musl'
run: sudo apt-get install -y musl-tools
@ -81,6 +84,13 @@ jobs:
- name: Running cargo build
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
run: |
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 }}.sha256
target/${{ matrix.target }}/release/xplr-${{ matrix.build }}.tar.gz.asc
xplr.snap
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@ -130,6 +141,10 @@ jobs:
source.tar.gz.asc
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Cleaning snapcraft
run: |
command rm --verbose ./*.snap
snapcraft clean
publish-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]
name = 'xplr'
version = '0.21.5'
version = '0.21.6'
authors = ['Arijit Basu <hi@arijitbasu.in>']
edition = '2021'
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']
[dependencies]
libc = "0.2.151"
libc = "0.2.153"
humansize = "2.1.3"
natord = "1.0.9"
anyhow = "1.0.79"
serde_yaml = "0.9.30"
anyhow = "1.0.81"
serde_yaml = "0.9.33"
crossterm = { version = "0.27.0", features = [], default-features = false }
ansi-to-tui = "3.1.0"
regex = "1.10.2"
ansi-to-tui = "4.0.1"
regex = "1.10.3"
gethostname = "0.4.3"
serde_json = "1.0.110"
serde_json = "1.0.114"
path-absolutize = "3.1.1"
which = "5.0.0"
nu-ansi-term = "0.49.0"
which = "6.0.0"
nu-ansi-term = "0.50.0"
textwrap = "0.16"
snailquote = "0.3.1"
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"
xdg = "2.5.2"
home = "0.5.9"
[dependencies.lscolors]
version = "0.16.0"
version = "0.17.0"
default-features = false
features = ["nu-ansi-term"]
@ -57,22 +57,22 @@ version = "2.0.4"
default-features = false
[dependencies.tui]
version = "0.25.0"
version = "0.26.1"
default-features = false
features = ['crossterm', 'serde', 'underline-color']
features = ['crossterm', 'serde']
package = 'ratatui'
[dependencies.serde]
version = "1.0.194"
version = "1.0.197"
features = []
default-features = false
[dependencies.indexmap]
version = "2.1.0"
version = "2.2.5"
features = ['serde']
[dependencies.mlua]
version = "0.9.2"
version = "0.9.6"
features = ['luajit', 'vendored', 'serialize', 'send']
[dependencies.tui-input]
@ -81,7 +81,7 @@ features = ['serde']
[dev-dependencies]
criterion = "0.5.1"
assert_cmd = "2.0.12"
assert_cmd = "2.0.14"
[profile.release]
lto = true

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

@ -35,6 +35,18 @@ The builtin go to path 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
The builtin selection ops mode.

@ -45,7 +45,7 @@ compatibility.
### 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.
- Rename `xplr.config.general.sort_and_filter_ui.search_identifier` to
@ -123,6 +123,10 @@ compatibility.
messages:
- NextVisitedDeepBranch (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.
@ -521,5 +525,5 @@ Else do the following:
[46]: https://github.com/sayanarijit/xplr/releases/tag/v0.18.0
[47]: https://github.com/sayanarijit/xplr/releases/tag/v0.19.4
[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

@ -11,6 +11,23 @@ xplr.util.version()
-- { 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
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(),
};
let hist = if &pwd == "/" {
pwd.clone()
} else {
format!("{0}/", &pwd)
};
let mut app = Self {
bin,
version: VERSION.to_string(),
config,
vroot,
initial_vroot,
pwd: pwd.clone(),
pwd,
initial_pwd,
directory_buffer: Default::default(),
last_focus: Default::default(),
@ -435,7 +441,7 @@ impl App {
explorer_config,
logs: Default::default(),
logs_hidden: Default::default(),
history: History::default().push(format!("{pwd}/")),
history: History::default().push(hist),
last_modes: Default::default(),
hostname,
hooks,
@ -1295,7 +1301,7 @@ impl App {
}
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)
}

@ -1296,6 +1296,22 @@ xplr.config.modes.builtin.default = {
"FocusPreviousSelection",
},
},
["m"] = {
help = "move to",
messages = {
"PopMode",
{ SwitchModeBuiltin = "move_to" },
{ SetInputBuffer = "" },
},
},
["c"] = {
help = "copy to",
messages = {
"PopMode",
{ SwitchModeBuiltin = "copy_to" },
{ SetInputBuffer = "" },
},
},
},
on_number = {
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.
--
-- Type: [Mode](https://xplr.dev/en/mode)
@ -1531,10 +1689,10 @@ xplr.config.modes.builtin.selection_ops = {
esac
fi
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"
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
done < "${XPLR_PIPE_SELECTION_OUT:?}"
echo
@ -1571,10 +1729,10 @@ xplr.config.modes.builtin.selection_ops = {
esac
fi
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"
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
done < "${XPLR_PIPE_SELECTION_OUT:?}"
echo
@ -1611,10 +1769,10 @@ xplr.config.modes.builtin.selection_ops = {
esac
fi
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"
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
done < "${XPLR_PIPE_SELECTION_OUT:?}"
echo
@ -1651,10 +1809,10 @@ xplr.config.modes.builtin.selection_ops = {
esac
fi
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"
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
done < "${XPLR_PIPE_SELECTION_OUT:?}"
echo

@ -160,24 +160,24 @@ mod tests {
assert!(check_version(VERSION, "foo path").is_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
// - Not yet
// 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
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
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
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)
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::WrapOptions;
use anyhow::Result;
use lazy_static::lazy_static;
use lscolors::LsColors;
use mlua::Error as LuaError;
use mlua::Lua;
@ -28,6 +29,10 @@ use std::borrow::Cow;
use std::path::PathBuf;
use std::process::Command;
lazy_static! {
static ref LS_COLORS: LsColors = LsColors::from_env().unwrap_or_default();
}
/// Get the xplr version details.
///
/// 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)
}
/// 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.
///
/// 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 = {} }
/// ```
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 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)
})?;
util.set("lscolor", func)?;
@ -838,6 +866,7 @@ pub(crate) fn create_table(lua: &Lua) -> Result<Table> {
let mut util = lua.create_table()?;
util = version(util, lua)?;
util = debug(util, lua)?;
util = clone(util, lua)?;
util = exists(util, lua)?;
util = is_dir(util, lua)?;

@ -18,7 +18,7 @@ use std::ops::BitXor;
use time::macros::format_description;
use tui::layout::Rect as TuiRect;
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::widgets::{
Block, BorderType as TuiBorderType, Borders as TuiBorders, Cell, List, ListItem,
@ -26,9 +26,10 @@ use tui::widgets::{
};
use tui::Frame;
const DEFAULT_STYLE: TuiStyle = TuiStyle::new();
lazy_static! {
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 {
@ -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)]
#[serde(deny_unknown_fields)]
pub struct Style {
@ -322,8 +376,8 @@ impl Style {
}
}
impl Into<TuiStyle> for Style {
fn into(self) -> TuiStyle {
impl From<Style> for TuiStyle {
fn from(val: Style) -> Self {
fn xor(modifiers: Option<IndexSet<Modifier>>) -> u16 {
modifiers
.unwrap_or_default()
@ -332,14 +386,13 @@ impl Into<TuiStyle> for Style {
.fold(0, BitXor::bitxor)
}
if *NO_COLOR {
*DEFAULT_STYLE
DEFAULT_STYLE
} else {
TuiStyle {
fg: self.fg,
bg: self.bg,
underline_color: None,
add_modifier: TuiModifier::from_bits_truncate(xor(self.add_modifiers)),
sub_modifier: TuiModifier::from_bits_truncate(xor(self.sub_modifiers)),
fg: val.fg.map(Into::into),
bg: val.bg.map(Into::into),
add_modifier: TuiModifier::from_bits_truncate(xor(val.add_modifiers)),
sub_modifier: TuiModifier::from_bits_truncate(xor(val.sub_modifiers)),
}
}
}
@ -378,8 +431,8 @@ impl From<&LsColorsStyle> for Style {
}
}
impl Into<nu_ansi_term::Style> for Style {
fn into(self) -> nu_ansi_term::Style {
impl From<Style> for nu_ansi_term::Style {
fn from(val: Style) -> Self {
fn convert_color(color: Color) -> Option<nu_ansi_term::Color> {
match color {
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();
style.foreground = self.fg.and_then(convert_color);
style.background = self.bg.and_then(convert_color);
style.is_bold = match_modifiers(&self, |m| m.contains(&Modifier::Bold));
style.is_dimmed = match_modifiers(&self, |m| m.contains(&Modifier::Dim));
style.is_italic = match_modifiers(&self, |m| m.contains(&Modifier::Italic));
style.foreground = val.fg.and_then(convert_color);
style.background = val.bg.and_then(convert_color);
style.is_bold = match_modifiers(&val, |m| m.contains(&Modifier::Bold));
style.is_dimmed = match_modifiers(&val, |m| m.contains(&Modifier::Dim));
style.is_italic = match_modifiers(&val, |m| m.contains(&Modifier::Italic));
style.is_underline =
match_modifiers(&self, |m| m.contains(&Modifier::Underlined));
style.is_blink = match_modifiers(&self, |m| {
match_modifiers(&val, |m| m.contains(&Modifier::Underlined));
style.is_blink = match_modifiers(&val, |m| {
m.contains(&Modifier::SlowBlink) || m.contains(&Modifier::RapidBlink)
});
style.is_reverse = match_modifiers(&self, |m| m.contains(&Modifier::Reversed));
style.is_hidden = match_modifiers(&self, |m| m.contains(&Modifier::Hidden));
style.is_reverse = match_modifiers(&val, |m| m.contains(&Modifier::Reversed));
style.is_hidden = match_modifiers(&val, |m| m.contains(&Modifier::Hidden));
style.is_strikethrough =
match_modifiers(&self, |m| m.contains(&Modifier::CrossedOut));
match_modifiers(&val, |m| m.contains(&Modifier::CrossedOut));
style
}
}
@ -658,11 +711,11 @@ pub fn block<'a>(config: PanelUiConfig, default_title: String) -> Block<'a> {
))
.title(Span::styled(
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_style(config.border_style.into())
.border_style(config.border_style)
}
fn draw_table(
@ -784,10 +837,10 @@ fn draw_table(
})
.unwrap_or_default()
.into_iter()
.map(|(text, style)| Cell::from(text).style(style.into()))
.map(|(text, style)| Cell::from(text).style(style))
.collect::<Vec<Cell>>();
Row::new(cols).style(row_style.to_owned().into())
Row::new(cols).style(row_style.to_owned())
})
.collect::<Vec<Row>>()
})
@ -822,8 +875,8 @@ fn draw_table(
};
let table = Table::new(rows, table_constraints)
.style(app_config.general.table.style.to_owned().into())
.highlight_style(app_config.general.focus_ui.style.to_owned().into())
.style(app_config.general.table.style.to_owned())
.highlight_style(app_config.general.focus_ui.style.to_owned())
.column_spacing(app_config.general.table.col_spacing.unwrap_or_default())
.block(block(
config,
@ -842,12 +895,12 @@ fn draw_table(
.iter()
.map(|c| {
Cell::from(c.format.to_owned().unwrap_or_default())
.style(c.style.to_owned().into())
.style(c.style.to_owned())
})
.collect::<Vec<Cell>>(),
)
.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);
@ -891,8 +944,7 @@ fn draw_selection(
string_to_text(out)
})
.map(|i| {
ListItem::new(i)
.style(app.config.general.selection.item.style.to_owned().into())
ListItem::new(i).style(app.config.general.selection.item.style.to_owned())
})
.collect();
@ -990,7 +1042,7 @@ fn draw_input_buffer(
let input_buf = Paragraph::new(Line::from(vec![
Span::styled(
app.input.prompt.to_owned(),
app.config.general.prompt.style.to_owned().into(),
app.config.general.prompt.style.to_owned(),
),
Span::raw(input.value()),
]))
@ -1061,9 +1113,9 @@ fn draw_sort_n_filter(
(
Span::styled(
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("")))
@ -1084,10 +1136,10 @@ fn draw_sort_n_filter(
.map(|f| format!("{f}{p}", p = &s.pattern))
.unwrap_or_else(|| s.pattern.clone());
(
Span::styled(f, ui.style.into()),
Span::styled(f, ui.style),
Span::styled(
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(
ui.format.to_owned().unwrap_or_default(),
ui.style.into(),
ui.style,
),
Span::styled(
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(
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])
.collect::<Vec<Span>>();
@ -1189,7 +1241,7 @@ fn draw_logs(
.collect::<Vec<_>>()
.join("\n");
ListItem::new(txt).style(cfg.style.to_owned().into())
ListItem::new(txt).style(cfg.style.to_owned())
})
.collect::<Vec<ListItem>>()
};
@ -1451,7 +1503,6 @@ pub fn draw(f: &mut Frame, app: &app::App, lua: &Lua) {
#[cfg(test)]
mod tests {
use super::*;
use tui::style::Color;
fn modifier(m: Modifier) -> Option<IndexSet<Modifier>> {
let mut x = IndexSet::new();

Loading…
Cancel
Save