Compare commits

...

50 Commits

Author SHA1 Message Date
Arijit Basu 68fb6fa1a2 Fix docs
Fixes: https://github.com/sayanarijit/xplr/issues/717
1 week ago
Arijit Basu a82ea6a3e5
Fix CI 1 week ago
Arijit Basu e13dd21728 Upgrade
Fixes: https://github.com/sayanarijit/xplr/issues/718
1 week ago
Arijit Basu 182a201b0d
Limit scroll padding 2 months ago
Arijit Basu e0b0466e42
v0.21.8 (#716)
- Added vim-like scrolling as the default scrolling method. Set
`xplr.config.general.paginated_scrolling = false` to disable ~ by
@ElSamhaa & @sayanarijit.
- Added `xplr.config.general.scroll_padding` config option to set the
padding in vim-like scrolling ~ by @ElSamhaa & @sayanarijit.
- Fixed some color rendering issues ~ by @har7an.
- Added feature flag so that xplr can be build with system Lua ~ by
@nekopsykose.
- Fixed `ScrollUpHalf` behavior.
- `xplr.util.lscolor()` won't return nil anymore.
- Arguments passed to the custom dynamic layout Lua function will
include `scrolltop` field.
- Fixed node_type resolution for directories with `.` in their name ~ by
@abhinavnatarajan.
- Dependency updates.
2 months ago
Arijit Basu 805e1594ed
Fix vim scrolling 2 months ago
Arijit Basu 41648ced34 Linting fixes 2 months ago
Arijit Basu 89d7bccce8 Update docs 2 months ago
Arijit Basu e15c1e8a8c
Lock ratatui 2 months ago
Arijit Basu 8afdf9e478
Fix node type resolution (#714)
Fixes #712 and #713.
2 months ago
Abhinav Natarajan a48dae008c Fix node type resolution
Fix node_type for directory with extension
2 months ago
Arijit Basu ad8afa9d38 Update deps 2 months ago
Arijit Basu c2a11059c8
Add yazi an alternative 2 months ago
Arijit Basu 6d7ccce282 Pass scrolltop in custom Lua function 2 months ago
Arijit Basu 90df0a2b5a vimlike_scrolling -> paginated_scrolling
Inspired by @ElSamhaa 's PR https://github.com/sayanarijit/xplr/pull/704
2 months ago
Arijit Basu ce52bcdf94 Revert vimlike scrolling
Use stateful ui widget.
2 months ago
Arijit Basu 6fb0781fe4 xplr.util.lscolor shouldn't return nil
Closes: https://github.com/sayanarijit/xplr/issues/705

Also update xplr version.
2 months ago
Arijit Basu c1bb251fef
Adds Vim-Like Scrolling to XPLR (#704)
- Added through a setting `vimlike_scrolling` which is turned off by
default
- A hard-coded _(for now)_ cushion of `5` lines that allows for
previewing the next lines while scrolling
- A separate struct `ScrollState` with getters and setters for the
`current_focus` field to disallow setting the field without updating the
`last_focus` field
3 months ago
Arijit Basu 976530ba70
Gen docs 3 months ago
Arijit Basu 96da7e1da8
Fix linting 3 months ago
Arijit Basu 96ffe8680b
Fix ScrollUpHalf 3 months ago
Ahmed ElSamhaa 1600ad9a9c Makes the preview cushion dynamic now, and sets an initial value 5 for it 3 months ago
Ahmed ElSamhaa 2a3d056bf1 Clarifies some comments 3 months ago
Ahmed ElSamhaa 91276f6871 Removes an unnecessary condition 3 months ago
Ahmed ElSamhaa 00bd54abe9 Removes unnecessary mut from the calc_skipped_rows fn 3 months ago
Ahmed ElSamhaa 95621af9eb Increases the preview_cushion to 5 like in vim 3 months ago
Ahmed ElSamhaa 5240b3904b Rolls back changes to the open terminal file 3 months ago
Ahmed ElSamhaa a6fb695ff9 Refactors the calc_skipped_rows function to make it even more readable 3 months ago
Ahmed ElSamhaa fd40de26e7 Adds tests for the ScrollState calc_skipped_rows fn 3 months ago
Ahmed ElSamhaa 87805509c5 Refactors the calc_skipped_rows function to make it more readable 3 months ago
Ahmed ElSamhaa 4aa367ca7c Makes the current_focus field private to limit usage to its setters and getters 3 months ago
Ahmed ElSamhaa 01606e0e60 Adds corresponding config setting for vimlike_scrolling 3 months ago
Ahmed ElSamhaa e834242f5d Adds vim-like scrolling 3 months ago
alice 7c6dffc2c6
cargo: allow building with system lua (#703)
useful for distros
3 months ago
har7an d5217f6677
cargo: Revert version update on `ansi-to-tui` (#702)
which causes custom styling to be lost on the currently selected line.
3 months ago
Arijit Basu 0285f0824c
Disable snap build 3 months ago
Arijit Basu a6b19425ae
Release v0.21.6 (#701)
- Snap build
- xplr.util.debug()
- `c` and `m` key bindings for quick copy and move.
- ScrollUpHalf fix
- Dependency updates
3 months ago
Arijit Basu 9db8b2cc19
Upgrade dependencies (#700) 3 months ago
mikoloism 68500f3a8e
[Feat] support `snapcraft` build package to releases (#697)
* build(snap): support snapcraft package

- add `snap/snapcraft.yaml` file

NOTE: under `devmode` until fit to release

* ci(gh-action): support `snapcraft` build and publish to `gh-release` page
4 months ago
Arijit Basu ded2e108bf
Add xplr.util.debug
Also update version
5 months ago
Arijit Basu 6e8f3da971
Quick copy and quick move (#692)
* Quick copy and quick move

- Press `c` to quickly copy the focused or selected path
- Press `m` to quickly move the focused or selected path
5 months ago
Arijit Basu d76a70fed4
Fix ScrollUpHalf 5 months ago
Arijit Basu 16673963aa
Minor fix 5 months ago
Arijit Basu b0ef9a5190
Remove unnecessary config example for now 5 months ago
Arijit Basu b70337708c
Minor fix 5 months ago
Arijit Basu 9127d15494
Use tree-view as example 5 months ago
Arijit Basu 66d9f7e586
Minor doc fix 5 months ago
Arijit Basu eab47a9044
Fix nixpkgs link 5 months ago
Arijit Basu a9e3752f56
Minor doc fix 5 months ago
Arijit Basu 470bea1265
NixOS install instructions 5 months ago

@ -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,9 +113,15 @@ 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 }}
# - name: Cleaning snapcraft
# run: |
# command rm --verbose ./*.snap
# snapcraft clean
publish-gpg-signature: publish-gpg-signature:
name: Publishing GPG signature name: Publishing GPG signature
runs-on: ubuntu-latest runs-on: ubuntu-latest

3
.gitignore vendored

@ -20,3 +20,6 @@ book/
# nix # nix
result result
# test files
/init.lua

@ -5,7 +5,7 @@
We as members, contributors, and leaders pledge to make participation in our We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status, identity and expression, level of experience, education, socioeconomic status,
nationality, personal appearance, race, religion, or sexual identity nationality, personal appearance, race, religion, or sexual identity
and orientation. and orientation.
@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our Examples of behavior that contributes to a positive environment for our
community include: community include:
* Demonstrating empathy and kindness toward other people - Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences - Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback - Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes, - Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience and learning from the experience
* Focusing on what is best not just for us as individuals, but for the - Focusing on what is best not just for us as individuals, but for the
overall community overall community
Examples of unacceptable behavior include: Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or - The use of sexualized language or imagery, and sexual attention or
advances of any kind advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks - Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment - Public or private harassment
* Publishing others' private information, such as a physical or email - Publishing others' private information, such as a physical or email
address, without their explicit permission address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a - Other conduct which could reasonably be considered inappropriate in a
professional setting professional setting
## Enforcement Responsibilities ## Enforcement Responsibilities
@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
### 4. Permanent Ban ### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community **Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals. individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within **Consequence**: A permanent ban from any sort of public interaction within

600
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.9'
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.155"
humansize = "2.1.3" humansize = "2.1.3"
natord = "1.0.9" natord = "1.0.9"
anyhow = "1.0.79" anyhow = "1.0.86"
serde_yaml = "0.9.30" serde_yaml = "0.9.34"
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 = "=3.1.0"
regex = "1.10.2" regex = "1.10.5"
gethostname = "0.4.3" gethostname = "0.4.3"
serde_json = "1.0.110" serde_json = "1.0.117"
path-absolutize = "3.1.1" path-absolutize = "3.1.1"
which = "5.0.0" which = "6.0.1"
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.36", 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,23 +57,23 @@ version = "2.0.4"
default-features = false default-features = false
[dependencies.tui] [dependencies.tui]
version = "0.25.0" version = "=0.26.1" # https://github.com/ratatui-org/ratatui/issues/1032
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.203"
features = [] features = []
default-features = false default-features = false
[dependencies.indexmap] [dependencies.indexmap]
version = "2.1.0" version = "2.2.6"
features = ['serde'] features = ['serde']
[dependencies.mlua] [dependencies.mlua]
version = "0.9.2" version = "0.9.8"
features = ['luajit', 'vendored', 'serialize', 'send'] features = ['luajit', 'serialize', 'send']
[dependencies.tui-input] [dependencies.tui-input]
version = "0.8.0" version = "0.8.0"
@ -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
@ -90,5 +90,5 @@ panic = 'abort'
strip = true strip = true
[features] [features]
default = ["vendored-lua"]
vendored-lua = ["mlua/vendored"]

@ -12,18 +12,6 @@ A hackable, minimal, fast TUI file explorer
<img src="https://img.shields.io/crates/v/xplr.svg" /> <img src="https://img.shields.io/crates/v/xplr.svg" />
</a> </a>
<a href="https://github.com/sayanarijit/xplr/commits">
<img src="https://img.shields.io/github/commit-activity/m/sayanarijit/xplr" />
</a>
<a href="https://matrix.to/#/#xplr-pub:matrix.org">
<img alt="Matrix" src="https://img.shields.io/matrix/xplr-pub:matrix.org?color=0DB787&label=matrix&logo=Matrix">
</a>
<a href="https://discord.gg/JmasSPCcz3">
<img src="https://img.shields.io/discord/834369918312382485?color=5865F2&label=discord&logo=Discord" />
</a>
</p> </p>
<p align="center"> <p align="center">
@ -38,7 +26,6 @@ https://user-images.githubusercontent.com/11632726/166747867-8a4573f2-cb2f-43a6-
[<a href="https://xplr.dev/en/awesome-hacks">Hacks</a>] [<a href="https://xplr.dev/en/awesome-hacks">Hacks</a>]
[<a href="https://xplr.dev/en/awesome-plugins">Plugins</a>] [<a href="https://xplr.dev/en/awesome-plugins">Plugins</a>]
[<a href="https://xplr.dev/en/awesome-integrations">Integrations</a>] [<a href="https://xplr.dev/en/awesome-integrations">Integrations</a>]
[<a href="https://xplr.dev/en/community">Community</a>]
</h3> </h3>
xplr is a terminal UI based file explorer that aims to increase our terminal xplr is a terminal UI based file explorer that aims to increase our terminal
@ -60,7 +47,7 @@ integration][15], enabling you to achieve insane terminal productivity.
- [[Article] What is a TUI file explorer & why would you need one? ~ xplr.stck.me](https://xplr.stck.me/post/25252/What-is-a-TUI-file-explorer-why-would-you-need-one) - [[Article] What is a TUI file explorer & why would you need one? ~ xplr.stck.me](https://xplr.stck.me/post/25252/What-is-a-TUI-file-explorer-why-would-you-need-one)
- [[Article] FOSSPicks - Linux Magazine](https://www.linux-magazine.com/Issues/2022/258/FOSSPicks/(offset)/6) - [[Article] FOSSPicks - Linux Magazine](<https://www.linux-magazine.com/Issues/2022/258/FOSSPicks/(offset)/6>)
## Packaging ## Packaging

@ -3,8 +3,8 @@
See [install.md](./docs/en/src/install.md#build-from-source) See [install.md](./docs/en/src/install.md#build-from-source)
Note: xplr ships with vendored luajit. If the platform can't compile this, Note: xplr ships with vendored luajit. If the platform can't compile this,
you need to grep out the feature "vendored" from the "mlua" dependency you need to compile using `--no-default-features` argument to avoid using
specified in [Cargo.toml](./Cargo.toml), and static link luajit yourself. vendored luajit, so that you can static link luajit yourself.
# Release # Release

@ -98,6 +98,7 @@ fn draw_benchmark(c: &mut Criterion) {
}); });
let lua = mlua::Lua::new(); let lua = mlua::Lua::new();
let mut ui = ui::UI::new(&lua);
let mut app = let mut app =
app::App::create("xplr".into(), None, PWD.into(), &lua, None, [].into()) app::App::create("xplr".into(), None, PWD.into(), &lua, None, [].into())
.expect("failed to create app"); .expect("failed to create app");
@ -121,7 +122,7 @@ fn draw_benchmark(c: &mut Criterion) {
c.bench_function("draw on terminal", |b| { c.bench_function("draw on terminal", |b| {
b.iter(|| { b.iter(|| {
terminal.draw(|f| ui::draw(f, &app, &lua)).unwrap(); terminal.draw(|f| ui.draw(f, &app)).unwrap();
}) })
}); });

@ -39,8 +39,6 @@
- [Awesome Integrations][20] - [Awesome Integrations][20]
- [Alternatives][22] - [Alternatives][22]
- [Upgrade Guide][23] - [Upgrade Guide][23]
- [Community][24]
- [Contribute][25]
[1]: introduction.md [1]: introduction.md
[2]: quickstart.md [2]: quickstart.md
@ -64,8 +62,6 @@
[20]: awesome-integrations.md [20]: awesome-integrations.md
[22]: alternatives.md [22]: alternatives.md
[23]: upgrade-guide.md [23]: upgrade-guide.md
[24]: community.md
[25]: contribute.md
[26]: column-renderer.md [26]: column-renderer.md
[27]: key-bindings.md [27]: key-bindings.md
[28]: configure-key-bindings.md [28]: configure-key-bindings.md

@ -15,6 +15,7 @@ These are the alternative TUI/CLI file managers/explorers you might want to chec
- [clifm][11] - [clifm][11]
- [clifm][12] (non curses) - [clifm][12] (non curses)
- [felix][14] - [felix][14]
- [yazi][15]
[add more][13] [add more][13]
@ -30,5 +31,6 @@ These are the alternative TUI/CLI file managers/explorers you might want to chec
[10]: https://git.2f30.org/noice/ [10]: https://git.2f30.org/noice/
[11]: https://github.com/pasqu4le/clifm [11]: https://github.com/pasqu4le/clifm
[12]: https://github.com/leo-arch/clifm [12]: https://github.com/leo-arch/clifm
[13]: community.md [13]: https://github.com/sayanarijit/xplr/edit/dev/docs/en/src/alternatives.md
[14]: https://github.com/kyoheiu/felix [14]: https://github.com/kyoheiu/felix
[15]: https://github.com/sxyazi/yazi

@ -6,7 +6,7 @@ too small or too niche for a full fledge [plugin][2].
Do you have something cool to share? Do you have something cool to share?
[Edit this file][3] or [share them here][4] or [let us know][5]. [Edit this file][3] or [share them here][4].
You can try these hacks by writing them to a file, say `hack.lua` and passing You can try these hacks by writing them to a file, say `hack.lua` and passing
it to xplr with `--extra-config` or `-C`. it to xplr with `--extra-config` or `-C`.
@ -526,7 +526,6 @@ xplr.config.modes.builtin.default.key_bindings.on_key.T = {
[2]: plugin.md [2]: plugin.md
[3]: https://github.com/sayanarijit/xplr/edit/main/docs/en/src/awesome-hacks.md [3]: https://github.com/sayanarijit/xplr/edit/main/docs/en/src/awesome-hacks.md
[4]: https://github.com/sayanarijit/xplr/discussions/categories/show-and-tell [4]: https://github.com/sayanarijit/xplr/discussions/categories/show-and-tell
[5]: community.md
[6]: https://gifyu.com/image/rGSR [6]: https://gifyu.com/image/rGSR
[7]: https://s4.gifyu.com/images/xplr-bookmark.gif [7]: https://s4.gifyu.com/images/xplr-bookmark.gif
[8]: https://github.com/sayanarijit [8]: https://github.com/sayanarijit

@ -1,12 +0,0 @@
# Community
Building an active community of awesome people and learning stuff together is
one of my reasons to publish this tool and maintain it. Hence, please feel free
to reach out via your preferred way.
- Real-time chat lovers can join our [**matrix room**][3] or [**discord channel**][1].
- Forum discussion veterans can [**start a new GitHub discussion**][2].
[1]: https://discord.gg/JmasSPCcz3
[2]: https://github.com/sayanarijit/xplr/discussions
[3]: https://matrix.to/#/#xplr-pub:matrix.org

@ -1,32 +0,0 @@
If you like xplr, and want to contribute, that would be really awesome.
You can contribute to this project in the following ways
- Contribute your time and expertise (read [CONTRIBUTING.md][1] for instructions).
- **Developers:** You can help me improve my code, fix things, implement features etc.
- **Repository maintainers:** You can save the users from the pain of managing xplr in their system manually.
- **Code Reviewers:** Teach me your ways of code.
- **Designers:** You can make the logo even more awesome, donate stickers and blog post worthy pictures.
- **Bloggers, YouTubers & broadcasters:** You can help spread the word.
- Contribute by donating or sponsoring me via any of the following ways.
- [GitHub Sponsors][5]
- [Open Collective][2]
- [ko-fi][3]
- [liberapay][6]
- [PayPal][7]
For further queries or concern related to `xplr`, [just ask us][4].
### Backers
<a href="https://opencollective.com/xplr#backer"><img src="https://opencollective.com/xplr/tiers/backer.svg?width=890" /></a>
[1]: https://github.com/sayanarijit/xplr/blob/main/CONTRIBUTING.md
[2]: https://opencollective.com/xplr
[3]: https://ko-fi.com/sayanarijit
[4]: community.md
[5]: https://github.com/sponsors/sayanarijit?o=esb
[6]: https://liberapay.com/sayanarijit
[7]: https://paypal.me/sayanarijit

@ -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 |
| ----- | ------ | ---------------- | | ----- | ------ | ---------------- |

@ -42,6 +42,19 @@ Set it to `true` if you want to hide all remaps in the help menu.
Type: boolean Type: boolean
#### xplr.config.general.paginated_scrolling
Set it to `true` if you want paginated scrolling.
Type: boolean
#### xplr.config.general.scroll_padding
Set the padding value to the scroll area.
Only applicable when `xplr.config.general.paginated_scrolling = false`.
Type: boolean
#### xplr.config.general.enforce_bounded_index_navigation #### xplr.config.general.enforce_bounded_index_navigation
Set it to `true` if you want the cursor to stay in the same position when Set it to `true` if you want the cursor to stay in the same position when

@ -41,6 +41,48 @@ repositories:
nix-env -f https://github.com/NixOS/nixpkgs/tarball/master -iA xplr nix-env -f https://github.com/NixOS/nixpkgs/tarball/master -iA xplr
``` ```
Or
```nix
# configuration.nix or darwin-configuration.nix
environment.systemPackages = with nixpkgs; [
xplr
# ...
];
```
#### [Home Manager][30]
```nix
# home.nix
home.packages = with nixpkgs; [
xplr
# ...
];
```
Or
```nix
# home.nix
programs.xplr = {
enable = true;
# Optional params:
plugins = {
tree-view = fetchFromGitHub {
owner = "sayanarijit";
repo = "tree-view.xplr";
};
local-plugin = "/home/user/.config/xplr/plugins/local-plugin";
};
extraConfig = ''
require("tree-view").setup()
require("local-plugin").setup()
'';
};
```
### Arch Linux ### Arch Linux
(same for Manjaro Linux) (same for Manjaro Linux)
@ -196,23 +238,6 @@ cargo build --locked --release --bin xplr
sudo cp target/release/xplr /usr/local/bin/ sudo cp target/release/xplr /usr/local/bin/
``` ```
## Android
### [Termux][24]
```bash
pkg install rust make binutils
cargo install --locked xplr
# Run
~/.cargo/bin/xplr
```
> Please note that xplr isn't heavily tested on Termux, hence things might need
> a little tweaking and fixing for a smooth user experience.
![termux demo][23]
[1]: #direct-download [1]: #direct-download
[2]: #from-cratesio [2]: #from-cratesio
[3]: #build-from-source [3]: #build-from-source
@ -222,7 +247,7 @@ cargo install --locked xplr
[7]: https://archlinux.org/packages/extra/x86_64/xplr [7]: https://archlinux.org/packages/extra/x86_64/xplr
[8]: https://aur.archlinux.org/packages/?O=0&SeB=n&K=xplr&outdated=&SB=n&SO=a&PP=50&do_Search=Go [8]: https://aur.archlinux.org/packages/?O=0&SeB=n&K=xplr&outdated=&SB=n&SO=a&PP=50&do_Search=Go
[9]: https://github.com/shubham-cpp/void-pkg-templates [9]: https://github.com/shubham-cpp/void-pkg-templates
[10]: https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/file-managers/xplr/default.nix [10]: https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/xp/xplr/package.nix
[11]: https://ports.macports.org/port/xplr [11]: https://ports.macports.org/port/xplr
[12]: https://formulae.brew.sh/formula/xplr [12]: https://formulae.brew.sh/formula/xplr
[13]: https://cgit.freebsd.org/ports/plain/misc/xplr/ [13]: https://cgit.freebsd.org/ports/plain/misc/xplr/
@ -236,9 +261,9 @@ cargo install --locked xplr
[21]: https://www.gnu.org/software/make/ [21]: https://www.gnu.org/software/make/
[22]: https://git-scm.com/ [22]: https://git-scm.com/
[23]: https://github.com/sayanarijit/xplr/assets/11632726/3b61e8c8-76f0-48e8-8734-50e9e7e495b7 [23]: https://github.com/sayanarijit/xplr/assets/11632726/3b61e8c8-76f0-48e8-8734-50e9e7e495b7
[24]: https://termux.dev/
[25]: https://gifyu.com/image/tF2D [25]: https://gifyu.com/image/tF2D
[26]: https://github.com/sayanarijit/xplr/releases/latest/download/xplr-linux-musl.tar.gz [26]: https://github.com/sayanarijit/xplr/releases/latest/download/xplr-linux-musl.tar.gz
[27]: https://pkgs.alpinelinux.org/packages?name=xplr [27]: https://pkgs.alpinelinux.org/packages?name=xplr
[28]: https://gpo.zugaina.org/Overlays/guru/app-misc/xplr [28]: https://gpo.zugaina.org/Overlays/guru/app-misc/xplr
[29]: https://formulae.brew.sh/formula/coreutils [29]: https://formulae.brew.sh/formula/coreutils
[30]: https://github.com/nix-community/home-manager/blob/master/modules/programs/xplr.nix

@ -84,8 +84,6 @@ Some of the coolest features xplr provide beside the basic stuff:
(`:` `q` `s`). (`:` `q` `s`).
- Quit with failure (`ctrl-c`). - Quit with failure (`ctrl-c`).
**Q.** What features should be added here? [let us know][20].
[1]: layouts.md [1]: layouts.md
[2]: configure-key-bindings.md [2]: configure-key-bindings.md
[3]: awesome-plugins.md [3]: awesome-plugins.md
@ -105,6 +103,5 @@ Some of the coolest features xplr provide beside the basic stuff:
[17]: node_types.md [17]: node_types.md
[18]: https://github.com/sayanarijit/xplr/blob/main/src/init.lua [18]: https://github.com/sayanarijit/xplr/blob/main/src/init.lua
[19]: messages.md#startfifo [19]: messages.md#startfifo
[20]: community.md
[21]: messages.md#virtual-root [21]: messages.md#virtual-root
[22]: configuration.md#hooks [22]: configuration.md#hooks

@ -495,6 +495,7 @@ It contains the following information:
- [layout_size][37] - [layout_size][37]
- [screen_size][37] - [screen_size][37]
- [scrolltop][57]
- [app][38] - [app][38]
### Size ### Size
@ -508,6 +509,12 @@ It contains the following information:
Every field is of integer type. Every field is of integer type.
### scrolltop
Type: integer
The start index of the visible nodes in the table.
### app ### app
This is a lightweight version of the [Lua Context][39]. In this context, the This is a lightweight version of the [Lua Context][39]. In this context, the
@ -587,3 +594,4 @@ Hence, only the following fields are available.
[54]: borders.md#border-type [54]: borders.md#border-type
[55]: #customlayout [55]: #customlayout
[56]: sum-type.md [56]: sum-type.md
[57]: #scrolltop

@ -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.

@ -90,13 +90,7 @@ And then we'd go on documenting whatever `Layout Config` is.
So, there you go. This is exactly what sum types are - glorified enums that can So, there you go. This is exactly what sum types are - glorified enums that can
have nested types in each branch. have nested types in each branch.
---
If you're still confused about something, or if you found an error in this
explanation, feel free to [discuss together][5].
[1]: https://en.wikipedia.org/wiki/Tagged_union [1]: https://en.wikipedia.org/wiki/Tagged_union
[2]: layout.md [2]: layout.md
[3]: message.md [3]: message.md
[4]: style.md#color [4]: style.md#color
[5]: community.md

@ -45,7 +45,7 @@ compatibility.
### Instructions ### Instructions
#### [v0.20.2][48] -> [v0.21.5][49] #### [v0.20.2][48] -> [v0.21.9][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,17 @@ 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.
- Since v0.21.8:
- Scroll behavior will default to vim-like continuous scrolling. You can set
`xplr.config.general.paginated_scrolling = true` to revert back to the
paginated scrolling.
- Set `xplr.config.general.scroll_padding` to customize the scroll padding.
- The calculated `scrolltop` value will be passed as part of the
`Content Rendeder Argument` in `Dynamic` layout renderer functions.
Thanks to @noahmayr for contributing to a major part of this release. Thanks to @noahmayr for contributing to a major part of this release.
@ -210,8 +221,6 @@ Thanks to @noahmayr for contributing to a major part of this release.
- ScrollUpHalf ---- { - ScrollUpHalf ---- {
- ScrollDownHalf -- } - ScrollDownHalf -- }
<sub>Like this project so far? **[Please consider contributing][5]**.</sub>
#### [v0.17.6][45] -> [v0.18.0][46] #### [v0.17.6][45] -> [v0.18.0][46]
- Key binding `f` `r` and `f` `R` will now filter using regex. - Key binding `f` `r` and `f` `R` will now filter using regex.
@ -477,7 +486,6 @@ Else do the following:
[2]: https://github.com/sayanarijit/xplr/releases/tag/v0.13.7 [2]: https://github.com/sayanarijit/xplr/releases/tag/v0.13.7
[3]: https://github.com/sayanarijit/xplr/releases/tag/v0.14.7 [3]: https://github.com/sayanarijit/xplr/releases/tag/v0.14.7
[4]: https://github.com/sayanarijit/xplr/pull/229#issue-662426960 [4]: https://github.com/sayanarijit/xplr/pull/229#issue-662426960
[5]: contribute.md
[6]: https://github.com/sayanarijit/xplr/releases/tag/v0.12.1 [6]: https://github.com/sayanarijit/xplr/releases/tag/v0.12.1
[7]: https://docs.rs/xplr/latest/xplr/app/enum.ExternalMsg.html#variant.CallLua [7]: https://docs.rs/xplr/latest/xplr/app/enum.ExternalMsg.html#variant.CallLua
[8]: https://docs.rs/xplr/latest/xplr/app/enum.ExternalMsg.html#variant.CallLuaSilently [8]: https://docs.rs/xplr/latest/xplr/app/enum.ExternalMsg.html#variant.CallLuaSilently
@ -521,5 +529,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.9
[50]: https://github.com/lotabout/skim#search-syntax [50]: https://github.com/lotabout/skim#search-syntax

@ -69,9 +69,7 @@ Visit [Awesome Plugins][5] for xplr plugin examples.
## Also See ## Also See
- [Tip: A list of hacks yet to make it as Lua plugins][15] - [Tip: A list of hacks yet to make it as Lua plugins][15]
- [Tip: Some UI and themeing tips][12] - [Tip: Some UI and theming tips][12]
- [Tip: A list of handy utility functions][13]
- [Tip: Share tips and tricks working with Lua][14]
- [Tutorial: Adding a New Mode][6] - [Tutorial: Adding a New Mode][6]
- [Example: Using Environment Variables and Pipes][7] - [Example: Using Environment Variables and Pipes][7]
- [Example: Using Lua Function Calls][8] - [Example: Using Lua Function Calls][8]
@ -92,7 +90,5 @@ Visit [Awesome Plugins][5] for xplr plugin examples.
[10]: column-renderer.md#example-customizing-table-renderer [10]: column-renderer.md#example-customizing-table-renderer
[11]: layout.md#example-render-a-custom-dynamic-table [11]: layout.md#example-render-a-custom-dynamic-table
[12]: https://github.com/sayanarijit/xplr/discussions/274 [12]: https://github.com/sayanarijit/xplr/discussions/274
[13]: https://github.com/sayanarijit/xplr/discussions/273 [15]: awesome-hacks.md
[14]: https://github.com/sayanarijit/xplr/discussions/250
[15]: https://github.com/sayanarijit/xplr/wiki/Hacks
[16]: https://github.com/sayanarijit/xplr/discussions/529#discussioncomment-4073734 [16]: https://github.com/sayanarijit/xplr/discussions/529#discussioncomment-4073734

@ -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.
@ -380,7 +397,7 @@ xplr.util.to_yaml({ foo = "bar" })
Get a [Style][3] object for the given path based on the LS_COLORS Get a [Style][3] object for the given path based on the LS_COLORS
environment variable. environment variable.
Type: function( path:string ) -> [Style][3]|nil Type: function( path:string ) -> [Style][3]
Example: Example:

@ -1,11 +1,11 @@
<!DOCTYPE HTML> <!DOCTYPE HTML>
<html lang="{{ language }}" class="sidebar-visible no-js {{ default_theme }}"> <html lang="{{ language }}" class="{{ default_theme }}" dir="{{ text_direction }}">
<head> <head>
<!-- Book generated using mdBook --> <!-- Book generated using mdBook -->
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>{{ title }}</title> <title>{{ title }}</title>
{{#if is_print }} {{#if is_print }}
<meta name="robots" content="noindex" /> <meta name="robots" content="noindex">
{{/if}} {{/if}}
{{#if base_url}} {{#if base_url}}
<base href="{{ base_url }}"> <base href="{{ base_url }}">
@ -15,10 +15,9 @@
<!-- Custom HTML head --> <!-- Custom HTML head -->
{{> head}} {{> head}}
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="{{ description }}"> <meta name="description" content="{{ description }}">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" /> <meta name="theme-color" content="#ffffff">
{{#if favicon_svg}} {{#if favicon_svg}}
<link rel="icon" href="{{ path_to_root }}favicon.svg"> <link rel="icon" href="{{ path_to_root }}favicon.svg">
@ -51,21 +50,22 @@
{{#if mathjax_support}} {{#if mathjax_support}}
<!-- MathJax --> <!-- MathJax -->
<script async type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script> <script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
{{/if}} {{/if}}
<!-- EthicalAds --> <!-- EthicalAds -->
<script async src="https://media.ethicalads.io/media/client/ethicalads.min.js"></script> <script async src="https://media.ethicalads.io/media/client/ethicalads.min.js"></script>
</head> </head>
<body> <body class="sidebar-visible no-js">
<div id="body-container">
<!-- Provide site root to javascript --> <!-- Provide site root to javascript -->
<script type="text/javascript"> <script>
var path_to_root = "{{ path_to_root }}"; var path_to_root = "{{ path_to_root }}";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}"; var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
</script> </script>
<!-- Work around some values being stored in localStorage wrapped in quotes --> <!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript"> <script>
try { try {
var theme = localStorage.getItem('mdbook-theme'); var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar'); var sidebar = localStorage.getItem('mdbook-sidebar');
@ -81,32 +81,38 @@
</script> </script>
<!-- Set the theme before any content is loaded, prevents flash --> <!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript"> <script>
var theme; var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { } try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; } if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html'); var html = document.querySelector('html');
html.classList.remove('no-js')
html.classList.remove('{{ default_theme }}') html.classList.remove('{{ default_theme }}')
html.classList.add(theme); html.classList.add(theme);
html.classList.add('js'); var body = document.querySelector('body');
body.classList.remove('no-js')
body.classList.add('js');
</script> </script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed --> <!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript"> <script>
var html = document.querySelector('html'); var body = document.querySelector('body');
var sidebar = 'hidden'; var sidebar = null;
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) { if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { } try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible'; sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
} }
html.classList.remove('sidebar-visible'); sidebar_toggle.checked = sidebar === 'visible';
html.classList.add("sidebar-" + sidebar); body.classList.remove('sidebar-visible');
body.classList.add("sidebar-" + sidebar);
</script> </script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents"> <nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox"> <div class="sidebar-scrollbox">
{{#toc}}{{/toc}} {{#toc}}{{/toc}}
<!-- EthicalAds --> <!-- EthicalAds -->
@ -116,30 +122,53 @@
data-ea-publisher="xplrdev" data-ea-publisher="xplrdev"
data-ea-type="image" data-ea-type="image"
></div> ></div>
</div> </div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div> <div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav> </nav>
<!-- Track and set sidebar scroll position -->
<script>
var sidebarScrollbox = document.querySelector('#sidebar .sidebar-scrollbox');
sidebarScrollbox.addEventListener('click', function(e) {
if (e.target.tagName === 'A') {
sessionStorage.setItem('sidebar-scroll', sidebarScrollbox.scrollTop);
}
}, { passive: true });
var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
sessionStorage.removeItem('sidebar-scroll');
if (sidebarScrollTop) {
// preserve sidebar scroll position when navigating via links within sidebar
sidebarScrollbox.scrollTop = sidebarScrollTop;
} else {
// scroll sidebar to current active section when navigating via "next/previous chapter" buttons
var activeSection = document.querySelector('#sidebar .active');
if (activeSection) {
activeSection.scrollIntoView({ block: 'center' });
}
}
</script>
<div id="page-wrapper" class="page-wrapper"> <div id="page-wrapper" class="page-wrapper">
<div class="page"> <div class="page">
{{> header}} {{> header}}
<div id="menu-bar-hover-placeholder"></div> <div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky bordered"> <div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons"> <div class="left-buttons">
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar"> <label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i> <i class="fa fa-bars"></i>
</button> </label>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list"> <button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i> <i class="fa fa-paint-brush"></i>
</button> </button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu"> <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">{{ theme_option "Light" }}</button></li> <li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">{{ theme_option "Rust" }}</button></li> <li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">{{ theme_option "Coal" }}</button></li> <li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">{{ theme_option "Navy" }}</button></li> <li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">{{ theme_option "Ayu" }}</button></li> <li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul> </ul>
{{#if search_enabled}} {{#if search_enabled}}
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar"> <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
@ -184,7 +213,7 @@
{{/if}} {{/if}}
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM --> <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript"> <script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible'); document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible'); document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) { Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
@ -206,7 +235,7 @@
{{/previous}} {{/previous}}
{{#next}} {{#next}}
<a rel="next" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right"> <a rel="next prefetch" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i> <i class="fa fa-angle-right"></i>
</a> </a>
{{/next}} {{/next}}
@ -224,7 +253,7 @@
{{/previous}} {{/previous}}
{{#next}} {{#next}}
<a rel="next" href="{{ path_to_root }}{{link}}" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right"> <a rel="next prefetch" href="{{ path_to_root }}{{link}}" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i> <i class="fa fa-angle-right"></i>
</a> </a>
{{/next}} {{/next}}
@ -232,10 +261,12 @@
</div> </div>
{{#if livereload}} {{#if live_reload_endpoint}}
<!-- Livereload script (if served using the cli tool) --> <!-- Livereload script (if served using the cli tool) -->
<script type="text/javascript"> <script>
var socket = new WebSocket("{{{livereload}}}"); const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsAddress = wsProtocol + "//" + location.host + "/" + "{{{live_reload_endpoint}}}";
const socket = new WebSocket(wsAddress);
socket.onmessage = function (event) { socket.onmessage = function (event) {
if (event.data === "reload") { if (event.data === "reload") {
socket.close(); socket.close();
@ -251,7 +282,7 @@
{{#if google_analytics}} {{#if google_analytics}}
<!-- Google Analytics Tag --> <!-- Google Analytics Tag -->
<script type="text/javascript"> <script>
var localAddrs = ["localhost", "127.0.0.1", ""]; var localAddrs = ["localhost", "127.0.0.1", ""];
// make sure we don't activate google analytics if the developer is // make sure we don't activate google analytics if the developer is
@ -269,43 +300,43 @@
{{/if}} {{/if}}
{{#if playground_line_numbers}} {{#if playground_line_numbers}}
<script type="text/javascript"> <script>
window.playground_line_numbers = true; window.playground_line_numbers = true;
</script> </script>
{{/if}} {{/if}}
{{#if playground_copyable}} {{#if playground_copyable}}
<script type="text/javascript"> <script>
window.playground_copyable = true; window.playground_copyable = true;
</script> </script>
{{/if}} {{/if}}
{{#if playground_js}} {{#if playground_js}}
<script src="{{ path_to_root }}ace.js" type="text/javascript" charset="utf-8"></script> <script src="{{ path_to_root }}ace.js"></script>
<script src="{{ path_to_root }}editor.js" type="text/javascript" charset="utf-8"></script> <script src="{{ path_to_root }}editor.js"></script>
<script src="{{ path_to_root }}mode-rust.js" type="text/javascript" charset="utf-8"></script> <script src="{{ path_to_root }}mode-rust.js"></script>
<script src="{{ path_to_root }}theme-dawn.js" type="text/javascript" charset="utf-8"></script> <script src="{{ path_to_root }}theme-dawn.js"></script>
<script src="{{ path_to_root }}theme-tomorrow_night.js" type="text/javascript" charset="utf-8"></script> <script src="{{ path_to_root }}theme-tomorrow_night.js"></script>
{{/if}} {{/if}}
{{#if search_js}} {{#if search_js}}
<script src="{{ path_to_root }}elasticlunr.min.js" type="text/javascript" charset="utf-8"></script> <script src="{{ path_to_root }}elasticlunr.min.js"></script>
<script src="{{ path_to_root }}mark.min.js" type="text/javascript" charset="utf-8"></script> <script src="{{ path_to_root }}mark.min.js"></script>
<script src="{{ path_to_root }}searcher.js" type="text/javascript" charset="utf-8"></script> <script src="{{ path_to_root }}searcher.js"></script>
{{/if}} {{/if}}
<script src="{{ path_to_root }}clipboard.min.js" type="text/javascript" charset="utf-8"></script> <script src="{{ path_to_root }}clipboard.min.js"></script>
<script src="{{ path_to_root }}highlight.js" type="text/javascript" charset="utf-8"></script> <script src="{{ path_to_root }}highlight.js"></script>
<script src="{{ path_to_root }}book.js" type="text/javascript" charset="utf-8"></script> <script src="{{ path_to_root }}book.js"></script>
<!-- Custom JS scripts --> <!-- Custom JS scripts -->
{{#each additional_js}} {{#each additional_js}}
<script type="text/javascript" src="{{ ../path_to_root }}{{this}}"></script> <script src="{{ ../path_to_root }}{{this}}"></script>
{{/each}} {{/each}}
{{#if is_print}} {{#if is_print}}
{{#if mathjax_support}} {{#if mathjax_support}}
<script type="text/javascript"> <script>
window.addEventListener('load', function() { window.addEventListener('load', function() {
MathJax.Hub.Register.StartupHook('End', function() { MathJax.Hub.Register.StartupHook('End', function() {
window.setTimeout(window.print, 100); window.setTimeout(window.print, 100);
@ -313,7 +344,7 @@
}); });
</script> </script>
{{else}} {{else}}
<script type="text/javascript"> <script>
window.addEventListener('load', function() { window.addEventListener('load', function() {
window.setTimeout(window.print, 100); window.setTimeout(window.print, 100);
}); });
@ -321,5 +352,6 @@
{{/if}} {{/if}}
{{/if}} {{/if}}
</div>
</body> </body>
</html> </html>

@ -0,0 +1,11 @@
v="0.4.40"
curl -L https://github.com/rust-lang/mdBook/releases/download/v$v/mdbook-v$v-x86_64-unknown-linux-gnu.tar.gz -o mdbook.tgz \
&& tar xzvf mdbook.tgz \
&& ./mdbook build docs/en \
&& mkdir dist \
&& mv -v docs/en/book/html dist/en \
&& mv -v assets dist \
&& mv -v docs/landing/index.html docs/landing/css docs/landing/js dist \
&& rm -v mdbook \
&& rm -v mdbook.tgz

@ -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

@ -335,12 +335,12 @@ impl App {
&config &config
.general .general
.initial_mode .initial_mode
.to_owned() .clone()
.unwrap_or_else(|| "default".into()), .unwrap_or_else(|| "default".into()),
) { ) {
Some(m) => m.clone().sanitized( Some(m) => m.clone().sanitized(
config.general.read_only, config.general.read_only,
config.general.global_key_bindings.to_owned(), config.general.global_key_bindings.clone(),
), ),
None => { None => {
bail!("'default' mode is missing") bail!("'default' mode is missing")
@ -351,7 +351,7 @@ impl App {
&config &config
.general .general
.initial_layout .initial_layout
.to_owned() .clone()
.unwrap_or_else(|| "default".into()), .unwrap_or_else(|| "default".into()),
) { ) {
Some(l) => l.clone(), Some(l) => l.clone(),
@ -387,7 +387,7 @@ impl App {
} }
if let Some(sorters) = &config.general.initial_sorting { if let Some(sorters) = &config.general.initial_sorting {
explorer_config.sorters = sorters.clone(); explorer_config.sorters.clone_from(sorters);
}; };
let hostname = gethostname().to_string_lossy().to_string(); let hostname = gethostname().to_string_lossy().to_string();
@ -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,
@ -814,7 +820,6 @@ impl App {
fn focus_previous(mut self) -> Result<Self> { fn focus_previous(mut self) -> Result<Self> {
let bounded = self.config.general.enforce_bounded_index_navigation; let bounded = self.config.general.enforce_bounded_index_navigation;
if let Some(dir) = self.directory_buffer_mut() { if let Some(dir) = self.directory_buffer_mut() {
dir.focus = if dir.focus == 0 { dir.focus = if dir.focus == 0 {
if bounded { if bounded {
@ -899,7 +904,6 @@ impl App {
fn focus_next(mut self) -> Result<Self> { fn focus_next(mut self) -> Result<Self> {
let bounded = self.config.general.enforce_bounded_index_navigation; let bounded = self.config.general.enforce_bounded_index_navigation;
if let Some(dir) = self.directory_buffer_mut() { if let Some(dir) = self.directory_buffer_mut() {
dir.focus = if (dir.focus + 1) == dir.total { dir.focus = if (dir.focus + 1) == dir.total {
if bounded { if bounded {
@ -911,6 +915,7 @@ impl App {
dir.focus + 1 dir.focus + 1
} }
}; };
Ok(self) Ok(self)
} }
@ -989,7 +994,7 @@ impl App {
fn follow_symlink(self) -> Result<Self> { fn follow_symlink(self) -> Result<Self> {
if let Some(pth) = self if let Some(pth) = self
.focused_node() .focused_node()
.and_then(|n| n.symlink.to_owned().map(|s| s.absolute_path)) .and_then(|n| n.symlink.clone().map(|s| s.absolute_path))
{ {
self.focus_path(&pth, true) self.focus_path(&pth, true)
} else { } else {
@ -1295,7 +1300,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)
} }
@ -1376,7 +1381,7 @@ impl App {
self = self.push_mode(); self = self.push_mode();
self.mode = mode.sanitized( self.mode = mode.sanitized(
self.config.general.read_only, self.config.general.read_only,
self.config.general.global_key_bindings.to_owned(), self.config.general.global_key_bindings.clone(),
); );
// Hooks // Hooks
@ -1401,7 +1406,7 @@ impl App {
self = self.push_mode(); self = self.push_mode();
self.mode = mode.sanitized( self.mode = mode.sanitized(
self.config.general.read_only, self.config.general.read_only,
self.config.general.global_key_bindings.to_owned(), self.config.general.global_key_bindings.clone(),
); );
// Hooks // Hooks
@ -1428,7 +1433,7 @@ impl App {
fn switch_layout_builtin(mut self, layout: &str) -> Result<Self> { fn switch_layout_builtin(mut self, layout: &str) -> Result<Self> {
if let Some(l) = self.config.layouts.builtin.get(layout) { if let Some(l) = self.config.layouts.builtin.get(layout) {
self.layout = l.to_owned(); self.layout = l.clone();
// Hooks // Hooks
if !self.hooks.on_layout_switch.is_empty() { if !self.hooks.on_layout_switch.is_empty() {
@ -1444,7 +1449,7 @@ impl App {
fn switch_layout_custom(mut self, layout: &str) -> Result<Self> { fn switch_layout_custom(mut self, layout: &str) -> Result<Self> {
if let Some(l) = self.config.layouts.get_custom(layout) { if let Some(l) = self.config.layouts.get_custom(layout) {
self.layout = l.to_owned(); self.layout = l.clone();
// Hooks // Hooks
if !self.hooks.on_layout_switch.is_empty() { if !self.hooks.on_layout_switch.is_empty() {
@ -1569,7 +1574,7 @@ impl App {
pub fn select(mut self) -> Result<Self> { pub fn select(mut self) -> Result<Self> {
let count = self.selection.len(); let count = self.selection.len();
if let Some(n) = self.focused_node().map(|n| n.to_owned()) { if let Some(n) = self.focused_node().cloned() {
self.selection.insert(n); self.selection.insert(n);
} }
@ -1624,7 +1629,7 @@ impl App {
pub fn un_select(mut self) -> Result<Self> { pub fn un_select(mut self) -> Result<Self> {
let count = self.selection.len(); let count = self.selection.len();
if let Some(n) = self.focused_node().map(|n| n.to_owned()) { if let Some(n) = self.focused_node().cloned() {
self.selection self.selection
.retain(|s| s.absolute_path != n.absolute_path); .retain(|s| s.absolute_path != n.absolute_path);
} }
@ -1798,7 +1803,7 @@ impl App {
.config .config
.general .general
.initial_sorting .initial_sorting
.to_owned() .clone()
.unwrap_or_default(); .unwrap_or_default();
Ok(self) Ok(self)
} }

@ -7,7 +7,7 @@ use crate::ui::block;
use crate::ui::string_to_text; use crate::ui::string_to_text;
use crate::ui::Constraint; use crate::ui::Constraint;
use crate::ui::ContentRendererArg; use crate::ui::ContentRendererArg;
use mlua::Lua; use crate::ui::UI;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tui::layout::Constraint as TuiConstraint; use tui::layout::Constraint as TuiConstraint;
use tui::layout::Rect as TuiRect; use tui::layout::Rect as TuiRect;
@ -60,12 +60,11 @@ pub struct CustomContent {
/// A cursed function from crate::ui. /// A cursed function from crate::ui.
pub fn draw_custom_content( pub fn draw_custom_content(
ui: &mut UI,
f: &mut Frame, f: &mut Frame,
screen_size: TuiRect,
layout_size: TuiRect, layout_size: TuiRect,
app: &app::App, app: &app::App,
content: CustomContent, content: CustomContent,
lua: &Lua,
) { ) {
let config = app.config.general.panel_ui.default.clone(); let config = app.config.general.panel_ui.default.clone();
let title = content.title; let title = content.title;
@ -85,12 +84,13 @@ pub fn draw_custom_content(
let ctx = ContentRendererArg { let ctx = ContentRendererArg {
app: app.to_lua_ctx_light(), app: app.to_lua_ctx_light(),
layout_size: layout_size.into(), layout_size: layout_size.into(),
screen_size: screen_size.into(), screen_size: ui.screen_size.into(),
scrolltop: ui.scrolltop as u16,
}; };
let render = lua::serialize(lua, &ctx) let render = lua::serialize(ui.lua, &ctx)
.map(|arg| { .map(|arg| {
lua::call(lua, &render, arg).unwrap_or_else(|e| format!("{e:?}")) lua::call(ui.lua, &render, arg).unwrap_or_else(|e| format!("{e:?}"))
}) })
.unwrap_or_else(|e| e.to_string()); .unwrap_or_else(|e| e.to_string());
@ -121,12 +121,13 @@ pub fn draw_custom_content(
let ctx = ContentRendererArg { let ctx = ContentRendererArg {
app: app.to_lua_ctx_light(), app: app.to_lua_ctx_light(),
layout_size: layout_size.into(), layout_size: layout_size.into(),
screen_size: screen_size.into(), screen_size: ui.screen_size.into(),
scrolltop: ui.scrolltop as u16,
}; };
let items = lua::serialize(lua, &ctx) let items = lua::serialize(ui.lua, &ctx)
.map(|arg| { .map(|arg| {
lua::call(lua, &render, arg) lua::call(ui.lua, &render, arg)
.unwrap_or_else(|e| vec![format!("{e:?}")]) .unwrap_or_else(|e| vec![format!("{e:?}")])
}) })
.unwrap_or_else(|e| vec![e.to_string()]) .unwrap_or_else(|e| vec![e.to_string()])
@ -161,7 +162,7 @@ pub fn draw_custom_content(
let widths = widths let widths = widths
.into_iter() .into_iter()
.map(|w| w.to_tui(screen_size, layout_size)) .map(|w| w.to_tui(ui.screen_size, layout_size))
.collect::<Vec<TuiConstraint>>(); .collect::<Vec<TuiConstraint>>();
let content = Table::new(rows, widths) let content = Table::new(rows, widths)
@ -182,12 +183,13 @@ pub fn draw_custom_content(
let ctx = ContentRendererArg { let ctx = ContentRendererArg {
app: app.to_lua_ctx_light(), app: app.to_lua_ctx_light(),
layout_size: layout_size.into(), layout_size: layout_size.into(),
screen_size: screen_size.into(), screen_size: ui.screen_size.into(),
scrolltop: ui.scrolltop as u16,
}; };
let rows = lua::serialize(lua, &ctx) let rows = lua::serialize(ui.lua, &ctx)
.map(|arg| { .map(|arg| {
lua::call(lua, &render, arg) lua::call(ui.lua, &render, arg)
.unwrap_or_else(|e| vec![vec![format!("{e:?}")]]) .unwrap_or_else(|e| vec![vec![format!("{e:?}")]])
}) })
.unwrap_or_else(|e| vec![vec![e.to_string()]]) .unwrap_or_else(|e| vec![vec![e.to_string()]])
@ -204,7 +206,7 @@ pub fn draw_custom_content(
let widths = widths let widths = widths
.into_iter() .into_iter()
.map(|w| w.to_tui(screen_size, layout_size)) .map(|w| w.to_tui(ui.screen_size, layout_size))
.collect::<Vec<TuiConstraint>>(); .collect::<Vec<TuiConstraint>>();
let mut content = Table::new(rows, &widths).block(block( let mut content = Table::new(rows, &widths).block(block(

@ -55,7 +55,7 @@ pub struct NodeTypeConfig {
impl NodeTypeConfig { impl NodeTypeConfig {
pub fn extend(mut self, other: &Self) -> Self { pub fn extend(mut self, other: &Self) -> Self {
self.style = self.style.extend(&other.style); self.style = self.style.extend(&other.style);
self.meta.extend(other.meta.to_owned()); self.meta.extend(other.meta.clone());
self self
} }
} }
@ -85,11 +85,11 @@ pub struct NodeTypesConfig {
impl NodeTypesConfig { impl NodeTypesConfig {
pub fn get(&self, node: &Node) -> NodeTypeConfig { pub fn get(&self, node: &Node) -> NodeTypeConfig {
let mut node_type = if node.is_symlink { let mut node_type = if node.is_symlink {
self.symlink.to_owned() self.symlink.clone()
} else if node.is_dir { } else if node.is_dir {
self.directory.to_owned() self.directory.clone()
} else { } else {
self.file.to_owned() self.file.clone()
}; };
let mut me = node.mime_essence.splitn(2, '/'); let mut me = node.mime_essence.splitn(2, '/');
@ -104,7 +104,7 @@ impl NodeTypesConfig {
node_type = node_type.extend(conf); node_type = node_type.extend(conf);
} }
if let Some(conf) = self.extension.get(&node.extension) { if let (Some(conf), false) = (self.extension.get(&node.extension), node.is_dir) {
node_type = node_type.extend(conf); node_type = node_type.extend(conf);
} }
@ -141,7 +141,7 @@ pub struct UiElement {
impl UiElement { impl UiElement {
pub fn extend(mut self, other: &Self) -> Self { pub fn extend(mut self, other: &Self) -> Self {
self.format = other.format.to_owned().or(self.format); self.format = other.format.clone().or(self.format);
self.style = self.style.extend(&other.style); self.style = self.style.extend(&other.style);
self self
} }
@ -353,6 +353,12 @@ pub struct GeneralConfig {
#[serde(default)] #[serde(default)]
pub global_key_bindings: KeyBindings, pub global_key_bindings: KeyBindings,
#[serde(default)]
pub paginated_scrolling: bool,
#[serde(default)]
pub scroll_padding: usize,
} }
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
@ -638,8 +644,8 @@ impl PanelUiConfig {
pub fn extend(mut self, other: &Self) -> Self { pub fn extend(mut self, other: &Self) -> Self {
self.title = self.title.extend(&other.title); self.title = self.title.extend(&other.title);
self.style = self.style.extend(&other.style); self.style = self.style.extend(&other.style);
self.borders = other.borders.to_owned().or(self.borders); self.borders = other.borders.clone().or(self.borders);
self.border_type = other.border_type.to_owned().or(self.border_type); self.border_type = other.border_type.or(self.border_type);
self.border_style = self.border_style.extend(&other.border_style); self.border_style = self.border_style.extend(&other.border_style);
self self
} }

@ -22,5 +22,5 @@ pub fn runtime_dir() -> PathBuf {
else { else {
return env::temp_dir(); return env::temp_dir();
}; };
dir.to_owned() dir.clone()
} }

@ -91,6 +91,17 @@ xplr.config.general.enable_recover_mode = false
-- Type: boolean -- Type: boolean
xplr.config.general.hide_remaps_in_help_menu = false xplr.config.general.hide_remaps_in_help_menu = false
-- Set it to `true` if you want paginated scrolling.
--
-- Type: boolean
xplr.config.general.paginated_scrolling = false
-- Set the padding value to the scroll area.
-- Only applicable when `xplr.config.general.paginated_scrolling = false`.
--
-- Type: boolean
xplr.config.general.scroll_padding = 5
-- Set it to `true` if you want the cursor to stay in the same position when -- Set it to `true` if you want the cursor to stay in the same position when
-- the focus is on the first path and you navigate to the previous path -- the focus is on the first path and you navigate to the previous path
-- (by pressing `up`/`k`), or when the focus is on the last path and you -- (by pressing `up`/`k`), or when the focus is on the last path and you
@ -478,7 +489,7 @@ xplr.config.general.sort_and_filter_ui.search_identifiers = {
-- --
-- Type: nullable string -- Type: nullable string
xplr.config.general.sort_and_filter_ui.search_direction_identifiers.ordered.format = xplr.config.general.sort_and_filter_ui.search_direction_identifiers.ordered.format =
"" ""
-- The shape of unordered indicator for search ordering identifiers in Sort & filter panel. -- The shape of unordered indicator for search ordering identifiers in Sort & filter panel.
-- --
@ -676,7 +687,7 @@ xplr.config.general.panel_ui.sort_and_filter.border_style = {}
-- Type: nullable list of [Node Sorter](https://xplr.dev/en/sorting#node-sorter-applicable) -- Type: nullable list of [Node Sorter](https://xplr.dev/en/sorting#node-sorter-applicable)
xplr.config.general.initial_sorting = { xplr.config.general.initial_sorting = {
{ sorter = "ByCanonicalIsDir", reverse = true }, { sorter = "ByCanonicalIsDir", reverse = true },
{ sorter = "ByIRelativePath", reverse = false }, { sorter = "ByIRelativePath", reverse = false },
} }
-- The name of one of the modes to use when xplr loads. -- The name of one of the modes to use when xplr loads.
@ -1296,6 +1307,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",
@ -1309,23 +1336,23 @@ xplr.config.modes.builtin.default = {
} }
xplr.config.modes.builtin.default.key_bindings.on_key["v"] = xplr.config.modes.builtin.default.key_bindings.on_key["v"] =
xplr.config.modes.builtin.default.key_bindings.on_key["space"] xplr.config.modes.builtin.default.key_bindings.on_key["space"]
xplr.config.modes.builtin.default.key_bindings.on_key["V"] = xplr.config.modes.builtin.default.key_bindings.on_key["V"] =
xplr.config.modes.builtin.default.key_bindings.on_key["ctrl-a"] xplr.config.modes.builtin.default.key_bindings.on_key["ctrl-a"]
xplr.config.modes.builtin.default.key_bindings.on_key["/"] = xplr.config.modes.builtin.default.key_bindings.on_key["/"] =
xplr.config.modes.builtin.default.key_bindings.on_key["ctrl-f"] xplr.config.modes.builtin.default.key_bindings.on_key["ctrl-f"]
xplr.config.modes.builtin.default.key_bindings.on_key["h"] = xplr.config.modes.builtin.default.key_bindings.on_key["h"] =
xplr.config.modes.builtin.default.key_bindings.on_key["left"] xplr.config.modes.builtin.default.key_bindings.on_key["left"]
xplr.config.modes.builtin.default.key_bindings.on_key["j"] = xplr.config.modes.builtin.default.key_bindings.on_key["j"] =
xplr.config.modes.builtin.default.key_bindings.on_key["down"] xplr.config.modes.builtin.default.key_bindings.on_key["down"]
xplr.config.modes.builtin.default.key_bindings.on_key["k"] = xplr.config.modes.builtin.default.key_bindings.on_key["k"] =
xplr.config.modes.builtin.default.key_bindings.on_key["up"] xplr.config.modes.builtin.default.key_bindings.on_key["up"]
xplr.config.modes.builtin.default.key_bindings.on_key["l"] = xplr.config.modes.builtin.default.key_bindings.on_key["l"] =
xplr.config.modes.builtin.default.key_bindings.on_key["right"] xplr.config.modes.builtin.default.key_bindings.on_key["right"]
xplr.config.modes.builtin.default.key_bindings.on_key["tab"] = xplr.config.modes.builtin.default.key_bindings.on_key["tab"] =
xplr.config.modes.builtin.default.key_bindings.on_key["ctrl-i"] -- compatibility workaround xplr.config.modes.builtin.default.key_bindings.on_key["ctrl-i"] -- compatibility workaround
xplr.config.modes.builtin.default.key_bindings.on_key["?"] = xplr.config.modes.builtin.default.key_bindings.on_key["?"] =
xplr.config.general.global_key_bindings.on_key["f1"] xplr.config.general.global_key_bindings.on_key["f1"]
-- The builtin debug error mode. -- The builtin debug error mode.
-- --
@ -1460,6 +1487,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 +1700,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 +1740,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 +1780,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 +1820,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
@ -1835,9 +2004,9 @@ xplr.config.modes.builtin.number = {
} }
xplr.config.modes.builtin.number.key_bindings.on_key["j"] = xplr.config.modes.builtin.number.key_bindings.on_key["j"] =
xplr.config.modes.builtin.number.key_bindings.on_key["down"] xplr.config.modes.builtin.number.key_bindings.on_key["down"]
xplr.config.modes.builtin.number.key_bindings.on_key["k"] = xplr.config.modes.builtin.number.key_bindings.on_key["k"] =
xplr.config.modes.builtin.number.key_bindings.on_key["up"] xplr.config.modes.builtin.number.key_bindings.on_key["up"]
-- The builtin go to mode. -- The builtin go to mode.
-- --
@ -2321,9 +2490,9 @@ xplr.config.modes.builtin.search = {
} }
xplr.config.modes.builtin.search.key_bindings.on_key["ctrl-n"] = xplr.config.modes.builtin.search.key_bindings.on_key["ctrl-n"] =
xplr.config.modes.builtin.search.key_bindings.on_key["down"] xplr.config.modes.builtin.search.key_bindings.on_key["down"]
xplr.config.modes.builtin.search.key_bindings.on_key["ctrl-p"] = xplr.config.modes.builtin.search.key_bindings.on_key["ctrl-p"] =
xplr.config.modes.builtin.search.key_bindings.on_key["up"] xplr.config.modes.builtin.search.key_bindings.on_key["up"]
-- The builtin filter mode. -- The builtin filter mode.
-- --
@ -2955,8 +3124,8 @@ xplr.fn.builtin.fmt_general_selection_item = function(n)
if n.is_dir then if n.is_dir then
shortened = shortened .. "/" shortened = shortened .. "/"
end end
local ls_style = xplr.util.lscolor(n.absolute_path)
local meta_style = xplr.util.node_type(n).style local meta_style = xplr.util.node_type(n).style
local ls_style = xplr.util.lscolor(n.absolute_path)
local style = xplr.util.style_mix({ ls_style, meta_style }) local style = xplr.util.style_mix({ ls_style, meta_style })
return xplr.util.paint(shortened:gsub("\n", nl), style) return xplr.util.paint(shortened:gsub("\n", nl), style)
end end
@ -2979,8 +3148,8 @@ end
xplr.fn.builtin.fmt_general_table_row_cols_1 = function(m) xplr.fn.builtin.fmt_general_table_row_cols_1 = function(m)
local nl = xplr.util.paint("\\n", { add_modifiers = { "Italic", "Dim" } }) local nl = xplr.util.paint("\\n", { add_modifiers = { "Italic", "Dim" } })
local r = m.tree .. m.prefix local r = m.tree .. m.prefix
local style = xplr.util.lscolor(m.absolute_path) local ls_style = xplr.util.lscolor(m.absolute_path)
style = xplr.util.style_mix({ style, m.style }) local style = xplr.util.style_mix({ ls_style, m.style })
if m.meta.icon == nil then if m.meta.icon == nil then
r = r .. "" r = r .. ""
@ -3003,7 +3172,7 @@ xplr.fn.builtin.fmt_general_table_row_cols_1 = function(m)
r = r .. "×" r = r .. "×"
else else
local symlink_path = local symlink_path =
xplr.util.shorten(m.symlink.absolute_path, { base = m.parent }) xplr.util.shorten(m.symlink.absolute_path, { base = m.parent })
if m.symlink.is_dir then if m.symlink.is_dir then
symlink_path = symlink_path .. "/" symlink_path = symlink_path .. "/"
end end
@ -3025,14 +3194,14 @@ xplr.fn.builtin.fmt_general_table_row_cols_2 = function(m)
local T = xplr.util.paint("T", { fg = "Red" }) local T = xplr.util.paint("T", { fg = "Red" })
return xplr.util return xplr.util
.permissions_rwx(m.permissions) .permissions_rwx(m.permissions)
:gsub("r", r) :gsub("r", r)
:gsub("w", w) :gsub("w", w)
:gsub("x", x) :gsub("x", x)
:gsub("s", s) :gsub("s", s)
:gsub("S", S) :gsub("S", S)
:gsub("t", t) :gsub("t", t)
:gsub("T", T) :gsub("T", T)
end end
-- Renders the fourth column in the table -- Renders the fourth column in the table

@ -647,7 +647,7 @@ impl Key {
Self::ShiftZ => Some('Z'), Self::ShiftZ => Some('Z'),
Self::Space => Some(' '), Self::Space => Some(' '),
Self::Special(c) => Some(c.to_owned()), Self::Special(c) => Some(*c),
_ => None, _ => None,
} }
@ -731,12 +731,6 @@ impl From<char> for Key {
} }
} }
impl From<String> for Key {
fn from(string: String) -> Self {
string.into()
}
}
impl From<&str> for Key { impl From<&str> for Key {
fn from(string: &str) -> Self { fn from(string: &str) -> Self {
if string.len() == 1 { if string.len() == 1 {

@ -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.9", "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.9", "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.8", "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.9", "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.9", "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.10", "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
@ -131,7 +160,7 @@ pub fn is_dir<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
/// ``` /// ```
pub fn is_file<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> { pub fn is_file<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
let func = let func =
lua.create_function(move |_, path: String| Ok(PathBuf::from(path).is_dir()))?; lua.create_function(move |_, path: String| Ok(PathBuf::from(path).is_file()))?;
util.set("is_file", func)?; util.set("is_file", func)?;
Ok(util) Ok(util)
} }
@ -625,7 +654,7 @@ pub fn to_yaml<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
/// Get a [Style][3] object for the given path based on the LS_COLORS /// Get a [Style][3] object for the given path based on the LS_COLORS
/// environment variable. /// environment variable.
/// ///
/// Type: function( path:string ) -> [Style][3]|nil /// Type: function( path:string ) -> [Style][3]
/// ///
/// Example: /// Example:
/// ///
@ -634,9 +663,11 @@ 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)
.unwrap_or_default();
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 +869,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)?;

@ -8,7 +8,8 @@ use crate::explorer;
use crate::lua; use crate::lua;
use crate::pipe; use crate::pipe;
use crate::pwd_watcher; use crate::pwd_watcher;
use crate::ui; use crate::ui::NO_COLOR;
use crate::ui::UI;
use crate::yaml; use crate::yaml;
use anyhow::{bail, Error, Result}; use anyhow::{bail, Error, Result};
use crossterm::event; use crossterm::event;
@ -285,7 +286,7 @@ impl Runner {
tx_pwd_watcher.send(app.pwd.clone())?; tx_pwd_watcher.send(app.pwd.clone())?;
let mut result = Ok(None); let mut result = Ok(None);
let session_path = app.session_path.to_owned(); let session_path = app.session_path.clone();
term::enable_raw_mode()?; term::enable_raw_mode()?;
@ -341,6 +342,9 @@ impl Runner {
None, None,
))?; ))?;
// UI
let mut ui = UI::new(&lua);
'outer: for task in rx_msg_in { 'outer: for task in rx_msg_in {
match app.handle_task(task) { match app.handle_task(task) {
Ok(a) => { Ok(a) => {
@ -411,7 +415,7 @@ impl Runner {
} }
ScrollUpHalf => { ScrollUpHalf => {
app = app.focus_next_by_relative_index( app = app.focus_previous_by_relative_index(
terminal.size()?.height as usize / 2, terminal.size()?.height as usize / 2,
)?; )?;
} }
@ -470,13 +474,13 @@ impl Runner {
} }
if app.pwd != last_pwd { if app.pwd != last_pwd {
last_pwd = app.pwd.clone(); last_pwd.clone_from(&app.pwd);
// $PWD watcher // $PWD watcher
tx_pwd_watcher.send(app.pwd.clone())?; tx_pwd_watcher.send(app.pwd.clone())?;
// OSC 7: Change CWD // OSC 7: Change CWD
if !(*ui::NO_COLOR) { if !(*NO_COLOR) {
write!( write!(
terminal.backend_mut(), terminal.backend_mut(),
"\x1b]7;file://{}{}\x1b\\", "\x1b]7;file://{}{}\x1b\\",
@ -493,7 +497,7 @@ impl Runner {
} }
// UI // UI
terminal.draw(|f| ui::draw(f, &app, &lua))?; terminal.draw(|f| ui.draw(f, &app))?;
} }
EnableMouse => { EnableMouse => {

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save