pull/664/head v0.21.3
Arijit Basu 10 months ago committed by GitHub
commit 8af1647c09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,4 +1,14 @@
# Why dynamic linking? # Why dynamic linking?
# See https://github.com/sayanarijit/xplr/issues/309 # See https://github.com/sayanarijit/xplr/issues/309
[target.x86_64-unknown-linux-gnu] [target.x86_64-unknown-linux-gnu]
rustflags = ["-C", "link-args=-rdynamic"] rustflags = ["-C", "link-args=-rdynamic"]
[target.aarch64-unknown-linux-gnu]
rustflags = ["-C", "linker=aarch64-linux-gnu-gcc", "-C", "link-args=-rdynamic"]
[target.aarch64-linux-android]
rustflags = ["-C", "linker=aarch64-linux-android-clang", "-C", "link-args=-rdynamic", "-C", "default-linker-libraries"]
[target.arm-unknown-linux-gnueabihf]
rustflags = ["-C", "linker=arm-linux-gnueabihf-gcc", "-C", "link-args=-rdynamic"]

@ -12,71 +12,74 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
os: build:
- macos-latest - macos
- ubuntu-latest - macos-aarch64
- ubuntu-20.04 - linux
- linux-musl
- linux-aarch64
- linux-arm
rust: [stable] rust: [stable]
include: include:
# See the list: https://github.com/cross-rs/cross # See the list: https://github.com/cross-rs/cross
- os: macos-latest - build: macos
artifact_prefix: macos os: macos-latest
target: x86_64-apple-darwin target: x86_64-apple-darwin
binary_postfix: ""
- os: ubuntu-latest - build: macos-aarch64
artifact_prefix: linux os: macos-latest
target: aarch64-apple-darwin
- build: linux
os: ubuntu-latest
target: x86_64-unknown-linux-gnu target: x86_64-unknown-linux-gnu
binary_postfix: ""
- os: ubuntu-20.04 - build: linux-musl
artifact_prefix: linux-musl os: ubuntu-latest
target: x86_64-unknown-linux-musl target: x86_64-unknown-linux-musl
binary_postfix: ""
# Will see later - build: linux-aarch64
os: ubuntu-latest
target: aarch64-unknown-linux-gnu
# - os: ubuntu-latest - build: linux-arm
# artifact_prefix: x86_64-android os: ubuntu-latest
# target: x86_64-linux-android target: arm-unknown-linux-gnueabihf
# binary_postfix: ''
#
# - os: ubuntu-latest
# artifact_prefix: aarch64-android
# target: aarch64-linux-android
# binary_postfix: ''
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Installing Rust toolchain - name: Installing Rust toolchain
uses: actions-rs/toolchain@v1 uses: dtolnay/rust-toolchain@stable
with: with:
toolchain: ${{ matrix.rust }} toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }} target: ${{ matrix.target }}
override: true
- name: Installing needed macOS dependencies - name: Installing needed macOS dependencies
if: matrix.os == 'macos-latest' if: matrix.os == 'macos-latest'
run: brew install openssl@1.1 run: brew install openssl@1.1
- name: Installing needed Ubuntu dependencies - name: Installing needed Ubuntu dependencies
if: matrix.os == 'ubuntu-latest' || matrix.os == 'ubuntu-20.04' if: matrix.os == 'ubuntu-latest'
run: | run: |
sudo apt-get update --fix-missing sudo apt-get update --fix-missing
sudo apt-get install -y -qq pkg-config libssl-dev libxcb1-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev sudo apt-get install -y --no-install-recommends liblua5.1-0-dev libluajit-5.1-dev gcc pkg-config curl git make ca-certificates
- if: matrix.build == 'linux-musl'
run: sudo apt-get install -y musl-tools
- if: matrix.build == 'linux-aarch64'
run: sudo apt-get install -y gcc-aarch64-linux-gnu
- name: Checking out sources - if: matrix.build == 'linux-arm'
uses: actions/checkout@v1 run: |
sudo apt-get install -y gcc-multilib
sudo apt-get install -y gcc-arm-linux-gnueabihf
sudo ln -s /usr/include/asm-generic/ /usr/include/asm
- name: Running cargo build - name: Running cargo build
uses: actions-rs/cargo@v1 run: cargo build --locked --release --target ${{ matrix.target }}
with:
use-cross: true
command: build
toolchain: ${{ matrix.rust }}
args: --locked --release --target ${{ matrix.target }}
- name: Install gpg secret key - name: Install gpg secret key
run: | run: |
@ -87,9 +90,9 @@ jobs:
shell: bash shell: bash
run: | run: |
cd target/${{ matrix.target }}/release cd target/${{ matrix.target }}/release
BINARY_NAME=xplr${{ matrix.binary_postfix }} BINARY_NAME=xplr
strip $BINARY_NAME strip $BINARY_NAME
RELEASE_NAME=xplr-${{ matrix.artifact_prefix }} RELEASE_NAME=xplr-${{ matrix.build }}
tar czvf $RELEASE_NAME.tar.gz $BINARY_NAME tar czvf $RELEASE_NAME.tar.gz $BINARY_NAME
shasum -a 256 $RELEASE_NAME.tar.gz > $RELEASE_NAME.sha256 shasum -a 256 $RELEASE_NAME.tar.gz > $RELEASE_NAME.sha256
cat <(echo "${{ secrets.GPG_PASS }}") | gpg --pinentry-mode loopback --passphrase-fd 0 --detach-sign --armor $RELEASE_NAME.tar.gz cat <(echo "${{ secrets.GPG_PASS }}") | gpg --pinentry-mode loopback --passphrase-fd 0 --detach-sign --armor $RELEASE_NAME.tar.gz
@ -98,9 +101,9 @@ jobs:
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
with: with:
files: | files: |
target/${{ matrix.target }}/release/xplr-${{ matrix.artifact_prefix }}.tar.gz target/${{ matrix.target }}/release/xplr-${{ matrix.build }}.tar.gz
target/${{ matrix.target }}/release/xplr-${{ matrix.artifact_prefix }}.sha256 target/${{ matrix.target }}/release/xplr-${{ matrix.build }}.sha256
target/${{ matrix.target }}/release/xplr-${{ matrix.artifact_prefix }}.tar.gz.asc target/${{ matrix.target }}/release/xplr-${{ matrix.build }}.tar.gz.asc
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@ -108,7 +111,7 @@ jobs:
name: Publishing GPG signature name: Publishing GPG signature
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- 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
@ -133,20 +136,16 @@ jobs:
name: Publishing to Cargo name: Publishing to Cargo
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1 - uses: dtolnay/rust-toolchain@stable
with: with:
toolchain: stable toolchain: stable
override: true
- run: | - run: |
sudo apt-get update --fix-missing sudo apt-get update --fix-missing
sudo apt-get install -y -qq pkg-config libssl-dev libxcb1-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev sudo apt-get install -y -qq pkg-config libssl-dev libxcb1-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev
- uses: actions-rs/cargo@v1 - run: cargo publish --allow-dirty
with:
command: publish
args: --allow-dirty
env: env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_API_KEY }} CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_API_KEY }}

@ -11,130 +11,134 @@ jobs:
name: Check name: Check
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1 - uses: dtolnay/rust-toolchain@stable
with: with:
toolchain: stable toolchain: stable
profile: minimal - run: cargo check
override: true
- uses: actions-rs/cargo@v1 fmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
components: rustfmt
- run: cargo fmt --all -- --check
clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
components: clippy
- run: cargo clippy -- -D warnings
spellcheck:
name: Spellcheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: codespell-project/actions-codespell@v1
with: with:
command: check ignore_words_file: .codespellignore
test: test:
name: Test Suite name: Test Suite
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
needs:
- check
- fmt
- clippy
- spellcheck
strategy: strategy:
matrix: matrix:
os: build:
- macos-latest - macos
- ubuntu-latest - macos-aarch64
- ubuntu-20.04 - linux
- linux-musl
- linux-aarch64
- linux-arm
rust: [stable] rust: [stable]
include: include:
- os: macos-latest # See the list: https://github.com/cross-rs/cross
artifact_prefix: macos
- build: macos
os: macos-latest
target: x86_64-apple-darwin target: x86_64-apple-darwin
binary_postfix: ""
- os: ubuntu-latest - build: macos-aarch64
artifact_prefix: linux os: macos-latest
target: aarch64-apple-darwin
- build: linux
os: ubuntu-latest
target: x86_64-unknown-linux-gnu target: x86_64-unknown-linux-gnu
binary_postfix: ""
- os: ubuntu-20.04 - build: linux-musl
artifact_prefix: linux-musl os: ubuntu-latest
target: x86_64-unknown-linux-musl target: x86_64-unknown-linux-musl
binary_postfix: ""
- build: linux-aarch64
os: ubuntu-latest
target: aarch64-unknown-linux-gnu
- build: linux-arm
os: ubuntu-latest
target: arm-unknown-linux-gnueabihf
env: env:
RUST_BACKTRACE: full RUST_BACKTRACE: full
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Installing Rust toolchain - name: Installing Rust toolchain
uses: actions-rs/toolchain@v1 uses: dtolnay/rust-toolchain@stable
with: with:
toolchain: ${{ matrix.rust }} toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }} target: ${{ matrix.target }}
override: true
- name: Installing needed macOS dependencies - name: Installing needed macOS dependencies
if: matrix.os == 'macos-latest' if: matrix.os == 'macos-latest'
run: brew install openssl@1.1 run: brew install openssl@1.1
- name: Installing needed Ubuntu dependencies - name: Installing needed Ubuntu dependencies
if: matrix.os == 'ubuntu-latest' || matrix.os == 'ubuntu-20.04' if: matrix.os == 'ubuntu-latest'
run: | run: |
sudo apt-get update --fix-missing sudo apt-get update --fix-missing
sudo apt-get install -y -qq pkg-config libssl-dev libxcb1-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev sudo apt-get install -y --no-install-recommends liblua5.1-0-dev libluajit-5.1-dev gcc pkg-config curl git make ca-certificates
- name: Build - if: matrix.build == 'linux-musl'
uses: actions-rs/cargo@v1 run: sudo apt-get install -y musl-tools
with:
command: build
toolchain: ${{ matrix.rust }}
args: --target ${{ matrix.target }}
- name: Test - if: matrix.build == 'linux-aarch64'
uses: actions-rs/cargo@v1 run: sudo apt-get install -y gcc-aarch64-linux-gnu
with:
command: test - if: matrix.build == 'linux-arm'
toolchain: ${{ matrix.rust }} run: |
args: --target ${{ matrix.target }} sudo apt-get install -y gcc-multilib
sudo apt-get install -y gcc-arm-linux-gnueabihf
sudo ln -s /usr/include/asm-generic/ /usr/include/asm
- run: cargo build --target ${{ matrix.target }}
- if: matrix.build == 'macos' || matrix.build == 'linux'
run: cargo test --target ${{ matrix.target }}
# bench: # bench:
# name: Benchmarks # name: Benchmarks
# runs-on: ubuntu-latest # runs-on: ubuntu-latest
# steps: # steps:
# - uses: actions/checkout@v2 # - uses: actions/checkout@v3
# - uses: actions-rs/toolchain@v1 # - uses: dtolnay/rust-toolchain@stable
# with: # with:
# toolchain: stable # toolchain: stable
# profile: minimal
# override: true
# # These dependencies are required for `clipboard` # # These dependencies are required for `clipboard`
# - run: sudo apt-get install -y -qq libxcb1-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev # - run: sudo apt-get install -y -qq libxcb1-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev
# - uses: actions-rs/cargo@v1 # - run: cargo bench
# with:
# command: bench
fmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
components: rustfmt
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
components: clippy
- uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
spellcheck:
name: Spellcheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: codespell-project/actions-codespell@v1
with:
ignore_words_file: .codespellignore

3
.gitignore vendored

@ -17,3 +17,6 @@ book/
# direnv # direnv
.direnv/ .direnv/
# nix
result

651
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.2' version = '0.21.3'
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.144" libc = "0.2.147"
humansize = "2.1.3" humansize = "2.1.3"
natord = "1.0.9" natord = "1.0.9"
anyhow = "1.0.71" anyhow = "1.0.72"
serde_yaml = "0.9.21" serde_yaml = "0.9.25"
crossterm = { version = "0.26.1", features = [], default-features = false } crossterm = { version = "0.27.0", features = [], default-features = false }
ansi-to-tui = "3.0.0" ansi-to-tui = "3.1.0"
regex = "1.8.1" regex = "1.9.3"
gethostname = "0.4.3" gethostname = "0.4.3"
serde_json = "1.0.96" serde_json = "1.0.104"
path-absolutize = "3.1.0" path-absolutize = "3.1.0"
which = "4.4.0" which = "4.4.0"
nu-ansi-term = "0.47.0" nu-ansi-term = "0.49.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.21", features = ["serde", "local-offset", "formatting", "macros"] } time = { version = "0.3.25", features = ["serde", "local-offset", "formatting", "macros"] }
jf = "0.3.1" jf = "0.6.2"
xdg = "2.5.0" xdg = "2.5.2"
home = "0.5.5" home = "0.5.5"
[dependencies.lscolors] [dependencies.lscolors]
version = "0.14.0" version = "0.15.0"
default-features = false default-features = false
features = ["nu-ansi-term"] features = ["nu-ansi-term"]
@ -57,18 +57,18 @@ version = "2.0.4"
default-features = false default-features = false
[dependencies.tui] [dependencies.tui]
version = "0.20.1" version = "0.22.0"
default-features = false default-features = false
features = ['crossterm', 'serde'] features = ['crossterm', 'serde']
package = 'ratatui' package = 'ratatui'
[dependencies.serde] [dependencies.serde]
version = "1.0.163" version = "1.0.183"
features = [] features = []
default-features = false default-features = false
[dependencies.indexmap] [dependencies.indexmap]
version = "1.9.3" version = "2.0.0"
features = ['serde'] features = ['serde']
[dependencies.mlua] [dependencies.mlua]
@ -76,16 +76,17 @@ version = "0.8.9"
features = ['luajit', 'vendored', 'serialize', 'send'] features = ['luajit', 'vendored', 'serialize', 'send']
[dependencies.tui-input] [dependencies.tui-input]
version = "0.7.0" version = "0.8.0"
features = ['serde'] features = ['serde']
[dev-dependencies] [dev-dependencies]
criterion = "0.4.0" criterion = "0.5.1"
assert_cmd = "2.0.11" assert_cmd = "2.0.12"
[profile.release] [profile.release]
lto = true lto = true
codegen-units = 1 codegen-units = 1
panic = 'abort' panic = 'abort'
strip = true
[features] [features]

@ -60,6 +60,8 @@ 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)
## Packaging ## Packaging
Package maintainers please refer to the [RELEASE.md](./RELEASE.md). Package maintainers please refer to the [RELEASE.md](./RELEASE.md).

@ -131,6 +131,14 @@ return {
{ LogSuccess = "Switched layout" }, { LogSuccess = "Switched layout" },
{ CallLuaSilently = "custom.some_plugin_with_hooks.on_layout_switch" }, { CallLuaSilently = "custom.some_plugin_with_hooks.on_layout_switch" },
} }
-- Add messages to send when the selection changes
--
-- Type: list of [Message](https://xplr.dev/en/message#message)s
on_selection_change = {
{ LogSuccess = "Selection changed" },
{ CallLuaSilently = "custom.some_plugin_with_hooks.on_selection_change" },
}
} }
``` ```

@ -587,12 +587,16 @@ Type: nullable list of [Node Sorter](https://xplr.dev/en/sorting#node-sorter-app
#### xplr.config.general.initial_mode #### xplr.config.general.initial_mode
The name of one of the modes to use when xplr loads. The name of one of the modes to use when xplr loads.
This isn't the default mode. To modify the default mode, overwrite
[xplr.config.modes.builtin.default](https://xplr.dev/en/modes#xplrconfigmodesbuiltindefault).
Type: nullable string Type: nullable string
#### xplr.config.general.initial_layout #### xplr.config.general.initial_layout
The name of one of the layouts to use when xplr loads. The name of one of the layouts to use when xplr loads.
This isn't the default layout. To modify the default layout, overwrite
[xplr.config.layouts.builtin.default](https://xplr.dev/en/layouts#xplrconfiglayoutsbuiltindefault).
Type: nullable string Type: nullable string

@ -198,44 +198,20 @@ sudo cp target/release/xplr /usr/local/bin/
## Android ## Android
### [Termux][23] ### [Termux][24]
[![xplr-termuxfd3c398d3cf4bcbc.md.jpg][24]][25] ```bash
pkg install rust make binutils
> Please note that xplr isn't heavily tested on Termux, hence things might cargo install --locked xplr
> need a little tweaking and fixing for a smooth usage experience.
- Install build dependencies
```bash
pkg install rustc cargo make
```
- Install `xplr`
```bash
cargo install --locked --force xplr
```
- Setup storage
```bash
termux-setup-storage
```
- Setup config and runtime dir
```bash # Run
export XDG_CONFIG_HOME="$PWD/storage/.config" ~/.cargo/bin/xplr
export XDG_RUNTIME_DIR="$PWD/storage/run" ```
mkdir -p "$XDG_CONFIG_HOME" "$XDG_RUNTIME_DIR" > Please note that xplr isn't heavily tested on Termux, hence things might need
``` > a little tweaking and fixing for a smooth user experience.
- Run ![termux demo][23]
```bash
~/.cargo/bin/xplr
```
[1]: #direct-download [1]: #direct-download
[2]: #from-cratesio [2]: #from-cratesio
@ -259,8 +235,8 @@ sudo cp target/release/xplr /usr/local/bin/
[20]: https://gcc.gnu.org/ [20]: https://gcc.gnu.org/
[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://termux.com/ [23]: https://github.com/sayanarijit/xplr/assets/11632726/3b61e8c8-76f0-48e8-8734-50e9e7e495b7
[24]: https://s3.gifyu.com/images/xplr-termuxfd3c398d3cf4bcbc.md.jpg [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

@ -2,15 +2,13 @@
#### Example: Defining Custom Layout #### Example: Defining Custom Layout
[![layout.png][23]][24]
```lua ```lua
xplr.config.layouts.builtin.default = { xplr.config.layouts.builtin.default = {
Horizontal = { Horizontal = {
config = { config = {
margin = 1, margin = 1,
horizontal_margin = 2, horizontal_margin = 1,
vertical_margin = 3, vertical_margin = 1,
constraints = { constraints = {
{ Percentage = 50 }, { Percentage = 50 },
{ Percentage = 50 }, { Percentage = 50 },
@ -24,6 +22,21 @@ xplr.config.layouts.builtin.default = {
} }
``` ```
Result:
```
╭ /home ─────────────╮╭ Help [default] ────╮
│ ╭─── path ││. show hidden │
│ ├▸[ð Desktop/] ││/ search │
│ ├ ð Documents/ ││: action │
│ ├ ð Downloads/ ││? global help │
│ ├ ð GitHub/ ││G go to bottom │
│ ├ ð Music/ ││V select/unselect│
│ ├ ð Pictures/ ││ctrl duplicate as │
│ ├ ð Public/ ││ctrl next visit │
╰────────────────────╯╰────────────────────╯
```
A layout is a [sum type][56] can be one of the following: A layout is a [sum type][56] can be one of the following:
- [Nothing][8] - [Nothing][8]
@ -86,7 +99,7 @@ Type: { Static = [Custom Panel][27] }
This is a custom layout to render dynamic content using a function defined in This is a custom layout to render dynamic content using a function defined in
[xplr.fn][28] that takes [Content Renderer Argument][36] and returns [Custom Panel][27]. [xplr.fn][28] that takes [Content Renderer Argument][36] and returns [Custom Panel][27].
Type: { Dynamic = [Content Renderer][35] } Type: { Dynamic = "[Content Renderer][35]" }
### Horizontal ### Horizontal
@ -97,7 +110,7 @@ It contains the following information:
- [config][15] - [config][15]
- [splits][17] - [splits][17]
Type: { Horizontal = { config = [config][15], splits = [splits][17] } Type: { Vertical = { config = [Layout Config][15], splits = { [Layout][17], ... } }
### Vertical ### Vertical
@ -108,7 +121,7 @@ It contains the following information:
- [config][15] - [config][15]
- [splits][17] - [splits][17]
Type: { Vertical = { config = [config][15], splits = [splits][17] } Type: { Vertical = { config = [Layout Config][15], splits = { [Layout][17], ... } }
## Layout Config ## Layout Config
@ -200,6 +213,16 @@ xplr.config.layouts.builtin.default = {
} }
``` ```
Result:
```
╭ custom title ────────╮
│custom body │
│ │
│ │
╰──────────────────────╯
```
#### Example: Render a custom dynamic paragraph #### Example: Render a custom dynamic paragraph
```lua ```lua
@ -215,6 +238,23 @@ xplr.fn.custom.render_layout = function(ctx)
end end
``` ```
Result:
```
╭/home/sayanarijit───────────────────────────╮
│mime_essence: inode/directory │
│relative_path: Desktop │
│is_symlink: false │
│is_readonly: false │
│parent: /home/sayanarijit │
│absolute_path: /home/sayanarijit/Desktop │
│is_broken: false │
│created: 1668087850396758714 │
│size: 4096 │
│gid: 100 │
╰────────────────────────────────────────────╯
```
### CustomList ### CustomList
A list to render. It contains the following fields: A list to render. It contains the following fields:
@ -235,6 +275,17 @@ xplr.config.layouts.builtin.default = {
} }
``` ```
Result:
```
╭ custom title ─────────────╮
│1 │
│2 │
│3 │
│ │
╰───────────────────────────╯
```
#### Example: Render a custom dynamic list #### Example: Render a custom dynamic list
```lua ```lua
@ -254,6 +305,18 @@ xplr.fn.custom.render_layout = function(ctx)
end end
``` ```
Result:
```
╭/home/sayanarijit──────────╮
│Desktop │
│0.21.2 │
│17336 │
│ │
│ │
╰───────────────────────────╯
```
## CustomTable ## CustomTable
A custom table to render. It contains the following fields: A custom table to render. It contains the following fields:
@ -283,6 +346,18 @@ xplr.config.layouts.builtin.default = {
} }
``` ```
Result:
```
╭ custom title ────────────────────╮
│a b │
│c d │
│ │
│ │
│ │
╰──────────────────────────────────╯
```
#### Example: Render a custom dynamic table #### Example: Render a custom dynamic table
```lua ```lua
@ -309,6 +384,23 @@ xplr.fn.custom.render_layout = function(ctx)
end end
``` ```
Result:
```
╭/home/sayanarijit───────────────────────────╮
│ │
│Layout height 12 │
│Layout width 46 │
│ │
│Screen height 12 │
│Screen width 46 │
│ │
│ │
│ │
│ │
╰────────────────────────────────────────────╯
```
### CustomLayout ### CustomLayout
A whole custom layout to render. It doesn't make sense to use it as a A whole custom layout to render. It doesn't make sense to use it as a
@ -347,6 +439,40 @@ xplr.fn.custom.render_layout = function(ctx)
end end
``` ```
Result:
```
╭─────────────────────╮╭─────────────────────╮
│Try your luck... ││Press ctrl-r │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
╰─────────────────────╯╰─────────────────────╯
```
Or
```
╭────────────────────────────────────────────╮
│Try your luck... │
│ │
│ │
│ │
╰────────────────────────────────────────────╯
╭────────────────────────────────────────────╮
│Press ctrl-r │
│ │
│ │
│ │
╰────────────────────────────────────────────╯
```
## Panel UI Config ## Panel UI Config
It contains the following optional fields: It contains the following optional fields:
@ -429,8 +555,6 @@ Hence, only the following fields are available.
[20]: #vertical_margin [20]: #vertical_margin
[21]: #constraints [21]: #constraints
[22]: #constraint [22]: #constraint
[23]: https://s6.gifyu.com/images/layout.png
[24]: https://gifyu.com/image/1X38
[25]: #static [25]: #static
[26]: #dynamic [26]: #dynamic
[27]: #custom-panel [27]: #custom-panel

@ -12,15 +12,13 @@ You can add new panels in `xplr.config.layouts.custom`.
##### Example: Defining Custom Layout ##### Example: Defining Custom Layout
![demo](https://s6.gifyu.com/images/layout.png)
```lua ```lua
xplr.config.layouts.builtin.default = { xplr.config.layouts.builtin.default = {
Horizontal = { Horizontal = {
config = { config = {
margin = 1, margin = 1,
horizontal_margin = 2, horizontal_margin = 1,
vertical_margin = 3, vertical_margin = 1,
constraints = { constraints = {
{ Percentage = 50 }, { Percentage = 50 },
{ Percentage = 50 }, { Percentage = 50 },
@ -34,6 +32,21 @@ xplr.config.layouts.builtin.default = {
} }
``` ```
Result:
```
╭ /home ─────────────╮╭ Help [default] ────╮
│ ╭─── path ││. show hidden │
│ ├▸[ð Desktop/] ││/ search │
│ ├ ð Documents/ ││: action │
│ ├ ð Downloads/ ││? global help │
│ ├ ð GitHub/ ││G go to bottom │
│ ├ ð Music/ ││V select/unselect│
│ ├ ð Pictures/ ││ctrl duplicate as │
│ ├ ð Public/ ││ctrl next visit │
╰────────────────────╯╰────────────────────╯
```
#### xplr.config.layouts.builtin.default #### xplr.config.layouts.builtin.default
The default layout The default layout

@ -29,23 +29,23 @@ them, but only a few modern programming languages allow nesting other types
into a sum type. into a sum type.
```rust ```rust
enum Result { enum Color {
Ok, Red,
Err, Green,
} }
``` ```
Here, `Result` can be one of two possible set of values: `Ok` and `Err`, just Here, `Color` can be one of two possible set of values: `Red` and `Green`, just
like `boolean`, but unlike `boolean`, being tagged allows `Result` to have more like `boolean`, but unlike `boolean`, being tagged allows `Color` to have more
than two variants if required, by changing the definition. than two variants if required, by changing the definition.
e.g. e.g.
```rust ```rust
enum Result { enum Color {
Ok, Red,
Err, Green,
Pending, Blue,
} }
``` ```
@ -53,35 +53,39 @@ We'd document it here as:
> Result is a sum type that can be one of the following: > Result is a sum type that can be one of the following:
> >
> - "Ok" > - "Red"
> - "Err" > - "Green"
> - "Pending" > - "Blue"
But some languages (like Rust, Haskell, Elm etc.) go even further, allowing us But some languages (like Rust, Haskell, Elm etc.) go even further, allowing us
to associate each branch of the enum with further nested types like: to associate each branch of the enum with further nested types like:
```rust ```rust
enum Result { enum Layout {
Ok(bool), Table,
Err(Error), HelpMenu,
Pending, Horizontal {
config: LayoutConfig, // A product type (similar to class/struct)
splits: Vec<Layout> // A list of "Layout"s (i.e. list of sum types)
},
} }
``` ```
Here, as we can see, unlike the first example, some of `Result`'s possible Here, as we can see, unlike the first example, some of `Layout`'s possible
variants can have further nested types associated with them. Note that `Error` variants can have further nested types associated with them. Note that
here can be either a sum type (e.g. enum), or a product type (e.g. `Horizontal` here can have a sum type (e.g. enum), or a product type (e.g.
class/struct), but whatever it is, it will only exist when `Result` is `Err`. class/struct), or both (any number of them actually) nested in it. But the
nested values will only exist when `Layout` is `Horizontal`.
We'd document it here as: We'd document it here as:
> Result is a sum type that can be one of the following: > Layout is a sum type that can be one of the following:
> >
> - { Ok = bool } > - "Table"
> - { Err = Error } > - "HelpMenu"
> - "Pending" > - { Horizontal = { config = Layout Config, splits = { Layout, ... } }
And then we'd go on documenting whatever `Error` is. 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.
@ -89,7 +93,7 @@ have nested types in each branch.
--- ---
If you're still confused about something, or if you found an error in this If you're still confused about something, or if you found an error in this
explaination, feel free to [discuss together][5]. 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

@ -45,7 +45,7 @@ compatibility.
### Instructions ### Instructions
#### [v0.20.2][48] -> [v0.21.2][49] #### [v0.20.2][48] -> [v0.21.3][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
@ -81,7 +81,7 @@ compatibility.
- `:ss` to create softlink of the selected items. - `:ss` to create softlink of the selected items.
- `:sh` to create hardlink of the selected items. - `:sh` to create hardlink of the selected items.
- `:se` to edit selection list in your $EDITOR. - `:se` to edit selection list in your $EDITOR.
- Better conflict handling: add suffix rather than overriding/skipping. - Better conflict handling: prompt for action.
- Navigate between the selected paths using the following messages: - Navigate between the selected paths using the following messages:
- FocusPreviousSelection (`ctrl-p`) - FocusPreviousSelection (`ctrl-p`)
- FocusNextSelection (`ctrl-n`) - FocusNextSelection (`ctrl-n`)
@ -118,6 +118,7 @@ compatibility.
- xplr.util.shell_escape - xplr.util.shell_escape
- Executables will me marked with the mime type: `application/x-executable`. - Executables will me marked with the mime type: `application/x-executable`.
- macOS legacy coreutils will be generally supported, but please update it. - macOS legacy coreutils will be generally supported, but please update it.
- Since v0.21.2 you can use the on_selection_change hook.
Thanks to @noahmayr for contributing to a major part of this release. Thanks to @noahmayr for contributing to a major part of this release.
@ -516,5 +517,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.2 [49]: https://github.com/sayanarijit/xplr/releases/tag/v0.21.3
[50]: https://github.com/lotabout/skim#search-syntax [50]: https://github.com/lotabout/skim#search-syntax

@ -151,7 +151,7 @@
<li class="nav-item"> <li class="nav-item">
<a <a
class="nav-link page-scroll" class="nav-link page-scroll"
href="https://xplr.stck.me" href="https://blog.xplr.dev"
target="_blank" target="_blank"
rel="noreferrer noopener" rel="noreferrer noopener"
> >

@ -1,97 +1,12 @@
{ {
"nodes": { "nodes": {
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1673956053,
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"lowdown-src": {
"flake": false,
"locked": {
"lastModified": 1633514407,
"narHash": "sha256-Dw32tiMjdK9t3ETl5fzGrutQTzh2rufgZV4A/BbxuD4=",
"owner": "kristapsdz",
"repo": "lowdown",
"rev": "d2c2b44ff6c27b936ec27358a2653caaef8f73b8",
"type": "github"
},
"original": {
"owner": "kristapsdz",
"repo": "lowdown",
"type": "github"
}
},
"nix": {
"inputs": {
"lowdown-src": "lowdown-src",
"nixpkgs": "nixpkgs",
"nixpkgs-regression": "nixpkgs-regression"
},
"locked": {
"lastModified": 1676545802,
"narHash": "sha256-EK4rZ+Hd5hsvXnzSzk2ikhStJnD63odF7SzsQ8CuSPU=",
"owner": "domenkozar",
"repo": "nix",
"rev": "7c91803598ffbcfe4a55c44ac6d49b2cf07a527f",
"type": "github"
},
"original": {
"owner": "domenkozar",
"ref": "relaxed-flakes",
"repo": "nix",
"type": "github"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1657693803, "lastModified": 1689422397,
"narHash": "sha256-G++2CJ9u0E7NNTAi9n5G8TdDmGJXcIjkJ3NF8cetQB8=", "narHash": "sha256-fnopownlSBGTBYxGdTdUPM215yG/UEEj3wgheBLIbHs=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "365e1b3a859281cf11b94f87231adeabbdd878a2",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-22.05-small",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-regression": {
"locked": {
"lastModified": 1643052045,
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1684472219,
"narHash": "sha256-cBTmj5Ad5pkU41GkyW/id7/A+s5FUlvj9jr3A35+lIE=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "9c82602247a58de900df71fdcc0cfdae2bf29189", "rev": "45ae0efbbce2aada6d5e8de6ace0c803b08ac9c7",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -102,9 +17,7 @@
}, },
"root": { "root": {
"inputs": { "inputs": {
"flake-compat": "flake-compat", "nixpkgs": "nixpkgs"
"nix": "nix",
"nixpkgs": "nixpkgs_2"
} }
} }
}, },

@ -3,30 +3,26 @@
inputs = { inputs = {
nixpkgs.url = "github:nixos/nixpkgs"; nixpkgs.url = "github:nixos/nixpkgs";
nix.url = "github:domenkozar/nix/relaxed-flakes";
flake-compat = {
url = "github:edolstra/flake-compat";
flake = false;
};
}; };
outputs = { self, nixpkgs, nix, ... }: outputs = inputs@{ self, nixpkgs, ... }:
let let
systems = [ lib = nixpkgs.lib;
"x86_64-linux"
"i686-linux" darwin = [ "x86_64-darwin" "aarch64-darwin" ];
"x86_64-darwin" linux = [ "x86_64-linux" "x86_64-linux-musl" "aarch64-linux" "aarch64-linux-android" "i86_64-linux" ];
"aarch64-linux" allSystems = darwin ++ linux;
"aarch64-darwin"
]; forEachSystem = systems: f: lib.genAttrs systems (system: f system);
forAllSystems = f: builtins.listToAttrs (map (name: { inherit name; value = f name; }) systems); forAllSystems = forEachSystem allSystems;
in in
{ {
packages = forAllSystems (system: packages = forAllSystems (system:
let let
pkgs = import nixpkgs { inherit system; }; pkgs = import nixpkgs { inherit system; };
in in
{ rec {
# e.g. nix build .#xplr
xplr = pkgs.rustPlatform.buildRustPackage rec { xplr = pkgs.rustPlatform.buildRustPackage rec {
name = "xplr"; name = "xplr";
src = ./.; src = ./.;
@ -34,6 +30,14 @@
lockFile = ./Cargo.lock; lockFile = ./Cargo.lock;
}; };
}; };
# e.g. nix build .#cross.x86_64-linux-musl.xplr --impure
cross = forEachSystem (lib.filter (sys: sys != system) allSystems) (targetSystem:
let
crossPkgs = import nixpkgs { localSystem = system; crossSystem = targetSystem; };
in
{ inherit (crossPkgs) xplr; }
);
} }
); );
defaultPackage = forAllSystems (system: self.packages.${system}.xplr); defaultPackage = forAllSystems (system: self.packages.${system}.xplr);
@ -54,6 +58,9 @@
default = pkgs.mkShell { default = pkgs.mkShell {
RUST_BACKTRACE = 1; RUST_BACKTRACE = 1;
# For cross compilation
NIXPKGS_ALLOW_UNSUPPORTED_SYSTEM = 1;
buildInputs = devRequirements; buildInputs = devRequirements;
packages = devRequirements; packages = devRequirements;
}; };

@ -1034,19 +1034,23 @@ impl App {
} }
fn enter(self) -> Result<Self> { fn enter(self) -> Result<Self> {
if let Some(path) = self.focused_node().map(|n| n.absolute_path.clone()) { if let Some(node) = self.focused_node() {
self.change_directory(&path, true) if node.is_dir || node.symlink.as_ref().map(|s| s.is_dir).unwrap_or(false) {
let path = node.absolute_path.clone();
self.change_directory(&path, true)
} else {
Ok(self)
}
} else { } else {
Ok(self) Ok(self)
} }
} }
fn back(self) -> Result<Self> { fn back(self) -> Result<Self> {
if let Some(p) = PathBuf::from(self.pwd.clone()) let pwd = self.pwd.clone();
.parent() if let Some(p) = PathBuf::from(&pwd).parent().and_then(|p| p.to_str()) {
.and_then(|p| p.to_str()) self.change_directory(p, false)
{ .and_then(|a| a.focus_path(&pwd, true))
self.change_directory(p, true)
} else { } else {
Ok(self) Ok(self)
} }
@ -1486,7 +1490,7 @@ impl App {
if dir.parent == self.pwd { if dir.parent == self.pwd {
self.directory_buffer = Some(dir); self.directory_buffer = Some(dir);
// Migh as well refresh the selection // Might as well refresh the selection
self = self.refresh_selection()?; self = self.refresh_selection()?;
}; };
@ -2035,8 +2039,10 @@ impl App {
let read_only = self.config.general.read_only; let read_only = self.config.general.read_only;
let global_kb = &self.config.general.global_key_bindings; let global_kb = &self.config.general.global_key_bindings;
builtin.into_iter() let modes = builtin.into_iter().chain(custom.into_iter());
.chain(custom.into_iter())
std::iter::once((self.mode.name.clone(), self.mode.clone()))
.chain(modes)
.map(|(name, mode)| { .map(|(name, mode)| {
(name, mode.sanitized(read_only, global_kb.clone())) (name, mode.sanitized(read_only, global_kb.clone()))
}) })

@ -206,12 +206,10 @@ pub fn print_msg_in(args: Vec<String>) -> Result<()> {
fn fmt_msg_in(args: Vec<String>) -> Result<String> { fn fmt_msg_in(args: Vec<String>) -> Result<String> {
let msg = match jf::format(args.into_iter().map(Into::into)) { let msg = match jf::format(args.into_iter().map(Into::into)) {
Ok(msg) => msg, Ok(msg) => msg,
Err(jf::Error::Usage) => {
bail!("usage: xplr -m TEMPLATE [VALUE]... [NAME=VALUE]...")
}
Err(jf::Error::Jf(e)) => bail!("xplr -m: {e}"), Err(jf::Error::Jf(e)) => bail!("xplr -m: {e}"),
Err(jf::Error::Json(e)) => bail!("xplr -m: json: {e}"), Err(jf::Error::Json(e)) => bail!("xplr -m: json: {e}"),
Err(jf::Error::Yaml(e)) => bail!("xplr -m: yaml: {e}"), Err(jf::Error::Yaml(e)) => bail!("xplr -m: yaml: {e}"),
Err(jf::Error::Io(e)) => bail!("xplr -m: io: {e}"),
}; };
// validate // validate

@ -680,11 +680,15 @@ xplr.config.general.initial_sorting = {
} }
-- The name of one of the modes to use when xplr loads. -- The name of one of the modes to use when xplr loads.
-- This isn't the default mode. To modify the default mode, overwrite
-- [xplr.config.modes.builtin.default](https://xplr.dev/en/modes#xplrconfigmodesbuiltindefault).
-- --
-- Type: nullable string -- Type: nullable string
xplr.config.general.initial_mode = "default" xplr.config.general.initial_mode = "default"
-- The name of one of the layouts to use when xplr loads. -- The name of one of the layouts to use when xplr loads.
-- This isn't the default layout. To modify the default layout, overwrite
-- [xplr.config.layouts.builtin.default](https://xplr.dev/en/layouts#xplrconfiglayoutsbuiltindefault).
-- --
-- Type: nullable string -- Type: nullable string
xplr.config.general.initial_layout = "default" xplr.config.general.initial_layout = "default"
@ -701,6 +705,17 @@ xplr.config.general.start_fifo = nil
-- Type: [Key Bindings](https://xplr.dev/en/configure-key-bindings#key-bindings) -- Type: [Key Bindings](https://xplr.dev/en/configure-key-bindings#key-bindings)
xplr.config.general.global_key_bindings = { xplr.config.general.global_key_bindings = {
on_key = { on_key = {
["f1"] = {
help = "global help menu",
messages = {
{
BashExec = [===[
[ -z "$PAGER" ] && PAGER="less -+F"
cat -- "${XPLR_PIPE_GLOBAL_HELP_MENU_OUT}" | ${PAGER:?}
]===],
},
},
},
["esc"] = { ["esc"] = {
messages = { messages = {
"PopMode", "PopMode",
@ -852,15 +867,13 @@ xplr.config.node_types.special = {}
-- --
-- ##### Example: Defining Custom Layout -- ##### Example: Defining Custom Layout
-- --
-- ![demo](https://s6.gifyu.com/images/layout.png)
--
-- ```lua -- ```lua
-- xplr.config.layouts.builtin.default = { -- xplr.config.layouts.builtin.default = {
-- Horizontal = { -- Horizontal = {
-- config = { -- config = {
-- margin = 1, -- margin = 1,
-- horizontal_margin = 2, -- horizontal_margin = 1,
-- vertical_margin = 3, -- vertical_margin = 1,
-- constraints = { -- constraints = {
-- { Percentage = 50 }, -- { Percentage = 50 },
-- { Percentage = 50 }, -- { Percentage = 50 },
@ -873,6 +886,21 @@ xplr.config.node_types.special = {}
-- } -- }
-- } -- }
-- ``` -- ```
--
-- Result:
--
-- ```
-- ╭ /home ─────────────╮╭ Help [default] ────╮
-- │ ╭─── path ││. show hidden │
-- │ ├▸[ð Desktop/] ││/ search │
-- │ ├ ð Documents/ ││: action │
-- │ ├ ð Downloads/ ││? global help │
-- │ ├ ð GitHub/ ││G go to bottom │
-- │ ├ ð Music/ ││V select/unselect│
-- │ ├ ð Pictures/ ││ctrl duplicate as │
-- │ ├ ð Public/ ││ctrl next visit │
-- ╰────────────────────╯╰────────────────────╯
-- ```
-- The default layout -- The default layout
-- --
@ -1062,17 +1090,6 @@ xplr.config.modes.builtin.default = {
{ SwitchModeBuiltin = "action" }, { SwitchModeBuiltin = "action" },
}, },
}, },
["?"] = {
help = "global help menu",
messages = {
{
BashExec = [===[
[ -z "$PAGER" ] && PAGER="less -+F"
cat -- "${XPLR_PIPE_GLOBAL_HELP_MENU_OUT}" | ${PAGER:?}
]===],
},
},
},
["G"] = { ["G"] = {
help = "go to bottom", help = "go to bottom",
messages = { messages = {
@ -1295,6 +1312,8 @@ 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.general.global_key_bindings.on_key["f1"]
-- The builtin debug error mode. -- The builtin debug error mode.
-- --
@ -1978,7 +1997,9 @@ xplr.config.modes.builtin.delete = {
messages = { messages = {
{ {
BashExec0 = [===[ BashExec0 = [===[
cat "${XPLR_PIPE_RESULT_OUT:?}" | xargs -0 printf '%q\n' while IFS= read -r -d '' PTH; do
printf '%q\n' "$PTH"
done < "${XPLR_PIPE_RESULT_OUT:?}"
echo echo
read -p "Permanently delete these files? [Y/n]: " ANS read -p "Permanently delete these files? [Y/n]: " ANS
[ "${ANS:-Y}" = "Y" ] || [ "$ANS" = "y" ] || exit 0 [ "${ANS:-Y}" = "Y" ] || [ "$ANS" = "y" ] || exit 0
@ -2006,7 +2027,9 @@ xplr.config.modes.builtin.delete = {
messages = { messages = {
{ {
BashExec0 = [===[ BashExec0 = [===[
cat "${XPLR_PIPE_RESULT_OUT:?}" | xargs -0 printf '%q\n' while IFS= read -r -d '' PTH; do
printf '%q\n' "$PTH"
done < "${XPLR_PIPE_RESULT_OUT:?}"
echo echo
read -p "Permanently delete these files? [Y/n]: " ANS read -p "Permanently delete these files? [Y/n]: " ANS
[ "${ANS:-Y}" = "Y" ] || [ "$ANS" = "y" ] || exit 0 [ "${ANS:-Y}" = "Y" ] || [ "$ANS" = "y" ] || exit 0
@ -2967,7 +2990,8 @@ xplr.fn.builtin.fmt_general_table_row_cols_1 = function(m)
if m.is_broken then if m.is_broken then
r = r .. "×" r = r .. "×"
else else
local symlink_path = xplr.util.shorten(m.symlink.absolute_path) local symlink_path =
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
@ -3076,6 +3100,14 @@ xplr.fn.custom = {}
-- { LogSuccess = "Switched layout" }, -- { LogSuccess = "Switched layout" },
-- { CallLuaSilently = "custom.some_plugin_with_hooks.on_layout_switch" }, -- { CallLuaSilently = "custom.some_plugin_with_hooks.on_layout_switch" },
-- } -- }
--
-- -- Add messages to send when the selection changes
-- --
-- -- Type: list of [Message](https://xplr.dev/en/message#message)s
-- on_selection_change = {
-- { LogSuccess = "Selection changed" },
-- { CallLuaSilently = "custom.some_plugin_with_hooks.on_selection_change" },
-- }
-- } -- }
-- ``` -- ```

@ -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.2", "foo path").is_ok()); assert!(check_version("0.21.3", "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.2", "foo path").is_err()); assert!(check_version("0.20.3", "foo path").is_err());
// Prev bugfix release is OK // Prev bugfix release is OK
assert!(check_version("0.21.1", "foo path").is_ok()); assert!(check_version("0.21.2", "foo path").is_ok());
// Next major release is ERR // Next major release is ERR
assert!(check_version("1.20.2", "foo path").is_err()); assert!(check_version("1.20.3", "foo path").is_err());
// Next minor release is ERR // Next minor release is ERR
assert!(check_version("0.22.2", "foo path").is_err()); assert!(check_version("0.22.3", "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.3", "foo path").is_err()); assert!(check_version("0.21.4", "foo path").is_err());
} }
} }

@ -216,17 +216,20 @@ mod tests {
#[test] #[test]
fn test_relative_to_parent() { fn test_relative_to_parent() {
let path = std::env::current_dir().unwrap(); let path = std::env::current_dir().unwrap().join("docs");
let parent = path.parent().unwrap(); let parent = path.parent().unwrap();
let relative = relative_to(parent, NONE).unwrap(); let base = default().with_base(path.to_str().unwrap());
let relative = relative_to(parent, Some(&base)).unwrap();
assert_eq!(relative, PathBuf::from("..")); assert_eq!(relative, PathBuf::from(".."));
let relative = relative_to(parent, Some(&default().with_prefix_dots())).unwrap(); let relative =
relative_to(parent, Some(&base.clone().with_prefix_dots())).unwrap();
assert_eq!(relative, PathBuf::from("..")); assert_eq!(relative, PathBuf::from(".."));
let relative = let relative =
relative_to(parent, Some(&default().without_suffix_dots())).unwrap(); relative_to(parent, Some(&base.clone().without_suffix_dots())).unwrap();
assert_eq!( assert_eq!(
relative, relative,
PathBuf::from("../..").join(parent.file_name().unwrap()) PathBuf::from("../..").join(parent.file_name().unwrap())
@ -234,12 +237,12 @@ mod tests {
let relative = relative_to( let relative = relative_to(
parent, parent,
Some(&default().with_prefix_dots().without_suffix_dots()), Some(&base.clone().with_prefix_dots().without_suffix_dots()),
) )
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
relative, relative,
PathBuf::from("../..").join(parent.file_name().unwrap()) PathBuf::from("../..").join(parent.clone().file_name().unwrap())
); );
} }

@ -40,15 +40,14 @@ pub fn get_tty() -> Result<fs::File> {
// returns physical path. As a workaround, this function tries to use `PWD` // returns physical path. As a workaround, this function tries to use `PWD`
// environment variable that is configured by shell. // environment variable that is configured by shell.
fn get_current_dir() -> Result<PathBuf, std::io::Error> { fn get_current_dir() -> Result<PathBuf, std::io::Error> {
let cur = std::env::current_dir();
if let Ok(pwd) = std::env::var("PWD") { if let Ok(pwd) = std::env::var("PWD") {
if pwd.is_empty() { if pwd.is_empty() {
cur std::env::current_dir()
} else { } else {
Ok(PathBuf::from(pwd)) Ok(PathBuf::from(pwd))
} }
} else { } else {
cur std::env::current_dir()
} }
} }

@ -20,7 +20,7 @@ use tui::backend::Backend;
use tui::layout::Rect as TuiRect; use tui::layout::Rect as TuiRect;
use tui::layout::{Constraint as TuiConstraint, Direction, Layout as TuiLayout}; use tui::layout::{Constraint as TuiConstraint, Direction, Layout as TuiLayout};
use tui::style::{Color, Modifier as TuiModifier, Style as TuiStyle}; use tui::style::{Color, Modifier as TuiModifier, Style as TuiStyle};
use tui::text::{Span, Spans, Text}; use tui::text::{Line, Span, Text};
use tui::widgets::{ use tui::widgets::{
Block, BorderType as TuiBorderType, Borders as TuiBorders, Cell, List, ListItem, Block, BorderType as TuiBorderType, Borders as TuiBorders, Cell, List, ListItem,
Paragraph, Row, Table, Paragraph, Row, Table,
@ -329,6 +329,7 @@ impl Into<TuiStyle> for Style {
TuiStyle { TuiStyle {
fg: self.fg, fg: self.fg,
bg: self.bg, bg: self.bg,
underline_color: None,
add_modifier: TuiModifier::from_bits_truncate(xor(self.add_modifiers)), add_modifier: TuiModifier::from_bits_truncate(xor(self.add_modifiers)),
sub_modifier: TuiModifier::from_bits_truncate(xor(self.sub_modifiers)), sub_modifier: TuiModifier::from_bits_truncate(xor(self.sub_modifiers)),
} }
@ -401,22 +402,22 @@ impl Into<nu_ansi_term::Style> for Style {
style.add_modifiers.as_ref().map_or(false, f) style.add_modifiers.as_ref().map_or(false, f)
} }
nu_ansi_term::Style { let mut style = nu_ansi_term::Style::new();
foreground: self.fg.and_then(convert_color), style.foreground = self.fg.and_then(convert_color);
background: self.bg.and_then(convert_color), style.background = self.bg.and_then(convert_color);
is_bold: match_modifiers(&self, |m| m.contains(&Modifier::Bold)), style.is_bold = match_modifiers(&self, |m| m.contains(&Modifier::Bold));
is_dimmed: match_modifiers(&self, |m| m.contains(&Modifier::Dim)), style.is_dimmed = match_modifiers(&self, |m| m.contains(&Modifier::Dim));
is_italic: match_modifiers(&self, |m| m.contains(&Modifier::Italic)), style.is_italic = match_modifiers(&self, |m| m.contains(&Modifier::Italic));
is_underline: match_modifiers(&self, |m| m.contains(&Modifier::Underlined)), style.is_underline =
is_blink: match_modifiers(&self, |m| { match_modifiers(&self, |m| m.contains(&Modifier::Underlined));
m.contains(&Modifier::SlowBlink) || m.contains(&Modifier::RapidBlink) style.is_blink = match_modifiers(&self, |m| {
}), m.contains(&Modifier::SlowBlink) || m.contains(&Modifier::RapidBlink)
is_reverse: match_modifiers(&self, |m| m.contains(&Modifier::Reversed)), });
is_hidden: match_modifiers(&self, |m| m.contains(&Modifier::Hidden)), style.is_reverse = match_modifiers(&self, |m| m.contains(&Modifier::Reversed));
is_strikethrough: match_modifiers(&self, |m| { style.is_hidden = match_modifiers(&self, |m| m.contains(&Modifier::Hidden));
m.contains(&Modifier::CrossedOut) style.is_strikethrough =
}), match_modifiers(&self, |m| m.contains(&Modifier::CrossedOut));
} style
} }
} }
@ -669,6 +670,7 @@ fn draw_table<B: Backend>(
let header_height = app_config.general.table.header.height.unwrap_or(1); let header_height = app_config.general.table.header.height.unwrap_or(1);
let height: usize = let height: usize =
(layout_size.height.max(header_height + 2) - (header_height + 2)).into(); (layout_size.height.max(header_height + 2) - (header_height + 2)).into();
let row_style = app_config.general.table.row.style.to_owned();
let rows = app let rows = app
.directory_buffer .directory_buffer
@ -767,17 +769,17 @@ fn draw_table<B: Backend>(
c.format.as_ref().map(|f| { c.format.as_ref().map(|f| {
let out = lua::call(lua, f, v.clone()) let out = lua::call(lua, f, v.clone())
.unwrap_or_else(|e| format!("{e:?}")); .unwrap_or_else(|e| format!("{e:?}"));
string_to_text(out) (string_to_text(out), c.style.to_owned())
}) })
}) })
.collect::<Vec<Text>>() .collect::<Vec<(Text, Style)>>()
}) })
.unwrap_or_default() .unwrap_or_default()
.iter() .into_iter()
.map(|x| Cell::from(x.to_owned())) .map(|(text, style)| Cell::from(text).style(style.into()))
.collect::<Vec<Cell>>(); .collect::<Vec<Cell>>();
Row::new(cols) Row::new(cols).style(row_style.to_owned().into())
}) })
.collect::<Vec<Row>>() .collect::<Vec<Row>>()
}) })
@ -831,7 +833,10 @@ fn draw_table<B: Backend>(
.to_owned() .to_owned()
.unwrap_or_default() .unwrap_or_default()
.iter() .iter()
.map(|c| Cell::from(c.format.to_owned().unwrap_or_default())) .map(|c| {
Cell::from(c.format.to_owned().unwrap_or_default())
.style(c.style.to_owned().into())
})
.collect::<Vec<Cell>>(), .collect::<Vec<Cell>>(),
) )
.height(header_height) .height(header_height)
@ -878,7 +883,10 @@ fn draw_selection<B: Backend>(
.unwrap_or_else(|| n.absolute_path.clone()); .unwrap_or_else(|| n.absolute_path.clone());
string_to_text(out) string_to_text(out)
}) })
.map(ListItem::new) .map(|i| {
ListItem::new(i)
.style(app.config.general.selection.item.style.to_owned().into())
})
.collect(); .collect();
// Selected items // Selected items
@ -973,7 +981,7 @@ fn draw_input_buffer<B: Backend>(
let width = layout_size.width.max(offset_width) - offset_width; let width = layout_size.width.max(offset_width) - offset_width;
let scroll = input.visual_scroll(width.into()) as u16; let scroll = input.visual_scroll(width.into()) as u16;
let input_buf = Paragraph::new(Spans::from(vec![ let input_buf = Paragraph::new(Line::from(vec![
Span::styled( Span::styled(
app.input.prompt.to_owned(), app.input.prompt.to_owned(),
app.config.general.prompt.style.to_owned().into(), app.config.general.prompt.style.to_owned().into(),
@ -1114,7 +1122,7 @@ fn draw_sort_n_filter<B: Backend>(
format!("({item_count}) ") format!("({item_count}) ")
}; };
let p = Paragraph::new(Spans::from(spans)) let p = Paragraph::new(Line::from(spans))
.block(block(config, format!(" Sort & filter {item_count}"))); .block(block(config, format!(" Sort & filter {item_count}")));
f.render_widget(p, layout_size); f.render_widget(p, layout_size);

Loading…
Cancel
Save