Compare commits

..

No commits in common. 'main' and 'v0.16.2' have entirely different histories.

@ -1,14 +1,4 @@
# Why dynamic linking?
# See https://github.com/sayanarijit/xplr/issues/309
[target.x86_64-unknown-linux-gnu]
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"]

@ -1,6 +0,0 @@
ratatui
crate
ser
enque
noice
ans

@ -1 +0,0 @@
use flake

@ -1,6 +1,6 @@
# These are supported funding model platforms
github: [ sayanarijit ]
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: xplr
ko_fi: sayanarijit

@ -15,21 +15,21 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
# - name: Install mdBook
# uses: peaceiris/actions-mdbook@v1
- name: Install mdBook
uses: peaceiris/actions-mdbook@v1
- name: Render book
run: |
# From cloudflare pages
curl -L https://github.com/rust-lang/mdBook/releases/download/v0.4.15/mdbook-v0.4.15-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
mv docs/CNAME dist
mdbook build -d gh-pages docs/en
mkdir gh-pages
mv docs/en/gh-pages gh-pages/en
mv docs/index.html gh-pages
mv docs/CNAME gh-pages
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
uses: peaceiris/actions-gh-pages@v2.5.1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
allow_empty_commit: true
keep_files: false
publish_dir: dist
emptyCommits: true
keepFiles: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PUBLISH_BRANCH: gh-pages
PUBLISH_DIR: dist
PUBLISH_DIR: gh-pages

@ -3,7 +3,7 @@ name: Continuous Deployment
on:
push:
tags:
- "v*.*.*"
- 'v*.*.*'
workflow_dispatch:
jobs:
@ -12,84 +12,50 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
build:
- macos
- macos-aarch64
- linux
- linux-musl
- linux-aarch64
- linux-arm
os:
- macos-latest
- ubuntu-latest
- ubuntu-latest
rust: [stable]
include:
# See the list: https://github.com/cross-rs/cross
- build: macos
os: macos-latest
- os: macos-latest
artifact_prefix: macos
target: x86_64-apple-darwin
- build: macos-aarch64
os: macos-latest
target: aarch64-apple-darwin
- build: linux
os: ubuntu-latest
binary_postfix: ''
- os: ubuntu-latest
artifact_prefix: linux
target: x86_64-unknown-linux-gnu
- build: linux-musl
os: ubuntu-latest
binary_postfix: ''
- os: ubuntu-latest
artifact_prefix: linux-musl
target: x86_64-unknown-linux-musl
- build: linux-aarch64
os: ubuntu-latest
target: aarch64-unknown-linux-gnu
- build: linux-arm
os: ubuntu-latest
target: arm-unknown-linux-gnueabihf
binary_postfix: ''
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Installing Rust toolchain
uses: dtolnay/rust-toolchain@stable
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
- name: Installing needed macOS dependencies
if: matrix.os == 'macos-latest'
run: brew install openssl@1.1
- name: Installing needed Ubuntu dependencies
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update --fix-missing
sudo apt-get install -y --no-install-recommends liblua5.1-0-dev libluajit-5.1-dev gcc pkg-config curl git make ca-certificates
sudo apt-get install -y snapd
# sudo snap install snapcraft --classic
# sudo snap install multipass --classic --beta
- if: matrix.build == 'linux-musl'
run: sudo apt-get install -y musl-tools
- if: matrix.build == 'linux-aarch64'
run: sudo apt-get install -y gcc-aarch64-linux-gnu
- if: matrix.build == 'linux-arm'
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
sudo apt-get update
sudo apt-get install -y -qq pkg-config libssl-dev libxcb1-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev
- name: Checking out sources
uses: actions/checkout@v1
- name: Running cargo build
run: cargo build --locked --release --target ${{ matrix.target }}
# - name: Running snapcraft build
# run: |
# snapcraft
# printf ' [ INFO ] generated <snapcraft> files include:\n'
# command ls -Al | grep "\.snap" | awk '{ print $9 }'
# mv ./*.snap ./xplr.snap
uses: actions-rs/cargo@v1
with:
command: build
toolchain: ${{ matrix.rust }}
args: --locked --release --target ${{ matrix.target }}
- name: Install gpg secret key
run: |
@ -100,8 +66,9 @@ jobs:
shell: bash
run: |
cd target/${{ matrix.target }}/release
BINARY_NAME=xplr
RELEASE_NAME=$BINARY_NAME-${{ matrix.build }}
BINARY_NAME=xplr${{ matrix.binary_postfix }}
strip $BINARY_NAME
RELEASE_NAME=xplr-${{ matrix.artifact_prefix }}
tar czvf $RELEASE_NAME.tar.gz $BINARY_NAME
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
@ -110,23 +77,17 @@ jobs:
uses: softprops/action-gh-release@v1
with:
files: |
target/${{ matrix.target }}/release/xplr-${{ matrix.build }}.tar.gz
target/${{ matrix.target }}/release/xplr-${{ matrix.build }}.sha256
target/${{ matrix.target }}/release/xplr-${{ matrix.build }}.tar.gz.asc
xplr.snap
target/${{ matrix.target }}/release/xplr-${{ matrix.artifact_prefix }}.tar.gz
target/${{ matrix.target }}/release/xplr-${{ matrix.artifact_prefix }}.sha256
target/${{ matrix.target }}/release/xplr-${{ matrix.artifact_prefix }}.tar.gz.asc
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# - name: Cleaning snapcraft
# run: |
# command rm --verbose ./*.snap
# snapcraft clean
publish-gpg-signature:
name: Publishing GPG signature
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Install gpg secret key
run: |
cat <(echo -e "${{ secrets.GPG_SECRET }}") | gpg --batch --import
@ -135,7 +96,7 @@ jobs:
- name: Signing archive with GPG
run: |
VERSION=${GITHUB_REF##*v}
git -c tar.tar.gz.command='gzip -cn' archive -o xplr-${VERSION:?}.tar.gz --format tar.gz --prefix "xplr-${VERSION:?}/" "v${VERSION}"
git archive -o xplr-${VERSION:?}.tar.gz --format tar.gz --prefix "xplr-${VERSION:?}/" "v${VERSION}"
cat <(echo "${{ secrets.GPG_PASS }}") | gpg --pinentry-mode loopback --passphrase-fd 0 --detach-sign --armor "xplr-${VERSION:?}.tar.gz"
mv "xplr-${VERSION:?}.tar.gz.asc" "source.tar.gz.asc"
@ -151,16 +112,15 @@ jobs:
name: Publishing to Cargo
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- run: |
sudo apt-get update --fix-missing
sudo apt-get update
sudo apt-get install -y -qq pkg-config libssl-dev libxcb1-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev
- run: cargo publish --allow-dirty
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_API_KEY }}
- uses: actions-rs/cargo@v1
with:
command: publish
args: --token ${{ secrets.CARGO_API_KEY }} --allow-dirty

@ -11,98 +11,51 @@ jobs:
name: Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
- run: cargo check
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
profile: minimal
override: true
- uses: actions-rs/cargo@v1
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:
ignore_words_file: .codespellignore
command: check
test:
name: Test Suite
runs-on: ${{ matrix.os }}
needs:
- check
- fmt
- clippy
- spellcheck
strategy:
matrix:
build:
- macos
- macos-aarch64
- linux
- linux-musl
- linux-aarch64
- linux-arm
os:
- macos-latest
- ubuntu-latest
- ubuntu-latest
rust: [stable]
include:
# See the list: https://github.com/cross-rs/cross
- build: macos
os: macos-latest
- os: macos-latest
artifact_prefix: macos
target: x86_64-apple-darwin
- build: macos-aarch64
os: macos-latest
target: aarch64-apple-darwin
- build: linux
os: ubuntu-latest
binary_postfix: ''
- os: ubuntu-latest
artifact_prefix: linux
target: x86_64-unknown-linux-gnu
- build: linux-musl
os: ubuntu-latest
binary_postfix: ''
- os: ubuntu-latest
artifact_prefix: linux-musl
target: x86_64-unknown-linux-musl
- build: linux-aarch64
os: ubuntu-latest
target: aarch64-unknown-linux-gnu
- build: linux-arm
os: ubuntu-latest
target: arm-unknown-linux-gnueabihf
binary_postfix: ''
env:
RUST_BACKTRACE: full
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Installing Rust toolchain
uses: dtolnay/rust-toolchain@stable
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
- name: Installing needed macOS dependencies
if: matrix.os == 'macos-latest'
@ -111,34 +64,67 @@ jobs:
- name: Installing needed Ubuntu dependencies
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update --fix-missing
sudo apt-get install -y --no-install-recommends liblua5.1-0-dev libluajit-5.1-dev gcc pkg-config curl git make ca-certificates
- 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
- if: matrix.build == 'linux-arm'
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
sudo apt-get update
sudo apt-get install -y -qq pkg-config libssl-dev libxcb1-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev
- run: cargo build --target ${{ matrix.target }}
- name: Build
uses: actions-rs/cargo@v1
with:
command: build
toolchain: ${{ matrix.rust }}
args: --target ${{ matrix.target }}
- if: matrix.build == 'macos' || matrix.build == 'linux'
run: cargo test --target ${{ matrix.target }}
- name: Test
uses: actions-rs/cargo@v1
with:
command: test
toolchain: ${{ matrix.rust }}
args: --target ${{ matrix.target }}
# bench:
# name: Benchmarks
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v3
# - uses: dtolnay/rust-toolchain@stable
# - uses: actions/checkout@v2
# - uses: actions-rs/toolchain@v1
# with:
# toolchain: stable
# profile: minimal
# override: true
# # 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: cargo bench
# - uses: actions-rs/cargo@v1
# 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

@ -1,23 +0,0 @@
name: "Push Binary Cache for Nix"
on:
pull_request:
push:
jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- uses: nixbuild/nix-quick-install-action@v19
with:
nix_conf: experimental-features = nix-command flakes
- uses: cachix/cachix-action@v11
with:
name: xplr
authtoken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- run: nix profile install .
- name: Run tests
run: |
xplr --version

11
.gitignore vendored

@ -12,14 +12,3 @@ book/
# Jetbrains config
.idea/
.venv/
# direnv
.direnv/
# nix
result
# test files
/init.lua

@ -5,7 +5,7 @@
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socioeconomic status,
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our
community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
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
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
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
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
## Enforcement Responsibilities
@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**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.
**Consequence**: A permanent ban from any sort of public interaction within

1634
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -1,94 +1,54 @@
[[bin]]
name = 'xplr'
[[bench]]
name = 'criterion'
harness = false
path = './benches/criterion.rs'
[package]
name = 'xplr'
version = '0.21.9'
authors = ['Arijit Basu <hi@arijitbasu.in>']
edition = '2021'
description = 'A hackable, minimal, fast TUI file explorer'
license = 'MIT'
readme = 'README.md'
repository = 'https://github.com/sayanarijit/xplr'
homepage = 'https://xplr.dev'
documentation = 'https://xplr.dev/en'
keywords = ['terminal', 'file', 'explorer', 'manager', 'tui']
categories = ['command-line-interface', 'command-line-utilities']
include = ['src/**/*', 'docs/en/src/**/*', 'LICENSE', 'README.md']
name = "xplr"
version = "0.16.2" # Update lua.rs
authors = ["Arijit Basu <sayanarijit@gmail.com>"]
edition = "2018"
description = "A hackable, minimal, fast TUI file explorer"
license = "MIT"
readme = "README.md"
repository = "https://github.com/sayanarijit/xplr"
homepage = "https://xplr.dev"
documentation = "https://xplr.dev/en"
keywords = ["terminal", "file", "explorer", "manager", "tui"]
categories = ["command-line-interface", "command-line-utilities"]
include = ["src/**/*", "docs/en/src/**/*", "LICENSE", "README.md"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[[bin]]
name = "xplr"
[dependencies]
libc = "0.2.155"
humansize = "2.1.3"
tui = { version = "0.16.0", default-features = false, features = ['crossterm', 'serde'] }
crossterm = "0.22.1"
dirs = "4.0.0"
serde = { version = "1.0.130", features = ["derive"] }
serde_yaml = "0.8.21"
mime_guess = "2.0.3"
anyhow = "1.0.45"
chrono = { version = "0.4.19", features = ["serde"] }
lazy_static = "1.4.0"
indexmap = { version = "1.7.0", features = ["serde"] }
natord = "1.0.9"
anyhow = "1.0.86"
serde_yaml = "0.9.34"
crossterm = { version = "0.27.0", features = [], default-features = false }
ansi-to-tui = "=3.1.0"
regex = "1.10.5"
gethostname = "0.4.3"
serde_json = "1.0.117"
path-absolutize = "3.1.1"
which = "6.0.1"
nu-ansi-term = "0.50.0"
textwrap = "0.16"
snailquote = "0.3.1"
skim = { version = "0.10.4", default-features = false }
time = { version = "0.3.36", features = ["serde", "local-offset", "formatting", "macros"] }
jf = "0.6.2"
xdg = "2.5.2"
home = "0.5.9"
[dependencies.lscolors]
version = "0.17.0"
default-features = false
features = ["nu-ansi-term"]
[dependencies.lazy_static]
version = "1.4.0"
default-features = false
[dependencies.mime_guess]
version = "2.0.4"
default-features = false
[dependencies.tui]
version = "=0.26.1" # https://github.com/ratatui-org/ratatui/issues/1032
default-features = false
features = ['crossterm', 'serde']
package = 'ratatui'
[dependencies.serde]
version = "1.0.203"
features = []
default-features = false
[dependencies.indexmap]
version = "2.2.6"
features = ['serde']
[dependencies.mlua]
version = "0.9.8"
features = ['luajit', 'serialize', 'send']
[dependencies.tui-input]
version = "0.8.0"
features = ['serde']
humansize = "1.1.1"
mlua = { version = "0.6.6", features = ["luajit", "vendored", "serialize", "send"] }
ansi-to-tui = "0.4.1"
libc = "0.2.106"
tui-input = { version = "0.1.2", features = [ "serde" ] }
[dev-dependencies]
criterion = "0.5.1"
assert_cmd = "2.0.14"
criterion = "0.3.5"
assert_cmd = "2.0.2"
[[bench]]
name = "criterion"
harness = false
path = "./benches/criterion.rs"
# https://github.com/johnthagen/min-sized-rust
[profile.release]
lto = true
codegen-units = 1
panic = 'abort'
strip = true
[features]
default = ["vendored-lua"]
vendored-lua = ["mlua/vendored"]

@ -12,54 +12,52 @@ A hackable, minimal, fast TUI file explorer
<img src="https://img.shields.io/crates/v/xplr.svg" />
</a>
</p>
<a href="https://github.com/sayanarijit/xplr/commits">
<img src="https://img.shields.io/github/commit-activity/m/sayanarijit/xplr" />
</a>
<p align="center">
<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>
https://user-images.githubusercontent.com/11632726/166747867-8a4573f2-cb2f-43a6-a23d-c99fc30c6594.mp4
</p>
<p align="center">
<img src="https://s3.gifyu.com/images/xplr-0.5.0.gif" />
</p>
<h3 align="center">
[<a href="https://xplr.dev/en/install">Install</a>]
[<a href="https://xplr.dev/en">Documentation</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-integrations">Integrations</a>]
[<a href="https://xplr.dev/en/community">Community</a>]
</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
productivity by being a flexible, interactive orchestrator for the ever growing
awesome command-line utilities that work with the file-system.
To achieve its goal, xplr strives to be a fast, minimal and more importantly,
To achieve its goal, `xplr` strives to be a fast, minimal and more importantly,
hackable file explorer.
xplr is not meant to be a replacement for the standard shell commands or the
GUI file managers. Rather, it aims to [integrate them all][14] and expose an
intuitive, scriptable, [keyboard controlled][2],
[real-time visual interface][1], also being an ideal candidate for [further
integration][15], enabling you to achieve insane terminal productivity.
`xplr` is not meant to be a replacement for the standard shell commands or the
GUI file managers. Rather, it aims to integrate them all and expose an
intuitive, scriptable, keyboard controlled, real-time visual interface, also
being an ideal candidate for further integration, enabling the users to achieve
insane terminal productivity.
## Introductions & Reviews
- [[VIDEO] XPLR: Insanely Hackable Lua File Manager ~ Brodie Robertson](https://youtu.be/MaVRtYh1IRU)
- [[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
Package maintainers please refer to the [RELEASE.md](./RELEASE.md).
<a href="https://repology.org/project/xplr/versions"><img src="https://repology.org/badge/vertical-allrepos/xplr.svg" /></a>
## Backers
<a href="https://opencollective.com/xplr#backer"><img src="https://opencollective.com/xplr/tiers/backer.svg?width=890" /></a>
[1]: https://xplr.dev/en/layouts
[2]: https://xplr.dev/en/configure-key-bindings
[14]: https://xplr.dev/en/awesome-plugins#integration
[15]: https://xplr.dev/en/awesome-integrations

@ -1,17 +1,7 @@
# Build
See [install.md](./docs/en/src/install.md#build-from-source)
Note: xplr ships with vendored luajit. If the platform can't compile this,
you need to compile using `--no-default-features` argument to avoid using
vendored luajit, so that you can static link luajit yourself.
# Release
The final binary `target/release/xplr` can be shipped with the following assets
- [License](./LICENSE)
- [Desktop Entry](./assets/desktop/xplr.desktop)
- [Desktop Icons](./assets/icon/)
- [Offline Docs](./docs/en/src)
- [Lua Configuration Example](./src/init.lua)
Process
-------
- [ ] Update `Cargo.toml` version.
- [ ] Update `config.yml` version.
- [ ] Update `Config::is_compatible()` and `Config::upgrade_notification()`.
- [ ] Publish GitHub tag.
- [ ] Update Wiki and docs.

@ -1,7 +1,7 @@
[Desktop Entry]
Type=Application
Name=xplr
Comment=Terminal file explorer
Comment=Terminal file manager
Exec=xplr
Terminal=true
Icon=xplr

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

@ -14,13 +14,13 @@ const PWD: &str = "/tmp/xplr_bench";
fn navigation_benchmark(c: &mut Criterion) {
fs::create_dir_all(PWD).unwrap();
(1..10000).for_each(|i| {
fs::File::create(std::path::Path::new(PWD).join(i.to_string())).unwrap();
fs::File::create(std::path::Path::new(PWD).join(i.to_string()))
.unwrap();
});
let lua = mlua::Lua::new();
let mut app =
app::App::create("xplr".into(), None, PWD.into(), &lua, None, [].into())
.expect("failed to create app");
let mut app = app::App::create(PWD.into(), &lua, None, [].into())
.expect("failed to create app");
app = app
.clone()
@ -94,14 +94,13 @@ fn navigation_benchmark(c: &mut Criterion) {
fn draw_benchmark(c: &mut Criterion) {
fs::create_dir_all(PWD).unwrap();
(1..10000).for_each(|i| {
fs::File::create(std::path::Path::new(PWD).join(i.to_string())).unwrap();
fs::File::create(std::path::Path::new(PWD).join(i.to_string()))
.unwrap();
});
let lua = mlua::Lua::new();
let mut ui = ui::UI::new(&lua);
let mut app =
app::App::create("xplr".into(), None, PWD.into(), &lua, None, [].into())
.expect("failed to create app");
let mut app = app::App::create(PWD.into(), &lua, None, [].into())
.expect("failed to create app");
app = app
.clone()
@ -122,7 +121,7 @@ fn draw_benchmark(c: &mut Criterion) {
c.bench_function("draw on terminal", |b| {
b.iter(|| {
terminal.draw(|f| ui.draw(f, &app)).unwrap();
terminal.draw(|f| ui::draw(f, &app, &lua)).unwrap();
})
});

@ -1,10 +0,0 @@
(import
(
let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in
fetchTarball {
url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
sha256 = lock.nodes.flake-compat.locked.narHash;
}
)
{ src = ./.; }
).defaultNix

@ -1,16 +1,11 @@
[book]
authors = ['Arijit Basu <hi@arijitbasu.in>']
title = 'xplr book'
description = 'A hackable, minimal, fast TUI file explorer'
src = 'src'
language = 'en'
authors = ["Arijit Basu <sayanarijit@gmail.com>"]
title = "xplr book"
description = "A hackable, minimal, fast TUI file explorer"
src = "src"
language = "en"
[output.html]
site-url = '/xplr/en/'
git-repository-url = 'https://github.com/sayanarijit/xplr'
edit-url-template = 'https://github.com/sayanarijit/xplr/edit/main/docs/en/{path}'
default-theme = 'dark'
preferred-dark-theme = 'coal'
[output.linkcheck]
optional = true
site-url = "/xplr/en/"
git-repository-url = "https://github.com/sayanarijit/xplr"
edit-url-template = "https://github.com/sayanarijit/xplr/edit/main/docs/en/{path}"

@ -5,40 +5,30 @@
- [Install][3]
- [Post Install][4]
- [Configuration][5]
- [General][6]
- [Node Types][10]
- [Layouts][9]
- [Modes][7]
- [Concept][32]
- [Sum Type][42]
- [Key Bindings][27]
- [Configure Key Bindings][28]
- [Default Key Bindings][14]
- [Debug Key Bindings][29]
- [Node Type][33]
- [Layout][34]
- [Mode][35]
- [General Config][6]
- [Modes][7]
- [Message][8]
- [Full List of Messages][38]
- [Input Operation][39]
- [Borders][31]
- [Layouts][9]
- [Node Types][10]
- [Style][11]
- [Searching][41]
- [Sorting][12]
- [Filtering][13]
- [Column Renderer][26]
- [Lua Function Calls][36]
- [xplr.util][40]
- [Environment Variables and Pipes][37]
- [Awesome Hacks][30]
- [Plugin][15]
- [Installing Plugins][16]
- [Writing Plugins][17]
- [Awesome Plugins][18]
- [Integration][19]
- [Awesome Integrations][20]
- [TODO][21]
- [Alternatives][22]
- [Upgrade Guide][23]
- [Community][24]
- [Contribute][25]
[1]: introduction.md
[2]: quickstart.md
@ -60,22 +50,12 @@
[18]: awesome-plugins.md
[19]: integration.md
[20]: awesome-integrations.md
[21]: todo.md
[22]: alternatives.md
[23]: upgrade-guide.md
[24]: community.md
[25]: contribute.md
[26]: column-renderer.md
[27]: key-bindings.md
[28]: configure-key-bindings.md
[29]: debug-key-bindings.md
[30]: awesome-hacks.md
[31]: borders.md
[32]: concept.md
[33]: node-type.md
[34]: layout.md
[35]: mode.md
[36]: lua-function-calls.md
[37]: environment-variables-and-pipes.md
[38]: messages.md
[39]: input-operation.md
[40]: xplr.util.md
[41]: searching.md
[42]: sum-type.md

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

@ -1,542 +0,0 @@
# Awesome Hacks
Here's a list of cool xplr hacks, i.e. snippets of code that you can just copy
and paste into your [configuration][1] or the appropriate file, that are
too small or too niche for a full fledge [plugin][2].
Do you have something cool to share?
[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
it to xplr with `--extra-config` or `-C`.
```bash
xplr -C hack.lua
```
### cd on quit
Change directory using xplr.
<details>
<summary>Expand for details</summary>
- Author: [@sayanarijit][8]
- Tested on: Linux
NOTE: This is a shell hack, rather than Lua config hack. Add this in
`.bashrc` or `.profile` file in your home directory.
With this alias set, you can navigate directories using xplr by entering
xcd command, and when you quit by pressing enter, you will enter the
directory.
You can of course, quit with plain Quit (i.e. by pressing q) to
gracefully cancel "cd on quit".
```bash
alias xcd='cd "$(xplr --print-pwd-as-result)"'
```
</details>
### Spawn multiple sessions in different tabs (iTerm2)
Creating a new session that starts with iTerm2.
<details>
<summary>Expand for details</summary>
- Author: [@lmburns][9]
- Requires: iTerm2
- Tested on: MacOS
```lua
xplr.config.modes.builtin.default.key_bindings.on_key["ctrl-n"] = {
help = "new session",
messages = {
{ BashExecSilently = [[
osascript <<EOF
tell application "iTerm2"
tell current window
create tab with default profile
tell current session to write text "xplr"
end tell
end tell
]]
},
},
}
```
</details>
### Bookmark
Bookmark files using `m` and fuzzy search bookmarks using backtick.
<details>
<summary>Expand for details</summary>
[![xplr-bookmark.gif][7]][6]
- Author: [@sayanarijit][8]
- Requires: fzf
- Tested on: Linux
```lua
xplr.config.modes.builtin.default.key_bindings.on_key.m = {
help = "bookmark",
messages = {
{
BashExecSilently0 = [===[
PTH="${XPLR_FOCUS_PATH:?}"
PTH_ESC=$(printf %q "$PTH")
if echo "${PTH:?}" >> "${XPLR_SESSION_PATH:?}/bookmarks"; then
"$XPLR" -m 'LogSuccess: %q' "$PTH_ESC added to bookmarks"
else
"$XPLR" -m 'LogError: %q' "Failed to bookmark $PTH_ESC"
fi
]===],
},
},
}
xplr.config.modes.builtin.default.key_bindings.on_key["`"] = {
help = "go to bookmark",
messages = {
{
BashExec0 = [===[
PTH=$(cat "${XPLR_SESSION_PATH:?}/bookmarks" | fzf --no-sort)
PTH_ESC=$(printf %q "$PTH")
if [ "$PTH" ]; then
"$XPLR" -m 'FocusPath: %q' "$PTH"
fi
]===],
},
},
}
```
</details>
### Persistent, multi-session bookmark
A bookmark mode that allows for a bookmark file to be used throughout multiples
sessions. It is set to the environment variable `$XPLR_BOOKMARK_FILE`. A
bookmark can be added, deleted, or jumped to.
<details>
<summary>Expand for details</summary>
- Author: [@lmburns][9]
- Requires: fzf, sd
- Tested on: MacOS
```lua
-- With `export XPLR_BOOKMARK_FILE="$HOME/bookmarks"`
-- Bookmark: mode binding
xplr.config.modes.builtin.default.key_bindings.on_key["b"] = {
help = "bookmark mode",
messages = {
{ SwitchModeCustom = "bookmark" },
},
}
xplr.config.modes.custom.bookmark = {
name = "bookmark",
key_bindings = {
on_key = {
m = {
help = "bookmark dir",
messages = {
{
BashExecSilently0 = [[
PTH="${XPLR_FOCUS_PATH:?}"
if [ -d "${PTH}" ]; then
PTH="${PTH}"
elif [ -f "${PTH}" ]; then
PTH=$(dirname "${PTH}")
fi
PTH_ESC=$(printf %q "$PTH")
if echo "${PTH:?}" >> "${XPLR_BOOKMARK_FILE:?}"; then
"$XPLR" -m 'LogSuccess: %q' "$PTH_ESC added to bookmarks"
else
"$XPLR" -m 'LogError: %q' "Failed to bookmark $PTH_ESC"
fi
]],
},
"PopMode",
},
},
g = {
help = "go to bookmark",
messages = {
{
BashExec0 = [===[
PTH=$(cat "${XPLR_BOOKMARK_FILE:?}" | fzf --no-sort)
if [ "$PTH" ]; then
"$XPLR" -m 'FocusPath: %q' "$PTH"
fi
]===],
},
"PopMode",
},
},
d = {
help = "delete bookmark",
messages = {
{
BashExec0 = [[
PTH=$(cat "${XPLR_BOOKMARK_FILE:?}" | fzf --no-sort)
sd "$PTH\n" "" "${XPLR_BOOKMARK_FILE:?}"
]],
},
"PopMode",
},
},
esc = {
help = "cancel",
messages = {
"PopMode",
},
},
},
},
}
```
</details>
### Another bookmark manager type thing, taken from [wfxr's zsh plugin][13].
Another bookmark manager type thing, taken from [wfxr's zsh plugin][13] which has colored output with fzf.
<details>
<summary>Expand for details</summary>
- Author: [@lmburns][9]
- Requires: fzf, exa
- Tested on: MacOS
```lua
xplr.config.modes.builtin.go_to.key_bindings.on_key.b = {
help = "bookmark jump",
messages = {
"PopMode",
{ BashExec0 = [===[
field='\(\S\+\s*\)'
esc=$(printf '\033')
N="${esc}[0m"
R="${esc}[31m"
G="${esc}[32m"
Y="${esc}[33m"
B="${esc}[34m"
pattern="s#^${field}${field}${field}${field}#$Y\1$R\2$N\3$B\4$N#"
PTH=$(sed 's#: # -> #' "$PATHMARKS_FILE"| nl| column -t \
| gsed "${pattern}" \
| fzf --ansi \
--height '40%' \
--preview="echo {}|sed 's#.*-> ##'| xargs exa --color=always" \
--preview-window="right:50%" \
| sed 's#.*-> ##')
if [ "$PTH" ]; then
"$XPLR" -m 'ChangeDirectory: %q' "$PTH"
fi
]===]
},
}
}
```
</details>
### Fuzzy search history
Fuzzy search the last visited directories.
<details>
<summary>Expand for details</summary>
- Author: [@sayanarijit][8]
- Requires: fzf
- Tested on: Linux
```lua
xplr.config.modes.builtin.go_to.key_bindings.on_key.h = {
help = "history",
messages = {
"PopMode",
{
BashExec0 = [===[
PTH=$(cat "${XPLR_PIPE_HISTORY_OUT:?}" | sort -z -u | fzf --read0)
if [ "$PTH" ]; then
"$XPLR" -m 'ChangeDirectory: %q' "$PTH"
fi
]===],
},
},
}
```
</details>
### Batch rename
Batch rename the selected or visible files and directories in $PWD.
<details>
<summary>Expand for details</summary>
[![xplr-rename.gif][11]][10]
- Author: [@sayanarijit][8]
- Requires: [pipe-rename][12]
- Tested on: Linux
```lua
xplr.config.modes.builtin.default.key_bindings.on_key.R = {
help = "batch rename",
messages = {
{
BashExec = [===[
SELECTION=$(cat "${XPLR_PIPE_SELECTION_OUT:?}")
NODES=${SELECTION:-$(cat "${XPLR_PIPE_DIRECTORY_NODES_OUT:?}")}
if [ "$NODES" ]; then
echo -e "$NODES" | renamer
"$XPLR" -m ExplorePwdAsync
fi
]===],
},
},
}
```
</details>
### Serve $PWD
Serve $PWD using a static web server via LAN.
<details>
<summary>Expand for details</summary>
- Author: [@sayanarijit][8]
- Requires: [sfz][14], fzf
- Tested on: Linux
```lua
xplr.config.modes.builtin.default.key_bindings.on_key.S = {
help = "serve $PWD",
messages = {
{
BashExec0 = [===[
IP=$(ip addr | grep -w inet | cut -d/ -f1 | grep -Eo '[0-9]{1,3}\.[0-9]{ 1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | fzf --prompt 'Select IP > ')
echo "IP: ${IP:?}"
read -p "Port (default 5000): " PORT
echo
sfz --all --cors --no-ignore --bind ${IP:?} --port ${PORT:-5000} . &
sleep 1 && read -p '[press enter to exit]'
kill -9 %1
]===],
},
},
}
```
</details>
### Image viewer (imv)
Preview images using [imv][17].
<details>
<summary>Expand for details</summary>
- Author: [@sayanarijit][8]
- Requires: [imv][17], [xdotool][18]
- Tested on: Linux, FreeBSD 13.1-RELEASE
```lua
xplr.config.modes.builtin.default.key_bindings.on_key.P = {
help = "preview",
messages = {
{
BashExecSilently0 = [===[
FIFO_PATH="/tmp/xplr.fifo"
if [ -e "$FIFO_PATH" ]; then
"$XPLR" -m StopFifo
rm -f -- "$FIFO_PATH"
else
mkfifo "$FIFO_PATH"
"$HOME/.local/bin/imv-open.sh" "$FIFO_PATH" "$XPLR_FOCUS_PATH" &
"$XPLR" -m 'StartFifo: %q' "$FIFO_PATH"
fi
]===],
},
},
}
```
$HOME/.local/bin/imv-open.sh
```bash
#!/usr/bin/env bash
FIFO_PATH="$1"
IMAGE="$2"
MAINWINDOW="$(xdotool getactivewindow)"
IMV_PID="$(pgrep imv)"
if [ ! "$IMV_PID" ]; then
imv "$IMAGE" &
IMV_PID=$!
fi
sleep 0.5
xdotool windowactivate "$MAINWINDOW"
while read -r path; do
imv-msg "$IMV_PID" close all
imv-msg "$IMV_PID" open "$path"
done < "$FIFO_PATH"
imv-msg "$IMV_PID" quit
[ -e "$FIFO_PATH" ] && rm -f -- "$FIFO_PATH"
```
</details>
### Text preview pane
Preview text files in a native xplr pane (should be fast enough).
<details>
<summary>Expand for details</summary>
- Author: [@sayanarijit][8]
- Requires: none
- Tested on: Linux, FreeBSD 13.1-RELEASE
```lua
local function stat(node)
return xplr.util.to_yaml(xplr.util.node(node.absolute_path))
end
local function read(path, height)
local p = io.open(path)
if p == nil then
return nil
end
local i = 0
local res = ""
for line in p:lines() do
if line:match("[^ -~\n\t]") then
p:close()
return
end
res = res .. line .. "\n"
if i == height then
break
end
i = i + 1
end
p:close()
return res
end
xplr.fn.custom.preview_pane = {}
xplr.fn.custom.preview_pane.render = function(ctx)
local title = nil
local body = ""
local n = ctx.app.focused_node
if n and n.canonical then
n = n.canonical
end
if n then
title = { format = n.absolute_path, style = xplr.util.lscolor(n.absolute_path) }
if n.is_file then
body = read(n.absolute_path, ctx.layout_size.height) or stat(n)
else
body = stat(n)
end
end
return { CustomParagraph = { ui = { title = title }, body = body } }
end
local preview_pane = { Dynamic = "custom.preview_pane.render" }
local split_preview = {
Horizontal = {
config = {
constraints = {
{ Percentage = 60 },
{ Percentage = 40 },
},
},
splits = {
"Table",
preview_pane,
},
},
}
xplr.config.layouts.builtin.default =
xplr.util.layout_replace(xplr.config.layouts.builtin.default, "Table", split_preview)
```
</details>
### Tere Navigation
Navigate using the [tere][19] file explorer (defaults to type-to-nav system).
<details>
<summary>Expand for details</summary>
- Author: [@sayanarijit][8]
- Requires: [tere][19]
- Tested on: Linux
```lua
xplr.config.modes.builtin.default.key_bindings.on_key.T = {
help = "tere nav",
messages = {
{ BashExec0 = [["$XPLR" -m 'ChangeDirectory: %q' "$(tere)"]] },
},
}
```
</details>
## Also See:
- [Awesome Plugins][15]
- [Awesome Integrations][16]
[1]: configuration.md
[2]: plugin.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
[6]: https://gifyu.com/image/rGSR
[7]: https://s4.gifyu.com/images/xplr-bookmark.gif
[8]: https://github.com/sayanarijit
[9]: https://github.com/lmburns
[10]: https://gifyu.com/image/rGbo
[11]: https://s4.gifyu.com/images/xplr-rename.gif
[12]: https://github.com/marcusbuffett/pipe-rename
[13]: https://github.com/wfxr/formarks
[14]: https://github.com/weihanglo/sfz
[15]: awesome-plugins.md
[16]: awesome-integrations.md
[17]: https://sr.ht/~exec64/imv
[18]: https://www.semicomplete.com/projects/xdotool
[19]: https://github.com/mgunyho/tere

@ -5,29 +5,27 @@ Here's a list of awesome xplr integrations that you might want to check out.
If none of the following integrations work for you, you can create your own and
[let us know][1].
### Editor
## Categories
- [**fm-nvim**][10] Neovim plugin that lets you use your favorite terminal file managers from within Neovim.
- [**vim-floaterm**][6] xplr integrated in vim-floaterm (Neo)vim plugin.
- [**xplr.vim**][5] Pick files in Vim using xplr.
- [Editor][2]
- [Shell][3]
- [Security Tools][4]
### Github
## Editor
- [**gh-xplr**][13] Explore GitHub repos using xplr via GitHub CLI.
- [**vim-floaterm**][6] xplr integrated in vim-floaterm (Neo)vim plugin.
- [**xplr.nvim**][9] Opens xplr inside nvim, and hosts a msgpack client inside xplr.
- [**xplr.vim**][5] Pick files in Vim using xplr.
- [**fm-nvim**][10] Neovim plugin that lets you use your favorite terminal file managers from within Neovim.
### Shell
## Shell
- [**powerlevel10k**][7] Powerlevel10k prompt for xplr shell.
### Security Tools
## Security Tools
- [**gpg-tui**][8] Import GPG certificates using xplr.
## Also See:
- [Awesome Hacks][11]
- [Awesome Plugins][12]
[1]: https://github.com/sayanarijit/xplr/discussions/categories/show-and-tell
[2]: #editor
[3]: #shell
@ -36,7 +34,5 @@ If none of the following integrations work for you, you can create your own and
[6]: https://github.com/voldikss/vim-floaterm#xplr
[7]: https://github.com/romkatv/powerlevel10k/blob/191d1b89e325ee3b6d2d75a394654aaf4f077a7c/internal/p10k.zsh#L4756-L4768
[8]: https://github.com/orhun/gpg-tui#importreceive
[9]: https://github.com/fhill2/xplr.nvim
[10]: https://github.com/is0n/fm-nvim
[11]: awesome-hacks.md
[12]: awesome-plugins.md
[13]: https://github.com/sayanarijit/gh-xplr

@ -1,69 +1,55 @@
# Awesome Plugins
Here's a list of awesome xplr plugins that you might want to [check out][48]. If none
Here's a list of awesome xplr plugins that you might want to check out. If none
of the following plugins work for you, it's very easy to
[write your own][1].
### Extension
## Categories
- [**sayanarijit/command-mode.xplr**][37] The missing command mode for xplr.
- [**igorepst/context-switch.xplr**][42] Context switch plugin for xplr.
- [**sayanarijit/dual-pane.xplr**][43] Implements support for dual-pane navigation into xplr.
- [**sayanarijit/map.xplr**][38] Visually inspect and interactively execute batch commands using xplr.
- [**sayanarijit/offline-docs.xplr**][51] Fetch the appropriate version of xplr docs and browse offline.
- [**sayanarijit/regex-search.xplr**][55] Bring back the regex based search in xplr.
- [**sayanarijit/registers.xplr**][49] Use multiple registers to store the selected paths.
- [**sayanarijit/tree-view.xplr**][61] Hackable tree view for xplr
- [**sayanarijit/tri-pane.xplr**][56] xplr plugin that implements ranger-like three pane layout.
- [**sayanarijit/type-to-nav.xplr**][28] Inspired by [nnn's type-to-nav mode][29] for xplr,
- [Extension][32]
- [Integration][2]
- [Theme][3]
## Extension
- [**comex.xplr**][21] One xplr plugin to compress and extract them all.
- [**command-mode.xplr**][37] The missing command mode for xplr.
- [**context-switch.xplr**][42] Context switch plugin for xplr.
- [**map.xplr**][38] Visually inspect and interactively execute batch commands using xplr.
- [**type-to-nav.xplr**][28] Inspired by [nnn's type-to-nav mode][29] for xplr,
with some tweaks.
- [**dtomvan/term.xplr**][39] Terminal integration for xplr.
- [**sayanarijit/wl-clipboard.xplr**][52] Copy and paste with system clipboard using wl-clipboard
- [**dtomvan/xpm.xplr**][47] The XPLR Plugin Manager.
- [**emsquid/style.xplr**][57] Helper plugin that allows you to integrate xplr's [Style][58] anywhere.
- [**term.xplr**][39] Terminal integration for xplr
### Integration
## Integration
- [**sayanarijit/alacritty.xplr**][33] [Alacritty][34] integration for xplr.
- [**sayanarijit/dragon.xplr**][4] Drag and drop files using [dragon][5].
- [**sayanarijit/dua-cli.xplr**][6] Get the disk usage using [dua-cli][7] with selection
- [**alacritty.xplr**][33] [Alacritty][34] integration for xplr.
- [**dragon.xplr**][4] Drag and drop files using [dragon][5].
- [**dua-cli.xplr**][6] Get the disk usage using [dua-cli][7] with selection
support.
- [**sayanarijit/fzf.xplr**][8] Fuzzy search using [fzf][9] to focus on a file or enter.
- [**sayanarijit/find.xplr**][44] An interactive finder plugin to complement [map.xplr][38].
- [**Junker/nuke.xplr**][53] Open files in apps by file type or mime.
- [**sayanarijit/nvim-ctrl.xplr**][35] Send files to running Neovim sessions using
- [**fzf.xplr**][8] Fuzzy search using [fzf][9] to focus on a file or enter
into a directory.
- [**nvim-ctrl.xplr**][35] Send files to running Neovim sessions using
[nvim-ctrl][36].
- [**dtomvan/ouch.xplr**][40] This plugin uses [ouch][41] to compress and decompress files.
- [**dtomvan/paste-rs.xplr**][23] Use this plugin to paste your files to
- [**ouch.xplr**][40] This plugin uses [ouch][41] to compress and decompress files.
- [**paste-rs.xplr**][23] Use this plugin to paste your files to
[paste.rs][24], and open/delete them later using [fzf][9].
- [**sayanarijit/preview-tabbed.xplr**][10] Preview paths using suckless [tabbed][11] and
- [**preview-tabbed.xplr**][10] Preview paths using suckless [tabbed][11] and
[nnn preview-tabbed][12].
- [**sayanarijit/qrcp.xplr**][26] Send and receive files via QR code using [qrcp][27].
- [**sayanarijit/scp.xplr**][54] Integrate xplr with scp.
- [**sayanarijit/trash-cli.xplr**][13] Trash files and directories using [trash-cli][14].
- [**sayanarijit/xclip.xplr**][15] Copy and paste with system clipboard using [xclip][16].
- [**sayanarijit/zoxide.xplr**][17] Change directory using the [zoxide][18] database.
### Theme
- [**sayanarijit/material-landscape.xplr**][19] Material Landscape
- [**sayanarijit/material-landscape2.xplr**][20] Material Landscape 2
- [**sayanarijit/zentable.xplr**][31] A clean, distraction free xplr table UI
- [**dy-sh/dysh-style.xplr**][63] Complements xplr theme with icons and highlighting.
- [**prncss-xyz/icons.xplr**][30] An icon theme for xplr.
- [**dtomvan/extra-icons.xplr**][50] Adds more icons to icons.xplr, compatible
with zentable.xplr.
- [**hartan/web-devicons.xplr**][59] Adds [nvim-web-devicons][60] to xplr with
optional coloring
- [**duganchen/one-table-column.xplr**][62] Moves file stats to a status bar.
- [**dy-sh/get-rid-of-index.xplr**][64] Removes the index column.
- [**qrcp.xplr**][26] Send and receive files via QR code using [qrcp][27].
- [**trash-cli.xplr**][13] Trash files and directories using [trash-cli][14].
- [**xargs.xplr**][22] Batch execute commands on the focused or selected files
using `xargs`.
- [**xclip.xplr**][15] Copy and paste with system clipboard using [xclip][16].
- [**zoxide.xplr**][17] Change directory using the [zoxide][18] database.
## Also See:
## Theme
- [Awesome Hacks][45]
- [Awesome Integrations][46]
- [**material-landscape.xplr**][19] Material Landscape
- [**material-landscape2.xplr**][20] Material Landscape 2
- [**zentable.xplr**][31] A clean, distraction free xplr table UI
- [**icons.xplr**][30] An icon theme for xplr.
[1]: writing-plugins.md
[1]: ./writing-plugins.md
[2]: #integration
[3]: #theme
[4]: https://github.com/sayanarijit/dragon.xplr
@ -83,13 +69,14 @@ of the following plugins work for you, it's very easy to
[18]: https://github.com/ajeetdsouza/zoxide
[19]: https://github.com/sayanarijit/material-landscape.xplr
[20]: https://github.com/sayanarijit/material-landscape2.xplr
[21]: https://github.com/sayanarijit/comex.xplr
[22]: https://github.com/sayanarijit/xargs.xplr
[23]: https://github.com/dtomvan/paste-rs.xplr
[24]: https://paste.rs
[25]: https://github.com/sayanarijit/completion.xplr
[26]: https://github.com/sayanarijit/qrcp.xplr
[27]: https://github.com/claudiodangelis/qrcp
[28]: https://github.com/sayanarijit/type-to-nav.xplr
[28]: https://github.com/prncss-xyz/type-to-nav.xplr
[29]: https://github.com/jarun/nnn/wiki/concepts#type-to-nav
[30]: https://github.com/prncss-xyz/icons.xplr
[31]: https://github.com/sayanarijit/zentable.xplr
@ -100,29 +87,7 @@ of the following plugins work for you, it's very easy to
[36]: https://github.com/chmln/nvim-ctrl
[37]: https://github.com/sayanarijit/command-mode.xplr
[38]: https://github.com/sayanarijit/map.xplr
[39]: https://github.com/dtomvan/term.xplr
[39]: https://github.com/igorepst/term.xplr
[40]: https://github.com/dtomvan/ouch.xplr
[41]: https://github.com/ouch-org/ouch
[42]: https://github.com/igorepst/context-switch.xplr
[43]: https://github.com/sayanarijit/dual-pane.xplr
[44]: https://github.com/sayanarijit/find.xplr
[45]: awesome-hacks.md
[46]: awesome-integrations.md
[47]: https://github.com/dtomvan/xpm.xplr
[48]: installing-plugins.md
[49]: https://github.com/sayanarijit/registers.xplr
[50]: https://github.com/dtomvan/extra-icons.xplr
[51]: https://github.com/sayanarijit/offline-docs.xplr
[52]: https://github.com/sayanarijit/wl-clipboard.xplr
[53]: https://github.com/Junker/nuke.xplr
[54]: https://github.com/sayanarijit/scp.xplr
[55]: https://github.com/sayanarijit/regex-search.xplr
[56]: https://github.com/sayanarijit/tri-pane.xplr
[57]: https://github.com/emsquid/style.xplr
[58]: style.md
[59]: https://gitlab.com/hartan/web-devicons.xplr
[60]: https://github.com/nvim-tree/nvim-web-devicons
[61]: https://github.com/sayanarijit/tree-view.xplr
[62]: https://github.com/duganchen/one-table-column.xplr
[63]: https://github.com/dy-sh/dysh-style.xplr
[64]: https://github.com/dy-sh/get-rid-of-index.xplr

@ -1,37 +0,0 @@
# Borders
xplr allows customizing the shape and style of the borders.
### Border
A border is a [sum type][2] that can be one of the following:
- "Top"
- "Right"
- "Bottom"
- "Left"
### Border Type
A border type is a [sum type][2] that can be one of the following:
- "Plain"
- "Rounded"
- "Double"
- "Thick"
### Border Style
The [style][1] of the borders.
## Example
```lua
xplr.config.general.panel_ui.default.borders = { "Top", "Right", "Bottom", "Left" }
xplr.config.general.panel_ui.default.border_type = "Thick"
xplr.config.general.panel_ui.default.border_style.fg = "Black"
xplr.config.general.panel_ui.default.border_style.bg = "Gray"
```
[1]: style.md#style
[2]: sum-type.md

@ -4,29 +4,6 @@ A column renderer is a Lua function that receives a [special argument][1] and
returns a string that will be displayed in each specific field of the
[files table][2].
## Example: Customizing Table Renderer
```lua
xplr.fn.custom.fmt_simple_column = function(m)
return m.prefix .. m.relative_path .. m.suffix
end
xplr.config.general.table.header.cols = {
{ format = " path" }
}
xplr.config.general.table.row.cols = {
{ format = "custom.fmt_simple_column" }
}
xplr.config.general.table.col_widths = {
{ Percentage = 100 }
}
-- With this config, you should only see a single column displaying the
-- relative paths.
```
xplr by default provides the following column renderers:
- `xplr.fn.builtin.fmt_general_table_row_cols_0`
@ -57,10 +34,6 @@ The special argument contains the following fields
- [size][13]
- [human_size][14]
- [permissions][15]
- [created][34]
- [last_modified][35]
- [uid][36]
- [gid][37]
- [canonical][16]
- [symlink][17]
- [index][18]
@ -73,7 +46,6 @@ The special argument contains the following fields
- [is_selected][25]
- [is_focused][26]
- [total][27]
- [style][38]
- [meta][28]
### parent
@ -155,30 +127,6 @@ Type: [Permission][30]
The [permissions][30] applied to the node.
### created
Type: nullable integer
Creation time in nanosecond since UNIX epoch.
### last_modified
Type: nullable integer
Last modification time in nanosecond since UNIX epoch.
### uid
Type: integer
User ID of the file owner.
### gid
Type: integer
Group ID of the file owner.
### canonical
Type: nullable [Resolved Node Metadata][31]
@ -255,12 +203,6 @@ Type: integer
The total number of the nodes.
### style
Type: [Style][39]
The applicable [style object][39] for the node.
### meta
Type: mapping of string and string
@ -298,13 +240,32 @@ It contains the following fields.
- [mime_essence][12]
- [size][13]
- [human_size][14]
- [created][34]
- [last_modified][35]
- [uid][36]
- [gid][37]
## Example: Customizing Table Renderer
```lua
xplr.fn.custom.fmt_simple_column = function(m)
return m.prefix .. m.relative_path .. m.suffix
end
xplr.config.general.table.header.cols = {
{ format = " path" }
}
xplr.config.general.table.row.cols = {
{ format = "custom.fmt_simple_column" }
}
xplr.config.general.table.col_widths = {
{ Percentage = 100 }
}
-- With this config, you should only see a single column displaying the
-- relative paths.
```
[1]: #table-renderer-argument
[2]: layout.md#table
[2]: layouts.md#table
[3]: #parent
[4]: #relative_path
[5]: #absolute_path
@ -330,15 +291,9 @@ It contains the following fields.
[25]: #is_selected
[26]: #is_focused
[27]: #total
[28]: #meta
[28]: meta
[29]: #size
[30]: #permission
[31]: #resolved-node-metadata
[32]: general-config.md#tabletree
[33]: node-type.md#meta
[34]: #created
[35]: #last_modified
[36]: #uid
[37]: #gid
[38]: #style
[39]: style.md#style
[33]: node_types.md#meta

@ -0,0 +1,12 @@
# 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,30 +0,0 @@
# Concept
These are the concepts that make xplr probably the most hackable terminal file
explorer.
- [Key Bindings][1]
- [Node Type][2]
- [Layout][3]
- [Mode][4]
- [Message][5]
- [Borders][6]
- [Style][7]
- [Sorting][8]
- [Filtering][9]
- [Column Renderer][10]
- [Lua Function Calls][11]
- [Environment Variables and Pipes][12]
[1]: key-bindings.md
[2]: node-type.md
[3]: layout.md
[4]: mode.md
[5]: message.md
[6]: borders.md
[7]: style.md
[8]: sorting.md
[9]: filtering.md
[10]: column-renderer.md
[11]: lua-function-calls.md
[12]: environment-variables-and-pipes.md

@ -1,156 +1,41 @@
# Configuration
xplr can be configured using [Lua][1] via a special file named `init.lua`,
which can be placed in `~/.config/xplr/` (local to user) or `/etc/xplr/`
(global) depending on the use case.
xplr can be configured using [Lua][1] via a special file
named `init.lua`
([example][2]), which
can be placed in `~/.config/xplr/` (user specific) or `/etc/xplr/` (global)
depending on the use case.
When xplr loads, it first executes the [built-in init.lua][2] to set the
default values, which is then overwritten by another config file, if found
using the following lookup order:
1. `--config /path/to/init.lua`
2. `~/.config/xplr/init.lua`
3. `/etc/xplr/init.lua`
The first one found will be loaded by xplr and the lookup will stop.
The loaded config can be further extended using the `-C` or `--extra-config`
command-line option.
[1]: https://www.lua.org
[2]: https://github.com/sayanarijit/xplr/blob/main/src/init.lua
[3]: https://xplr.dev/en/upgrade-guide
## Config
The xplr configuration, exposed via `xplr.config` Lua API contains the
following sections.
See:
- [xplr.config.general](https://xplr.dev/en/general-config)
- [xplr.config.node_types](https://xplr.dev/en/node_types)
- [xplr.config.layouts](https://xplr.dev/en/layouts)
- [xplr.config.modes](https://xplr.dev/en/modes)
## Function
While `xplr.config` defines all the static parts of the configuration,
`xplr.fn` defines all the dynamic parts using functions.
See: [Lua Function Calls](https://xplr.dev/en/lua-function-calls)
As always, `xplr.fn.builtin` is where the built-in functions are defined
that can be overwritten.
#### xplr.fn.builtin.try_complete_path
Tries to auto complete the path in the input buffer
#### xplr.fn.builtin.fmt_general_table_row_cols_0
Renders the first column in the table
When a user specific configuration is available, the global configuration file
will be ignored.
#### xplr.fn.builtin.fmt_general_table_row_cols_1
However, it's also possible to place the file anywhere, with any name and use
the command-line argument `-c` / `--config` to specify its path explicitely. In
that case, both `~/.config/xplr/init.lua` and `/etc/xplr/init.lua` will be
ignored.
Renders the second column in the table
## How Config Is Loaded
#### xplr.fn.builtin.fmt_general_table_row_cols_2
Renders the third column in the table
#### xplr.fn.builtin.fmt_general_table_row_cols_3
Renders the fourth column in the table
#### xplr.fn.builtin.fmt_general_table_row_cols_4
Renders the fifth column in the table
#### xplr.fn.custom
This is where the custom functions can be added.
There is currently no restriction on what kind of functions can be defined
in `xplr.fn.custom`.
You can also use nested tables such as
`xplr.fn.custom.my_plugin.my_function` to define custom functions.
## Hooks
This section of the configuration cannot be overwritten by another config
file or plugin, since this is an optional lua return statement specific to
each config file. It can be used to define things that should be explicit
for reasons like performance concerns, such as hooks.
Plugins should expose the hooks, and require users to subscribe to them
explicitly.
Example:
```lua
return {
-- Add messages to send when the xplr loads.
-- This is similar to the `--on-load` command-line option.
--
-- Type: list of [Message](https://xplr.dev/en/message#message)s
on_load = {
{ LogSuccess = "Configuration successfully loaded!" },
{ CallLuaSilently = "custom.some_plugin_with_hooks.on_load" },
},
-- Add messages to send when the directory changes.
--
-- Type: list of [Message](https://xplr.dev/en/message#message)s
on_directory_change = {
{ LogSuccess = "Changed directory" },
{ CallLuaSilently = "custom.some_plugin_with_hooks.on_directory_change" },
},
-- Add messages to send when the focus changes.
--
-- Type: list of [Message](https://xplr.dev/en/message#message)s
on_focus_change = {
{ LogSuccess = "Changed focus" },
{ CallLuaSilently = "custom.some_plugin_with_hooks.on_focus_change" },
}
When xplr loads, it first executes the built-in
[init.lua][2] to set
the default values, which is then overwritten by another config file, if found
using the following lookup order:
-- Add messages to send when the mode is switched.
--
-- Type: list of [Message](https://xplr.dev/en/message#message)s
on_mode_switch = {
{ LogSuccess = "Switched mode" },
{ CallLuaSilently = "custom.some_plugin_with_hooks.on_mode_switch" },
}
**--config /path/to/init.lua** > **~/.config/xplr/init.lua** > **/etc/xplr/init.lua**
-- Add messages to send when the layout is switched
--
-- Type: list of [Message](https://xplr.dev/en/message#message)s
on_layout_switch = {
{ LogSuccess = "Switched layout" },
{ CallLuaSilently = "custom.some_plugin_with_hooks.on_layout_switch" },
}
## config
-- 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" },
}
}
```
The xplr configuration, exposed as `xplr.config` Lua API contains the
following fields:
---
- [general][3]
- [modes][4]
- [layouts][5]
- [node_types][6]
> Note:
>
> It's not recommended to copy the entire configuration, unless you want to
> freeze it and miss out on useful updates to the defaults.
>
> Instead, you can use this as a reference to overwrite only the parts you
> want to update.
>
> If you still want to copy the entire configuration, make sure to put your
> customization before the return statement.
[1]: https://www.lua.org/
[2]: https://github.com/sayanarijit/xplr/blob/main/src/init.lua
[3]: general-config.md
[4]: modes.md
[5]: layouts.md
[6]: node_types.md

@ -5,8 +5,6 @@ In xplr, each keyboard input passes through a bunch of handlers (e.g. `on_key`,
configured to with an [action][16], it will intercept the key and produce
[messages][18] for xplr to handle.
Try [debug key bindings][31] to understand how key bindings actually work.
## Key Bindings
Key bindings contains the following information:
@ -86,7 +84,7 @@ Default action to perform in case if a keyboard input not mapped via any of the
## Key
A key is a [sum type][36] can be one of the following:
A key can be one of the following:
- 0, 1, ... 9
- a, b, ... z
@ -155,12 +153,12 @@ xplr.config.modes.custom.fzxplr = {
messages = {
{
BashExec = [===[
PTH=$(cat "${XPLR_PIPE_DIRECTORY_NODES_OUT:?}" | awk -F/ '{print $NF}' | fzf)
if [ -d "$PTH" ]; then
"$XPLR" -m 'ChangeDirectory: %q' "$PTH"
else
"$XPLR" -m 'FocusPath: %q' "$PTH"
fi
PTH=$(cat "${XPLR_PIPE_DIRECTORY_NODES_OUT:?}" | awk -F/ '{print $NF}' | fzf)
if [ -d "$PTH" ]; then
echo ChangeDirectory: "'"${PWD:?}/${PTH:?}"'" >> "${XPLR_PIPE_MSG_IN:?}"
else
echo FocusPath: "'"${PWD:?}/${PTH:?}"'" >> "${XPLR_PIPE_MSG_IN:?}"
fi
]===]
},
"PopMode",
@ -180,10 +178,12 @@ As you can see, the key `F` in mode `fzxplr` (the name can be anything)
executes a script in `bash`.
`BashExec`, `PopMode`, `SwitchModeBuiltin`, `ChangeDirectory` and `FocusPath`
are [messages][18], `$XPLR`, `$XPLR_PIPE_DIRECTORY_NODES_OUT` are
[environment variables][22] exported by `xplr` before executing the command.
They contain the path to the [input][23] and [output][24] pipes that allows
external tools to interact with `xplr`.
are [messages][18], `$XPLR_PIPE_MSG_IN`,
`$XPLR_PIPE_DIRECTORY_NODES_OUT` are
[environment variables][22] exported by `xplr`
before executing the command. They contain the path to the
[input][23] and [output][24] pipes that
allows external tools to interact with `xplr`.
Now that we have our new mode ready, let's add an entry point to this mode via
the `default` mode.
@ -218,9 +218,9 @@ Visit [Awesome Plugins][27] for more [integration][28] options.
[19]: install.md
[20]: post-install.md
[21]: https://github.com/junegunn/fzf
[22]: environment-variables-and-pipes.md#environment-variables
[23]: environment-variables-and-pipes.md#input-pipe
[24]: environment-variables-and-pipes.md#output-pipes
[22]: message.md#environment-variables
[23]: message.md#input-pipe
[24]: message.md#output-pipes
[25]: https://s3.gifyu.com/images/xplr-fzf.gif
[26]: https://gifyu.com/image/tW86
[27]: awesome-plugins.md
@ -230,4 +230,3 @@ Visit [Awesome Plugins][27] for more [integration][28] options.
[33]: #on_character
[34]: #on_navigation
[35]: #on_function
[36]: sum-type.md

@ -0,0 +1,25 @@
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.
- You can [fuel me with coins of encouragement][2] or [buy me a coffee][3].
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,62 +5,51 @@ create a `test.lua` file with the following script, launch xplr with
`xplr --extra-config test.lua`, press `#` and play around.
```lua
-- The global key bindings inherited by all the modes.
xplr.config.general.global_key_bindings = {
on_key = {
esc = {
help = "escape",
messages = {
{ LogInfo = "global on_key(esc) called" },
"PopMode",
},
},
["ctrl-c"] = {
help = "terminate",
messages = {
"Terminate",
},
},
},
}
-- Press `#` to enter the `debug key bindings` mode.
xplr.config.modes.builtin.default.key_bindings.on_key["#"] = {
help = "test",
messages = {
"PopMode",
{ SwitchModeCustom = "debug_key_bindings" },
{ SwitchModeCustom = "test" },
},
}
-- The `debug key bindings` mode.
xplr.config.modes.custom.debug_key_bindings = {
name = "debug key bindings",
xplr.config.modes.custom.test = {
name = "test",
key_bindings = {
on_key = {
["1"] = {
messages = {
{ LogInfo = "on_key(1) called" },
{ LogInfo = "on_key called" },
},
},
a = {
messages = {
{ LogInfo = "on_key(a) called" },
{ LogInfo = "on_key called" },
},
},
["`"] = {
messages = {
{ LogInfo = "on_key(`) called" },
{ LogInfo = "on_key called" },
},
},
tab = {
messages = {
{ LogInfo = "on_key(tab) called" },
{ LogInfo = "on_key called" },
},
},
esc = {
messages = {
"PopMode",
},
},
["ctrl-c"] = {
messages = {
"Terminate",
},
},
f1 = {
messages = {
{ LogInfo = "on_key(f1) called" },
{ LogInfo = "on_key called" },
},
},
},

@ -7,342 +7,215 @@ requirements.
When you press `?` in [default mode][3], you can see the complete list
of [modes][4] and the key mappings for each mode.
[1]: https://www.vim.org/
[2]: https://github.com/jarun/nnn/
[3]: #default
[4]: modes.md
### default
| key | remaps | action |
| --------- | ------ | ------------------- |
| ( | | prev deep branch |
| ) | | next deep branch |
| . | | show hidden |
| / | ctrl-f | search |
| : | | action |
| ? | f1 | global help menu |
| G | | go to bottom |
| V | ctrl-a | select/unselect all |
| c | | copy to |
| ctrl-d | | duplicate as |
| ctrl-i | tab | next visited path |
| ctrl-n | | next selection |
| ctrl-o | | last visited path |
| ctrl-p | | prev selection |
| ctrl-r | | refresh screen |
| ctrl-u | | clear selection |
| ctrl-w | | switch layout |
| d | | delete |
| down | j | down |
| enter | | quit with result |
| f | | filter |
| g | | go to |
| h | left | back |
| k | up | up |
| l | right | enter |
| m | | move to |
| page-down | | scroll down |
| page-up | | scroll up |
| q | | quit |
| r | | rename |
| s | | sort |
| space | v | toggle selection |
| { | | scroll up half |
| } | | scroll down half |
| ~ | | go home |
| [0-9] | | input |
### go_to_path
| key | remaps | action |
| ----- | ------ | ---------------- |
| enter | | submit |
| f1 | | global help menu |
| tab | | try complete |
| key | remaps | action |
| ------ | ------ | ------------------- |
| . | | show hidden |
| / | ctrl-f | search |
| : | | action |
| ? | | global help menu |
| G | | go to bottom |
| V | ctrl-a | select/unselect all |
| ctrl-c | | terminate |
| ctrl-i | tab | next visited path |
| ctrl-o | | last visited path |
| 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 |
| q | | quit |
| r | | rename |
| s | | sort |
| space | v | toggle selection |
| ~ | | go home |
| [0-9] | | input |
### rename
### recover
| key | remaps | action |
| ----- | ------ | ---------------- |
| enter | | submit |
| f1 | | global help menu |
| tab | | try complete |
| key | remaps | action |
| ------ | ------ | --------- |
| ctrl-c | | terminate |
| esc | | escape |
### recover
### filter
| 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
| key | remaps | action |
| ----- | ------ | -------------------- |
| ! | | shell |
| c | | create |
| e | | open in editor |
| f1 | | global help menu |
| l | | logs |
| m | | toggle mouse |
| p | | edit permissions |
| q | | quit options |
| s | | selection operations |
| v | | vroot |
| [0-9] | | go to index |
| key | remaps | action |
| ------ | ------ | ------------------------- |
| R | | relative does not contain |
| ctrl-c | | terminate |
| ctrl-r | | reset filters |
| ctrl-u | | clear filters |
| enter | esc | done |
| r | | relative does contain |
### default
### number
| key | remaps | action |
| --------- | ------ | ------------------- |
| ( | | prev deep branch |
| ) | | next deep branch |
| . | | show hidden |
| / | ctrl-f | search |
| : | | action |
| ? | f1 | global help menu |
| G | | go to bottom |
| V | ctrl-a | select/unselect all |
| c | | copy to |
| ctrl-d | | duplicate as |
| ctrl-i | tab | next visited path |
| ctrl-n | | next selection |
| ctrl-o | | last visited path |
| ctrl-p | | prev selection |
| ctrl-r | | refresh screen |
| ctrl-u | | clear selection |
| ctrl-w | | switch layout |
| d | | delete |
| down | j | down |
| enter | | quit with result |
| f | | filter |
| g | | go to |
| h | left | back |
| k | up | up |
| l | right | enter |
| m | | move to |
| page-down | | scroll down |
| page-up | | scroll up |
| q | | quit |
| r | | rename |
| s | | sort |
| space | v | toggle selection |
| { | | scroll up half |
| } | | scroll down half |
| ~ | | go home |
| [0-9] | | input |
### debug_error
| key | remaps | action |
| ----- | ------ | ------------------- |
| enter | | open logs in editor |
| f1 | | global help menu |
| q | | quit |
### create_directory
| key | remaps | action |
| ----- | ------ | ---------------- |
| enter | | submit |
| f1 | | global help menu |
| tab | | try complete |
### selection_ops
| key | remaps | action |
| --- | ------ | ---------------- |
| c | | copy here |
| e | | edit selection |
| f1 | | global help menu |
| h | | hardlink here |
| l | | list selection |
| m | | move here |
| s | | softlink here |
| u | | clear selection |
### relative_path_does_not_match_regex
| key | remaps | action |
| ----- | ------ | ---------------- |
| enter | | submit |
| f1 | | global help menu |
### create_file
| key | remaps | action |
| ----- | ------ | ---------------- |
| enter | | submit |
| f1 | | global help menu |
| tab | | try complete |
### 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 |
| key | remaps | action |
| ------ | ------ | --------- |
| ctrl-c | | terminate |
| down | j | to down |
| enter | | to index |
| esc | | cancel |
| k | up | to up |
| [0-9] | | input |
### go to
| key | remaps | action |
| ------ | ------ | -------------- |
| ctrl-c | | terminate |
| esc | | cancel |
| f | | follow symlink |
| g | | top |
| x | | open in gui |
### search
| key | remaps | action |
| ------ | ------ | ---------------- |
| ctrl-c | | terminate |
| ctrl-n | down | down |
| ctrl-p | up | up |
| enter | esc | focus |
| left | | back |
| right | | enter |
| tab | | toggle selection |
### selection ops
| key | remaps | action |
| ------ | ------ | ----------- |
| c | | copy here |
| ctrl-c | | terminate |
| esc | | cancel |
| m | | move here |
| x | | open in gui |
### action to
| key | remaps | action |
| ------ | ------ | -------------------- |
| ! | | shell |
| c | | create |
| ctrl-c | | terminate |
| e | | open in editor |
| esc | | cancel |
| l | | logs |
| m | | toggle mouse |
| q | | quit options |
| s | | selection operations |
| [0-9] | | go to index |
### create
| key | remaps | action |
| --- | ------ | ---------------- |
| d | | create directory |
| f | | create file |
| f1 | | global help menu |
| key | remaps | action |
| ------ | ------ | ---------------- |
| ctrl-c | | terminate |
| d | | create directory |
| esc | | cancel |
| f | | create file |
### create file
| key | remaps | action |
| ------ | ------ | ----------- |
| ctrl-c | | terminate |
| enter | | create file |
| esc | | cancel |
### vroot
### create directory
| key | remaps | action |
| ------ | ------ | ---------------- |
| . | | vroot $PWD |
| / | | vroot / |
| ctrl-r | | reset vroot |
| ctrl-u | | unset vroot |
| f1 | | global help menu |
| v | | toggle vroot |
| ~ | | vroot $HOME |
| ctrl-c | | terminate |
| enter | | create directory |
| esc | | cancel |
### search
### rename
| key | remaps | action |
| ------ | ------ | ----------------------- |
| ctrl-a | | toggle search algorithm |
| ctrl-f | | fuzzy search |
| ctrl-n | down | down |
| ctrl-p | up | up |
| ctrl-r | | regex search |
| ctrl-s | | sort (no search order) |
| ctrl-z | | toggle ordering |
| enter | | submit |
| esc | | cancel |
| f1 | | global help menu |
| left | | back |
| right | | enter |
| tab | | toggle selection |
### switch_layout
| key | remaps | action |
| --- | ------ | -------------------- |
| 1 | | default |
| 2 | | no help menu |
| 3 | | no selection panel |
| 4 | | no help or selection |
| f1 | | global help menu |
| key | remaps | action |
| ------ | ------ | --------- |
| ctrl-c | | terminate |
| enter | | rename |
| esc | | cancel |
### delete
| key | remaps | action |
| ------ | ------ | ------------ |
| D | | force delete |
| ctrl-c | | terminate |
| d | | delete |
| esc | | cancel |
### sort
| key | remaps | action |
| --------- | ------ | --------------------------------- |
| ! | | reverse sorters |
| C | | by created reverse |
| E | | by canonical extension reverse |
| L | | by last modified reverse |
| M | | by canonical mime essence reverse |
| N | | by node type reverse |
| R | | by relative path reverse |
| S | | by size reverse |
| backspace | | remove last sorter |
| c | | by created |
| ctrl-c | | terminate |
| ctrl-r | | reset sorters |
| ctrl-u | | clear sorters |
| e | | by canonical extension |
| enter | | submit |
| f1 | | global help menu |
| l | | by last modified |
| enter | esc | done |
| m | | by canonical mime essence |
| n | | by node type |
| r | | by relative path |
| s | | by size |
### number
| key | remaps | action |
| ----- | ------ | ---------------- |
| down | j | to down |
| enter | | to index |
| f1 | | global help menu |
| k | up | to up |
| [0-9] | | input |
### copy_to
| key | remaps | action |
| ----- | ------ | ---------------- |
| enter | | submit |
| f1 | | global help menu |
| tab | | try complete |
### edit_permissions
| key | remaps | action |
| ------ | ------ | ---------------- |
| G | | -group |
| M | | min |
| O | | -other |
| U | | -user |
| ctrl-r | | reset |
| enter | | submit |
| f1 | | global help menu |
| g | | +group |
| m | | max |
| o | | +other |
| u | | +user |
### delete
| key | remaps | action |
| --- | ------ | ---------------- |
| D | | force delete |
| d | | delete |
| f1 | | global help menu |
### move_to
| key | remaps | action |
| ----- | ------ | ---------------- |
| enter | | submit |
| f1 | | global help menu |
| tab | | try complete |
### 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 |
| ----- | ------ | ---------------- |
| enter | | submit |
| f1 | | global help menu |
| tab | | try complete |
| key | remaps | action |
| ------ | ------ | ------------------------- |
| R | | relative does not contain |
| ctrl-c | | terminate |
| ctrl-r | | reset filters |
| ctrl-u | | clear filters |
| enter | esc | done |
| r | | relative does contain |
### relative path does contain
| key | remaps | action |
| ------ | ------ | ------------ |
| ctrl-c | | terminate |
| enter | | apply filter |
| esc | | cancel |
### relative path does not contain
| key | remaps | action |
| ------ | ------ | ------------ |
| ctrl-c | | terminate |
| enter | | apply filter |
| esc | | cancel |
### switch layout
| key | remaps | action |
| ------ | ------ | -------------------- |
| 1 | | default |
| 2 | | no help menu |
| 3 | | no selection panel |
| 4 | | no help or selection |
| ctrl-c | | terminate |
| esc | | cancel |
[1]: https://www.vim.org/
[2]: https://github.com/jarun/nnn/
[3]: #default
[4]: modes.md

@ -1,230 +0,0 @@
# Environment Variables and Pipes
Alternative to `CallLua`, `CallLuaSilently` messages that call Lua functions,
there are `Call0`, `CallSilently0`, `BashExec0`, `BashExecSilently0` messages
that call shell commands.
### Example: Simple file opener using xdg-open and $XPLR_FOCUS_PATH
```lua
xplr.config.modes.builtin.default.key_bindings.on_key["X"] = {
help = "open",
messages = {
{
BashExecSilently0 = [===[
xdg-open "${XPLR_FOCUS_PATH:?}"
]===],
},
},
}
```
However, unlike the Lua functions, these shell commands have to read the useful
information and send messages via environment variables and temporary files
called "pipe"s. These environment variables and files are only available when
a command is being executed.
### Example: Using Environment Variables and Pipes
```lua
xplr.config.modes.builtin.default.key_bindings.on_key["space"] = {
help = "ask name and greet",
messages = {
{
BashExec0 = [===[
echo "What's your name?"
read name
greeting="Hello $name!"
message="$greeting You are inside $PWD"
"$XPLR" -m 'LogSuccess: %q' "$message"
]===]
}
}
}
-- Now, when you press "space" in default mode, you will be prompted for your
-- name. Enter your name to receive a nice greeting and to know your location.
```
Visit the [**fzf integration tutorial**][19] for another example.
To see the environment variables and pipes, invoke the shell by typing `:!` in default
mode and run the following command:
```
env | grep ^XPLR
```
You will see something like:
```
XPLR=xplr
XPLR_FOCUS_INDEX=0
XPLR_MODE=action to
XPLR_PIPE_SELECTION_OUT=/run/user/1000/xplr/session/122278/pipe/selection_out
XPLR_INPUT_BUFFER=
XPLR_PIPE_GLOBAL_HELP_MENU_OUT=/run/user/1000/xplr/session/122278/pipe/global_help_menu_out
XPLR_PID=122278
XPLR_PIPE_MSG_IN=/run/user/1000/xplr/session/122278/pipe/msg_in
XPLR_PIPE_LOGS_OUT=/run/user/1000/xplr/session/122278/pipe/logs_out
XPLR_PIPE_RESULT_OUT=/run/user/1000/xplr/session/122278/pipe/result_out
XPLR_PIPE_HISTORY_OUT=/run/user/1000/xplr/session/122278/pipe/history_out
XPLR_FOCUS_PATH=/home/sayanarijit/Documents/GitHub/xplr/docs/en/book
XPLR_SESSION_PATH=/run/user/1000/xplr/session/122278
XPLR_APP_VERSION=0.14.3
XPLR_PIPE_DIRECTORY_NODES_OUT=/run/user/1000/xplr/session/122278/pipe/directory_nodes_out
```
The environment variables starting with `XPLR_PIPE_` are the temporary files
called ["pipe"s][18].
The other variables are single-line variables containing simple information:
- [XPLR][38]
- [XPLR_APP_VERSION][30]
- [XPLR_FOCUS_INDEX][31]
- [XPLR_FOCUS_PATH][32]
- [XPLR_INPUT_BUFFER][33]
- [XPLR_INITIAL_PWD][40]
- [XPLR_MODE][34]
- [XPLR_PID][35]
- [XPLR_SESSION_PATH][36]
- [XPLR_VROOT][39]
### Environment variables
#### XPLR
The binary path of xplr command.
#### XPLR_APP_VERSION
Self-explanatory.
#### XPLR_FOCUS_INDEX
Contains the index of the currently focused item, as seen in
[column-renderer/index][10].
#### XPLR_FOCUS_PATH
Contains the full path of the currently focused node.
#### XPLR_INITIAL_PWD
The $PWD then xplr started.
#### XPLR_INPUT_BUFFER
The line currently in displaying in the xplr input buffer. For e.g. the search
input while searching. See [Reading Input][37].
#### XPLR_MODE
Contains the mode xplr is currently in, see [modes][11].
#### XPLR_PID
Contains the process ID of the current xplr process.
#### XPLR_SESSION_PATH
Contains the current session path, like /tmp/runtime-"$USER"/xplr/session/"$XPLR_PID"/,
you can find temporary files here, such as pipes.
#### XPLR_VROOT
Contains the path of current virtual root, is set.
### Pipes
#### Input pipe
Currently there is only one input pipe.
- [XPLR_PIPE_MSG_IN][20]
#### Output pipes
`XPLR_PIPE_*_OUT` are the output pipes that contain data which cannot be
exposed directly via environment variables, like multi-line strings.
These pipes can be accessed as plain text files located in $XPLR_SESSION_PATH.
Depending on the message (e.g. `Call` or `Call0`), each line will be separated
by newline or null character (`\n` or `\0`).
- [XPLR_PIPE_SELECTION_OUT][21]
- [XPLR_PIPE_GLOBAL_HELP_MENU_OUT][22]
- [XPLR_PIPE_LOGS_OUT][23]
- [XPLR_PIPE_RESULT_OUT][24]
- [XPLR_PIPE_HISTORY_OUT][25]
- [XPLR_PIPE_DIRECTORY_NODES_OUT][26]
#### XPLR_PIPE_MSG_IN
Append new messages to this pipe in [YAML][27] (or [JSON][7]) syntax. These
messages will be read and handled by xplr after the command execution.
Depending on the message (e.g. `Call` or `Call0`) you need to separate each
message using newline or null character (`\n` or `\0`).
> **_NOTE:_** Since version `v0.20.0`, it's recommended to avoid writing
> directly to this file, as safely escaping YAML strings is a lot of work. Use
> `xplr -m` / `xplr --pipe-msg-in` to pass messages to xplr in a safer way.
>
> It uses [jf][41] syntax to safely convert an YAML template into a valid message.
>
> Example: `"$XPLR" -m 'ChangeDirectory: %q' "${HOME:?}"`
#### XPLR_PIPE_SELECTION_OUT
List of selected paths.
#### XPLR_PIPE_GLOBAL_HELP_MENU_OUT
The full help menu.
#### XPLR_PIPE_LOGS_OUT
List of logs.
#### XPLR_PIPE_RESULT_OUT
Result (selected paths if any, else the focused path)
#### XPLR_PIPE_HISTORY_OUT
List of last visited paths (similar to jump list in vim).
#### XPLR_PIPE_DIRECTORY_NODES_OUT
List of paths, filtered and sorted as displayed in the [files table][28].
[7]: https://www.json.org
[10]: column-renderer.md#index
[11]: modes.md#modes
[18]: #pipes
[19]: configure-key-bindings.md#tutorial-adding-a-new-mode
[20]: #xplr_pipe_msg_in
[21]: #xplr_pipe_selection_out
[22]: #xplr_pipe_global_help_menu_out
[23]: #xplr_pipe_logs_out
[24]: #xplr_pipe_result_out
[25]: #xplr_pipe_history_out
[26]: #xplr_pipe_directory_nodes_out
[27]: https://www.yaml.org
[28]: layout.md#table
[30]: #xplr_app_version
[31]: #xplr_focus_index
[32]: #xplr_focus_path
[33]: #xplr_input_buffer
[34]: #xplr_mode
[35]: #xplr_pid
[36]: #xplr_session_path
[37]: messages.md#reading-input
[38]: #xplr
[39]: #xplr_vroot
[40]: #xplr_initial_pwd
[41]: https://github.com/sayanarijit/jf

@ -25,7 +25,7 @@ It contains the following information:
### filter
A filter is a [sum type][5] that can be one of the following:
A filter can be one of the following:
- "RelativePathIs"
- "RelativePathIsNot"
@ -43,10 +43,6 @@ A filter is a [sum type][5] that can be one of the following:
- "RelativePathDoesNotEndWith"
- "IRelativePathDoesEndWith"
- "IRelativePathDoesNotEndWith"
- "RelativePathDoesMatchRegex"
- "RelativePathDoesNotMatchRegex"
- "IRelativePathDoesMatchRegex"
- "IRelativePathDoesNotMatchRegex"
- "AbsolutePathIs"
- "AbsolutePathIsNot"
- "IAbsolutePathIs"
@ -63,10 +59,8 @@ A filter is a [sum type][5] that can be one of the following:
- "AbsolutePathDoesNotEndWith"
- "IAbsolutePathDoesEndWith"
- "IAbsolutePathDoesNotEndWith"
- "AbsolutePathDoesMatchRegex"
- "AbsolutePathDoesNotMatchRegex"
- "IAbsolutePathDoesMatchRegex"
- "IAbsolutePathDoesNotMatchRegex"
TODO: document each
### input
@ -90,4 +84,3 @@ Here, `ToggleNodeFilter` is a [message][4] that adds or removes
[2]: #filter
[3]: #input
[4]: message.md
[5]: sum-type.md

@ -1,628 +1,503 @@
### General Configuration
# General Config
The general configuration properties are grouped together in
`xplr.config.general`.
This configuration is exposed via the `xplr.config.general` API. It contains
the following fields:
#### xplr.config.general.disable_debug_error_mode
Set it to `true` if you want to ignore the startup errors. You can still see
the errors in the logs.
## enable_mouse
Type: boolean
#### xplr.config.general.enable_mouse
Set it to `true` enable scrolling using mouse.
Set it to `true` if you want to enable mouse scrolling.
## show_hidden
Type: boolean
#### xplr.config.general.show_hidden
Set it to `true` to show hidden files.
Set it to `true` to show hidden files by default.
## read_only
Type: boolean
#### xplr.config.general.read_only
Set it to `true` to use only a subset of selected operations that forbids
executing commands or performing write operations on the file-system.
Type: boolean
#### xplr.config.general.enable_recover_mode
Set it to `true` if you want to enable a safety feature that will save you
from yourself when you type recklessly.
## enable_recover_mode
Type: boolean
#### xplr.config.general.hide_remaps_in_help_menu
Set it to `true` if you want to enable a safety feature that will save you from
yourself when you type recklessly.
Set it to `true` if you want to hide all remaps in the help menu.
## initial_layout
Type: boolean
Type: string
#### xplr.config.general.paginated_scrolling
The name of one of the [layout][2] to use when xplr loads.
Set it to `true` if you want paginated scrolling.
## initial_mode
Type: boolean
Type: string
#### xplr.config.general.scroll_padding
The name of one of the [mode][3] to use when xplr loads.
Set the padding value to the scroll area.
Only applicable when `xplr.config.general.paginated_scrolling = false`.
## initial_sorting
Type: boolean
Type: list of [Node Sorter Applicable][4]
#### xplr.config.general.enforce_bounded_index_navigation
Initial group if sorters applied to the nodes list in the table.
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
(by pressing `up`/`k`), or when the focus is on the last path and you
navigate to the next path (by pressing `down`/`j`).
The default behavior is to rotate from the last/first path.
## table.style
Type: boolean
Type: [Style][1]
#### xplr.config.general.prompt.format
Default style of the table.
This is the shape of the prompt for the input buffer.
## table.col_spacing
Type: nullable string
Type: nullable integer
#### xplr.config.general.prompt.style
Default spacing of the columns in the table.
This is the style of the prompt for the input buffer.
## table.col_widths
Type: [Style](https://xplr.dev/en/style)
Type: nullable list of [Constraint][5]
#### xplr.config.general.logs.info.format
Width of each column in the table.
The string to indicate an information in logs.
## table.header.height
Type: nullable string
#### xplr.config.general.logs.info.style
Type: nullable integer
The style for the information logs.
Height of the table header.
Type: [Style](https://xplr.dev/en/style)
## table.header.style
#### xplr.config.general.logs.success.format
Type: [Style][1]
The string to indicate an success in logs.
Style of table header.
Type: nullable string
## table.header.cols
#### xplr.config.general.logs.success.style
Type: list of column configuration
The style for the success logs.
Each column config contains `format` field (string) and `style` field
([Style][1]), that define the content and style of header.
Type: [Style](https://xplr.dev/en/style)
## table.row.height
#### xplr.config.general.logs.warning.format
Type: nullable integer
The string to indicate an warnings in logs.
Height of each row in the table.
Type: nullable string
## table.row.style
#### xplr.config.general.logs.warning.style
Type: [Style][1]
The style for the warnings logs.
Style of table rows.
Type: [Style](https://xplr.dev/en/style)
## table.row.cols
#### xplr.config.general.logs.error.format
Type: list of column configuration
The string to indicate an error in logs.
Each column config contains `format` field (string) and `style` field
([Style][1]).
Type: nullable string
However, unlike [table.header.cols][6], the `format` field here
points to a [column renderer function][7].
#### xplr.config.general.logs.error.style
## table.tree
The style for the error logs.
Type: list of tree configuration
Type: [Style](https://xplr.dev/en/style)
It expects a list of three items. The first component of the tree, then the
middle components, and finally the last component of the tree.
#### xplr.config.general.table.header.cols
Each item requires the `format` field which is a string, and the `style` field,
which is the [Style][1] object.
Columns to display in the table header.
Example:
Type: nullable list of tables with the following fields:
```lua
xplr.config.general.table.tree = {
{ format = "├─", style = { add_modifiers = { "Bold" }, bg = nil, fg = "Blue", sub_modifiers = nil } },
{ format = "├─", style = { add_modifiers = { "Bold" }, bg = nil, fg = "Blue", sub_modifiers = nil } },
{ format = "╰─", style = { add_modifiers = { "Bold" }, bg = nil, fg = "Blue", sub_modifiers = nil } },
}
```
- format: nullable string
- style: [Style](https://xplr.dev/en/style)
## start_fifo
#### xplr.config.general.table.header.style
Style of the table header.
Type: nullable string
Type: [Style](https://xplr.dev/en/style)
Start a FIFO file when xplr loads. It will track the absolute path of the file
under focus.
#### xplr.config.general.table.header.height
## prompt.format
Height of the table header.
Type: string
Type: nullable integer
This is the shape of the prompt for the input buffer.
#### xplr.config.general.table.row.cols
## prompt.style
Columns to display in each row in the table.
Type: [Style][1]
Type: nullable list of tables with the following fields:
Style of the input prompt.
- format: nullable string
- style: [Style](https://xplr.dev/en/style)
## logs.error.format
#### xplr.config.general.table.row.style
Type: string
Default style of the table.
The string to indicate an error in logs.
Type: [Style](https://xplr.dev/en/style)
## logs.error.style
#### xplr.config.general.table.row.height
Type: [Style][1]
Height of the table rows.
Style for errors shown in logs.
Type: nullable integer
## logs.info.format
#### xplr.config.general.table.style
Type: string
Default style of the table.
The string to indicate an information in logs.
Type: [Style](https://xplr.dev/en/style)
## logs.info.style
#### xplr.config.general.table.tree
Type: [Style][1]
Tree to display in the table.
Style for infos shown in logs.
Type: nullable list of tables with the following fields:
## logs.success.format
- format: nullable string
- style: [Style](https://xplr.dev/en/style)
Type: string
#### xplr.config.general.table.col_spacing
The string to indicate a successful operation in logs.
Spacing between the columns in the table.
## logs.success.style
Type: nullable integer
Type: [Style][1]
#### xplr.config.general.table.col_widths
Style for successful operations in logs.
Constraint for the column widths.
## logs.warning.format
Type: nullable list of [Constraint](https://xplr.dev/en/layouts#constraint)
Type: string
#### xplr.config.general.selection.item.format
The string that indicates a warning in logs.
Renderer for each item in the selection list.
## logs.warning.style
Type: nullable string
Type: [Style][1]
#### xplr.config.general.selection.item.style
Style for warnings in logs.
Style for each item in the selection list.
## default_ui.prefix
Type: [Style](https://xplr.dev/en/style)
Type: nullable string
#### xplr.config.general.search.algorithm
The content that is placed before the item name for each row.
The default search algorithm
## default_ui.suffix
Type: [Search Algorithm](https://xplr.dev/en/searching#algorithm)
Type: nullable string
#### xplr.config.general.search.unordered
The content which is appended to each item name for each row.
The default search ordering
## default_ui.style
Type: boolean
Type: [Style][1]
#### xplr.config.general.default_ui.prefix
Style for the default user interface.
The content that is placed before the item name for each row by default.
## focus_ui.prefix
Type: nullable string
#### xplr.config.general.default_ui.suffix
The string placed before the item name for a focused row.
The content which is appended to each item name for each row by default.
## focus_ui.suffix
Type: nullable string
#### xplr.config.general.default_ui.style
The string placed after the item name for a focused row.
The default style of each item for each row.
## focus_ui.style
Type: [Style](https://xplr.dev/en/style)
Type: [Style][1]
#### xplr.config.general.focus_ui.prefix
Style for focused elements.
The string placed before the item name for a focused row.
## selection_ui.prefix
Type: nullable string
#### xplr.config.general.focus_ui.suffix
The string that is placed before the item name for a selected row.
The string placed after the item name for a focused row.
## selection_ui.suffix
Type: nullable string
#### xplr.config.general.focus_ui.style
Style for focused item.
Type: [Style](https://xplr.dev/en/style)
#### xplr.config.general.selection_ui.prefix
The string placed before the item name for a selected row.
Type: nullable string
#### xplr.config.general.selection_ui.suffix
The string placed after the item name for a selected row.
Type: nullable string
## selection_ui.style
#### xplr.config.general.selection_ui.style
Type: [Style][1]
Style for selected rows.
Type: [Style](https://xplr.dev/en/style)
## focus_selection_ui.prefix
#### xplr.config.general.focus_selection_ui.prefix
Type: nullable string
The string placed before item name for a selected row that gets the focus.
Type: nullable string
## focus_selection_ui.suffix
#### xplr.config.general.focus_selection_ui.suffix
Type: nullable string
The string placed after the item name for a selected row that gets the focus.
Type: nullable string
## focus_selection_ui.style
#### xplr.config.general.focus_selection_ui.style
Type: [Style][1]
Style for a selected row that gets the focus.
Type: [Style](https://xplr.dev/en/style)
#### xplr.config.general.sort_and_filter_ui.separator.format
The shape of the separator for the Sort & filter panel.
## sort_and_filter_ui.separator.format
Type: nullable string
#### xplr.config.general.sort_and_filter_ui.separator.style
The shape of the separator for the `Sort & filter` panel.
The style of the separator for the Sort & filter panel.
## sort_and_filter_ui.separator.style
Type: [Style](https://xplr.dev/en/style)
Type: [Style][1]
#### xplr.config.general.sort_and_filter_ui.default_identifier.format
Style for `Sort & filter` panel separator.
The content of the default identifier in Sort & filter panel.
## sort_and_filter_ui.default_identifier.format
Type: nullable string
#### xplr.config.general.sort_and_filter_ui.default_identifier.style
The content of the default identifier in `Sort & filter` panel.
Style for the default identifier in Sort & filter panel.
## sort_and_filter_ui.default_identifier.style
Type: [Style](https://xplr.dev/en/style)
Type: [Style][1]
#### xplr.config.general.sort_and_filter_ui.sort_direction_identifiers.forward.format
Style for the default identifier in `Sort & filter` panel.
The shape of the forward direction indicator for sort identifiers in Sort & filter panel.
Type: nullable string
## sort_and_filter_ui.filter_identifiers
#### xplr.config.general.sort_and_filter_ui.sort_direction_identifiers.forward.style
Type: Table with [filter name][8] as key
Style of forward direction indicator in Sort & filter panel.
The value contains format field (string) and style field (Style), that define
the content and style for the given filter.
Type: [Style](https://xplr.dev/en/style)
Example:
#### xplr.config.general.sort_and_filter_ui.sort_direction_identifiers.reverse.format
```lua
xplr.config.general.sort_and_filter_ui.filter_identifiers.AbsolutePathDoesContain = {
format = "abs=~",
style = { add_modifiers = { "Bold" }, bg = nil, fg = nil, sub_modifiers = nil },
}
```
The shape of the reverse direction indicator for sort identifiers in Sort & filter panel.
## sort_and_filter_ui.sort_direction_identifiers.forward.format
Type: nullable string
#### xplr.config.general.sort_and_filter_ui.sort_direction_identifiers.reverse.style
Style of reverse direction indicator in Sort & filter panel.
Type: [Style](https://xplr.dev/en/style)
#### xplr.config.general.sort_and_filter_ui.sorter_identifiers
The shape of the forward direction indicator for sort identifiers in
`Sort & filter` panel.
The identifiers used to denote applied sorters in the Sort & filter panel.
## sort_and_filter_ui.sort_direction_identifiers.forward.style
Type: nullable mapping of the following key-value pairs:
Type: [Style][1]
- key: [Sorter](https://xplr.dev/en/sorting#sorter)
- value:
- format: nullable string
- style: [Style](https://xplr.dev/en/style)
Style of forward direction indicator.
#### xplr.config.general.sort_and_filter_ui.filter_identifiers
The identifiers used to denote applied filters in the Sort & filter panel.
Type: nullable mapping of the following key-value pairs:
- key: [Filter](https://xplr.dev/en/filtering#filter)
- value:
- format: nullable string
- style: [Style](https://xplr.dev/en/style)
#### xplr.config.general.sort_and_filter_ui.search_identifiers
The identifiers used to denote applied search input.
Type: { format = nullable string, style = [Style](https://xplr.dev/en/style) }
#### xplr.config.general.sort_and_filter_ui.search_direction_identifiers.ordered.format
The shape of ordered indicator for search ordering identifiers in Sort & filter panel.
## sort_and_filter_ui.sort_direction_identifiers.reverse.format
Type: nullable string
#### xplr.config.general.sort_and_filter_ui.search_direction_identifiers.unordered.format
The shape of the reverse direction indicator for sort identifiers in
`Sort & filter` panel.
The shape of unordered indicator for search ordering identifiers in Sort & filter panel.
## sort_and_filter_ui.sort_direction_identifiers.reverse.style
Type: nullable string
#### xplr.config.general.panel_ui.default.title.format
The content for panel title by default.
Type: nullable string
#### xplr.config.general.panel_ui.default.title.style
Type: [Style][1]
The style for panel title by default.
Style of reverse direction indicator.
Type: [Style](https://xplr.dev/en/style)
## sort_and_filter_ui.sorter_identifiers
#### xplr.config.general.panel_ui.default.style
Type: Table with [sorter name][9] as key
Style of the panels by default.
The value contains format field (string) and style field (Style), that define
the content and style for the given sorter.
Type: [Style](https://xplr.dev/en/style)
Example:
#### xplr.config.general.panel_ui.default.borders
```lua
sort_and_filter_ui.sorter_identifiers.ByCanonicalAbsolutePath = {
format = "[c]abs",
style = { add_modifiers = nil, bg = nil, fg = "Yellow", sub_modifiers = nil },
}
```
Defines where to show borders for the panels by default.
## panel_ui.default.borders
Type: nullable list of [Border](https://xplr.dev/en/borders#border)
Type: nullable list of strings
#### xplr.config.general.panel_ui.default.border_type
Defines where to show borders for the panels.
Type of the borders by default.
The possible values are any combination of: "Top", "Bottom", "Left" and
"Right".
Type: nullable [Border Type](https://xplr.dev/en/borders#border-type)
Example:
#### xplr.config.general.panel_ui.default.border_style
```lua
-- Show Left and Right borders only
xplr.config.general.panel_ui.default.borders = {
"Right",
"Left",
}
```
Style of the panel borders by default.
## panel_ui.default.style
Type: [Style](https://xplr.dev/en/style)
Type: [Style][1]
#### xplr.config.general.panel_ui.table.title.format
Default style for panels.
The content for the table panel title.
## panel_ui.default.title.format
Type: nullable string
#### xplr.config.general.panel_ui.table.title.style
Style of the table panel title.
Type: [Style](https://xplr.dev/en/style)
The content for panel title.
#### xplr.config.general.panel_ui.table.style
## panel_ui.default.title.style
Style of the table panel.
Type: [Style][1]
Type: [Style](https://xplr.dev/en/style)
Style for panel title.
#### xplr.config.general.panel_ui.table.borders
## panel_ui.help_menu.borders
Defines where to show borders for the table panel.
Type: nullable list of strings
Type: nullable list of [Border](https://xplr.dev/en/borders#border)
Defines where to show borders for the `Help` panel.
#### xplr.config.general.panel_ui.table.border_type
The possible values are any combination of: "Top", "Bottom", "Left" and
"Right".
Type of the borders for table panel.
## panel_ui.help_menu.style
Type: nullable [Border Type](https://xplr.dev/en/borders#border-type)
Type: [Style][1]
#### xplr.config.general.panel_ui.table.border_style
Style for `Help` panel.
Style of the table panel borders.
Type: [Style](https://xplr.dev/en/style)
#### xplr.config.general.panel_ui.help_menu.title.format
The content for the help menu panel title.
## panel_ui.help_menu.title.format
Type: nullable string
#### xplr.config.general.panel_ui.help_menu.title.style
Style of the help menu panel title.
Type: [Style](https://xplr.dev/en/style)
#### xplr.config.general.panel_ui.help_menu.style
Style of the help menu panel.
Type: [Style](https://xplr.dev/en/style)
The content for `Help` panel title.
#### xplr.config.general.panel_ui.help_menu.borders
## panel_ui.help_menu.title.style
Defines where to show borders for the help menu panel.
Type: [Style][1]
Type: nullable list of [Border](https://xplr.dev/en/borders#border)
Style for `Help` panel title.
#### xplr.config.general.panel_ui.help_menu.border_type
## panel_ui.input_and_logs.borders
Type of the borders for help menu panel.
Type: nullable list of strings
Type: nullable [Border Type](https://xplr.dev/en/borders#border-type)
Defines where to show borders for the `Input` & `Logs` panel.
#### xplr.config.general.panel_ui.help_menu.border_style
The possible values are any combination of: "Top", "Bottom", "Left" and
"Right".
Style of the help menu panel borders.
## panel_ui.input_and_logs.style
Type: [Style](https://xplr.dev/en/style)
Type: [Style][1]
#### xplr.config.general.panel_ui.input_and_logs.title.format
Style for `Input` & `Logs` panel.
The content for the input & logs panel title.
## panel_ui.input_and_logs.title.format
Type: nullable string
#### xplr.config.general.panel_ui.input_and_logs.title.style
The content for the `Input` and `Logs` panel title.
Style of the input & logs panel title.
## panel_ui.input_and_logs.title.style
Type: [Style](https://xplr.dev/en/style)
Type: [Style][1]
#### xplr.config.general.panel_ui.input_and_logs.borders
Style for `Input` & `Logs` panel title.
#### xplr.config.general.panel_ui.input_and_logs.style
## panel_ui.selection.borders
Style of the input & logs panel.
Type: nullable list of strings
Type: [Style](https://xplr.dev/en/style)
Defines where to show borders for the input & logs panel.
Defines where to show borders for the `Selection` panel.
Type: nullable list of [Border](https://xplr.dev/en/borders#border)
The possible values are any combination of: "Top", "Bottom", "Left" and
"Right".
#### xplr.config.general.panel_ui.input_and_logs.border_type
## panel_ui.selection.style
Type of the borders for input & logs panel.
Type: [Style][1]
Type: nullable [Border Type](https://xplr.dev/en/borders#border-type)
Style for `Selection` panel.
#### xplr.config.general.panel_ui.input_and_logs.border_style
Style of the input & logs panel borders.
Type: [Style](https://xplr.dev/en/style)
#### xplr.config.general.panel_ui.selection.title.format
The content for the selection panel title.
## panel_ui.selection.title.format
Type: nullable string
#### xplr.config.general.panel_ui.selection.title.style
Style of the selection panel title.
Type: [Style](https://xplr.dev/en/style)
#### xplr.config.general.panel_ui.selection.borders
The content for `Selection` panel title.
#### xplr.config.general.panel_ui.selection.style
## panel_ui.selection.title.style
Style of the selection panel.
Type: [Style][1]
Type: [Style](https://xplr.dev/en/style)
Defines where to show borders for the selection panel.
Style for `Selection` panel title.
Type: nullable list of [Border](https://xplr.dev/en/borders#border)
## panel_ui.sort_and_filter.borders
#### xplr.config.general.panel_ui.selection.border_type
Type: nullable list of strings
Type of the borders for selection panel.
Defines where to show borders for the `Sort & filter` panel.
Type: nullable [Border Type](https://xplr.dev/en/borders#border-type)
The possible values are any combination of: "Top", "Bottom", "Left" and
"Right".
#### xplr.config.general.panel_ui.selection.border_style
## panel_ui.sort_and_filter.style
Style of the selection panel borders.
Type: [Style][1]
Type: [Style](https://xplr.dev/en/style)
Style for `Sort & filter` panel.
#### xplr.config.general.panel_ui.sort_and_filter.title.format
The content for the sort & filter panel title.
Type: nullable string
#### xplr.config.general.panel_ui.sort_and_filter.title.style
Style of the sort & filter panel title.
Type: [Style](https://xplr.dev/en/style)
#### xplr.config.general.panel_ui.sort_and_filter.style
Style of the sort & filter panel.
Type: [Style](https://xplr.dev/en/style)
#### xplr.config.general.panel_ui.sort_and_filter.borders
Defines where to show borders for the sort & filter panel.
Type: nullable list of [Border](https://xplr.dev/en/borders#border)
#### xplr.config.general.panel_ui.sort_and_filter.border_type
Type of the borders for sort & filter panel.
Type: nullable [Border Type](https://xplr.dev/en/borders#border-type)
#### xplr.config.general.panel_ui.sort_and_filter.border_style
Style of the sort & filter panel borders.
Type: [Style](https://xplr.dev/en/style)
#### xplr.config.general.initial_sorting
Initial group if sorters applied to the nodes list in the table.
Type: nullable list of [Node Sorter](https://xplr.dev/en/sorting#node-sorter-applicable)
#### xplr.config.general.initial_mode
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).
## panel_ui.sort_and_filter.title.format
Type: nullable string
#### xplr.config.general.initial_layout
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
The content for `Sort & filter` panel title.
#### xplr.config.general.start_fifo
Set it to a file path to start fifo when xplr loads.
Generally it is used to integrate with external tools like previewers.
Type: nullable string
## panel_ui.sort_and_filter.title.style
#### xplr.config.general.global_key_bindings
Type: [Style][1]
Use it to define a set of key bindings that are available by default in
every [mode](https://xplr.dev/en/mode). They can be overwritten.
Style for `Sort & filter` panel title.
Type: [Key Bindings](https://xplr.dev/en/configure-key-bindings#key-bindings)
[1]: style.md
[2]: layouts.md
[3]: modes.md
[4]: sorting.md#node-sorter-applicable
[5]: layouts.md#constraint
[6]: #tableheadercols
[7]: column-renderer.md
[8]: filtering.md#filter
[9]: sorting.md#sorter

@ -1,27 +0,0 @@
# Input Operation
Cursor based input operation is a [sum type][3] can be one of the following:
- { SetCursor = int }
- { InsertCharacter = str }
- "GoToPreviousCharacter"
- "GoToNextCharacter"
- "GoToPreviousWord"
- "GoToNextWord"
- "GoToStart"
- "GoToEnd"
- "DeletePreviousCharacter"
- "DeleteNextCharacter"
- "DeletePreviousWord"
- "DeleteNextWord"
- "DeleteLine"
- "DeleteTillEnd"
## Also See:
- [Message][1]
- [Full List of Messages][2]
[1]: message.md
[2]: messages.md
[3]: sum-type.md

@ -1,12 +1,12 @@
# Try in Docker
If you prefer to try it before installing, here's the snippet for your
If you prefer to try it before installing, here's the snipper for your
convenience.
```bash
docker run -w / -it --rm ubuntu sh -uec '
apt-get update -y
apt-get install -y wget tar vim less
apt-get install -y wget tar vim
wget https://github.com/sayanarijit/xplr/releases/latest/download/xplr-linux.tar.gz
tar -xzvf xplr-linux.tar.gz
./xplr
@ -24,7 +24,8 @@ version of xplr, but they have one common drawback - the user will need to keep
an eye on the releases, and manually upgrade xplr when a new version is
available.
One way to keep an eye on the releases is to [watch the repository][4].
One way to keep an eye on the releases is to
[watch the repository][4].
## Community Maintained Repositories
@ -33,60 +34,8 @@ repositories:
[![packaging status][5]][6]
### Cross-platform
#### [Nixpkgs][10]
```
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
(same for Manjaro Linux)
#### [Official Community Repo][7]
```
@ -95,34 +44,31 @@ sudo pacman -S xplr
#### [AUR][8]
Git version:
Binary version:
```
paru -S xplr-git
paru -S xplr-bin
```
### Alpine Linux
#### [Edge Testing Repo][27]
Git version:
```
# Add the following line in /etc/apk/repositories:
# https://dl-cdn.alpinelinux.org/alpine/edge/testing
apk add xplr bash less
paru -S xplr-git
```
### Void Linux
#### [void-templates by shubham][9]
### Gentoo
### Nix(OS)
#### [Overlay GURU][28]
#### [Nixpkgs][10]
### macOS
```
nix-env -f https://github.com/NixOS/nixpkgs/tarball/master -iA xplr
```
Make sure you have the latest version of [GNU core utilities][29] installed.
### macOS
#### [MacPorts][11]
@ -182,14 +128,13 @@ One can directly download the standalone binary from the
Currently, the following options are available for direct download:
- [GNU/Linux][16]
- [Linux musl][26]
- [Linux][16]
- [macOS][17]
Command-line instructions:
```bash
platform="linux" # or "macos" / "linux-musl"
platform="linux" # or "macos"
# Download
wget https://github.com/sayanarijit/xplr/releases/latest/download/xplr-$platform.tar.gz
@ -238,16 +183,57 @@ cargo build --locked --release --bin xplr
sudo cp target/release/xplr /usr/local/bin/
```
## Android
### [Termux][23]
[![xplr-termuxfd3c398d3cf4bcbc.md.jpg][24]][25]
> Please note that xplr isn't heavily tested on Termux, hence things might
> 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
export XDG_CONFIG_HOME="$PWD/storage/.config"
export XDG_RUNTIME_DIR="$PWD/storage/run"
mkdir -p "$XDG_CONFIG_HOME" "$XDG_RUNTIME_DIR"
```
- Run
```bash
~/.cargo/bin/xplr
```
[1]: #direct-download
[2]: #from-cratesio
[2]: #from-a-hrefhttpscratesiocratesxplrcratesioa
[3]: #build-from-source
[4]: https://github.com/sayanarijit/xplr/watchers
[5]: https://repology.org/badge/vertical-allrepos/xplr.svg
[6]: https://repology.org/project/xplr/versions
[7]: https://archlinux.org/packages/extra/x86_64/xplr
[7]: https://archlinux.org/packages/community/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
[9]: https://github.com/shubham-cpp/void-pkg-templates
[10]: https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/xp/xplr/package.nix
[10]: https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/misc/xplr
[11]: https://ports.macports.org/port/xplr
[12]: https://formulae.brew.sh/formula/xplr
[13]: https://cgit.freebsd.org/ports/plain/misc/xplr/
@ -260,10 +246,6 @@ sudo cp target/release/xplr /usr/local/bin/
[20]: https://gcc.gnu.org/
[21]: https://www.gnu.org/software/make/
[22]: https://git-scm.com/
[23]: https://github.com/sayanarijit/xplr/assets/11632726/3b61e8c8-76f0-48e8-8734-50e9e7e495b7
[23]: https://termux.com/
[24]: https://s3.gifyu.com/images/xplr-termuxfd3c398d3cf4bcbc.md.jpg
[25]: https://gifyu.com/image/tF2D
[26]: https://github.com/sayanarijit/xplr/releases/latest/download/xplr-linux-musl.tar.gz
[27]: https://pkgs.alpinelinux.org/packages?name=xplr
[28]: https://gpo.zugaina.org/Overlays/guru/app-misc/xplr
[29]: https://formulae.brew.sh/formula/coreutils
[30]: https://github.com/nix-community/home-manager/blob/master/modules/programs/xplr.nix

@ -1,20 +1,12 @@
# Installing Plugins
One way to install plugins is to use a plugin manager like [dtomvan/xpm.xplr][1].
But you can also install and manage plugins manually.
## Install Manually
Until we get a cool plugin manager, let's install plugins manually using the
following procedure:
- Add the following line in `~/.config/xplr/init.lua`
```lua
local home = os.getenv("HOME")
package.path = home
.. "/.config/xplr/plugins/?/init.lua;"
.. home
.. "/.config/xplr/plugins/?.lua;"
.. package.path
package.path = os.getenv("HOME") .. '/.config/xplr/plugins/?/src/init.lua'
```
- Clone the plugin
@ -33,29 +25,3 @@ But you can also install and manage plugins manually.
-- The setup arguments might differ for different plugins.
-- Visit the project README for setup instructions.
```
## Luarocks Support
Some plugins may require [luarocks][2] to work.
Setup luarocks with the following steps:
- Install luarocks (via your package managers or follow the [official guide][2]).
- Add `eval "$(luarocks path --lua-version 5.1)"` in your `.bashrc` or `.zshrc`.
- Add the following lines in `~/.config/xplr/init.lua`
```lua
package.path = os.getenv("LUA_PATH") .. ";" .. package.path
package.cpath = os.getenv("LUA_CPATH") .. ";" .. package.cpath
```
Now you can install packages using luarocks. Be sure to append `--lua-version`.
Example:
```bash
luarocks install luafilesystem --local --lua-version 5.1
```
[1]: https://github.com/dtomvan/xpm.xplr
[2]: https://luarocks.org

@ -8,71 +8,94 @@ To achieve its goal, xplr strives to be a fast, minimal and more importantly,
hackable file explorer.
xplr is not meant to be a replacement for the standard shell commands or the
GUI file managers. Rather, it aims to [integrate them all][14] and expose an
intuitive, scriptable, [keyboard controlled][2],
[real-time visual interface][1], also being an ideal candidate for [further
integration][15], enabling you to achieve insane terminal productivity.
GUI file managers. Rather, it aims to integrate them all and expose an
intuitive, scriptable, keyboard controlled, real-time visual interface, also
being an ideal candidate for further integration, enabling the users to achieve
insane terminal productivity.
## Concept
## Features
### Hackable
xplr is built with configurability in mind. So it allows you to perform a vast
set of operations and make it look and behave just the way you want.
set of operations and make it behave just the way you want.
A few things you can do with the xplr configuration
- [Hacks][16]
- [Plugins][3]
- [Integrations][15]
- [Hack the layout][1]
- [Hack the key bindings][2]
- [Extend with plugins][3]
### Fast
## Fast
Although speed is not the primary concern, xplr is already fast enough so that
you can take it out for a walk into your `node_modules` or `/nix/store` any
time you want, and it will only get faster. Still, if you feel like it's
somehow making you slow, just report it. Most probably we're just waiting for
someone to complain.
**Tip:** A quick and easy way to optimize the UI rendering is reducing the
number of columns in the table.
### Minimal
xplr is being referred to as a _File Explorer_, not a _File Manager_. This
is because at the core, xplr is only an explorer, and [outsources][18] the file
management operations to external commands. This helps xplr stay minimal, and
focus only on doing what it does best.
So, just like speed, minimalism isn't as as aggressively pursued as
hackability. xplr simply prefers to stay minimal and looks for the opportunity
to lose some kb if it makes sense.
## Features
Some of the coolest features xplr provide beside the basic stuff:
- [Embedded LuaJIT][5] for portability and extensibility.
- [A simple modal system based on message passing][10] to control xplr session
using:
- [Keyboard inputs][11]
- [Shell Commands][12]
- [Lua Functions][13]
- [Hooks][22]
- Easy, typesafe message passing with `-m MSG` or `-M MSG` subcommands.
- [Readline-like input buffer][9] with customizable behavior to read user
inputs.
- [Switchable recover mode][7] that saves you from doing unwanted things when
in a hurry.
- [Customizable layouts][1] with built-in panels. For e.g.
- **Selection list** to show you the selected paths in real-time.
- **Help menu** to show you the available keys bindings in each mode.
- **Input & logs** to read input and display logs.
- **Filter and sort pipeline** to show you the applied filters and sorters.
- [Custom file properties][17] with custom colors can be displayed in the table.
- [FIFO manager][19] to manage a FIFO file that can be used to
time you want. I currently
[measure the most commonly used operations][4]
and I have seen it improve significantly over time, and it's only the start.
**Tip:** A quick and easy way to optimize UI rendering is reducing the number
of columns in the table.
**Note:** If you feel xplr is not behaving at its optimal, this is probably
because I am waiting for someone to complain. I want to avoid optimizing things
I don't need to, because optimization often requires either complexity or
feature sacrifice or both.
## Minimalist
xplr prefers to stay minimal, both in terms of features and binary size, but
just like speed, minimalism isn't as aggressively pursued as configurability.
If adding some feature, lines of code, or a dependency allows the users to be a
little more productive or allows xplr to be a little more configurable, it will
be considered. But of-course, the `bulk vs productivity gain per user` balance
will also be considered in the decision-making.
## Other features
- [Embedded LuaJIT][5] for
portability and extensibility.
- **Switchable recover mode:** Saves you from doing unwanted things when in a
hurry.
- **Sane (vim-like) defaults:**
- Use `h`, `j`, `k`, `l` or arrow keys
for basic navigation.
- Go to top using `g` `g`, and bottom using `G`.
- Travel history using `ctrl-o` and `ctrl-i`.
- Go to home directory using `~`.
- Enter search mode with `/` or `ctrl-f`.
- Go to absolute index (e.g. `4`) using `4` `enter` or
`:` `4` `enter`.
- Go to relative index (e.g. `4` `down`) using `4` `down` or
`:` `4` `down`.
- Follow symlink using `g` `f`.
- Open in GUI using `g` `x`.
- Spawn terminal using `:` `!`.
- Toggle selection using `v` or `space`.
- Toggle select all using `V` or `ctrl-a`.
- Clear selections using `ctrl-u`.
- **Separate keys for navigation:** navigation keys are separated from the
action keys (e.g. file opening action) to avoid mistakenly performing
unwanted actions while navigating.
- **Always visible panels** to save you brain cycles:
- Selection list.
- Help menu.
- Input & logs.
- Filter and sort pipeline.
- **Batch creation:** Create multiple files and directories without repeating
keys.
- **Batch sort & filter:** Apply sorters and filters in without repeating keys.
- **Custom file properties:** Display custom file properties with custom colors
in the table using Lua functions.
- **Input buffer:** Read user input using the built-in input buffer with
customizable behavior.
- **Switchable layouts:** Switch layouts dynamically without leaving `xplr`.
- **Saved locations:** Never lose context when traveling back and forth
directories.
- **Auto refresh state:** Auto refresh app state when the `$PWD` changes.
- **Manually refresh UI** when other apps mess it up.
- **FIFO-based previews:** Easy to manage FIFO file that can be used to
[integrate with previewers][6].
- [Virtual root][21] with `--vroot` and `:v` key bindings.
- **Different quit options:**
- Quit with success without any output (`q`).
- Quit with success and the result printed on stdout (`enter`).
@ -85,23 +108,8 @@ Some of the coolest features xplr provide beside the basic stuff:
- Quit with failure (`ctrl-c`).
[1]: layouts.md
[2]: configure-key-bindings.md
[2]: modes.md
[3]: awesome-plugins.md
[4]: https://github.com/sayanarijit/xplr/tree/main/benches
[5]: https://github.com/sayanarijit/xplr/discussions/183
[6]: https://github.com/sayanarijit/xplr/pull/229
[7]: modes.md#xplrconfigmodesbuiltinrecover
[8]: default-key-bindings.md
[9]: https://github.com/sayanarijit/xplr/pull/397
[10]: messages.md
[11]: configure-key-bindings.md
[12]: mode.md#input-pipe
[13]: lua-function-calls.md
[14]: awesome-plugins.md#integration
[15]: awesome-integrations.md
[16]: awesome-hacks.md
[17]: node_types.md
[18]: https://github.com/sayanarijit/xplr/blob/main/src/init.lua
[19]: messages.md#startfifo
[21]: messages.md#virtual-root
[22]: configuration.md#hooks

@ -1,597 +0,0 @@
# Layout
#### Example: Defining Custom Layout
```lua
xplr.config.layouts.builtin.default = {
Horizontal = {
config = {
margin = 1,
horizontal_margin = 1,
vertical_margin = 1,
constraints = {
{ Percentage = 50 },
{ Percentage = 50 },
}
},
splits = {
"Table",
"HelpMenu",
}
}
}
```
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:
- [Nothing][8]
- [Table][9]
- [InputAndLogs][10]
- [Selection][11]
- [HelpMenu][12]
- [SortAndFilter][13]
- [Static][25]
- [Dynamic][26]
- [Horizontal][14]
- [Vertical][16]
- CustomContent (deprecated, use `Static` or `Dynamic`)
### Nothing
This layout contains a blank panel.
Type: "Nothing"
### Table
This layout contains the table displaying the files and directories in the current
directory.
### InputAndLogs
This layout contains the panel displaying the input prompt and logs.
Type: "InputAndLogs"
### Selection
This layout contains the panel displaying the selected paths.
Type: "Selection"
### HelpMenu
This layout contains the panel displaying the help menu for the current mode in
real-time.
Type: "HelpMenu"
### SortAndFilter
This layout contains the panel displaying the pipeline of sorters and filters applied on
the list of paths being displayed.
Type: "SortAndFilter"
### Static
This is a custom layout to render static content.
Type: { Static = [Custom Panel][27] }
### Dynamic
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].
Type: { Dynamic = "[Content Renderer][35]" }
### Horizontal
This is a special layout that splits the panel into two horizontal parts.
It contains the following information:
- [config][15]
- [splits][17]
Type: { Vertical = { config = [Layout Config][15], splits = { [Layout][17], ... } }
### Vertical
This is a special layout that splits the panel into two vertical parts.
It contains the following information:
- [config][15]
- [splits][17]
Type: { Vertical = { config = [Layout Config][15], splits = { [Layout][17], ... } }
## Layout Config
A layout config contains the following information:
- [margin][18]
- [horizontal_margin][19]
- [vertical_margin][20]
- [constraints][21]
### margin
Type: nullable integer
The width of the margin in all direction.
### horizontal_Margin
Type: nullable integer
The width of the horizontal margins. Overwrites the [margin][18] value.
### vertical_Margin
Type: nullable integer
The width of the vertical margins. Overwrites the [margin][18] value.
### constraints
Type: nullable list of [Constraint][22]
The constraints applied on the layout.
## Constraint
A constraint is a [sum type][56] can be one of the following:
- { Percentage = int }
- { Ratio = { int, int } }
- { Length = { int }
- { LengthLessThanScreenHeight = int }
- { LengthLessThanScreenWidth = int }
- { LengthLessThanLayoutHeight = int }
- { LengthLessThanLayoutWidth = int }
- { Max = int }
- { MaxLessThanScreenHeight = int }
- { MaxLessThanScreenWidth = int }
- { MaxLessThanLayoutHeight = int }
- { MaxLessThanLayoutWidth = int }
- { Min = int }
- { MinLessThanScreenHeight = int }
- { MinLessThanScreenWidth = int }
- { MinLessThanLayoutHeight = int }
- { MinLessThanLayoutWidth = int }
## splits
Type: list of [Layout][3]
The list of child layouts to fit into the parent layout.
## Custom Panel
Custom panel is a [sum type][56] can be one of the following:
- [CustomParagraph][29]
- [CustomList][30]
- [CustomTable][31]
- [CustomLayout][55]
### CustomParagraph
A paragraph to render. It contains the following fields:
- **ui** (nullable [Panel UI Config][32]): Optional UI config for the panel.
- **body** (string): The string to render.
#### Example: Render a custom static paragraph
```lua
xplr.config.layouts.builtin.default = {
Static = {
CustomParagraph = {
ui = { title = { format = " custom title " } },
body = "custom body",
},
},
}
```
Result:
```
╭ custom title ────────╮
│custom body │
│ │
│ │
╰──────────────────────╯
```
#### Example: Render a custom dynamic paragraph
```lua
xplr.config.layouts.builtin.default = { Dynamic = "custom.render_layout" }
xplr.fn.custom.render_layout = function(ctx)
return {
CustomParagraph = {
ui = { title = { format = ctx.app.pwd } },
body = xplr.util.to_yaml(ctx.app.focused_node),
},
}
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
A list to render. It contains the following fields:
- **ui** (nullable [Panel UI Config][32]): Optional UI config for the panel.
- **body** (list of string): The list of strings to display.
#### Example: Render a custom static list
```lua
xplr.config.layouts.builtin.default = {
Static = {
CustomList = {
ui = { title = { format = " custom title " } },
body = { "1", "2", "3" },
},
},
}
```
Result:
```
╭ custom title ─────────────╮
│1 │
│2 │
│3 │
│ │
╰───────────────────────────╯
```
#### Example: Render a custom dynamic list
```lua
xplr.config.layouts.builtin.default = { Dynamic = "custom.render_layout" }
xplr.fn.custom.render_layout = function(ctx)
return {
CustomList = {
ui = { title = { format = ctx.app.pwd } },
body = {
(ctx.app.focused_node or {}).relative_path or "",
ctx.app.version,
tostring(ctx.app.pid),
},
},
}
end
```
Result:
```
╭/home/sayanarijit──────────╮
│Desktop │
│0.21.2 │
│17336 │
│ │
│ │
╰───────────────────────────╯
```
## CustomTable
A custom table to render. It contains the following fields:
- **ui** (nullable [Panel UI Config][32]): Optional UI config for the panel.
- **widths** (list of [Constraint][22]): Width of the columns.
- **col_spacing** (nullable int): Spacing between columns. Defaults to 1.
- **body** (list of list of string): The rows and columns to render.
#### Example: Render a custom static table
```lua
xplr.config.layouts.builtin.default = {
Static = {
CustomTable = {
ui = { title = { format = " custom title " } },
widths = {
{ Percentage = 50 },
{ Percentage = 50 },
},
body = {
{ "a", "b" },
{ "c", "d" },
},
},
},
}
```
Result:
```
╭ custom title ────────────────────╮
│a b │
│c d │
│ │
│ │
│ │
╰──────────────────────────────────╯
```
#### Example: Render a custom dynamic table
```lua
xplr.config.layouts.builtin.default = {Dynamic = "custom.render_layout" }
xplr.fn.custom.render_layout = function(ctx)
return {
CustomTable = {
ui = { title = { format = ctx.app.pwd } },
widths = {
{ Percentage = 50 },
{ Percentage = 50 },
},
body = {
{ "", "" },
{ "Layout height", tostring(ctx.layout_size.height) },
{ "Layout width", tostring(ctx.layout_size.width) },
{ "", "" },
{ "Screen height", tostring(ctx.screen_size.height) },
{ "Screen width", tostring(ctx.screen_size.width) },
},
},
}
end
```
Result:
```
╭/home/sayanarijit───────────────────────────╮
│ │
│Layout height 12 │
│Layout width 46 │
│ │
│Screen height 12 │
│Screen width 46 │
│ │
│ │
│ │
│ │
╰────────────────────────────────────────────╯
```
### CustomLayout
A whole custom layout to render. It doesn't make sense to use it as a
[Static][25] layout, but can be very useful to render as a [Dynamic][26] layout
for use cases where the structure of the layout needs to change without having
to switch modes.
> WARNING: Rendering the same dynamic custom layout recursively will result in
> a ugly crash.
#### Example: Render a custom dynamic layout
```lua
xplr.config.layouts.builtin.default = { Dynamic = "custom.render_layout" }
xplr.fn.custom.render_layout = function(ctx)
local inner = {
config = {
constraints = {
{ Percentage = 50 },
{ Percentage = 50 },
},
},
splits = {
{ Static = { CustomParagraph = { body = "Try your luck..." } } },
{ Static = { CustomParagraph = { body = "Press ctrl-r" } } },
},
}
local layout_type = "Vertical"
if math.random(1, 2) == 1 then
layout_type = "Horizontal"
end
return { CustomLayout = { [layout_type] = inner } }
end
```
Result:
```
╭─────────────────────╮╭─────────────────────╮
│Try your luck... ││Press ctrl-r │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
╰─────────────────────╯╰─────────────────────╯
```
Or
```
╭────────────────────────────────────────────╮
│Try your luck... │
│ │
│ │
│ │
╰────────────────────────────────────────────╯
╭────────────────────────────────────────────╮
│Press ctrl-r │
│ │
│ │
│ │
╰────────────────────────────────────────────╯
```
## Panel UI Config
It contains the following optional fields:
- **title** ({ format = "string", style = [Style][33] }): the title of the panel.
- **style** ([Style][33]): The style of the panel body.
- **borders** (nullable list of [Border][34]): The shape of the borders.
- **border_type** ([Border Type][54]): The type of the borders.
- **border_style** ([Style][33]): The style of the borders.
## Content Renderer
It is a Lua function that receives [a special argument][36] as input and
returns some output that can be rendered in the UI. It is used to render
content body for the custom dynamic layouts.
## Content Renderer Argument
It contains the following information:
- [layout_size][37]
- [screen_size][37]
- [scrolltop][57]
- [app][38]
### Size
It contains the following information:
- x
- y
- height
- width
Every field is of integer type.
### scrolltop
Type: integer
The start index of the visible nodes in the table.
### app
This is a lightweight version of the [Lua Context][39]. In this context, the
heavyweight fields like [directory_buffer][50] are omitted for performance
reasons.
Hence, only the following fields are available.
- [version][40]
- [pwd][41]
- [initial_pwd][53]
- [vroot][52]
- [focused_node][42]
- [selection][43]
- [mode][44]
- [layout][45]
- [input_buffer][46]
- [pid][47]
- [session_path][48]
- [explorer_config][49]
## Also See:
- [xplr.config.layouts][51]
[1]: #builtin
[2]: #custom
[3]: #layout
[4]: #default
[5]: #no_help
[6]: #no_selection
[7]: #no_help_no_selection
[8]: #nothing
[9]: #table
[10]: #inputandlogs
[11]: #selection
[12]: #helpmenu
[13]: #sortandfilter
[14]: #horizontal
[15]: #layout-config
[16]: #vertical
[17]: #splits
[18]: #margin
[19]: #horizontal_margin
[20]: #vertical_margin
[21]: #constraints
[22]: #constraint
[25]: #static
[26]: #dynamic
[27]: #custom-panel
[28]: configuration.md#function
[29]: #customparagraph
[30]: #customlist
[31]: #customtable
[32]: #panel-ui-config
[33]: style.md#style
[34]: borders.md#border
[35]: #content-renderer
[36]: #content-renderer-argument
[37]: #size
[38]: #app
[39]: lua-function-calls.md#lua-context
[40]: lua-function-calls.md#version
[41]: lua-function-calls.md#pwd
[42]: lua-function-calls.md#focused_node
[43]: lua-function-calls.md#selection
[44]: lua-function-calls.md#mode
[45]: lua-function-calls.md#layout
[46]: lua-function-calls.md#input_buffer
[47]: lua-function-calls.md#pid
[48]: lua-function-calls.md#session_path
[49]: lua-function-calls.md#explorer_config
[50]: lua-function-calls.md#directory_buffer
[51]: layouts.md
[52]: lua-function-calls.md#vroot
[53]: lua-function-calls.md#initial_pwd
[54]: borders.md#border-type
[55]: #customlayout
[56]: sum-type.md
[57]: #scrolltop

@ -1,24 +1,16 @@
### Layouts
# Layouts
xplr layouts define the structure of the UI, i.e. how many panel we see,
placement and size of the panels, how they look etc.
This is configuration exposed via the `xplr.config.layouts` API.
`xplr.config.layouts.builtin` contain some built-in panels which can be
overridden, but you can't add or remove panels in it.
You can add new panels in `xplr.config.layouts.custom`.
#### Example: Defining Custom Layout
##### Example: Defining Custom Layout
[![layout.png][23]][24]
```lua
xplr.config.layouts.builtin.default = {
Horizontal = {
config = {
margin = 1,
horizontal_margin = 1,
vertical_margin = 1,
horizontal_margin = 2,
vertical_margin = 3,
constraints = {
{ Percentage = 50 },
{ Percentage = 50 },
@ -32,57 +24,438 @@ xplr.config.layouts.builtin.default = {
}
```
Result:
xplr layouts define the structure of the UI, i.e. how many panel we see,
placement and size of the panels, how they look etc.
This is configuration exposed via the `xplr.config.layouts` API. It contains
the following fields:
- [builtin][1]
- [custom][2]
The users can switch between these layouts at run-time.
## builtin
Type: mapping of string and [Layout][3]
This is exposed by the `xplr.config.layouts.builtin` API.
xplr by default provides the following builtin layouts:
- [default][4]
- [no_help][5]
- [no_selection][6]
- [no_help_no_selection][7]
### default
Type: [Layout][3]
This is the default layout we see when we run xplr.
### no_help
Type: [Layout][3]
This layout hides the help menu.
### no_selection
Type: [Layout][3]
This layout hides the selection panel.
### no_help_no_selection
Type: [Layout][3]
This layout hides both the help menu and the selection panel.
## custom
Type: mapping of string and [Layout][3]
This is exposed by the `xplr.config.layouts.custom` API.
It allows the users to define any custom layout.
Example:
```lua
xplr.config.layouts.custom.example = "Nothing"
xplr.config.general.initial_layout = "example"
-- when you load xplr, you should see a blank screen
```
## Layout
A layout can be one of the following:
- ["Nothing"][8]
- ["Table"][9]
- ["InputAndLogs"][10]
- ["Selection"][11]
- ["HelpMenu"][12]
- ["SortAndFilter"][13]
- { [CustomContent][25] = { [title][33], [body][34] }
- { [Horizontal][14] = { [config][15], [splits][17] }
- { [Vertical][16] = { [config][15], [splits][17] }
### Nothing
This layout contains a blank panel.
### Table
This layout contains the table displaying the files and directories in the
current directory.
### InputAndLogs
This layout contains the panel displaying the input prompt and logs.
### Selection
This layout contains the panel displaying the selected paths.
### HelpMenu
This layout contains the panel displaying the help menu for the current mode in
real-time.
### SortAndFilter
This layout contains the panel displaying the pipeline of sorters and filters
applied of the list of paths being displayed.
### Horizontal
This is a special layout that splits the panel into two horizontal parts.
It contains the following information:
- [config][15]
- [splits][17]
### Vertical
This is a special layout that splits the panel into two vertical parts.
It contains the following information:
- [config][15]
- [splits][17]
## Layout Config
A layout config contains the following information:
- [margin][18]
- [horizontal_margin][19]
- [vertical_margin][20]
- [constraints][21]
### margin
Type: nullable integer
The width of the margin in all direction.
### horizontal_Margin
Type: nullable integer
The width of the horizontal margins. Overwrites the [margin][18] value.
### vertical_Margin
Type: nullable integer
The width of the vertical margins. Overwrites the [margin][18] value.
### constraints
Type: nullable list of [Constraint][22]
The constraints applied on the layout.
## Constraint
A constraint can be one of the following:
- { Percentage = int }
- { Ratio = { int, int } }
- { Length = { int }
- { LengthLessThanScreenHeight = int }
- { LengthLessThanScreenWidth = int }
- { LengthLessThanLayoutHeight = int }
- { LengthLessThanLayoutWidth = int }
- { Max = int }
- { MaxLessThanScreenHeight = int }
- { MaxLessThanScreenWidth = int }
- { MaxLessThanLayoutHeight = int }
- { MaxLessThanLayoutWidth = int }
- { Min = int }
- { MinLessThanScreenHeight = int }
- { MinLessThanScreenWidth = int }
- { MinLessThanLayoutHeight = int }
- { MinLessThanLayoutWidth = int }
TODO: document each constraint.
## splits
Type: list of [Layout][3]
The list of child layouts to fit into the parent layout.
## Custom Content
Custom content is a special layout to render something custom.
It contains the following information:
- [title][33]
- [body][34]
### title
Type: nullable string
The title of the panel.
### body
Type: [Content Body][26]
The body of the panel.
## Content Body
Content body can be one of the following:
- [StaticParagraph][27]
- [DynamicParagraph][28]
- [StaticList][29]
- [DynamicList][30]
- [StaticTable][31]
- [DynamicTable][32]
### Static Paragraph
A paragraph to render. It contains the following fields:
- **render** (string): The string to render.
#### Example: Render a custom static paragraph
```lua
xplr.config.layouts.builtin.default = {
CustomContent = {
title = "custom title",
body = {
StaticParagraph = { render = "custom body" },
},
},
}
```
╭ /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 │
╰────────────────────╯╰────────────────────╯
### Dynamic Paragraph
A [Lua function][35] to render a custom paragraph.
It contains the following fields:
- **render** (string): The [lua function][35] that returns the paragraph to
render.
#### Example: Render a custom dynamic paragraph
```lua
xplr.config.layouts.builtin.default = {
CustomContent = {
title = "custom title",
body = { DynamicParagraph = { render = "custom.render_layout" } },
},
}
xplr.fn.custom.render_layout = function(ctx)
return ctx.app.pwd
end
```
#### xplr.config.layouts.builtin.default
### Static List
The default layout
A list to render. It contains the following fields:
Type: [Layout](https://xplr.dev/en/layout)
- **render** (list of string): The list to render.
#### xplr.config.layouts.builtin.no_help
#### Example: Render a custom static list
The layout without help menu
```lua
xplr.config.layouts.builtin.default = {
CustomContent = {
title = "custom title",
body = {
StaticList = { render = { "1", "2", "3" } },
},
},
}
```
Type: [Layout](https://xplr.dev/en/layout)
### Dynamic List
#### xplr.config.layouts.builtin.no_selection
A [Lua function][35] to render a custom list.
It contains the following fields:
The layout without selection panel
- **render** (string): The [lua function][35] that returns the list to render.
Type: [Layout](https://xplr.dev/en/layout)
#### Example: Render a custom dynamic list
#### xplr.config.layouts.builtin.no_help_no_selection
```lua
xplr.config.layouts.builtin.default = {
CustomContent = {
title = "custom title",
body = { DynamicList = { render = "custom.render_layout" } },
},
}
The layout without help menu and selection panel
xplr.fn.custom.render_layout = function(ctx)
return ctx.app.history.paths
end
```
Type: [Layout](https://xplr.dev/en/layout)
### Static Table
#### xplr.config.layouts.custom
A table to render. It contains the following fields:
This is where you can define custom layouts
- **widths** (list of [Constraint][22]): Width of the columns.
- **col_spacing** (nullable int): Spacing between columns. Defaults to 1.
- **render** (list of list of string): The rows and columns to render.
Type: mapping of the following key-value pairs:
#### Example: Render a custom static table
- key: string
- value: [Layout](https://xplr.dev/en/layout)
```lua
xplr.config.layouts.builtin.default = {
CustomContent = {
title = "custom title",
body = {
StaticTable = {
widths = {
{ Percentage = 50 },
{ Percentage = 50 },
},
col_spacing = 1,
render = {
{ "a", "b" },
{ "c", "d" },
},
},
},
},
}
```
Example:
### Dynamic Table
A [Lua function][35] to render a custom table.
It contains the following fields:
- **widths** (list of [Constraint][22]): Width of the columns.
- **col_spacing** (nullable int): Spacing between columns. Defaults to 1.
- **render** (string): The [lua function][35] that returns the table to render.
#### Example: Render a custom dynamic table
```lua
xplr.config.layouts.custom.example = "Nothing" -- Show a blank screen
xplr.config.general.initial_layout = "example" -- Load the example layout
xplr.config.layouts.builtin.default = {
CustomContent = {
title = "custom title",
body = {
DynamicTable = {
widths = {
{ Percentage = 50 },
{ Percentage = 50 },
},
col_spacing = 1,
render = "custom.render_layout",
},
},
},
}
xplr.fn.custom.render_layout = function(ctx)
return {
{ "", "" },
{ "Layout height", tostring(ctx.layout_size.height) },
{ "Layout width", tostring(ctx.layout_size.width) },
{ "", "" },
{ "Screen height", tostring(ctx.screen_size.height) },
{ "Screen width", tostring(ctx.screen_size.width) },
}
end
```
## Content Renderer
It is a Lua function that receives [a special argument][36] as input and
returns some output that can be rendered in the UI. It is used to render
content body for the custom dynamic layouts.
## Content Renderer Argument
It contains the following information:
- [layout_size][37]
- [screen_size][37]
- [app][38]
## Size
It contains the following information:
- x
- y
- height
- width
Every field is of integer type.
[1]: #builtin
[2]: #custom
[3]: #layout
[4]: #default
[5]: #no_help
[6]: #no_selection
[7]: #no_help_no_selection
[8]: #nothing
[9]: #table
[10]: #inputandlogs
[11]: #selection
[12]: #helpmenu
[13]: #sortandfilter
[14]: #horizontal
[15]: #layout-config
[16]: #vertical
[17]: #splits
[18]: #margin
[19]: #horizontal_margin
[20]: #vertical_margin
[21]: #constraints
[22]: #constraint
[23]: https://s6.gifyu.com/images/layout.png
[24]: https://gifyu.com/image/1X38
[25]: #custom-content
[26]: #content-body
[27]: #static-paragraph
[28]: #dynamic-paragraph
[29]: #static-list
[30]: #dynamic-list
[31]: #static-table
[32]: #dynamic-table
[33]: #title
[34]: #body
[35]: #content-renderer
[36]: #content-renderer-argument
[37]: #size
[38]: message.md#calllua-argument

@ -1,444 +0,0 @@
# Lua Function Calls
xplr allows you to define lua functions using the `xplr.fn.custom` Lua API.
These functions can be called using messages like `CallLua`, `CallLuaSilently`.
When called the function receives a [special argument][14] that
contains some useful information. The function can optionally return a list of
messages which will be handled by xplr.
## Example: Using Lua Function Calls
```lua
-- Define the function
xplr.fn.custom.ask_name_and_greet = function(app)
print("What's your name?")
local name = io.read()
local greeting = "Hello " .. name .. "!"
local message = greeting .. " You are inside " .. app.pwd
return {
{ LogSuccess = message },
}
end
-- Map the function to a key (space)
xplr.config.modes.builtin.default.key_bindings.on_key.space = {
help = "ask name and greet",
messages = {
{ CallLua = "custom.ask_name_and_greet" }
}
}
-- Now, when you press "space" in default mode, you will be prompted for your
-- name. Enter your name to receive a nice greeting and to know your location.
```
Visit the [xplr.util][85] API docs for some useful utility / helper functions
that you can use in your Lua function calls.
## Lua Context
This is a special argument passed to the lua functions when called using the
`CallLua`, `CallLuaSilently` messages.
It contains the following information:
- [version][29]
- [pwd][31]
- [initial_pwd][76]
- [vroot][75]
- [focused_node][32]
- [directory_buffer][33]
- [selection][34]
- [mode][35]
- [layout][36]
- [input_buffer][37]
- [pid][38]
- [session_path][39]
- [explorer_config][40]
- [history][41]
- [last_modes][42]
### version
Type: string
xplr version. Can be used to test compatibility.
### pwd
Type: string
The present working directory.
### initial_pwd
Type: string
The initial working directory when xplr started.
### vroot
Type: nullable string
The current virtual root.
### focused_node
Type: nullable [Node][44]
The node under focus.
### directory_buffer
Type: nullable [Directory Buffer][62]
The directory buffer being rendered.
### selection
Type: list of selected [Node][44]s
The selected nodes.
### mode
Type: [Mode][8]
Current mode.
### layout
Type: [Layout][11]
Current layout.
### input_buffer
Type: nullable string
The input buffer.
### pid
Type: integer
The xplr session PID.
### session_path
Type: string
The session path.
### explorer_config
Type: [Explorer Config][66]
The configuration for exploring paths.
### history
Type: [History][70]
### last_modes
Type: list of [Mode][8]
Last modes, not popped yet.
## Node
A node contains the following fields:
- [parent][45]
- [relative_path][46]
- [absolute_path][47]
- [extension][48]
- [is_symlink][49]
- [is_broken][50]
- [is_dir][51]
- [is_file][52]
- [is_readonly][53]
- [mime_essence][54]
- [size][55]
- [human_size][56]
- [permissions][57]
- [created][71]
- [last_modified][72]
- [uid][73]
- [gid][74]
- [canonical][58]
- [symlink][59]
### parent
Type: string
The parent path of the node.
### relative_path
Type: string
The path relative to the parent, i.e. the file/directory name with extension.
### absolute_path
Type: string
The absolute path (without resolving symlinks) of the node.
### extension
Type: string
The extension of the node.
### is_symlink
Type: boolean
`true` if the node is a symlink.
### is_broken
Type: boolean
`true` if the node is a broken symlink.
### is_dir
Type: boolean
`true` if the node is a directory.
### is_file
Type: boolean
`true` if the node is a file.
### is_readonly
Type: boolean
`true` if the node is real-only.
### mime_essence
Type: string
The mime type of the node. For e.g. `text/csv`, `image/jpeg` etc.
### size
Type: integer
The size of the exact node. The size of a directory won't be calculated
recursively.
### human_size
Type: string
Like size but in human readable format.
### permissions
Type: [Permission][60]
The [permissions][60] applied to the node.
### created
Type: nullable integer
Creation time in nanosecond since UNIX epoch.
### last_modified
Type: nullable integer
Last modification time in nanosecond since UNIX epoch.
### uid
Type: integer
User ID of the file owner.
### gid
Type: integer
Group ID of the file owner.
### canonical
Type: nullable [Resolved Node Metadata][61]
If the node is a symlink, it will hold information about the symlink resolved
node. Else, it will hold information the actual node. It the symlink is broken,
it will be null.
### symlink
Type: nullable [Resolved Node Metadata][61]
If the node is a symlink and is not broken, it will hold information about the
symlink resolved node. However, it will never hold information about the actual
node. It will instead be null.
## Directory Buffer
Directory buffer contains the following fields:
- [parent][45]
- [nodes][63]
- [total][64]
- [focus][65]
### parent
Type: string
The parent path of the node.
### nodes
Type: list of [Node][44]s
A list of visible nodes.
### total
Type: int
The count of nodes being rendered.
### focus
Type: int
The index of the node under focus. It can be `0` even when there's no node to
focus on.
## History
History contains the following fields:
- [loc][68]
- [paths][69]
### loc
Type: int
Location of the current path in history.
### paths
Type: list of string
Visited paths.
## Explorer Config
Explorer config contains the following fields:
- [filters][77]
- [sorters][78]
- [searcher][79]
### filters
List of filters to apply.
Type: list of [Node Filter Applicable][80]
### sorters
Add list or sorters to the pipeline.
Type: list of [Node Sorter Applicable][81]
### searcher
The searcher to use (if any).
Type: nullable [Node Searcher Applicable][82]
## Also See:
- [xplr.util][85]
[7]: https://www.json.org
[8]: modes.md#mode
[9]: modes.md#builtin
[10]: modes.md#custom
[11]: layouts.md
[12]: layouts.md#builtin
[13]: layouts.md#custom
[14]: #lua-context
[15]: filtering.md#filter
[16]: filtering.md
[17]: sorting.md#sorter
[29]: #version
[30]: #config
[31]: #pwd
[32]: #focused_node
[33]: #directory_buffer
[34]: #selection
[35]: #mode
[36]: #layout
[37]: #input_buffer
[38]: #pid
[39]: #session_path
[40]: #explorer_config
[41]: #history
[42]: #last_modes
[43]: configuration.md#config
[44]: #node
[45]: #parent
[46]: #relative_path
[47]: #absolute_path
[48]: #extension
[49]: #is_symlink
[50]: #is_broken
[51]: #is_dir
[52]: #is_file
[53]: #is_readonly
[54]: #mime_essence
[55]: #size
[56]: #human_size
[57]: #permissions
[58]: #canonical
[59]: #symlink
[60]: column-renderer.md#permission
[61]: column-renderer.md#resolved-node-metadata
[62]: #directory-buffer
[63]: #nodes
[64]: #total
[65]: #focus
[66]: #explorer-config
[67]: #history
[68]: #loc
[69]: #paths
[70]: #history-1
[71]: #created
[72]: #last_modified
[73]: #uid
[74]: #gid
[75]: #vroot
[76]: #initial_pwd
[77]: #filters
[78]: #sorters
[79]: #searcher
[80]: filtering.md#node-filter-applicable
[81]: sorting.md#node-sorter-applicable
[82]: searching.md#node-searcher-applicable
[85]: xplr.util.md

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -1,66 +0,0 @@
# Mode
A mode contains the following information:
- [name][5]
- [help][6]
- [extra_help][7]
- [key_bindings][9]
- [layout][10]
- [prompt][13]
### name
Type: string
This is the name of the mode visible in the help menu.
### help
Type: nullable string
If specified, the help menu will display this instead of the auto generated
mappings.
### extra_help
Type: nullable string
If specified, the help menu will display this along-side the auto generated
help menu.
### key_bindings
Type: [Key Bindings][8]
The key bindings available in that mode.
### layout
Type: nullable [Layout][11]
If specified, this layout will be used to render the UI.
### prompt
Type: nullable string
If set, this prompt will be displayed in the input buffer when in this mode.
## Also See:
- [xplr.config.modes][12]
[1]: #builtin
[2]: #custom
[3]: #mode
[4]: default-key-bindings.md
[5]: #name
[6]: #help
[7]: #extra_help
[8]: configure-key-bindings.md#key-bindings
[9]: #key_bindings
[10]: #layout
[11]: layout.md#layout
[12]: modes.md
[13]: #prompt

@ -1,192 +1,128 @@
### Modes
# Modes
xplr is a modal file explorer. That means the users switch between different
modes, each containing a different set of key bindings to avoid clashes.
Users can switch between these modes at run-time.
modes, each containing a different set of key bindings to avoid clashes. Users
can switch between these modes at run-time.
The modes can be configured using the `xplr.config.modes` Lua API.
`xplr.config.modes.builtin` contain some built-in modes which can be
overridden, but you can't add or remove modes in it.
It contains the following fields:
#### xplr.config.modes.builtin.default
- [builtin][1]
- [custom][2]
The builtin default mode.
Visit the [Default Key Bindings](https://xplr.dev/en/default-key-bindings)
to see what each mode does.
## builtin
Type: [Mode](https://xplr.dev/en/mode)
Type: mapping of string and [Mode][3]
#### xplr.config.modes.builtin.debug_error
This is exposed by the `xplr.config.modes.builtin` API.
The builtin debug error mode.
xplr by default provides the following builtin modes:
Type: [Mode](https://xplr.dev/en/mode)
- default
- recover
- selection_ops
- create
- create_directory
- create_file
- number
- go_to
- rename
- delete
- action
- search
- filter
- relative_path_does_contain
- relative_path_does_not_contain
- sort
- switch_layout
- quit
#### xplr.config.modes.builtin.recover
Visit the [Default Key Bindings][4] to see what each mode
does.
The builtin recover mode.
## custom
Type: [Mode](https://xplr.dev/en/mode)
Type: mapping of string and [Mode][3]
#### xplr.config.modes.builtin.go_to_path
This is exposed by the `xplr.config.modes.custom` API.
The builtin go to path mode.
It allows the users to define custom modes.
Type: [Mode](https://xplr.dev/en/mode)
#### xplr.config.modes.builtin.move_to
The builtin move_to mode.
Type: [Mode](https://xplr.dev/en/mode)
#### xplr.config.modes.builtin.copy_to
The builtin copy_to mode.
Type: [Mode](https://xplr.dev/en/mode)
#### xplr.config.modes.builtin.selection_ops
The builtin selection ops mode.
Type: [Mode](https://xplr.dev/en/mode)
#### xplr.config.modes.builtin.create
The builtin create mode.
Type: [Mode](https://xplr.dev/en/mode)
#### xplr.config.modes.builtin.create_directory
The builtin create directory mode.
Type: [Mode](https://xplr.dev/en/mode)
#### xplr.config.modes.builtin.create_file
The builtin create file mode.
Type: [Mode](https://xplr.dev/en/mode)
#### xplr.config.modes.builtin.number
The builtin number mode.
Type: [Mode](https://xplr.dev/en/mode)
#### xplr.config.modes.builtin.go_to
The builtin go to mode.
Type: [Mode](https://xplr.dev/en/mode)
#### xplr.config.modes.builtin.rename
The builtin rename mode.
Type: [Mode](https://xplr.dev/en/mode)
#### xplr.config.modes.builtin.duplicate_as
The builtin duplicate as mode.
Type: [Mode](https://xplr.dev/en/mode)
#### xplr.config.modes.builtin.delete
The builtin delete mode.
Type: [Mode](https://xplr.dev/en/mode)
#### xplr.config.modes.builtin.action
The builtin action mode.
Type: [Mode](https://xplr.dev/en/mode)
#### xplr.config.modes.builtin.quit
The builtin quit mode.
Type: [Mode](https://xplr.dev/en/mode)
#### xplr.config.modes.builtin.search
The builtin search mode.
Type: [Mode](https://xplr.dev/en/mode)
#### xplr.config.modes.builtin.filter
The builtin filter mode.
Type: [Mode](https://xplr.dev/en/mode)
#### xplr.config.modes.builtin.relative_path_does_match_regex
The builtin relative_path_does_match_regex mode.
Type: [Mode](https://xplr.dev/en/mode)
Example:
#### xplr.config.modes.builtin.relative_path_does_not_match_regex
```lua
xplr.config.modes.custom.example = {
name = "example",
key_bindings = {
on_key = {
enter = {
help = "default mode",
messages = {
"PopMode",
{ SwitchModeBuiltin = "default" }
}
}
}
}
}
The builtin relative_path_does_not_match_regex mode.
xplr.config.general.initial_mode = "example"
Type: [Mode](https://xplr.dev/en/mode)
-- when you load xplr, you should be in the "example" mode,
-- pressing "enter" should take you to the "default" mode.
```
#### xplr.config.modes.builtin.sort
## Mode
The builtin sort mode.
A mode contains the following information:
Type: [Mode](https://xplr.dev/en/mode)
- [name][5]
- [help][6]
- [extra_help][7]
- [key_bindings][8]
- [layout][10]
#### xplr.config.modes.builtin.switch_layout
### name
The builtin switch layout mode.
Type: string
Type: [Mode](https://xplr.dev/en/mode)
This is the name of the mode visible in the help menu.
#### xplr.config.modes.builtin.vroot
### help
The builtin vroot mode.
Type: nullable string
Type: [Mode](https://xplr.dev/en/mode)
If specified, the help menu will display this instead of the auto generated
mappings.
#### xplr.config.modes.builtin.edit_permissions
### extra_help
The builtin edit permissions mode.
Type: nullable string
Type: [Mode](https://xplr.dev/en/mode)
If specified, the help menu will display this along-side the auto generated
help menu.
#### xplr.config.modes.custom
### key_bindings
This is where you define custom modes.
Type: [Key Bindings][9]
Type: mapping of the following key-value pairs:
The key bindings available in that mode.
- key: string
- value: [Mode](https://xplr.dev/en/mode)
### layout
Example:
Type: nullable [Layout][11]
```lua
xplr.config.modes.custom.example = {
name = "example",
key_bindings = {
on_key = {
enter = {
help = "default mode",
messages = {
"PopMode",
{ SwitchModeBuiltin = "default" },
},
},
},
},
}
If specified, this layout will be used to render the UI.
xplr.config.general.initial_mode = "example"
```
[1]: #builtin
[2]: #custom
[3]: #mode
[4]: default-key-bindings.md
[5]: #name
[6]: #help
[7]: #extra_help
[8]: configure-key-bindings.md#key-bindings
[9]: #key-bindings
[10]: #layout
[11]: layouts.md#Layout

@ -1,36 +0,0 @@
# Node Type
A node-type contains the following fields:
- [meta][4]
- [style][5]
### meta
Type: mapping of string and string
A meta field can contain custom metadata about a node. By default, the "icon"
metadata is set for the [directory][1], [file][2], and
[symlink][3] nodes.
Example:
```lua
xplr.config.node_types.file = {
meta = {
icon = "f",
foo = "bar",
}
}
```
## Also See:
- [xplr.config.node_types][6]
[1]: node_types.md#directory
[2]: node_types.md#file
[3]: node_types.md#symlink
[4]: #meta
[5]: style.md
[6]: node_types.md

@ -1,86 +1,78 @@
### Node Types
# Node Types
This section defines how to deal with different kinds of nodes (files,
directories, symlinks etc.) based on their properties.
One node can fall into multiple categories. For example, a node can have the
_extension_ `md`, and also be a _file_. In that case, the properties from
the more specific category i.e. _extension_ will be used.
This configuration defines how to deal with different kinds of nodes (files,
directories, symlinks etc.) in a directory.
This can be configured using the `xplr.config.node_types` Lua API.
#### xplr.config.node_types.directory.style
It contains the following fields:
The style for the directory nodes
- [directory][1]
- [file][2]
- [symlink][3]
- [mime_essence][4]
- [extension][5]
- [special][6]
One node can fall into multiple categories. For example, a node can have the
extension `md`, and be a `file`. In that case, the properties from the more
specific category i.e. extension will be used.
Type: [Style](https://xplr.dev/en/style)
The priority is:
#### xplr.config.node_types.directory.meta.icon
**special** > **extension** > **mime_essence** > **symlink** > **file** > **directory**
Metadata for the directory nodes.
You can set as many metadata as you want.
### directory
Type: nullable string
Type: [NodeType Config][7]
Properties related to directories are defined here.
Contains the following fields:
Example:
```lua
xplr.config.node_types.directory.meta.foo = "foo"
xplr.config.node_types.directory.meta.bar = "bar"
xplr.config.node_types.directory.meta.icon = ""
xplr.config.node_types.directory.style.add_modifiers = { "Bold" }
```
#### xplr.config.node_types.file.style
The style for the file nodes.
### file
Type: [Style](https://xplr.dev/en/style)
Type: [NodeType Config][7]
#### xplr.config.node_types.file.meta.icon
Properties related to regular files are defined here.
Metadata for the file nodes.
You can set as many metadata as you want.
Type: nullable string
Contains the following fields:
Example:
```lua
xplr.config.node_types.file.meta.foo = "foo"
xplr.config.node_types.file.meta.bar = "bar"
xplr.config.node_types.file.meta.icon = ""
xplr.config.node_types.file.style.fg = "White"
```
#### xplr.config.node_types.symlink.style
The style for the symlink nodes.
Type: [Style](https://xplr.dev/en/style)
### symlink
#### xplr.config.node_types.symlink.meta.icon
Type: [NodeType Config][7]
Metadata for the symlink nodes.
You can set as many metadata as you want.
Type: nullable string
Properties related to symlink are defined here.
Example:
```lua
xplr.config.node_types.symlink.meta.foo = "foo"
xplr.config.node_types.symlink.meta.bar = "bar"
xplr.config.node_types.symlink.meta.icon = ""
xplr.config.node_types.symlink.style.add_modifiers = { "Italic" }
```
#### xplr.config.node_types.mime_essence
### mime_essence
Metadata and style based on mime types.
It is possible to use the wildcard `*` to match all mime sub types. It will
be overwritten by the more specific sub types that are defined.
Type: mapping of mime-type and mapping of mime-subtype and [NodeType Config][7]
Type: mapping of the following key-value pairs:
Properties related to files with specific mime types are defined here.
- key: string
- value:
- key: string
- value: [Node Type](https://xplr.dev/en/node-type)
It is possible to use the wildcard `*` to match all mime subtypes. It will be
overwritten by the more specific sub types that are defined.
Example:
@ -88,10 +80,10 @@ Example:
xplr.config.node_types.mime_essence = {
application = {
-- application/*
["*"] = { meta = { icon = "a" } },
["*"] = { meta = { icon = "a" } }
-- application/pdf
pdf = { meta = { icon = "" }, style = { fg = "Blue" } },
pdf = { meta = { icon = "" } },
-- application/zip
zip = { meta = { icon = ""} },
@ -99,34 +91,65 @@ xplr.config.node_types.mime_essence = {
}
```
#### xplr.config.node_types.extension
Metadata and style based on extension.
### extension
Type: mapping of the following key-value pairs:
Type: mapping of extension and [NodeType Config][7]
- key: string
- value: [Node Type](https://xplr.dev/en/node-type)
Properties related to files with specific extension are defined here.
Example:
```lua
xplr.config.node_types.extension.md = { meta = { icon = "" }, style = { fg = "Blue" } }
xplr.config.node_types.extension.md = { meta = { icon = "" } }
xplr.config.node_types.extension.rs = { meta = { icon = "🦀" } }
```
#### xplr.config.node_types.special
Metadata and style based on special file names.
### special
Type: mapping of the following key-value pairs:
Type: mapping of name and [NodeType Config][7]
- key: string
- value: [Node Type](https://xplr.dev/en/node-type)
Properties related to files and directories with special names are defined
here.
Example:
```lua
xplr.config.node_types.special["Cargo.toml"] = { meta = { icon = "" } }
xplr.config.node_types.special["Downloads"] = { meta = { icon = "" }, style = { fg = "Blue" } }
xplr.config.node_types.special["Downloads"] = { meta = { icon = "" } }
```
## NodeType Config
A node-type config contains the following fields:
- [meta][8]
- [style][9]
### meta
Type: mapping of string and string
A meta field can contain custom metadata about a node. By default, the "icon"
metadata is set for the [directory][1], [file][2], and
[symlink][3] nodes.
Example:
```lua
xplr.config.node_types.file = {
meta = {
icon = "f",
foo = "bar",
}
}
```
[1]: #directory
[2]: #file
[3]: #symlink
[4]: #mime_essence
[5]: #extension
[6]: #special
[7]: #nodetype-config
[8]: #meta
[9]: style.md

@ -7,15 +7,25 @@ Once [installed][1], use the following steps to setup and run xplr.
```bash
mkdir -p ~/.config/xplr
version="$(xplr --version | awk '{print $2}')"
version="$(xplr | grep ^version: | cut -d' ' -f 2)"
echo "version = '${version:?}'" > ~/.config/xplr/init.lua
# When the app loads, press `#`
echo version = '"'${version:?}'"' > ~/.config/xplr/init.lua
```
Then
**[copy from here][2]**
and remove / comment out what you don't want to customize.
> **Note:** You don't generally need to create the config file. You can use the
> default configuration for basic operations. However, creating the config file
> is recommended because the project is in its early stage and the defaults
> might change. Creating the config file will save you from unexpected behavior
> when you [upgrade][3].
> Also, the default configuration is meant to be overwritten to suit your
> workflow.
## Run
```

@ -1,77 +0,0 @@
# Searching
xplr supports searching paths using different algorithm. The search mechanism
generally appears between filters and sorters in the `Sort & filter` panel.
Example:
```
fzy:foo↓
```
This line means that the nodes visible on the table are being filtered using the
[fuzzy matching][1] algorithm on the input `foo`. The arrow means that ranking based
ordering is being applied, i.e. [sorters][2] are being ignored.
## Node Searcher Applicable
Node Searcher contains the following fields:
- [pattern][3]
- [recoverable_focus][4]
- [algorithm][5]
- [unordered][7]
### pattern
The patterns used to search.
Type: string
### recoverable_focus
Where to focus when search is cancelled.
Type: nullable string
### algorithm
Search algorithm to use. Defaults to the value set in
[xplr.config.general.search.algorithm][8].
It can be one of the following:
- Fuzzy
- Regex
### unordered
Whether to skip ordering the search result by algorithm based ranking. Defaults
to the value set in [xplr.config.general.search.unordered][9].
Type: boolean
## Example:
```lua
local searcher = {
pattern = "pattern to search",
recoverable_focus = "/path/to/focus/on/cancel",
algorithm = "Fuzzy",
unordered = false,
}
xplr.util.explore({ searcher = searcher })
```
See [xplr.util.explore][6].
[1]: https://en.wikipedia.org/wiki/Approximate_string_matching
[2]: sorting.md
[3]: #pattern
[4]: #recoverable_focus
[5]: #algorithm
[6]: xplr.util.md#xplrutilexplore
[7]: #unordered
[8]: general-config.md#xplrconfiggeneralsearchalgorithm
[9]: general-config.md#xplrconfiggeneralsearchunordered

@ -27,7 +27,7 @@ It contains the following information:
### sorter
A sorter is a [sum type][4] that can be one of the following:
A sorter can be one of the following:
- "ByRelativePath"
- "ByIRelativePath"
@ -39,8 +39,6 @@ A sorter is a [sum type][4] that can be one of the following:
- "ByIsReadonly"
- "ByMimeEssence"
- "BySize"
- "ByCreated"
- "ByLastModified"
- "ByCanonicalAbsolutePath"
- "ByICanonicalAbsolutePath"
- "ByCanonicalExtension"
@ -49,8 +47,6 @@ A sorter is a [sum type][4] that can be one of the following:
- "ByCanonicalIsReadonly"
- "ByCanonicalMimeEssence"
- "ByCanonicalSize"
- "ByCanonicalCreated"
- "ByCanonicalLastModified"
- "BySymlinkAbsolutePath"
- "ByISymlinkAbsolutePath"
- "BySymlinkExtension"
@ -59,8 +55,8 @@ A sorter is a [sum type][4] that can be one of the following:
- "BySymlinkIsReadonly"
- "BySymlinkMimeEssence"
- "BySymlinkSize"
- "BySymlinkCreated"
- "BySymlinkLastModified"
TODO: document each
### reverse
@ -82,4 +78,3 @@ This snippet defines the initial sorting logic to be applied when xplr loads.
[1]: #node-sorter-applicable
[2]: #sorter
[3]: #reverse
[4]: sum-type.md

@ -33,7 +33,7 @@ Modifiers to remove.
## Color
Color is a [sum type][7] that can be one of the following:
Color can be one of the following:
- "Reset"
- "Black"
@ -57,7 +57,7 @@ Color is a [sum type][7] that can be one of the following:
## Modifier
Modifier is a [sum type][7] that can be one of the following:
Modifier can be one of the following:
- "Bold"
- "Dim"
@ -84,4 +84,3 @@ xplr.config.general.prompt.style.sub_modifiers = { "Hidden" }
[4]: #sub_modifiers
[5]: #color
[6]: #modifier
[7]: sum-type.md

@ -1,96 +0,0 @@
# Sum Type
> This section isn't specific to xplr. However, since xplr configuration makes
> heavy use of this particular data type, even though it isn't available in
> most of the mainstream programming languages (yet), making it a wild or
> unfamiliar concept for many, it's worth doing a quick introduction here.
>
> If you're already familiar with [Sum Type / Tagged Union][1] (e.g. Rust's
> enum), you can skip ahead.
While reading this doc, you'll come across some data types like [Layout][2],
[Color][4], [Message][3] etc. that says something like "x is a sum type that
can be any of the following", and then you'll see a list of strings and/or lua
tables just below.
Yes, they are actually sum types, i.e. they can be any of the given set of
tagged variants listed there.
Notice the word "be". Unlike classes or structs (aka product types), they can't
"have" values, they can only "be" the value, or rather, be one of the possible
set of values.
Also notice the word "tagged". Unlike the single variant `null`, or the dual
variant `boolean` types, the variants of sum types are tagged (i.e. named), and
may further have, or be, value or values of any data type.
A simple example of a sum type is an enum. Many programming languages have
them, but only a few modern programming languages allow nesting other types
into a sum type.
```rust
enum Color {
Red,
Green,
}
```
Here, `Color` can be one of two possible set of values: `Red` and `Green`, just
like `boolean`, but unlike `boolean`, being tagged allows `Color` to have more
than two variants if required, by changing the definition.
e.g.
```rust
enum Color {
Red,
Green,
Blue,
}
```
We'd document it here as:
> Result is a sum type that can be one of the following:
>
> - "Red"
> - "Green"
> - "Blue"
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:
```rust
enum Layout {
Table,
HelpMenu,
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 `Layout`'s possible
variants can have further nested types associated with them. Note that
`Horizontal` here can have a sum type (e.g. enum), or a product type (e.g.
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:
> Layout is a sum type that can be one of the following:
>
> - "Table"
> - "HelpMenu"
> - { Horizontal = { config = Layout Config, splits = { Layout, ... } }
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
have nested types in each branch.
[1]: https://en.wikipedia.org/wiki/Tagged_union
[2]: layout.md
[3]: message.md
[4]: style.md#color

@ -0,0 +1,34 @@
# TODO
- [x] Saner key bindings.
- [x] Pipes.
- [x] Native search & filter.
- [x] Create, copy, move, delete files directly.
- [x] logging support.
- [x] Version compatibility instructions.
- [x] Implement CLI arguments.
- ~Add support for tabs and/or panes (non native)~ [hacked][1] | [discussion][2]
- ~Implement bookmarks.~ [hacked][3]
- [x] Add sorting support.
- [x] Add filter support.
- [x] File previews.
- [x] Implement plugins support (or some way to easily share configuration).
- [x] Bigger (and better) help menu.
- [x] Offline docs.
- [ ] Support for background services
- [x] ~Customize~ switch UI at run-time.
- [ ] More tests and benchmarks.
- [ ] Measure code coverage.
- [ ] Improve the [vim plugin][4].
- [ ] Cleanup, refactor, optimize.
[add more][5]
Like this project so far? **[Please consider contributing][6].**
[1]: https://github.com/sayanarijit/xplr/wiki/Hacks#spawn-multiple-sessions-in-different-windows
[2]: https://github.com/sayanarijit/xplr/discussions/15
[3]: https://github.com/sayanarijit/xplr/wiki/Hacks#bookmark
[4]: https://github.com/sayanarijit/xplr.vim
[5]: https://github.com/sayanarijit/xplr/discussions/2
[6]: contribute.md

@ -32,248 +32,20 @@ Knowing that we use the `{major}.{minor}.{patch}` versioning format,
e.g.
- `1.0.0` -> `1.0.x`: Patch (fully compatible).
- `1.0.0` -> `1.0.x`: Bug fix (fully compatible).
- `1.0.0` -> `1.x.x`: Only backwards compatible. You can't generally use for
e.g. `app-1.0.0` with `config-1.1.0`. But vice versa is fine.
- `1.0.0` -> `x.x.x`: Not compatible at all.
Note that until we're `v1`, we'll be using the `{minor}` version number as
`{major}`, and the `{patch}` number as `{minor}` to determine
`{major}`, and the `{patch}` fix number as `{minor}` to determine
compatibility.
</details>
### Instructions
#### [v0.20.2][48] -> [v0.21.9][49]
- Some plugins might stop rendering colors. Wait for them to update.
- Rename `xplr.config.general.sort_and_filter_ui.search_identifier` to
`xplr.config.general.sort_and_filter_ui.search_identifiers`.
- Resolved Node API will not contain the `permissions` field anymore.
Use the utility function `xplr.util.node` to get its permissions.
- Layout `CustomContent` has been undocumented. It will stay for compatibility,
but you should prefer using the following new layouts, because they support
custom title:
- Static
- Dynamic
- Use the new messages for improved search operations:
- Search
- SearchFromInput
- SearchFuzzyUnordered
- SearchFuzzyUnorderedFromInput
- SearchRegex
- SearchRegexFromInput
- SearchRegexUnordered
- SearchRegexUnorderedFromInput
- ToggleSearchAlgorithm
- EnableSearchOrder
- DisableSearchOrder
- ToggleSearchOrder
- Use skim's [search syntax][50] to customize the search.
- Set your preferred search algorithm and ordering:
`xplr.config.general.search.algorithm = "Fuzzy" -- or "Regex"`.
`xplr.config.general.search.unordered = false -- or true`
- You need to clear the selection list manually after performing batch
operation like copy, softlink creation etc.
- Use the following new key bindings:
- `:sl` to list selection in a $PAGER.
- `:ss` to create softlink of the selected items.
- `:sh` to create hardlink of the selected items.
- `:se` to edit selection list in your $EDITOR.
- Better conflict handling: prompt for action.
- Navigate between the selected paths using the following messages:
- FocusPreviousSelection (`ctrl-p`)
- FocusNextSelection (`ctrl-n`)
- Use `LS_COLORS` environment variable, along with the following utility
- functions for applying better styling/theaming.
- xplr.util.lscolor
- xplr.util.paint
- xplr.util.textwrap
- xplr.util.style_mix
- Use new the fields in Column Renderer Argument:
- style
- permissions
- Use the following config to specify how the paths in selection list should be
rendered:
- xplr.config.general.selection.item.format
- xplr.config.general.selection.item.style
- Use the following utility functions to work with the file permissions:
- xplr.util.permissions_rwx
- xplr.util.permissions_octal
- Type `:p` to edit file permissions interactively.
- Also check out the following utility functions:
- xplr.util.layout_replace
- xplr.util.relative_to
- xplr.util.shorthand
- xplr.util.clone
- xplr.util.exists
- xplr.util.is_dir
- xplr.util.is_file
- xplr.util.is_symlink
- xplr.util.is_absolute
- xplr.util.path_split
- xplr.util.node
- xplr.util.node_type
- xplr.util.shell_escape
- Executables will me marked with the mime type: `application/x-executable`.
- macOS legacy coreutils will be generally supported, but please update it.
- Since v0.21.2 you can use the on_selection_change hook.
- Since v0.21.4 you can use function keys upto F24 and the following new
messages:
- NextVisitedDeepBranch (bound to `)` key)
- PreviousVisitedDeepBranch (bound to `(` key)
- Since v0.21.6:
- You can use `c` and `m` keys in default mode to quickly copy
and move focused or selected files, without having to change directory.
- Use `xplr.util.debug()` to debug lua values.
- 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.
#### [v0.19.4][47] -> [v0.20.2][48]
- BREAKING: xplr shell (`:!`) will default to null (`\0`) delimited pipes, as
opposed to newline (`\n`) delimited ones (i.e. will use `Call0` instead of
`Call`).
- Use new messages for safer file path handling (`\0` delimited):
- Call0
- CallSilently0
- BashExec0
- BashExecSilently0
- Use new sub-commands for safer message passing:
- `-m FORMAT [ARGUMENT]...` / `--pipe-msg-in FORMAT [ARGUMENT]...`
- `-M FORMAT [ARGUMENT]...` / `--print-msg-in FORMAT [ARGUMENT]...`
Where FORMAT is a YAML string that may contain `%s`, `%q` and `%%`
placeholders and ARGUMENT is the value per placeholder. See `init.lua`.
- Following hooks can be defined in the config files using an optional
`return { on_* = { list, of, messages }, ... }` statement at the end.
- on_load
- on_focus_change
- on_directory_change
- on_mode_switch (since v0.20.2)
- on_layout_switch (since v0.20.2)
- Use `--vroot` to isolate navigation of an xplr session inside a specific
directory. Interaction still requires passing full path, and shell,
lua functions etc still can access paths outside vroot.
- Use the following messages to switch vroot at runtime, or the use key
bindings available in the new builtin mode "vroot" (mapped to `:` `v`).
- SetVroot
- UnsetVroot
- ToggleVroot
- ResetVroot
- Use `$XPLR_INITIAL_PWD` and Lua equivalent to implement workspace like
features without using virtual root. Use keys `gi` to go to the initial
working directory from anywhere.
- Use the convenient `xplr.util` utility functions in your Lua function calls.
See xplr.util API docs.
#### [v0.18.0][46] -> [v0.19.4][47]
- BREAKING: The builtin modes cannot be accessed using space separated names
anymore. Use underscore separated mode names. For e.g.
`SwitchModeBuiltin: create file` becomes `SwitchModeBuiltin: create_file` and
so on. Kindly go through your config, find and update them, or copy from the
latest `init.lua`.
- Now you can use `xplr.config.general.global_key_bindings` to define a set of
key bindings that are available by default in every mode. e.g. `esc`
and `ctrl-c`, and remove boilerplate code from your config.
- You can use the new builtin mode `go_to_path` which can be used for typing or
pasting paths to enter into or to focus on. Type `g` `p` to enter this mode.
- Now you can use basic tab completion in the `go_to_path`, `create_file`,
`create_directory`, `rename` and `duplicate_as` modes.
- Use the builtin function `xplr.fn.builtin.try_complete_path` to add easy tab
completion support into your own configuration.
- Now you can open OSC 7 compatible terminals into the `xplr`'s current working
directory by spawning new terminal sessions via the terminal supported key
bindings.
- Use `NO_COLOR` environment variable to disable OSC 7 compliance along with
colors.
- If you have fully copied the default `init.lua` locally, you might want to
go through the latest improvements in `init.lua`. Specifically the `search`,
`filter` and `sort` modes. Also, search for `SetInputPrompt` and the `tab`
key bindings.
- Since version 0.19.1, you can access uid and gid of the file owner in the Lua
API.
- The input buffer will support more readline-like keys.
Also, added "DeleteTillEnd" as another cursor based "InputOperation" option.
- Fixed applying regex based filters via the CLI and `$XPLR_PIPE_MSG_IN` pipe.
- You can use the `prompt` field to define input prompt for each mode, instead
of using the `SetInputPrompt` message.
- Since version v0.19.4, the native search will default to skim-v2 based fuzzy
matching. `esc` while in search mode will recover the initial focus. People
who prefer the regex based search, can use the `regex-search.xplr` plugin.
The following messages will be available for search based operations:
- SearchFuzzy
- SearchFuzzyFromInput
- AcceptSearch
- CancelSearch
- Since version v0.19.4, quick scrolling operations are supported using the
following messages and keys:
- ScrollUp -------- page-up
- ScrollDown ------ page-down
- ScrollUpHalf ---- {
- ScrollDownHalf -- }
#### [v0.17.6][45] -> [v0.18.0][46]
- Key binding `f` `r` and `f` `R` will now filter using regex.
- Key binding `f` `backspace` will now remove the last filter.
- Search mode now defaults to regex search.
- `Node` metadata in the Lua API will contain two new fields:
- `created`
- `last_modified`
- The last column in the files table now displays the last modification time.
- You can now use `--read0`, `--write0` and `-0`/`--null` to read and/or print
null character delimited paths.
- You can now the following regex filters:
- `RelativePathDoesMatchRegex`
- `RelativePathDoesNotMatchRegex`
- `IRelativePathDoesMatchRegex`
- `IRelativePathDoesNotMatchRegex`
- `AbsolutePathDoesMatchRegex`
- `AbsolutePathDoesNotMatchRegex`
- `IAbsolutePathDoesMatchRegex`
- `IAbsolutePathDoesNotMatchRegex`
- You can use a new `SetInputPrompt` to set the input prompt dynamically.
- You can now use the following timestamp based sorters:
- "ByCreated"
- "ByLastModified"
- "ByCanonicalCreated"
- "ByCanonicalLastModified"
- "BySymlinkCreated"
- "BySymlinkLastModified"
#### [v0.16.4][44] -> [v0.17.6][45]
- Deprecated `app.directory_buffer`, `app.history`, and `app.last_modes` in
the custom dynamic layout renderer context.
As of now, there's no way to access these fields in dynamic layouts. While
`app.history` and `app.last_modes` can be re-added upon request
(with justification), `app.directory_buffer` has been deprecated for good.
However, there's no change in the `CallLua*` context.
- Set `xplr.config.general.hide_remaps_in_help_menu` to `true` to hide the
remaps in help menu.
- `None` will be serialized to `nil` in Lua.
- `LuaEval` can now return a function that will be called with the Lua Context
argument. Refer to the `Full List of Messages` doc for example.
- From version v0.17.1, set `xplr.config.general.disable_debug_error_mode` to
`true` to disable switching to the "debug error" mode when startup errors
occur.
- From version v0.17.2, you can use CLI argument `--print-pwd-as-result` for cd
on quit, and key binding `ctrl-d` to duplicate a path in the same directory
with a different name.
- Since version v0.17.3, you can use `border_type`, `border_style` to customize
borders, and `enforce_bounded_index_navigation` to customize up/down
navigation behavior when focus is on the top or bottom node.
#### [v0.15.2][43] -> [v0.16.4][44]
#### [v0.15.2][43] -> [v0.16.2][44]
- Deprecated `config.general.cursor`. The default terminal cursor will be used
for the time being.
@ -283,7 +55,7 @@ Thanks to @noahmayr for contributing to a major part of this release.
`RemoveInputBufferLastWord`, `SetInputBuffer = ""` etc. messages with a
single `UpdateInputBufferFromKey` message.
- You can now pass multiple paths as command-line arguments or via stdin to
select paths, e.g. `xplr -- $PWD /path/to/select/1 /path/to/select/2`.
select paths, e.g. `xplr - $PWD /path/to/select/1 /path/to/select/2`.
- Pass `--force-focus` to focus on the first path even if it's a directory,
e.g. `xplr . --force-focus`.
- Use new messages `LuaEval` and `LuaEvalSilently` to run Lua code without
@ -294,6 +66,8 @@ Thanks to @noahmayr for contributing to a major part of this release.
- on_navigation
- on_function
<sub>Like this project so far? **[Please consider contributing][5]**.</sub>
#### [v0.14.7][3] -> [v0.15.2][43]
- Deprecated `config` field from `CallLua` argument. Use the globally available
@ -319,7 +93,7 @@ Thanks to @noahmayr for contributing to a major part of this release.
- You can disable the recover mode using `config.general.disable_recover_mode = true`.
- Try running `xplr --help`. Yes, CLI has been implemented.
- Since version `v0.14.3`, `StartFifo` and `ToggleFifo` will write to the FIFO
path when called. So, there's no need to pipe the focus path explicitly.
path when called. So, there's no need to pipe the focus path explicitely.
- Since version `v0.14.3`, general config `xplr.config.start_fifo` is available
which can be set to a file path to start a fifo when xplr starts.
- Since version `v0.14.4`, `$XPLR_SESSION_PATH` can be used to dump session
@ -330,7 +104,7 @@ Thanks to @noahmayr for contributing to a major part of this release.
#### [v0.12.1][6] -> [v0.13.7][2]
- Lua functions called using [`CallLua`][7] and [`CallLuaSilently`][8] messages will receive [`CallLuaArg`][9] object as the function argument (instead of the [`App`][10] object).
- Each `node_types` config will inherit defaults from matching less specific `node_types` config and overwrite them.
- Each `node_types` config will inherit defaults from matching less specifig `node_types` config and overwrite them.
- Since version `v0.13.2`, you don't need to use/send `Refresh` anymore. It will be auto-handled by xplr.
#### [v0.11.1][11] -> [v0.12.1][6]
@ -486,6 +260,7 @@ Else do the following:
[2]: https://github.com/sayanarijit/xplr/releases/tag/v0.13.7
[3]: https://github.com/sayanarijit/xplr/releases/tag/v0.14.7
[4]: https://github.com/sayanarijit/xplr/pull/229#issue-662426960
[5]: contribute.md
[6]: https://github.com/sayanarijit/xplr/releases/tag/v0.12.1
[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
@ -524,10 +299,4 @@ Else do the following:
[41]: https://github.com/sayanarijit/xplr/releases/tag/v0.3.0
[42]: https://github.com/sayanarijit/xplr/releases/tag/v0.14.4
[43]: https://github.com/sayanarijit/xplr/releases/tag/v0.15.2
[44]: https://github.com/sayanarijit/xplr/releases/tag/v0.16.4
[45]: https://github.com/sayanarijit/xplr/releases/tag/v0.17.6
[46]: https://github.com/sayanarijit/xplr/releases/tag/v0.18.0
[47]: https://github.com/sayanarijit/xplr/releases/tag/v0.19.4
[48]: https://github.com/sayanarijit/xplr/releases/tag/v0.20.2
[49]: https://github.com/sayanarijit/xplr/releases/tag/v0.21.9
[50]: https://github.com/lotabout/skim#search-syntax
[44]: https://github.com/sayanarijit/xplr/releases/tag/v0.16.2

@ -14,18 +14,20 @@ integers. They will be plugged using the `require()` function in Lua.
A minimal plugin should confirm to the following structure:
```
.
plugin-name
├── README.md
└── init.lua
└── src
└── init.lua
```
You can also use [this template][2].
You can also use
[this template][2].
### README.md
This is where you document what the plugin does, how to use it, etc.
### init.lua
### src/init.lua
This file is executed to load the plugin. It should expose a `setup()`
function, which will be used by the users to setup the plugin.
@ -50,18 +52,6 @@ to append `.xplr` to the name to make them distinguishable. Similar to the
Finally, after publishing, don't hesitate to
[let us know][4].
## Best practices
- Try not to execute a lot of commands at startup, it may make xplr slow to
start.
- When executing commands, prefer `Call0` over `Call`, `BashExec0` over
`BashExec` and so on. File names may contain newline characters
(e.g. `foo$'\n'bar`).
- File names may also contain quotes. Avoid writing directly to
`$XPLR_PIPE_MSG_IN`. Use `xplr -m` / `xplr --pipe-msg-in` instead.
- Check for empty variables using the syntax `${FOO:?}` or use a default value
`${FOO:-defaultvalue}`.
## Examples
Visit [Awesome Plugins][5] for xplr plugin examples.
@ -69,14 +59,15 @@ Visit [Awesome Plugins][5] for xplr plugin examples.
## Also See
- [Tip: A list of hacks yet to make it as Lua plugins][15]
- [Tip: Some UI and theming tips][12]
- [Tip: Some UI and themeing 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]
- [Example: Using Environment Variables and Pipes][7]
- [Example: Using Lua Function Calls][8]
- [Example: Defining Custom Layout][9]
- [Example: Customizing Table Renderer][10]
- [Example: Render a custom dynamic table][11]
- [Example: Implementing a directory visit counter][16]
[1]: https://www.lua.org
[2]: https://github.com/sayanarijit/plugin-template1.xplr
@ -84,11 +75,12 @@ Visit [Awesome Plugins][5] for xplr plugin examples.
[4]: https://github.com/sayanarijit/xplr/discussions/categories/show-and-tell
[5]: awesome-plugins.md
[6]: configure-key-bindings.md#tutorial-adding-a-new-mode
[7]: environment-variables-and-pipes.md#example-using-environment-variables-and-pipes
[8]: lua-function-calls.md#example-using-lua-function-calls
[9]: layout.md#example-defining-custom-layout
[7]: message.md#example-using-environment-variables-and-pipes
[8]: message.md#example-using-lua-function-calls
[9]: layouts.md#example-defining-custom-layout
[10]: column-renderer.md#example-customizing-table-renderer
[11]: layout.md#example-render-a-custom-dynamic-table
[11]: layouts.md#example-render-a-custom-dynamic-table
[12]: https://github.com/sayanarijit/xplr/discussions/274
[15]: awesome-hacks.md
[16]: https://github.com/sayanarijit/xplr/discussions/529#discussioncomment-4073734
[13]: https://github.com/sayanarijit/xplr/discussions/273
[14]: https://github.com/sayanarijit/xplr/discussions/250
[15]: https://github.com/sayanarijit/xplr/wiki/Hacks

@ -1,528 +0,0 @@
### xplr.util.version
Get the xplr version details.
Type: function() -> { major: number, minor: number, patch: number }
Example:
```lua
xplr.util.version()
-- { major = 0, minor = 0, patch = 0 }
```
### xplr.util.debug
Print the given value to the console, and return it as a string.
Useful for debugging.
Type: function( value ) -> string
Example:
```lua
xplr.util.debug({ foo = "bar", bar = function() end })
-- {
-- ["bar"] = function: 0x55e5cebdeae0,
-- ["foo"] = "bar",
-- }
```
### xplr.util.clone
Clone/deepcopy a Lua value. Doesn't work with functions.
Type: function( value ) -> value
Example:
```lua
local val = { foo = "bar" }
local val_clone = xplr.util.clone(val)
val.foo = "baz"
print(val_clone.foo)
-- "bar"
```
### xplr.util.exists
Check if the given path exists.
Type: function( path:string ) -> boolean
Example:
```lua
xplr.util.exists("/foo/bar")
-- true
```
### xplr.util.is_dir
Check if the given path is a directory.
Type: function( path:string ) -> boolean
Example:
```lua
xplr.util.is_dir("/foo/bar")
-- true
```
### xplr.util.is_file
Check if the given path is a file.
Type: function( path:string ) -> boolean
Example:
```lua
xplr.util.is_file("/foo/bar")
-- true
```
### xplr.util.is_symlink
Check if the given path is a symlink.
Type: function( path:string ) -> boolean
Example:
```lua
xplr.util.is_file("/foo/bar")
-- true
```
### xplr.util.is_absolute
Check if the given path is an absolute path.
Type: function( path:string ) -> boolean
Example:
```lua
xplr.util.is_absolute("/foo/bar")
-- true
```
### xplr.util.path_split
Split a path into its components.
Type: function( path:string ) -> boolean
Example:
```lua
xplr.util.path_split("/foo/bar")
-- { "/", "foo", "bar" }
xplr.util.path_split(".././foo")
-- { "..", "foo" }
```
### xplr.util.node
Get [Node][5] information of a given path.
Doesn't check if the path exists.
Returns nil if the path is "/".
Errors out if absolute path can't be obtained.
Type: function( path:string ) -> [Node][5]|nil
Example:
```lua
xplr.util.node("./bar")
-- { parent = "/pwd", relative_path = "bar", absolute_path = "/pwd/bar", ... }
xplr.util.node("/")
-- nil
```
### xplr.util.node_type
Get the configured [Node Type][6] of a given [Node][5].
Type: function( [Node][5], [xplr.config.node_types][7]|nil ) -> [Node Type][6]
If the second argument is missing, global config `xplr.config.node_types`
will be used.
Example:
```lua
xplr.util.node_type(app.focused_node)
-- { style = { fg = "Red", ... }, meta = { icon = "", ... } ... }
xplr.util.node_type(xplr.util.node("/foo/bar"), xplr.config.node_types)
-- { style = { fg = "Red", ... }, meta = { icon = "", ... } ... }
```
### xplr.util.dirname
Get the directory name of a given path.
Type: function( path:string ) -> path:string|nil
Example:
```lua
xplr.util.dirname("/foo/bar")
-- "/foo"
```
### xplr.util.basename
Get the base name of a given path.
Type: function( path:string ) -> path:string|nil
Example:
```lua
xplr.util.basename("/foo/bar")
-- "bar"
```
### xplr.util.absolute
Get the absolute path of the given path by prepending $PWD.
It doesn't check if the path exists.
Type: function( path:string ) -> path:string
Example:
```lua
xplr.util.absolute("foo/bar")
-- "/tmp/foo/bar"
```
### xplr.util.relative_to
Get the relative path based on the given base path or current working dir.
Will error if it fails to determine a relative path.
Type: function( path:string, options:table|nil ) -> path:string
Options type: { base:string|nil, with_prefix_dots:bookean|nil, without_suffix_dots:boolean|nil }
- If `base` path is given, the path will be relative to it.
- If `with_prefix_dots` is true, the path will always start with dots `..` / `.`
- If `without_suffix_dots` is true, the name will be visible instead of dots `..` / `.`
Example:
```lua
xplr.util.relative_to("/present/working/directory")
-- "."
xplr.util.relative_to("/present/working/directory/foo")
-- "foo"
xplr.util.relative_to("/present/working/directory/foo", { with_prefix_dots = true })
-- "./foo"
xplr.util.relative_to("/present/working/directory", { without_suffix_dots = true })
-- "../directory"
xplr.util.relative_to("/present/working")
-- ".."
xplr.util.relative_to("/present/working", { without_suffix_dots = true })
-- "../../working"
xplr.util.relative_to("/present/working/directory", { base = "/present/foo/bar" })
-- "../../working/directory"
```
### xplr.util.shorten
Shorten the given absolute path using the following rules:
- either relative to your home dir if it makes sense
- or relative to the current working directory
- or absolute path if it makes the most sense
Type: Similar to `xplr.util.relative_to`
Example:
```lua
xplr.util.shorten("/home/username/.config")
-- "~/.config"
xplr.util.shorten("/present/working/directory")
-- "."
xplr.util.shorten("/present/working/directory/foo")
-- "foo"
xplr.util.shorten("/present/working/directory/foo", { with_prefix_dots = true })
-- "./foo"
xplr.util.shorten("/present/working/directory", { without_suffix_dots = true })
-- "../directory"
xplr.util.shorten("/present/working/directory", { base = "/present/foo/bar" })
-- "../../working/directory"
xplr.util.shorten("/tmp")
-- "/tmp"
```
### xplr.util.explore
Explore directories with the given explorer config.
Type: function( path:string, [ExplorerConfig][1]|nil ) -> { [Node][2], ... }
Example:
```lua
xplr.util.explore("/tmp")
-- { { absolute_path = "/tmp/a", ... }, ... }
xplr.util.explore("/tmp", app.explorer_config)
-- { { absolute_path = "/tmp/a", ... }, ... }
```
### xplr.util.shell_execute
Execute shell commands safely.
Type: function( program:string, args:{ string, ... }|nil ) -> { stdout = string, stderr = string, returncode = number|nil }
Example:
```lua
xplr.util.shell_execute("pwd")
-- { stdout = "/present/working/directory", stderr = "", returncode = 0 }
xplr.util.shell_execute("bash", {"-c", "xplr --help"})
-- { stdout = "xplr...", stderr = "", returncode = 0 }
```
### xplr.util.shell_quote
Quote commands and paths safely.
Type: function( string ) -> string
Example:
```lua
xplr.util.shell_quote("a'b\"c")
-- 'a'"'"'b"c'
```
### xplr.util.shell_escape
Escape commands and paths safely.
Type: function( string ) -> string
Example:
```lua
xplr.util.shell_escape("a'b\"c")
-- "\"a'b\\\"c\""
```
### xplr.util.from_json
Load JSON string into Lua value.
Type: function( string ) -> any
Example:
```lua
xplr.util.from_json([[{"foo": "bar"}]])
-- { foo = "bar" }
```
### xplr.util.to_json
Dump Lua value into JSON (i.e. also YAML) string.
Type: function( value ) -> string
Example:
```lua
xplr.util.to_json({ foo = "bar" })
-- [[{ "foo": "bar" }]]
xplr.util.to_json({ foo = "bar" }, { pretty = true })
-- [[{
-- "foo": "bar"
-- }]]
```
### xplr.util.from_yaml
Load YAML (i.e. also JSON) string into Lua value.
Type: function( string ) -> value
Example:
```lua
xplr.util.from_yaml([[{foo: bar}]])
-- { foo = "bar" }
```
### xplr.util.to_yaml
Dump Lua value into YAML string.
Type: function( value ) -> string
Example:
```lua
xplr.util.to_yaml({ foo = "bar" })
-- "foo: bar"
```
### xplr.util.lscolor
Get a [Style][3] object for the given path based on the LS_COLORS
environment variable.
Type: function( path:string ) -> [Style][3]
Example:
```lua
xplr.util.lscolor("Desktop")
-- { fg = "Red", bg = nil, add_modifiers = {}, sub_modifiers = {} }
```
### xplr.util.paint
Apply style (escape sequence) to string using a given [Style][3] object.
Type: function( string, [Style][3]|nil ) -> string
Example:
```lua
xplr.util.paint("Desktop", { fg = "Red", bg = nil, add_modifiers = {}, sub_modifiers = {} })
-- "\u001b[31mDesktop\u001b[0m"
```
### xplr.util.style_mix
Mix multiple [Style][3] objects into one.
Type: function( { [Style][3], [Style][3], ... } ) -> [Style][3]
Example:
```lua
xplr.util.style_mix({{ fg = "Red" }, { bg = "Blue" }, { add_modifiers = {"Bold"} }})
-- { fg = "Red", bg = "Blue", add_modifiers = { "Bold" }, sub_modifiers = {} }
```
### xplr.util.textwrap
Wrap the given text to fit the specified width.
It will try to not split words when possible.
Type: function( string, options:number|table ) -> { string, ...}
Options type: { width = number, initial_indent = string|nil, subsequent_indent = string|nil, break_words = boolean|nil }
Example:
```lua
xplr.util.textwrap("this will be cut off", 11)
-- { "this will', 'be cut off" }
xplr.util.textwrap(
"this will be cut off",
{ width = 12, initial_indent = "", subsequent_indent = " ", break_words = false }
)
-- { "this will be", " cut off" }
```
### xplr.util.layout_replace
Find the target layout in the given layout and replace it with the replacement layout,
returning a new layout.
Type: function( layout:[Layout][4], target:[Layout][4], replacement:[Layout][4] ) -> layout:[Layout][4]
Example:
```lua
local layout = {
Horizontal = {
splits = {
"Table", -- Target
"HelpMenu",
},
config = ...,
}
}
xplr.util.layout_replace(layout, "Table", "Selection")
-- {
-- Horizontal = {
-- splits = {
-- "Selection", -- Replacement
-- "HelpMenu",
-- },
-- config = ...
-- }
-- }
```
### xplr.util.permissions_rwx
Convert [Permission][8] to rwxrwxrwx representation with special bits.
Type: function( [Permission][8] ) -> string
Example:
```lua
xplr.util.permissions_rwx({ user_read = true })
-- "r--------"
xplr.util.permissions_rwx(app.focused_node.permission)
-- "rwxrwsrwT"
```
### xplr.util.permissions_octal
Convert [Permission][8] to octal representation.
Type: function( [Permission][8] ) -> { number, number, number, number }
Example:
```lua
xplr.util.permissions_octal({ user_read = true })
-- { 0, 4, 0, 0 }
xplr.util.permissions_octal(app.focused_node.permission)
-- { 0, 7, 5, 4 }
```
[1]: https://xplr.dev/en/lua-function-calls#explorer-config
[2]: https://xplr.dev/en/lua-function-calls#node
[3]: https://xplr.dev/en/style
[4]: https://xplr.dev/en/layout
[5]: https://xplr.dev/en/lua-function-calls#node
[6]: https://xplr.dev/en/node-type
[7]: https://xplr.dev/en/node_types
[8]: https://xplr.dev/en/column-renderer#permission

Binary file not shown.

Before

Width:  |  Height:  |  Size: 789 B

@ -1,357 +0,0 @@
<!DOCTYPE HTML>
<html lang="{{ language }}" class="{{ default_theme }}" dir="{{ text_direction }}">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>{{ title }}</title>
{{#if is_print }}
<meta name="robots" content="noindex">
{{/if}}
{{#if base_url}}
<base href="{{ base_url }}">
{{/if}}
<!-- Custom HTML head -->
{{> head}}
<meta name="description" content="{{ description }}">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
{{#if favicon_svg}}
<link rel="icon" href="{{ path_to_root }}favicon.svg">
{{/if}}
{{#if favicon_png}}
<link rel="shortcut icon" href="{{ path_to_root }}favicon.png">
{{/if}}
<link rel="stylesheet" href="{{ path_to_root }}css/variables.css">
<link rel="stylesheet" href="{{ path_to_root }}css/general.css">
<link rel="stylesheet" href="{{ path_to_root }}css/chrome.css">
{{#if print_enable}}
<link rel="stylesheet" href="{{ path_to_root }}css/print.css" media="print">
{{/if}}
<!-- Fonts -->
<link rel="stylesheet" href="{{ path_to_root }}FontAwesome/css/font-awesome.css">
{{#if copy_fonts}}
<link rel="stylesheet" href="{{ path_to_root }}fonts/fonts.css">
{{/if}}
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="{{ path_to_root }}highlight.css">
<link rel="stylesheet" href="{{ path_to_root }}tomorrow-night.css">
<link rel="stylesheet" href="{{ path_to_root }}ayu-highlight.css">
<!-- Custom theme stylesheets -->
{{#each additional_css}}
<link rel="stylesheet" href="{{ ../path_to_root }}{{ this }}">
{{/each}}
{{#if mathjax_support}}
<!-- MathJax -->
<script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
{{/if}}
<!-- EthicalAds -->
<script async src="https://media.ethicalads.io/media/client/ethicalads.min.js"></script>
</head>
<body class="sidebar-visible no-js">
<div id="body-container">
<!-- Provide site root to javascript -->
<script>
var path_to_root = "{{ path_to_root }}";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('{{ default_theme }}')
html.classList.add(theme);
var body = document.querySelector('body');
body.classList.remove('no-js')
body.classList.add('js');
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
var body = document.querySelector('body');
var sidebar = null;
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
body.classList.remove('sidebar-visible');
body.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
{{#toc}}{{/toc}}
<!-- EthicalAds -->
<div
id="docs-sidebar-bottom"
class="dark flat"
data-ea-publisher="xplrdev"
data-ea-type="image"
></div>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</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 class="page">
{{> header}}
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<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>
</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">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light</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">Coal</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">Ayu</button></li>
</ul>
{{#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">
<i class="fa fa-search"></i>
</button>
{{/if}}
</div>
<h1 class="menu-title">{{ book_title }}</h1>
<div class="right-buttons">
{{#if print_enable}}
<a href="{{ path_to_root }}print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
{{/if}}
{{#if git_repository_url}}
<a href="{{git_repository_url}}" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa {{git_repository_icon}}"></i>
</a>
{{/if}}
{{#if git_repository_edit_url}}
<a href="{{git_repository_edit_url}}" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></i>
</a>
{{/if}}
</div>
</div>
{{#if search_enabled}}
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
{{/if}}
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
{{{ content }}}
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
{{#previous}}
<a rel="prev" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
{{/previous}}
{{#next}}
<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>
</a>
{{/next}}
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
{{#previous}}
<a rel="prev" href="{{ path_to_root }}{{link}}" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
{{/previous}}
{{#next}}
<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>
</a>
{{/next}}
</nav>
</div>
{{#if live_reload_endpoint}}
<!-- Livereload script (if served using the cli tool) -->
<script>
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsAddress = wsProtocol + "//" + location.host + "/" + "{{{live_reload_endpoint}}}";
const socket = new WebSocket(wsAddress);
socket.onmessage = function (event) {
if (event.data === "reload") {
socket.close();
location.reload();
}
};
window.onbeforeunload = function() {
socket.close();
}
</script>
{{/if}}
{{#if google_analytics}}
<!-- Google Analytics Tag -->
<script>
var localAddrs = ["localhost", "127.0.0.1", ""];
// make sure we don't activate google analytics if the developer is
// inspecting the book locally...
if (localAddrs.indexOf(document.location.hostname) === -1) {
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', '{{google_analytics}}', 'auto');
ga('send', 'pageview');
}
</script>
{{/if}}
{{#if playground_line_numbers}}
<script>
window.playground_line_numbers = true;
</script>
{{/if}}
{{#if playground_copyable}}
<script>
window.playground_copyable = true;
</script>
{{/if}}
{{#if playground_js}}
<script src="{{ path_to_root }}ace.js"></script>
<script src="{{ path_to_root }}editor.js"></script>
<script src="{{ path_to_root }}mode-rust.js"></script>
<script src="{{ path_to_root }}theme-dawn.js"></script>
<script src="{{ path_to_root }}theme-tomorrow_night.js"></script>
{{/if}}
{{#if search_js}}
<script src="{{ path_to_root }}elasticlunr.min.js"></script>
<script src="{{ path_to_root }}mark.min.js"></script>
<script src="{{ path_to_root }}searcher.js"></script>
{{/if}}
<script src="{{ path_to_root }}clipboard.min.js"></script>
<script src="{{ path_to_root }}highlight.js"></script>
<script src="{{ path_to_root }}book.js"></script>
<!-- Custom JS scripts -->
{{#each additional_js}}
<script src="{{ ../path_to_root }}{{this}}"></script>
{{/each}}
{{#if is_print}}
{{#if mathjax_support}}
<script>
window.addEventListener('load', function() {
MathJax.Hub.Register.StartupHook('End', function() {
window.setTimeout(window.print, 100);
});
});
</script>
{{else}}
<script>
window.addEventListener('load', function() {
window.setTimeout(window.print, 100);
});
</script>
{{/if}}
{{/if}}
</div>
</body>
</html>

@ -0,0 +1,114 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="/assets/icon/xplr64.png" />
<link rel="stylesheet" href="style.css" />
<title>xplr</title>
<!-- ------------- SEO TAGS --------------------->
<meta name="title" content="xplr" />
<meta
name="description"
content="A hackable, minimal, fast TUI file explorer"
/>
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:url" content="https://xplr.dev/" />
<meta name="twitter:title" content="xplr" />
<meta
name="twitter:description"
content="A hackable, minimal, fast TUI file explorer"
/>
<meta
name="twitter:image"
content="https://s3.gifyu.com/images/imaged284d8b830767ac4.png"
/>
<meta name="twitter:site" content="https://xplr.dev" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://xplr.dev" />
<meta property="og:title" content="xplr" />
<meta
property="og:description"
content="A hackable, minimal, fast TUI file explorer"
/>
<meta
property="og:image"
content="https://s3.gifyu.com/images/imaged284d8b830767ac4.png"
/>
<meta
property="og:image:alt"
content="A hackable, minimal, fast TUI file explorer"
/>
<meta itemprop="name" content="xplr" />
<meta
itemprop="description"
content="A hackable, minimal, fast TUI file explorer"
/>
<link
rel="icon"
type="image/png"
href="https://s3.gifyu.com/images/icon2194563908b29554.png"
/>
<meta name="theme-color" content="#ffffff" />
</head>
<body>
<div class="main-page">
<div class="para">
<h1>xplr</h1>
<p>
<span style="font-size: 4rem">A</span> hackable, minimal, fast TUI
file explorer
</p>
<br />
<p><a href="/en/install.html">Try or Install</a></p>
</div>
<div class="main-video">
<img
src="https://s3.gifyu.com/images/xplr-0.5.0.gif"
alt=""
width="100%"
/>
</div>
</div>
<div class="links">
<a href="/en/">
<div class="linkCad mrgL">
<img
src="/assets/homepage_icons/document_logo.png"
alt=""
width="35%"
/>
<p>Documentation</p>
</div>
</a>
<a href="https://github.com/sayanarijit/xplr">
<div class="linkCad mrgR">
<img src="/assets/homepage_icons/pr_logo.png" alt="" width="40%" />
<p>Source Code</p>
</div>
</a>
<a href="https://github.com/sayanarijit/xplr/releases">
<div class="linkCad mrgL">
<img
src="/assets/homepage_icons/release_logo.png"
alt=""
width="35%"
/>
<p>Releases</p>
</div>
</a>
<a href="https://discord.com/invite/JmasSPCcz3">
<div class="linkCad mrgR">
<img src="/assets/homepage_icons/disc_logo.png" alt="" width="40%" />
<p>Discord</p>
</div>
</a>
</div>
</body>
</html>

@ -1 +0,0 @@
../../assets

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -1,235 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<title>xplr - A hackable, minimal, fast TUI file explorer</title>
<meta
name="description"
content="xplr is a hackable, minimal, fast TUI file explorer "
/>
<!--Inter UI font-->
<link href="https://rsms.me/inter/inter.css" rel="stylesheet" />
<!--vendors styles-->
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
/>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick.min.css"
/>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick-theme.min.css"
/>
<!-- Icons -->
<link
rel="icon"
type="image/png"
sizes="126x128"
href="/assets/icon/xplr128.png"
/>
<link
rel="icon"
type="image/png"
sizes="64x64"
href="/assets/icon/xplr64.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/assets/icon/xplr32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/assets/icon/xplr16.png"
/>
<!-- Bootstrap CSS / Color Scheme -->
<link rel="stylesheet" href="css/default.css" id="theme-color" />
<!-- ------------- SEO TAGS --------------------->
<meta name="title" content="xplr" />
<meta
name="description"
content="A hackable, minimal, fast TUI file explorer"
/>
<meta name="twitter:title" content="xplr" />
<meta
name="twitter:description"
content="A hackable, minimal, fast TUI file explorer"
/>
<meta
name="twitter:image"
content="https://s8.gifyu.com/images/xplr-0.17.6.jpg"
/>
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:url" content="https://xplr.dev" />
<meta property="og:title" content="xplr" />
<meta property="og:type" content="object" />
<meta property="og:url" content="https://xplr.dev" />
<meta
property="og:image"
content="https://s8.gifyu.com/images/xplr-0.17.6.jpg"
/>
<meta itemprop="name" content="xplr" />
<meta
itemprop="description"
content="A hackable, minimal, fast TUI file explorer"
/>
<!-- Youtube Embed -->
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/gh/paulirish/lite-youtube-embed@master/src/lite-yt-embed.min.css"
/>
<script
defer="defer"
src="https://cdn.jsdelivr.net/gh/paulirish/lite-youtube-embed@master/src/lite-yt-embed.min.js"
></script>
<!-- EthicalAds by "Read The Docs" folks -->
<script
async
src="https://media.ethicalads.io/media/client/ethicalads.min.js"
></script>
</head>
<body>
<!--navigation-->
<section>
<div class="container">
<nav class="navbar navbar-expand-md navbar-dark">
<a class="navbar-brand heading-black" href="/">
<img src="/assets/icon/xplr32.png" alt="xplr" />
</a>
<button
class="navbar-toggler navbar-toggler-right border-0"
type="button"
data-toggle="collapse"
data-target="#navbarCollapse"
aria-controls="navbarCollapse"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span data-feather="grid"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<a
class="nav-link page-scroll"
href="/en/introduction.html#features"
>
Features
</a>
</li>
<li class="nav-item">
<a class="nav-link page-scroll" href="/en">Documentation</a>
</li>
<li class="nav-item">
<a class="nav-link page-scroll" href="/en/community.html">
Community
</a>
</li>
<li class="nav-item">
<a
class="nav-link page-scroll"
href="https://blog.xplr.dev"
target="_blank"
rel="noreferrer noopener"
>
Blog
</a>
</li>
<li class="nav-item">
<a
class="nav-link page-scroll"
href="https://github.com/sayanarijit/xplr"
target="_blank"
rel="noreferrer noopener"
>
GitHub
</a>
</li>
</ul>
</div>
</nav>
<hr />
</div>
</section>
<section>
<div class="container">
<div
id="landing-under-navbar"
class="dark flat"
data-ea-publisher="xplrdev"
data-ea-type="text"
></div>
</div>
</section>
<!--hero header-->
<section id="home">
<div class="container">
<div class="row">
<div class="col-md-8 col-sm-10 col-12 mx-auto my-auto text-center">
<h1 class="heading-black">xplr</h1>
<p class="lead">A hackable, minimal, fast TUI file explorer</p>
<div id="QJaEMeVo9Uw" class="eleventy-plugin-youtube-embed pb-3">
<lite-youtube
videoid="QJaEMeVo9Uw"
style="
background-image: url('https://s8.gifyu.com/images/xplr-0.17.6.jpg');
"
>
<div class="lty-playbtn"></div>
</lite-youtube>
</div>
<a
class="btn btn-primary d-inline-flex flex-row align-items-center py-3"
href="/en/install.html"
class="mb-3"
>
Try or Install
<em class="ml-2" data-feather="arrow-right"></em>
</a>
</div>
</div>
</div>
</section>
<!--scroll to top-->
<div class="scroll-top">
<i class="fa fa-angle-up" aria-hidden="true"></i>
</div>
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/feather-icons/4.7.3/feather.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick.min.js"></script>
<script src="js/scripts.js"></script>
</body>
</html>

@ -1,73 +0,0 @@
$(function () {
// init feather icons
feather.replace();
// init tooltip & popovers
$('[data-toggle="tooltip"]').tooltip();
$('[data-toggle="popover"]').popover();
//page scroll
$('a.page-scroll').bind('click', function (event) {
var $anchor = $(this);
$('html, body').stop().animate({
scrollTop: $($anchor.attr('href')).offset().top - 20
}, 1000);
event.preventDefault();
});
// slick slider
$('.slick-about').slick({
slidesToShow: 1,
slidesToScroll: 1,
autoplay: true,
autoplaySpeed: 3000,
dots: true,
arrows: false
});
//toggle scroll menu
var scrollTop = 0;
$(window).scroll(function () {
var scroll = $(window).scrollTop();
//adjust menu background
if (scroll > 80) {
if (scroll > scrollTop) {
$('.smart-scroll').addClass('scrolling').removeClass('up');
} else {
$('.smart-scroll').addClass('up');
}
} else {
// remove if scroll = scrollTop
$('.smart-scroll').removeClass('scrolling').removeClass('up');
}
scrollTop = scroll;
// adjust scroll to top
if (scroll >= 600) {
$('.scroll-top').addClass('active');
} else {
$('.scroll-top').removeClass('active');
}
return false;
});
// scroll top top
$('.scroll-top').click(function () {
$('html, body').stop().animate({
scrollTop: 0
}, 1000);
});
/**Theme switcher - DEMO PURPOSE ONLY */
$('.switcher-trigger').click(function () {
$('.switcher-wrap').toggleClass('active');
});
$('.color-switcher ul li').click(function () {
var color = $(this).attr('data-color');
$('#theme-color').attr("href", "css/" + color + ".css");
$('.color-switcher ul li').removeClass('active');
$(this).addClass('active');
});
});

@ -1,561 +0,0 @@
/*!
* Created by Kroplet (https://www.kroplet.com)
* The easiest way to create Bootstrap 4 themes.
*/
/*****************
Custom CSS
*****************/
body {
border-color: $black;
}
.small-xl {
font-size: 90%;
}
.img-faded {
opacity: 0.5;
}
.font-weight-medium {
font-weight: 600;
}
.heading-black {
font-weight: 800;
}
/* Box shadow */
.btn {
text-transform: uppercase;
font-size: 15px;
@each $color, $value in $theme-colors {
&.btn-#{$color} {
box-shadow: 0 8px 16px rgba($value, 0.3);
transition: all 0.2s ease-out;
&:hover {
box-shadow: 0 8px 20px rgba($value, 0.35);
}
&:active {
box-shadow: none !important;
}
}
}
}
/*Navbar*/
.navbar {
&.navbar-transparent {
opacity: 0.98;
}
@media (max-width: 992px) {
&.navbar-transparent {
background-color: rgba($black, 0.4);
}
}
.navbar-brand {
font-size: 1.5rem;
font-weight: 900;
color: $primary;
text-transform: uppercase;
}
.navbar-nav .nav-item {
margin: 0 0.7rem;
.nav-link {
font-weight: 600;
}
}
}
.section-angle {
position: relative;
border-color: inherit;
background: $black;
&:before,
&:after {
width: 0;
height: 0;
position: absolute;
content: '';
left: 0;
border: 0 solid transparent;
z-index: 3;
}
&.top-left:before,
&.top-right:before {
top: 0;
border-left-width: 100vw;
}
&.bottom-left:after,
&.bottom-right:after {
bottom: 0;
border-right-width: 100vw;
}
&.bottom-left:after {
border-right-color: inherit;
}
&.bottom-right:after {
border-bottom-color: inherit;
}
&.top-left:before {
border-top-color: inherit;
}
&.top-right:before {
border-left-color: inherit;
}
@include media-breakpoint-up('lg') {
&.bottom-right:after,
&.top-right:before {
border-bottom-width: 2rem;
}
&.bottom-left:after,
&.top-left:before {
border-top-width: 2rem;
}
}
@include media-breakpoint-down('sm') {
&.bottom-right:after,
&.top-right:before {
border-bottom-width: 1rem;
}
&.bottom-left:after,
&.top-left:before {
border-top-width: 1rem;
}
}
}
/*smart scrolling*/
.smart-scroll {
position: fixed;
top: 0;
z-index: 1020;
width: 100%;
transition: all 0.3s ease-out;
&.scrolling {
transform: translateY(-100%);
&.up {
background-color: rgba($black, 0.9);
transform: translateY(0);
transition: all 0.3s ease-out;
}
}
}
/**dividers */
.divider {
position: relative;
&.top-divider:before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
display: block;
height: 1px;
background: $gray-900;
background: linear-gradient(
to right,
rgba($primary, 0.1) 0,
$gray-900 50%,
rgba($primary, 0.1) 100%
);
}
&.bottom-divider:after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
display: block;
height: 1px;
background: $gray-900;
background: linear-gradient(
to right,
rgba($primary, 0.24) 0,
$gray-900 50%,
rgba($primary, 0.24) 100%
);
}
}
/*vertical heights */
.vh-100 {
height: 100vh;
}
@media (min-width: 576px) {
.vh-sm-100 {
height: 100vh;
}
}
@media (min-width: 768px) {
.vh-md-100 {
height: 100vh;
}
}
.bg-hero {
background-color: $black;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100%25' height='100%25' viewBox='0 0 800 800'%3E%3Cg %3E%3Ccircle fill='#{$black}' cx='400' cy='400' r='600'/%3E%3Ccircle fill='#{darken($primary,40%)}' cx='400' cy='400' r='500'/%3E%3Ccircle fill='#{darken($primary,30%)}' cx='400' cy='400' r='400'/%3E%3Ccircle fill='#{darken($primary,20%)}' cx='400' cy='400' r='300'/%3E%3Ccircle fill='#{darken($primary,10%)}' cx='400' cy='400' r='200'/%3E%3Ccircle fill='#{$primary}' cx='400' cy='400' r='100'/%3E%3C/g%3E%3C/svg%3E");
/* background by SVGBackgrounds.com */
background-attachment: fixed;
background-size: cover;
position: relative;
&:before {
position: absolute;
z-index: 1;
width: 100%;
height: 100%;
display: block;
left: 0;
top: 0;
content: '';
background-color: rgba($black, 0.7);
}
.container {
z-index: 2;
position: relative;
}
}
/*features boxes*/
.feature-boxes {
text-align: center;
.box {
padding: 3rem;
}
}
/*features-list */
.features-list {
list-style: none;
li {
float: left;
width: 50%;
margin-top: 0;
margin-bottom: 1.75rem;
font-size: 1.05rem;
padding-left: 1.75rem;
font-weight: 500;
&:before {
position: relative;
font-family: FontAwesome;
font-size: 14px;
content: '\f10c';
color: $primary;
margin: 0 0.75rem 0 0;
}
}
}
/*Pricing tables*/
.pricing-table {
.pricing-list {
margin-bottom: 3rem;
margin-top: 2rem;
li {
margin-bottom: 1rem;
font-size: 1.05rem;
font-weight: 500;
}
}
.card {
transition: all 0.25s ease-out;
text-align: center;
.card-body {
padding: 2.25rem 2rem;
}
&.active {
color: white;
background-color: transparent;
h2,
h3 {
color: $white;
}
}
}
}
/**slick slider */
.slick-dots {
top: -4rem;
li {
button::before {
font-size: 0.75rem !important;
line-height: 3.5rem !important;
}
button::before,
&.slick-active button:before {
color: $primary !important;
}
}
}
.slick-next:before,
.slick-prev:before {
color: rgba($primary, 0.2);
}
.slick-next:hover:before,
.slick-prev:hover:before {
color: $primary;
}
/*footer*/
footer {
ul > li {
padding: 0.5rem 0;
}
a {
color: rgba($white, 0.7);
text-decoration: none;
font-weight: 500;
transition: all 0.25s ease-out;
&:hover {
color: rgba($white, 0.9);
text-decoration: underline;
}
}
h5 {
font-size: 1rem;
text-transform: uppercase;
}
}
/*social icons*/
.social {
a {
width: 45px;
height: 45px;
background: transparent;
display: block;
text-align: center;
color: gray-100;
border-radius: 4px;
font-size: 18px;
line-height: 45px;
&:hover {
background: $primary;
color: $black;
transition: all 0.4s ease-in-out;
}
}
&.social-sm a {
width: 35px;
height: 35px;
font-size: 16px;
line-height: 35px;
}
&.social-rounded a {
border-radius: 50%;
}
}
/*scroll to top */
.scroll-top {
bottom: 20px;
font-size: 20px;
height: 40px;
position: fixed;
text-align: center;
width: 40px;
z-index: 10;
cursor: pointer;
transition: 0.3s;
border-radius: 50%;
line-height: 40px;
right: -100px;
color: $white;
background-color: rgba($primary, 0.5);
&:hover {
background-color: rgba($primary, 1);
transition: all 0.4s ease-in-out;
}
&.active {
right: 20px;
}
}
/* Icon Boxes */
.icon-box {
position: relative;
border-radius: 50%;
display: inline-block;
vertical-align: middle;
background-color: $white;
margin: 1rem;
@each $color, $value in $theme-colors {
&.box-#{$color} {
color: $value;
background-color: rgba($value, 0.1);
}
}
.icon-box-inner {
display: flex;
flex-direction: row;
align-items: center;
padding: 1.5rem;
&.small {
padding: 1.25rem;
}
&.small-xs {
padding: 1rem;
}
}
}
/*all themes colors*/
.bg-black {
background-color: $black;
}
.bg-blue {
background-color: $blue;
}
.bg-indigo {
background-color: $indigo;
}
.bg-purple {
background-color: $purple;
}
.bg-pink {
background-color: $pink;
}
.bg-red {
background-color: $red;
}
.bg-orange {
background-color: $orange;
}
.bg-yellow {
background-color: $yellow;
}
.bg-green {
background-color: $green;
}
.bg-teal {
background-color: $teal;
}
.bg-cyan {
background-color: $cyan;
}
/*theme switcher*/
.switcher-wrap {
position: fixed;
top: 250px;
width: 250px;
background: $gray-900;
color: $body-color;
z-index: 100;
padding: 20px;
left: -250px;
transition: 0.3s;
&.active {
left: 0;
}
ul {
margin: 0;
padding: 0;
list-style: none;
li {
margin-bottom: 0.5rem;
a {
color: $body-color;
&:hover {
color: $primary;
}
}
}
}
.color-switcher ul li {
width: 28px;
height: 28px;
float: left;
margin: 3px;
margin-bottom: 10px;
cursor: pointer;
transition: 0.3s;
&.active {
border: 3px solid $gray-800;
}
}
.switcher-trigger {
position: absolute;
left: 100%;
width: 40px;
height: 40px;
background: $gray-900;
top: 0;
font-size: 20px;
text-align: center;
color: rgba($primary, 0.5);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
border-radius: 0 2px 2px 0;
&:hover {
color: $primary;
}
}
}
@media screen and (max-width: 768px) {
.switcher-wrap {
display: none;
}
}
.ea-text {
text-align: center;
}

@ -1,848 +0,0 @@
/*!
* Created by Kroplet (https://www.kroplet.com)
* The easiest way to create Bootstrap 4 themes.
*/
//
//Colors
//
// Base Colors
$white: #ffffff;
$gray-100: #eceff1;
$gray-200: #cfd8dc;
$gray-300: #b0bec5;
$gray-400: #90a4ae;
$gray-500: #78909c;
$gray-600: #607d8b;
$gray-700: #546e7a;
$gray-800: #455a64;
$gray-900: #263238;
$black: #000000;
$blue: #2979ff;
$indigo: #3d5afe;
$purple: #d500f9;
$pink: #f50057;
$red: #ff1744;
$orange: #ff9100;
$yellow: #ffea00;
$green: #00e676;
$teal: #1de9b6;
$cyan: #00e5ff;
// Theme Colors
$primary: $teal;
$secondary: $white;
$success: $green;
$info: $cyan;
$warning: $yellow;
$danger: $red;
$light: $gray-100;
$dark: darken($gray-900, 10%);
$theme-colors: ();
$theme-colors: map-merge(
(
'primary': $primary,
'secondary': $secondary,
'success': $success,
'info': $info,
'warning': $warning,
'danger': $danger,
'light': $light,
'dark': $dark,
),
$theme-colors
);
$theme-color-interval: 8%;
$yiq-contrasted-threshold: 150;
$yiq-text-dark: darken($gray-900, 10%);
$yiq-text-light: $white;
//
//Global
//
$enable-caret: true;
$enable-rounded: true;
$enable-shadows: false;
$enable-gradients: false;
$enable-transitions: true;
$enable-hover-media-query: false;
$enable-grid-classes: true;
$enable-print-styles: true;
//
//Spacing
//
$spacer: 1rem;
$spacers: (
0: 0,
1: (
$spacer * 0.25,
),
2: (
$spacer * 0.5,
),
3: $spacer,
4: (
$spacer * 1.5,
),
5: (
$spacer * 3,
),
6: (
$spacer * 6,
),
7: (
$spacer * 9,
),
8: (
$spacer * 12,
),
9: (
$spacer * 15,
),
);
$sizes: (
25: 25%,
50: 50%,
75: 75%,
100: 100%,
);
//
//Body
//
$body-bg: $black;
$body-color: $gray-200;
//
//Links
//
$link-color: $primary;
$link-decoration: none;
$link-hover-color: darken($primary, 20%);
$link-hover-decoration: none;
//
//Paragraphs
//
$paragraph-margin-bottom: 1rem;
//
//GridBreakpoints
//
$grid-breakpoints: (
xs: 0,
sm: 576px,
md: 768px,
lg: 992px,
xl: 1200px,
);
//
//GridContainers
//
$container-max-widths: (
sm: 540px,
md: 720px,
lg: 960px,
xl: 1140px,
);
//
//GridColumns
//
$grid-columns: 12;
$grid-gutter-width: 30px;
//
//Components
//
$line-height-lg: 1.5;
$line-height-sm: 1.5;
$border-width: 2px;
$border-color: $gray-200;
$border-radius: 0.3rem;
$border-radius-lg: 0.4rem;
$border-radius-sm: 0.25rem;
$box-shadow-sm: 0 0.125rem 0.25rem rgba($black, 0.075);
$box-shadow: 0 0.5rem 1rem rgba($black, 0.15);
$box-shadow-lg: 0 1rem 3rem rgba($black, 0.175);
$component-active-color: $white;
$component-active-bg: theme-color('primary');
$caret-width: 0.3em;
$transition-base: all 0.25s ease-in-out;
$transition-fade: opacity 0.15s linear;
$transition-collapse: height 0.35s ease;
//
//Fonts
//
$font-family-sans-serif: 'Inter UI', -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif, 'Apple Color Emoji',
'Segoe UI Emoji', 'Segoe UI Symbol';
$font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas,
'Liberation Mono', 'Courier New', monospace;
$font-family-base: $font-family-sans-serif;
$font-size-base: 1rem;
$font-size-lg: ($font-size-base * 1.25);
$font-size-sm: ($font-size-base * 0.875);
$font-weight-light: 300;
$font-weight-normal: 400;
$font-weight-bold: 700;
$font-weight-base: $font-weight-normal;
$line-height-base: 1.5;
$h1-font-size: $font-size-base * 3;
$h2-font-size: $font-size-base * 2.5;
$h3-font-size: $font-size-base * 1.5;
$h4-font-size: $font-size-base * 1.375;
$h5-font-size: $font-size-base * 1.25;
$h6-font-size: $font-size-base;
$headings-margin-bottom: $spacer;
$headings-font-family: inherit;
$headings-font-weight: $font-weight-bold;
$headings-line-height: 1.5;
$headings-color: $white;
$display1-size: 5rem;
$display2-size: 4.5rem;
$display3-size: 3.5rem;
$display4-size: 2.5rem;
$display1-weight: 300;
$display2-weight: 300;
$display3-weight: 300;
$display4-weight: 300;
$display-line-height: $headings-line-height;
$lead-font-size: ($font-size-base * 1.2);
$lead-font-weight: 500;
$small-font-size: 80%;
$text-muted: $gray-500;
$blockquote-small-color: $gray-500;
$blockquote-font-size: ($font-size-base * 1.25);
$hr-border-color: $gray-800;
$hr-border-width: $border-width;
$mark-padding: 0.2em;
$dt-font-weight: $font-weight-bold;
$kbd-box-shadow: inset 0 -0.1rem 0 rgba($black, 0.25);
$nested-kbd-font-weight: $font-weight-bold;
$list-inline-padding: 0.5rem;
$mark-bg: #fcf8e3;
$hr-margin-y: $spacer;
//
//Tables
//
$table-cell-padding: 0.75rem;
$table-cell-padding-sm: 0.3rem;
$table-bg: transparent;
$table-accent-bg: rgba($black, 0.05);
$table-hover-bg: rgba($black, 0.075);
$table-active-bg: $table-hover-bg;
$table-border-width: $border-width;
$table-border-color: $gray-100;
$table-head-bg: $gray-100;
$table-head-color: $gray-700;
$table-dark-bg: $gray-900;
$table-dark-accent-bg: rgba($white, 0.05);
$table-dark-hover-bg: rgba($white, 0.075);
$table-dark-border-color: lighten($gray-900, 7.5%);
$table-dark-color: $body-bg;
$table-striped-order: odd;
$table-caption-color: $text-muted;
//
//Buttons
//
$input-btn-padding-y: 0.55rem;
$input-btn-padding-x: 1.5rem;
$input-btn-line-height: $line-height-base;
$input-btn-focus-width: 0.2rem;
$input-btn-focus-color: rgba(theme-color('primary'), 0.25);
$input-btn-focus-box-shadow: none;
$input-btn-padding-y-sm: 0.375rem;
$input-btn-padding-x-sm: 1rem;
$input-btn-line-height-sm: $line-height-sm;
$input-btn-padding-y-lg: 0.75rem;
$input-btn-padding-x-lg: 1.5rem;
$input-btn-line-height-lg: $line-height-lg;
$input-btn-border-width: $border-width;
$btn-padding-y: $input-btn-padding-y;
$btn-padding-x: $input-btn-padding-x;
$btn-line-height: $input-btn-line-height;
$btn-padding-y-sm: $input-btn-padding-y-sm;
$btn-padding-x-sm: $input-btn-padding-x-sm;
$btn-line-height-sm: $input-btn-line-height-sm;
$btn-padding-y-lg: $input-btn-padding-y-lg;
$btn-padding-x-lg: $input-btn-padding-x-lg;
$btn-line-height-lg: $input-btn-line-height-lg;
$btn-border-width: $input-btn-border-width;
$btn-font-weight: 500;
$btn-box-shadow: 0 2px 8px rgba($black, 0.1);
$btn-focus-width: $input-btn-focus-width;
$btn-focus-box-shadow: $input-btn-focus-box-shadow;
$btn-disabled-opacity: 0.65;
$btn-active-box-shadow: inset 0 3px 5px rgba($black, 0.125);
$btn-link-disabled-color: $gray-600;
$btn-block-spacing-y: 0.5rem;
$btn-border-radius: $border-radius;
$btn-border-radius-lg: $border-radius-lg;
$btn-border-radius-sm: $border-radius-sm;
$btn-transition: all 0.2s;
//
//Forms
//
$label-margin-bottom: 0.5rem;
$input-padding-y: $input-btn-padding-y;
$input-padding-x: $input-btn-padding-x;
$input-line-height: $input-btn-line-height;
$input-padding-y-sm: $input-btn-padding-y-sm;
$input-padding-x-sm: $input-btn-padding-x-sm;
$input-line-height-sm: $input-btn-line-height-sm;
$input-padding-y-lg: $input-btn-padding-y-lg;
$input-padding-x-lg: $input-btn-padding-x-lg;
$input-line-height-lg: $input-btn-line-height-lg;
$input-bg: $white;
$input-disabled-bg: $gray-100;
$input-color: $gray-700;
$input-border-color: $border-color;
$input-border-width: $border-width;
$input-box-shadow: none;
$input-border-radius: $border-radius;
$input-border-radius-lg: $border-radius-lg;
$input-border-radius-sm: $border-radius-sm;
$input-focus-bg: $input-bg;
$input-focus-border-color: $primary;
$input-focus-color: $input-color;
$input-focus-width: $input-btn-focus-width;
$input-focus-box-shadow: $input-btn-focus-box-shadow;
$input-placeholder-color: $gray-500;
$input-plaintext-color: $body-color;
$input-height-border: $input-btn-border-width * 2;
$input-height-inner: ($font-size-base * $input-btn-line-height) +
($input-btn-padding-y * 2);
$input-height: calc(#{$input-height-inner} + #{$input-height-border});
$input-height-inner-sm: ($font-size-sm * $input-btn-line-height-sm) +
($input-btn-padding-y-sm * 2);
$input-height-sm: calc(#{$input-height-inner-sm} + #{$input-height-border});
$input-height-inner-lg: ($font-size-lg * $input-btn-line-height-lg) +
($input-btn-padding-y-lg * 2);
$input-height-lg: calc(#{$input-height-inner-lg} + #{$input-height-border});
$input-transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
$form-text-margin-top: 0.25rem;
$form-check-input-gutter: 1.25rem;
$form-check-input-margin-y: 0.3rem;
$form-check-input-margin-x: 0.25rem;
$form-check-inline-margin-x: 0.75rem;
$form-check-inline-input-margin-x: 0.3125rem;
$form-group-margin-bottom: 1rem;
$input-group-addon-color: $white;
$input-group-addon-bg: $dark;
$input-group-addon-border-color: $dark;
$form-feedback-margin-top: $form-text-margin-top;
$form-feedback-font-size: $small-font-size;
$form-feedback-valid-color: theme-color('success');
$form-feedback-invalid-color: theme-color('danger');
//
//CustomForms
//
$custom-control-gutter: 1.75rem;
$custom-control-spacer-y: 0.25rem;
$custom-control-spacer-x: 1rem;
$custom-control-indicator-size: 1.125rem;
$custom-control-indicator-bg: $gray-100;
$custom-control-indicator-bg-size: 50% 50%;
$custom-control-indicator-box-shadow: inset 0 0.25rem 0.25rem rgba($black, 0.1);
$custom-control-indicator-disabled-bg: $input-disabled-bg;
$custom-control-label-disabled-color: $gray-200;
$custom-control-indicator-checked-color: $white;
$custom-control-indicator-checked-bg: theme-color('primary');
$custom-control-indicator-checked-box-shadow: none;
$custom-control-indicator-focus-box-shadow: 0 0 0 1px $body-bg,
$input-btn-focus-box-shadow;
$custom-control-indicator-active-color: $white;
$custom-control-indicator-active-bg: lighten(theme-color('primary'), 35%);
$custom-control-indicator-active-box-shadow: none;
$custom-checkbox-indicator-border-radius: $border-radius;
$custom-checkbox-indicator-icon-checked: str-replace(
url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='#{$custom-control-indicator-checked-color}' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E"),
'#',
'%23'
);
$custom-checkbox-indicator-indeterminate-bg: theme-color('primary');
$custom-checkbox-indicator-indeterminate-color: $custom-control-indicator-checked-color;
$custom-checkbox-indicator-icon-indeterminate: str-replace(
url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='#{$custom-checkbox-indicator-indeterminate-color}' d='M0 2h4'/%3E%3C/svg%3E"),
'#',
'%23'
);
$custom-checkbox-indicator-indeterminate-box-shadow: none;
$custom-radio-indicator-border-radius: 50%;
$custom-radio-indicator-icon-checked: str-replace(
url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='#{$custom-control-indicator-checked-color}'/%3E%3C/svg%3E"),
'#',
'%23'
);
$custom-select-padding-y: 0.375rem;
$custom-select-padding-x: 0.75rem;
$custom-select-height: $input-height;
$custom-select-indicator-padding: 1rem;
$custom-select-line-height: $input-btn-line-height;
$custom-select-color: $input-color;
$custom-select-disabled-color: $gray-600;
$custom-select-bg: $input-bg;
$custom-select-disabled-bg: $input-disabled-bg;
$custom-select-bg-size: 8px 10px;
$custom-select-indicator-color: $gray-800;
$custom-select-indicator: str-replace(
url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='#{$custom-select-indicator-color}' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E"),
'#',
'%23'
);
$custom-select-border-width: $input-btn-border-width;
$custom-select-border-color: $input-border-color;
$custom-select-border-radius: $border-radius;
$custom-select-focus-border-color: $input-focus-border-color;
$custom-select-focus-box-shadow: inset 0 1px 2px rgba($black, 0.075),
$input-btn-focus-box-shadow;
$custom-select-font-size-sm: 75%;
$custom-select-height-sm: $input-height-sm;
$custom-select-font-size-lg: 125%;
$custom-select-height-lg: $input-height-lg;
$custom-range-track-width: 100%;
$custom-range-track-height: 0.5rem;
$custom-range-track-cursor: pointer;
$custom-range-track-bg: $gray-300;
$custom-range-track-border-radius: 1rem;
$custom-range-track-box-shadow: inset 0 0.25rem 0.25rem rgba($black, 0.1);
$custom-range-thumb-width: 1rem;
$custom-range-thumb-height: $custom-range-thumb-width;
$custom-range-thumb-bg: $component-active-bg;
$custom-range-thumb-border: 0;
$custom-range-thumb-border-radius: 1rem;
$custom-range-thumb-box-shadow: 0 0.1rem 0.25rem rgba($black, 0.1);
$custom-range-thumb-focus-box-shadow: 0 0 0 1px $body-bg,
$input-btn-focus-box-shadow;
$custom-range-thumb-active-bg: lighten($component-active-bg, 35%);
$custom-file-height: $input-height;
$custom-file-focus-border-color: $input-focus-border-color;
$custom-file-focus-box-shadow: $input-btn-focus-box-shadow;
$custom-file-padding-y: $input-btn-padding-y;
$custom-file-padding-x: $input-btn-padding-x;
$custom-file-line-height: $input-btn-line-height;
$custom-file-color: $gray-300;
$custom-file-bg: $input-bg;
$custom-file-border-width: $input-btn-border-width;
$custom-file-border-color: $dark;
$custom-file-border-radius: $input-border-radius;
$custom-file-box-shadow: $input-box-shadow;
$custom-file-button-color: $white;
$custom-file-button-bg: $input-group-addon-bg;
$custom-file-text: (
en: 'Browse',
);
//
//Dropdowns
//
$dropdown-min-width: 10rem;
$dropdown-padding-y: 0.75rem;
$dropdown-spacer: 0.125rem;
$dropdown-bg: $white;
$dropdown-border-color: $gray-100;
$dropdown-border-radius: $border-radius;
$dropdown-border-width: $border-width;
$dropdown-divider-bg: $gray-100;
$dropdown-box-shadow: 0 0.5rem 1rem rgba($black, 0.175);
$dropdown-link-color: $gray-700;
$dropdown-link-hover-color: $gray-900;
$dropdown-link-hover-bg: $gray-100;
$dropdown-link-active-color: $component-active-color;
$dropdown-link-active-bg: $component-active-bg;
$dropdown-link-disabled-color: $gray-600;
$dropdown-item-padding-y: 0.25rem;
$dropdown-item-padding-x: 1.5rem;
$dropdown-header-color: $gray-400;
//
//ZindexMasterList
//
$zindex-dropdown: 1000;
$zindex-sticky: 1020;
$zindex-fixed: 1030;
$zindex-modal-backdrop: 1040;
$zindex-modal: 1050;
$zindex-popover: 1060;
$zindex-tooltip: 1070;
//
//Navs
//
$nav-link-padding-y: 0.25rem;
$nav-link-padding-x: 1rem;
$nav-link-disabled-color: $gray-400;
$nav-tabs-border-color: $gray-100;
$nav-tabs-border-width: $border-width;
$nav-tabs-border-radius: $border-radius;
$nav-tabs-link-hover-border-color: $gray-100 $gray-100 $nav-tabs-border-color;
$nav-tabs-link-active-color: $white;
$nav-tabs-link-active-bg: $dark;
$nav-tabs-link-active-border-color: $gray-200 $gray-200 $nav-tabs-link-active-bg;
$nav-pills-border-radius: $border-radius;
$nav-pills-link-active-color: $component-active-color;
$nav-pills-link-active-bg: $component-active-bg;
$nav-divider-color: $gray-200;
$nav-divider-margin-y: ($spacer / 2);
//
//Navbar
//
$navbar-padding-y: 1.5rem;
$navbar-padding-x: 1.25rem;
$navbar-nav-link-padding-x: 0.75rem;
$navbar-brand-font-size: $font-size-lg;
$nav-link-height: (
$font-size-base * $line-height-base + $nav-link-padding-y * 2
);
$navbar-brand-height: $navbar-brand-font-size * $line-height-base;
$navbar-brand-padding-y: ($nav-link-height - $navbar-brand-height) / 2;
$navbar-toggler-padding-y: 0.25rem;
$navbar-toggler-padding-x: 0.75rem;
$navbar-toggler-font-size: $font-size-lg;
$navbar-toggler-border-radius: $btn-border-radius;
$navbar-dark-color: rgba($white, 0.7);
$navbar-dark-hover-color: rgba($white, 0.9);
$navbar-dark-active-color: $white;
$navbar-dark-disabled-color: rgba($white, 0.3);
$navbar-dark-toggler-icon-bg: str-replace(
url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='#{$navbar-dark-color}' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"),
'#',
'%23'
);
$navbar-dark-toggler-border-color: rgba($white, 0.1);
$navbar-light-color: rgba($black, 0.7);
$navbar-light-hover-color: rgba($primary, 0.9);
$navbar-light-active-color: $primary;
$navbar-light-disabled-color: rgba($black, 0.3);
$navbar-light-toggler-icon-bg: str-replace(
url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='#{$navbar-light-color}' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"),
'#',
'%23'
);
$navbar-light-toggler-border-color: rgba($black, 0.1);
//
//Pagination
//
$pagination-padding-y: 0.5rem;
$pagination-padding-x: 0.75rem;
$pagination-padding-y-sm: 0.25rem;
$pagination-padding-x-sm: 0.5rem;
$pagination-padding-y-lg: 0.75rem;
$pagination-padding-x-lg: 1.5rem;
$pagination-line-height: 1;
$pagination-color: $white;
$pagination-bg: $dark;
$pagination-border-width: 0px;
$pagination-border-color: transparent;
$pagination-focus-box-shadow: $input-btn-focus-box-shadow;
$pagination-focus-outline: 0;
$pagination-hover-color: $gray-100;
$pagination-hover-bg: $gray-600;
$pagination-hover-border-color: $gray-700;
$pagination-active-color: $white;
$pagination-active-bg: $gray-600;
$pagination-active-border-color: $gray-700;
$pagination-disabled-color: $white;
$pagination-disabled-bg: $gray-400;
$pagination-disabled-border-color: transparent;
//
//Jumbotron
//
$jumbotron-padding: 2rem;
$jumbotron-bg: $gray-100;
//
//Cards
//
$card-spacer-y: 0.75rem;
$card-spacer-x: 1.25rem;
$card-border-width: 0px;
$card-border-radius: $border-radius;
$card-border-color: rgba($black, 0.125);
$card-inner-border-radius: calc(#{$card-border-radius} - #{$card-border-width});
$card-cap-bg: rgba($black, 0.03);
$card-bg: $gray-900;
$card-img-overlay-padding: 1.25rem;
$card-group-margin: ($grid-gutter-width / 2);
$card-deck-margin: $card-group-margin;
$card-columns-count: 3;
$card-columns-gap: 1.25rem;
$card-columns-margin: $card-spacer-y;
//
//Tooltips
//
$tooltip-font-size: $font-size-sm;
$tooltip-max-width: 200px;
$tooltip-color: $white;
$tooltip-bg: $black;
$tooltip-border-radius: $border-radius;
$tooltip-opacity: 0.9;
$tooltip-padding-y: 0.25rem;
$tooltip-padding-x: 0.5rem;
$tooltip-margin: 0;
$tooltip-arrow-width: 0.8rem;
$tooltip-arrow-height: 0.4rem;
$tooltip-arrow-color: $tooltip-bg;
//
//Popovers
//
$popover-font-size: $font-size-sm;
$popover-bg: $white;
$popover-max-width: 276px;
$popover-border-width: $border-width;
$popover-border-color: rgba($black, 0.2);
$popover-border-radius: $border-radius-lg;
$popover-box-shadow: 0 0.25rem 0.5rem rgba($black, 0.2);
$popover-header-bg: $dark;
$popover-header-color: $white;
$popover-header-padding-y: 0.65rem;
$popover-header-padding-x: 0.85rem;
$popover-body-color: $body-color;
$popover-body-padding-y: $popover-header-padding-y;
$popover-body-padding-x: $popover-header-padding-x;
$popover-arrow-width: 1rem;
$popover-arrow-height: 0.5rem;
$popover-arrow-color: $popover-bg;
$popover-arrow-outer-color: fade-in($popover-border-color, 0.05);
//
//Badges
//
$badge-font-size: 75%;
$badge-font-weight: $font-weight-bold;
$badge-padding-y: 0.35em;
$badge-padding-x: 0.5em;
$badge-border-radius: 4px;
$badge-pill-padding-x: 0.6em;
$badge-pill-border-radius: 10rem;
//
//Modals
//
$modal-inner-padding: 1.5rem;
$modal-dialog-margin: 0.5rem;
$modal-dialog-margin-y-sm-up: 1.75rem;
$modal-title-line-height: $line-height-base;
$modal-content-bg: $white;
$modal-content-border-color: $modal-content-bg;
$modal-content-border-width: $border-width;
$modal-content-border-radius: $border-radius-lg;
$modal-content-box-shadow-xs: 0 0.25rem 0.5rem rgba($black, 0.5);
$modal-content-box-shadow-sm-up: 0 0.5rem 1rem rgba($black, 0.5);
$modal-backdrop-bg: $black;
$modal-backdrop-opacity: 0.5;
$modal-header-border-color: $modal-content-bg;
$modal-footer-border-color: $modal-header-border-color;
$modal-header-border-width: $modal-content-border-width;
$modal-footer-border-width: $modal-header-border-width;
$modal-header-padding: 1.5rem;
$modal-lg: 800px;
$modal-md: 600px;
$modal-sm: 400px;
$modal-transition: transform 0.3s ease-out;
//
//Alerts
//
$alert-padding-y: 0.75rem;
$alert-padding-x: 1.5rem;
$alert-margin-bottom: 1rem;
$alert-border-radius: $border-radius;
$alert-link-font-weight: $font-weight-bold;
$alert-border-width: $border-width;
$alert-bg-level: -11;
$alert-border-level: -11;
$alert-color-level: 6;
//
//ProgressBars
//
$progress-height: 0.375rem;
$progress-font-size: ($font-size-base * 0.75);
$progress-bg: $gray-200;
$progress-border-radius: $border-radius;
$progress-box-shadow: inset 0 0.1rem 0.1rem rgba($black, 0.1);
$progress-bar-color: $white;
$progress-bar-bg: theme-color('primary');
$progress-bar-animation-timing: 1s linear infinite;
$progress-bar-transition: width 0.6s ease;
//
//ListGroup
//
$list-group-bg: $white;
$list-group-border-color: rgba($black, 0.125);
$list-group-border-width: $border-width;
$list-group-border-radius: $border-radius;
$list-group-item-padding-y: 0.75rem;
$list-group-item-padding-x: 1.25rem;
$list-group-hover-bg: $gray-100;
$list-group-active-color: $component-active-color;
$list-group-active-bg: $component-active-bg;
$list-group-active-border-color: $list-group-active-bg;
$list-group-disabled-color: $gray-400;
$list-group-disabled-bg: $list-group-bg;
$list-group-action-color: $gray-700;
$list-group-action-hover-color: $list-group-action-color;
$list-group-action-active-color: $body-color;
$list-group-action-active-bg: $gray-100;
//
//Images
//
$thumbnail-padding: 0px;
$thumbnail-bg: $white;
$thumbnail-border-width: 2px;
$thumbnail-border-color: $gray-100;
$thumbnail-border-radius: $border-radius;
$thumbnail-box-shadow: 0 1px 2px rgba($black, 0.075);
//
//Figures
//
$figure-caption-font-size: 90%;
$figure-caption-color: $gray-400;
//
//Breadcrumbs
//
$breadcrumb-padding-y: 0.75rem;
$breadcrumb-padding-x: 1rem;
$breadcrumb-item-padding: 0.5rem;
$breadcrumb-margin-bottom: 1rem;
$breadcrumb-bg: $gray-100;
$breadcrumb-divider-color: $gray-400;
$breadcrumb-active-color: $gray-400;
$breadcrumb-divider: '/';
$breadcrumb-border-radius: $border-radius;
//
//Carousel
//
$carousel-control-color: $white;
$carousel-control-width: 15%;
$carousel-control-opacity: 0.5;
$carousel-indicator-width: 30px;
$carousel-indicator-height: 3px;
$carousel-indicator-spacer: 3px;
$carousel-indicator-active-bg: $white;
$carousel-caption-width: 70%;
$carousel-caption-color: $white;
$carousel-control-icon-width: 20px;
$carousel-control-prev-icon-bg: str-replace(
url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='#{$carousel-control-color}' viewBox='0 0 8 8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E"),
'#',
'%23'
);
$carousel-control-next-icon-bg: str-replace(
url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='#{$carousel-control-color}' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E"),
'#',
'%23'
);
$carousel-transition: transform 0.6s ease;
//
//Close
//
$close-font-size: $font-size-base * 2;
$close-font-weight: $font-weight-bold;
$close-color: $black;
$close-text-shadow: 0 1px 0 $white;
//
//Code
//
$code-font-size: 87.5%;
$code-color: $pink;
$kbd-padding-y: 0.2rem;
$kbd-padding-x: 0.4rem;
$kbd-font-size: $code-font-size;
$kbd-color: $white;
$kbd-bg: $gray-900;
$pre-color: $gray-900;
$pre-scrollable-max-height: 340px;
$print-page-size: a3;
$print-body-min-width: map-get($grid-breakpoints, 'lg');
//
//Extra SASS variables
//
$link-border-width: 0px;
$link-border-style: solid;
$link-border-color: transparent;
$link-hover-border-width: 0px;
$link-hover-border-style: solid;
$link-hover-border-color: transparent;
$link-font-size: inherit;
$link-font-weight: inherit;
$link-background-color: transparent;
$link-hover-background-color: transparent;
$link-footer-color: $link-color;
$link-footer-decoration: $link-decoration;
$link-footer-hover-color: $link-hover-color;
$link-footer-hover-decoration: $link-hover-decoration;
$paragraph-color: inherit;
$paragraph-bold-text-weight: bolder;
$paragraph-bold-text-color: inherit;
$btn-text-transform: none;
$btn-font-size: $font-size-base;
$btn-font-size-lg: $font-size-lg;
$btn-font-size-sm: $font-size-sm;
$btn-background-image: none;
$btn-hover-background-image: none;
$navbar-nav-link-padding-y: 0.5rem;
$navbar-nav-link-text-transform: none;
$navbar-nav-link-font-size: inherit;
$navbar-nav-link-font-weight: inherit;

@ -1,11 +0,0 @@
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

@ -1,279 +0,0 @@
"""Generate docs from comments."""
import os
from dataclasses import dataclass
from typing import List
# Messages --------------------------------------------------------------------
MESSAGES_DOC_TEMPLATE = """
# Full List of Messages
xplr [messages][1] categorized based on their purpose.
## Categories
{categories}
{msgs}
## Also See:
- [Message][1]
[1]: message.md
""".strip()
CONFIGURATION_DOC_TEMPLATE = """
# Configuration
{doc}
""".strip()
@dataclass
class MsgSection:
title: str
body: List[str]
@dataclass
class MsgCategory:
title: str
sections: List[MsgSection]
@dataclass
class MsgResult:
categories: List[MsgCategory]
msgs: List[str]
def gen_messages():
"""Generate messages.md"""
path = "./src/msg/in_/external.rs"
res = []
reading = False
with open(path) as f:
lines = iter(f.read().splitlines())
for line in lines:
line = line.strip()
if line.startswith("pub enum ExternalMsg {"):
reading = True
continue
if not reading:
continue
if line == "}":
break
if line.startswith("/// ### "):
line = line.lstrip("/// ### ").rstrip("-").strip()
sec = MsgSection(title=None, body=[])
cat = MsgCategory(title=line, sections=[sec])
res.append(cat)
continue
if line.startswith("/// "):
line = line.lstrip("/// ").strip()
res[-1].sections[-1].body.append(line)
continue
if not line or line == "///":
res[-1].sections[-1].body.append("")
continue
if line.endswith(","):
line = line.split(",")[0].split("(")[0]
res[-1].sections[-1].title = line
sec = MsgSection(title=None, body=[])
res[-1].sections.append(sec)
continue
result = MsgResult(categories=[], msgs=[])
for cat in res:
slug = cat.title.lower().replace(" ", "-")
result.categories.append(f"- [{cat.title}](#{slug})")
result.msgs.append(f"### {cat.title}")
result.msgs.append("")
for sec in cat.sections:
if not sec.title:
continue
result.msgs.append(f"#### {sec.title}")
result.msgs.append("")
for line in sec.body:
result.msgs.append(f"{line}")
result.msgs.append("")
messages = MESSAGES_DOC_TEMPLATE.format(
categories="\n".join(result.categories), msgs="\n".join(result.msgs)
)
print(messages)
with open("./docs/en/src/messages.md", "w") as f:
print(messages, file=f)
# Configuration ---------------------------------------------------------------
def gen_configuration():
"""Generate the following docs.
- configuration.md
- general-config.md
- node_types.md
- layouts.md
- modes.md
- modes.md
"""
path = "./src/init.lua"
configuration = [[]]
general = [[]]
node_types = [[]]
layouts = [[]]
modes = [[]]
with open(path) as f:
lines = iter(f.read().splitlines())
reading = None
for line in lines:
if line.startswith("---"):
continue
if (
line.startswith("-- # Configuration ")
or line.startswith("-- ## Config ")
or line.startswith("-- ## Function ")
or line.startswith("-- ## On Load ")
):
reading = configuration
if line.startswith("-- ### General Configuration "):
reading = general
if line.startswith("-- ### Node Types "):
reading = node_types
if line.startswith("-- ### Layouts "):
reading = layouts
if line.startswith("-- ### Modes "):
reading = modes
if not reading:
continue
if line.startswith("-- ") or line == "--":
if line.startswith("-- #") and line.endswith("--"):
line = "\n{0}\n".format(line.rstrip("-"))
reading[-1].append(line[3:])
continue
if line.startswith("xplr.") and reading[-1]:
reading[-1].insert(0, "\n#### {0}\n".format(line.split()[0]))
continue
if not line.strip() and reading[-1]:
reading.append([])
continue
with open("./docs/en/src/configuration.md", "w") as f:
doc = "\n".join(["\n".join(c) for c in configuration])
print(doc)
print(doc, file=f)
with open("./docs/en/src/general-config.md", "w") as f:
doc = "\n".join(["\n".join(c) for c in general])
print(doc)
print(doc, file=f)
with open("./docs/en/src/node_types.md", "w") as f:
doc = "\n".join(["\n".join(c) for c in node_types])
print(doc)
print(doc, file=f)
with open("./docs/en/src/layouts.md", "w") as f:
doc = "\n".join(["\n".join(c) for c in layouts])
print(doc)
print(doc, file=f)
with open("./docs/en/src/modes.md", "w") as f:
doc = "\n".join(["\n".join(c) for c in modes])
print(doc)
print(doc, file=f)
# xplr.util -------------------------------------------------------------------
@dataclass
class Function:
doc: List[str]
name: str
def gen_xplr_util():
path = "./src/lua/util.rs"
functions: List[Function] = []
with open(path) as f:
lines = iter(f.read().splitlines())
reading = None
for line in lines:
if line.startswith("///"):
if reading:
reading.doc.append(line[4:])
else:
reading = Function(doc=[line[4:]], name="")
if line.startswith("pub fn") and reading:
reading.name = "\n### xplr.util." + line.split("<")[0].split()[-1] + "\n"
functions.append(reading)
reading = None
continue
with open("./docs/en/src/xplr.util.md", "w") as f:
for function in functions:
print(function.name)
print(function.name, file=f)
print("\n".join(function.doc))
print("\n".join(function.doc), file=f)
if reading:
print("\n".join(reading.doc), file=f)
def format_docs():
os.system("prettier --write docs/en/src")
def main():
gen_messages()
gen_configuration()
gen_xplr_util()
format_docs()
if __name__ == "__main__":
main()

@ -0,0 +1,151 @@
@import url('https://fonts.googleapis.com/css2?family=Share+Tech+Mono&display=swap');
* {
padding: 0;
margin: 0;
}
body {
color: #2d3436;
}
h1 {
padding-left: 5px;
font-family: 'Share Tech Mono', monospace;
font-size: 5rem;
padding-top: 2vh;
text-align: center;
transform: translateY(-50px);
}
.main-page {
display: grid;
grid-template-columns: repeat(2, 1fr);
background-color: #dfe6e9;
min-height: 100vh;
}
.para {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-family: Arial, Helvetica, sans-serif;
font-size: 2rem;
color: rgba(0, 0, 0, 0.9);
text-align: center;
}
.para .more a {
font-size: 1.5rem;
color: #636e72;
}
.main-video {
display: flex;
justify-content: center;
align-items: center;
}
.main-video img {
margin-right: 0rem;
}
.links {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-gap: 2rem;
margin: 5rem 0;
}
.links .linkCad {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 40vh;
box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.15);
color: #2d3436;
font-family: sans-serif;
cursor: pointer;
}
.links .linkCad:hover {
box-shadow: 0px 2px 15px rgba(0, 0, 0, 0.2);
}
.links a {
text-decoration: none;
}
.links .linkCad p {
padding-top: 2rem;
font-weight: 600;
}
.mrgL {
margin-left: 60%;
}
.mrgR {
margin-right: 60%;
}
@media (max-width: 1300px) {
.mrgL {
margin-left: 50%;
}
.mrgR {
margin-right: 50%;
}
}
@media (max-width: 1050px) {
.mrgL {
margin-left: 40%;
}
.mrgR {
margin-right: 40%;
}
.main-page {
display: block;
}
.para {
padding: 5rem 0;
}
}
@media (max-width: 850px) {
.mrgL {
margin-left: 30%;
}
.mrgR {
margin-right: 30%;
}
}
@media (max-width: 700px) {
.mrgL {
margin-left: 15%;
}
.mrgR {
margin-right: 15%;
}
}
@media (max-width: 550px) {
.mrgL {
margin-left: 0%;
}
.mrgR {
margin-right: 0%;
}
.links {
display: block;
}
.links {
margin-top: 0rem;
}
.linkCad {
margin-bottom: 4rem;
}
}

@ -1,26 +0,0 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1704262187,
"narHash": "sha256-N4j9qghlp/Eb3Z11WF7Cb9U91AXwpascUbLH7YKMcLc=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "65f0d241783c94a08e4c9a3870736fc8854dd520",
"type": "github"
},
"original": {
"owner": "nixos",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

@ -1,70 +0,0 @@
{
description = "xplr - A hackable, minimal, fast TUI file explorer";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs";
};
outputs = inputs@{ self, nixpkgs, ... }:
let
lib = nixpkgs.lib;
darwin = [ "x86_64-darwin" "aarch64-darwin" ];
linux = [ "x86_64-linux" "x86_64-linux-musl" "aarch64-linux" "aarch64-linux-android" "i86_64-linux" ];
allSystems = darwin ++ linux;
forEachSystem = systems: f: lib.genAttrs systems (system: f system);
forAllSystems = forEachSystem allSystems;
in
{
packages = forAllSystems (system:
let
pkgs = import nixpkgs { inherit system; };
in
rec {
# e.g. nix build .#xplr
xplr = pkgs.rustPlatform.buildRustPackage rec {
name = "xplr";
src = ./.;
cargoLock = {
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);
devShells = forAllSystems (system:
let
pkgs = import nixpkgs { inherit system; };
devRequirements = with pkgs; [
gcc
gnumake
clippy
rustc
cargo
rustfmt
rust-analyzer
];
in
{
default = pkgs.mkShell {
RUST_BACKTRACE = 1;
# For cross compilation
NIXPKGS_ALLOW_UNSUPPORTED_SYSTEM = 1;
buildInputs = devRequirements;
packages = devRequirements;
};
}
);
};
}

@ -1,4 +1,4 @@
edition = "2021"
max_width = 89
max_width = 80
tab_spaces = 4
use_field_init_shorthand = true

@ -1,26 +0,0 @@
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

File diff suppressed because it is too large Load Diff

@ -1,12 +1,13 @@
#![allow(clippy::too_many_arguments)]
use std::env;
use xplr::cli::{self, Cli};
use xplr::cli::Cli;
use xplr::runner;
fn main() {
let cli = Cli::parse(env::args()).unwrap_or_else(|e| {
eprintln!("error: {e}");
eprintln!("error: {}", e);
std::process::exit(1);
});
@ -15,32 +16,22 @@ fn main() {
xplr [FLAG]... [OPTION]... [PATH] [SELECTION]..."###;
let flags = r###"
- Reads new-line (\n) separated paths from stdin
-- Denotes the end of command-line flags and options
--force-focus Focuses on the given <PATH>, even if it is a directory
-h, --help Prints help information
-m, --pipe-msg-in Helps safely passing messages to the active xplr
session, use %%, %s and %q as the placeholders
-M, --print-msg-in Like --pipe-msg-in, but prints the message instead of
passing to the active xplr session
--print-pwd-as-result Prints the present working directory when quitting
with `PrintResultAndQuit`
--read-only Enables read-only mode (config.general.read_only)
--read0 Reads paths separated using the null character (\0)
--write0 Prints paths separated using the null character (\0)
-0 --null Combines --read0 and --write0
-V, --version Prints version information"###;
- Reads paths from stdin
-- Denotes the end of command-line flags and options
--read-only Enables read-only mode (config.general.read_only)
-h, --help Prints help information
-V, --version Prints version information"###;
let options = r###"
-c, --config <PATH> Specifies a custom config file (default is
"$HOME/.config/xplr/init.lua")
-C, --extra-config <PATH>... Specifies extra config files to load
--on-load <MESSAGE>... Sends messages when xplr loads
--vroot <PATH> Treats the specified path as the virtual root"###;
-c, --config <PATH> Specifies a custom config file (default is
"$HOME/.config/xplr/init.lua")
-C, --extra-config <PATH>... Specifies extra config files to load
--on-load <MESSAGE>... Sends messages when xplr loads
--force-focus Focuses on the given <PATH>, even if directory."###;
let args = r###"
<PATH> Path to focus on, or enter if directory, (default is `.`)
<SELECTION>... Paths to select, requires <PATH> to be set explicitly"###;
<PATH> Path to focus on, or enter if directory, (default is `.`)
<SELECTION>... Paths to select, requires <PATH> to be set explicitely"###;
let help = format!(
"xplr {}\n{}\n{}\n\nUSAGE:{}\n\nFLAGS:{}\n\nOPTIONS:{}\n\nARGS:{}",
@ -54,28 +45,16 @@ fn main() {
);
let help = help.trim();
println!("{help}");
println!("{}", help);
} else if cli.version {
println!("xplr {}", xplr::app::VERSION);
} else if !cli.pipe_msg_in.is_empty() {
if let Err(err) = cli::pipe_msg_in(cli.pipe_msg_in) {
eprintln!("error: {err}");
std::process::exit(1);
}
} else if !cli.print_msg_in.is_empty() {
if let Err(err) = cli::print_msg_in(cli.print_msg_in) {
eprintln!("error: {err}");
std::process::exit(1);
}
} else {
match runner::from_cli(cli).and_then(|a| a.run()) {
Ok(Some(out)) => {
print!("{out}");
}
Ok(Some(out)) => print!("{}", out),
Ok(None) => {}
Err(err) => {
if !err.to_string().is_empty() {
eprintln!("error: {err}");
eprintln!("error: {}", err);
};
std::process::exit(1);
@ -148,42 +127,42 @@ mod tests {
.stderr("");
}
// TODO fix GitHub CI failures
// TODO: Fix running GitHub action
//
// #[test]
// fn test_cli_path_arg_valid() {
// Command::cargo_bin("xplr")
// .unwrap()
// .arg("src")
// .arg("/tmp")
// .arg("--on-load")
// .arg("PrintResultAndQuit")
// .assert()
// .success()
// .code(0)
// .stderr("");
//
// Command::cargo_bin("xplr")
// .unwrap()
// .arg("src")
// .arg("/tmp")
// .arg("--on-load")
// .arg("PrintResultAndQuit")
// .assert()
// .success()
// .code(0)
// .stderr("");
//
// Command::cargo_bin("xplr")
// .unwrap()
// .arg("--on-load")
// .arg("PrintResultAndQuit")
// .arg("--")
// .arg("src")
// .arg("/tmp")
// .assert()
// .success()
// .code(0)
// .stderr("");
// }
//
// #[test]
// fn test_cli_path_stdin_valid() {
// Command::cargo_bin("xplr")
@ -191,25 +170,10 @@ mod tests {
// .arg("-")
// .arg("--on-load")
// .arg("PrintResultAndQuit")
// .write_stdin("src\n")
// .write_stdin("/tmp\n")
// .assert()
// .success()
// .code(0)
// .stderr("");
// }
//
// #[test]
// fn test_on_load_yaml_parsing() {
// Command::cargo_bin("xplr")
// .unwrap()
// .arg("--on-load")
// .arg("Call: {command: touch, args: [foo]}")
// .arg("Quit")
// .assert()
// .success()
// .code(0)
// .stderr("");
//
// std::fs::remove_file("foo").unwrap();
// }
}

@ -1,81 +1,56 @@
use crate::{app, yaml};
use anyhow::{bail, Context, Result};
use app::ExternalMsg;
use path_absolutize::*;
use serde_json as json;
use std::fs::File;
use std::io::{BufRead, BufReader, Write};
use anyhow::{bail, Result};
use std::collections::VecDeque;
use std::env;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
use std::{env, fs};
use crate::app;
/// The arguments to pass
#[derive(Debug, Clone, Default)]
pub struct Cli {
pub bin: String,
pub version: bool,
pub help: bool,
pub read_only: bool,
pub force_focus: bool,
pub print_pwd_as_result: bool,
pub read0: bool,
pub write0: bool,
pub vroot: Option<PathBuf>,
pub config: Option<PathBuf>,
pub extra_config: Vec<PathBuf>,
pub on_load: Vec<app::ExternalMsg>,
pub pipe_msg_in: Vec<String>,
pub print_msg_in: Vec<String>,
pub paths: Vec<PathBuf>,
}
impl Cli {
fn read_path(arg: &str) -> Result<PathBuf> {
fn read_path(&mut self, arg: &str) -> Result<()> {
if arg.is_empty() {
bail!("empty string passed")
};
let path = PathBuf::from(arg).absolutize()?.to_path_buf();
let path = PathBuf::from(arg);
if path.exists() {
Ok(path)
self.paths.push(path);
Ok(())
} else {
bail!("path doesn't exist: {}", path.to_string_lossy())
bail!("path doesn't exist: {}", path.to_string_lossy().to_string())
}
}
/// Parse arguments from the command-line
pub fn parse(args: env::Args) -> Result<Self> {
let mut args: VecDeque<String> = args.skip(1).collect();
let mut cli = Self::default();
let mut args = args.peekable();
cli.bin = args
.next()
.map(which::which)
.context("failed to parse xplr binary path")?
.context("failed to find xplr binary path")?
.absolutize()?
.to_path_buf()
.to_string_lossy()
.to_string();
let mut flag_ends = false;
while let Some(arg) = args.next() {
while let Some(arg) = args.pop_front() {
if flag_ends {
cli.paths.push(Cli::read_path(&arg)?);
cli.read_path(&arg)?;
} else {
match arg.as_str() {
// Flags
"-" => {
let reader = BufReader::new(std::io::stdin());
if cli.read0 {
for path in reader.split(b'\0') {
cli.paths
.push(Cli::read_path(&String::from_utf8(path?)?)?);
}
} else {
for path in reader.lines() {
cli.paths.push(Cli::read_path(&path?)?);
}
};
for path in BufReader::new(std::io::stdin()).lines() {
cli.read_path(&path?)?;
}
}
"-h" | "--help" => {
@ -86,53 +61,36 @@ impl Cli {
cli.version = true;
}
"--read0" => {
cli.read0 = true;
}
"--write0" => {
cli.write0 = true;
}
"-0" | "--null" => {
cli.read0 = true;
cli.write0 = true;
}
"--" => {
flag_ends = true;
}
// Options
"-c" | "--config" => {
cli.config = Some(
args.next()
.map(|a| Cli::read_path(&a))
.with_context(|| format!("usage: xplr {arg} PATH"))??,
);
}
"--vroot" => {
cli.vroot = Some(
args.next()
.map(|a| Cli::read_path(&a))
.with_context(|| format!("usage: xplr {arg} PATH"))??,
);
cli.config = args.pop_front().map(PathBuf::from)
}
"-C" | "--extra-config" => {
while let Some(path) =
args.next_if(|path| !path.starts_with('-'))
{
cli.extra_config.push(Cli::read_path(&path)?);
while let Some(path) = args.pop_front() {
if path.starts_with('-') {
args.push_front(path);
break;
} else {
cli.extra_config.push(PathBuf::from(path));
}
}
}
"--read-only" => cli.read_only = true,
"--on-load" => {
while let Some(msg) = args.next_if(|msg| !msg.starts_with('-')) {
cli.on_load.push(yaml::from_str(&msg)?);
while let Some(msg) = args.pop_front() {
if msg.starts_with('-') {
args.push_front(msg);
break;
} else {
cli.on_load.push(serde_yaml::from_str(&msg)?);
}
}
}
@ -140,34 +98,9 @@ impl Cli {
cli.force_focus = true;
}
"--print-pwd-as-result" => {
cli.print_pwd_as_result = true;
}
"-m" | "--pipe-msg-in" => {
cli.pipe_msg_in.extend(args.by_ref());
if cli.pipe_msg_in.is_empty() {
bail!("usage: xplr {} FORMAT [ARGUMENT]...", arg)
}
}
"-M" | "--print-msg-in" => {
cli.print_msg_in.extend(args.by_ref());
if cli.print_msg_in.is_empty() {
bail!("usage: xplr {} FORMAT [ARGUMENT]...", arg)
}
}
// path
path => {
if path.starts_with('-') && !flag_ends {
bail!(
"invalid argument: {0:?}, try `-- {0:?}` or `--help`",
path
)
} else {
cli.paths.push(Cli::read_path(path)?);
}
cli.read_path(path)?;
}
}
}
@ -175,45 +108,3 @@ impl Cli {
Ok(cli)
}
}
pub fn pipe_msg_in(args: Vec<String>) -> Result<()> {
let mut msg = fmt_msg_in(args)?;
if let Ok(path) = std::env::var("XPLR_PIPE_MSG_IN") {
let delimiter = fs::read(&path)?
.first()
.cloned()
.context("failed to detect delimmiter")?;
msg.push(delimiter.into());
File::options()
.append(true)
.open(&path)?
.write_all(msg.as_bytes())?;
} else {
println!("{msg}");
};
Ok(())
}
pub fn print_msg_in(args: Vec<String>) -> Result<()> {
let msg = fmt_msg_in(args)?;
print!("{msg}");
Ok(())
}
fn fmt_msg_in(args: Vec<String>) -> Result<String> {
let msg = match jf::format(args.into_iter().map(Into::into)) {
Ok(msg) => msg,
Err(jf::Error::Jf(e)) => bail!("xplr -m: {e}"),
Err(jf::Error::Json(e)) => bail!("xplr -m: json: {e}"),
Err(jf::Error::Yaml(e)) => bail!("xplr -m: yaml: {e}"),
Err(jf::Error::Io(e)) => bail!("xplr -m: io: {e}"),
};
// validate
let _: ExternalMsg = json::from_str(&msg)?;
Ok(msg)
}

@ -1,224 +0,0 @@
// Things of the past, mostly bad decisions, which cannot erased, stays in this
// haunted module.
use crate::app;
use crate::lua;
use crate::ui::block;
use crate::ui::string_to_text;
use crate::ui::Constraint;
use crate::ui::ContentRendererArg;
use crate::ui::UI;
use serde::{Deserialize, Serialize};
use tui::layout::Constraint as TuiConstraint;
use tui::layout::Rect as TuiRect;
use tui::widgets::Cell;
use tui::widgets::List;
use tui::widgets::ListItem;
use tui::widgets::Paragraph;
use tui::widgets::Row;
use tui::widgets::Table;
use tui::Frame;
/// A cursed enum from crate::ui.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub enum ContentBody {
/// A paragraph to render
StaticParagraph { render: String },
/// A Lua function that returns a paragraph to render
DynamicParagraph { render: String },
/// List to render
StaticList { render: Vec<String> },
/// A Lua function that returns lines to render
DynamicList { render: String },
/// A table to render
StaticTable {
widths: Vec<Constraint>,
col_spacing: Option<u16>,
render: Vec<Vec<String>>,
},
/// A Lua function that returns a table to render
DynamicTable {
widths: Vec<Constraint>,
col_spacing: Option<u16>,
render: String,
},
}
/// A cursed struct from crate::ui.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct CustomContent {
pub title: Option<String>,
pub body: ContentBody,
}
/// A cursed function from crate::ui.
pub fn draw_custom_content(
ui: &mut UI,
f: &mut Frame,
layout_size: TuiRect,
app: &app::App,
content: CustomContent,
) {
let config = app.config.general.panel_ui.default.clone();
let title = content.title;
let body = content.body;
match body {
ContentBody::StaticParagraph { render } => {
let render = string_to_text(render);
let content = Paragraph::new(render).block(block(
config,
title.map(|t| format!(" {t} ")).unwrap_or_default(),
));
f.render_widget(content, layout_size);
}
ContentBody::DynamicParagraph { render } => {
let ctx = ContentRendererArg {
app: app.to_lua_ctx_light(),
layout_size: layout_size.into(),
screen_size: ui.screen_size.into(),
scrolltop: ui.scrolltop as u16,
};
let render = lua::serialize(ui.lua, &ctx)
.map(|arg| {
lua::call(ui.lua, &render, arg).unwrap_or_else(|e| format!("{e:?}"))
})
.unwrap_or_else(|e| e.to_string());
let render = string_to_text(render);
let content = Paragraph::new(render).block(block(
config,
title.map(|t| format!(" {t} ")).unwrap_or_default(),
));
f.render_widget(content, layout_size);
}
ContentBody::StaticList { render } => {
let items = render
.into_iter()
.map(string_to_text)
.map(ListItem::new)
.collect::<Vec<ListItem>>();
let content = List::new(items).block(block(
config,
title.map(|t| format!(" {t} ")).unwrap_or_default(),
));
f.render_widget(content, layout_size);
}
ContentBody::DynamicList { render } => {
let ctx = ContentRendererArg {
app: app.to_lua_ctx_light(),
layout_size: layout_size.into(),
screen_size: ui.screen_size.into(),
scrolltop: ui.scrolltop as u16,
};
let items = lua::serialize(ui.lua, &ctx)
.map(|arg| {
lua::call(ui.lua, &render, arg)
.unwrap_or_else(|e| vec![format!("{e:?}")])
})
.unwrap_or_else(|e| vec![e.to_string()])
.into_iter()
.map(string_to_text)
.map(ListItem::new)
.collect::<Vec<ListItem>>();
let content = List::new(items).block(block(
config,
title.map(|t| format!(" {t} ")).unwrap_or_default(),
));
f.render_widget(content, layout_size);
}
ContentBody::StaticTable {
widths,
col_spacing,
render,
} => {
let rows = render
.into_iter()
.map(|cols| {
Row::new(
cols.into_iter()
.map(string_to_text)
.map(Cell::from)
.collect::<Vec<Cell>>(),
)
})
.collect::<Vec<Row>>();
let widths = widths
.into_iter()
.map(|w| w.to_tui(ui.screen_size, layout_size))
.collect::<Vec<TuiConstraint>>();
let content = Table::new(rows, widths)
.column_spacing(col_spacing.unwrap_or(1))
.block(block(
config,
title.map(|t| format!(" {t} ")).unwrap_or_default(),
));
f.render_widget(content, layout_size);
}
ContentBody::DynamicTable {
widths,
col_spacing,
render,
} => {
let ctx = ContentRendererArg {
app: app.to_lua_ctx_light(),
layout_size: layout_size.into(),
screen_size: ui.screen_size.into(),
scrolltop: ui.scrolltop as u16,
};
let rows = lua::serialize(ui.lua, &ctx)
.map(|arg| {
lua::call(ui.lua, &render, arg)
.unwrap_or_else(|e| vec![vec![format!("{e:?}")]])
})
.unwrap_or_else(|e| vec![vec![e.to_string()]])
.into_iter()
.map(|cols| {
Row::new(
cols.into_iter()
.map(string_to_text)
.map(Cell::from)
.collect::<Vec<Cell>>(),
)
})
.collect::<Vec<Row>>();
let widths = widths
.into_iter()
.map(|w| w.to_tui(ui.screen_size, layout_size))
.collect::<Vec<TuiConstraint>>();
let mut content = Table::new(rows, &widths).block(block(
config,
title.map(|t| format!(" {t} ")).unwrap_or_default(),
));
if let Some(col_spacing) = col_spacing {
content = content.column_spacing(col_spacing);
};
f.render_widget(content, layout_size);
}
}
}

@ -3,10 +3,7 @@ use crate::app::HelpMenuLine;
use crate::app::NodeFilter;
use crate::app::NodeSorter;
use crate::app::NodeSorterApplicable;
use crate::node::Node;
use crate::search::SearchAlgorithm;
use crate::ui::Border;
use crate::ui::BorderType;
use crate::ui::Constraint;
use crate::ui::Layout;
use crate::ui::Style;
@ -16,7 +13,7 @@ use std::collections::BTreeMap;
use std::collections::HashMap;
use std::collections::HashSet;
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Action {
#[serde(default)]
@ -55,7 +52,7 @@ pub struct NodeTypeConfig {
impl NodeTypeConfig {
pub fn extend(mut self, other: &Self) -> Self {
self.style = self.style.extend(&other.style);
self.meta.extend(other.meta.clone());
self.meta.extend(other.meta.to_owned());
self
}
}
@ -82,41 +79,7 @@ pub struct NodeTypesConfig {
pub special: HashMap<String, NodeTypeConfig>,
}
impl NodeTypesConfig {
pub fn get(&self, node: &Node) -> NodeTypeConfig {
let mut node_type = if node.is_symlink {
self.symlink.clone()
} else if node.is_dir {
self.directory.clone()
} else {
self.file.clone()
};
let mut me = node.mime_essence.splitn(2, '/');
let mimetype: String = me.next().map(|s| s.into()).unwrap_or_default();
let mimesub: String = me.next().map(|s| s.into()).unwrap_or_default();
if let Some(conf) = self
.mime_essence
.get(&mimetype)
.and_then(|t| t.get(&mimesub).or_else(|| t.get("*")))
{
node_type = node_type.extend(conf);
}
if let (Some(conf), false) = (self.extension.get(&node.extension), node.is_dir) {
node_type = node_type.extend(conf);
}
if let Some(conf) = self.special.get(&node.relative_path) {
node_type = node_type.extend(conf);
}
node_type
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct UiConfig {
#[serde(default)]
@ -129,7 +92,7 @@ pub struct UiConfig {
pub style: Style,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct UiElement {
#[serde(default)]
@ -141,7 +104,7 @@ pub struct UiElement {
impl UiElement {
pub fn extend(mut self, other: &Self) -> Self {
self.format = other.format.clone().or(self.format);
self.format = other.format.to_owned().or(self.format);
self.style = self.style.extend(&other.style);
self
}
@ -182,23 +145,6 @@ pub struct TableConfig {
pub col_widths: Option<Vec<Constraint>>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SelectionConfig {
#[serde(default)]
pub item: UiElement,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SearchConfig {
#[serde(default)]
pub algorithm: SearchAlgorithm,
#[serde(default)]
pub unordered: bool,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LogsConfig {
@ -225,16 +171,6 @@ pub struct SortDirectionIdentifiersUi {
pub reverse: UiElement,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SearchDirectionIdentifiersUi {
#[serde(default)]
pub ordered: UiElement,
#[serde(default)]
pub unordered: UiElement,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SortAndFilterUi {
@ -252,12 +188,6 @@ pub struct SortAndFilterUi {
#[serde(default)]
pub filter_identifiers: HashMap<NodeFilter, UiElement>,
#[serde(default)]
pub search_direction_identifiers: SearchDirectionIdentifiersUi,
#[serde(default)]
pub search_identifiers: HashMap<SearchAlgorithm, UiElement>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
@ -285,9 +215,6 @@ pub struct PanelUi {
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct GeneralConfig {
#[serde(default)]
pub disable_debug_error_mode: bool,
#[serde(default)]
pub enable_mouse: bool,
@ -300,12 +227,6 @@ pub struct GeneralConfig {
#[serde(default)]
pub enable_recover_mode: bool,
#[serde(default)]
pub hide_remaps_in_help_menu: bool,
#[serde(default)]
pub enforce_bounded_index_navigation: bool,
#[serde(default)]
pub prompt: UiElement,
@ -315,12 +236,6 @@ pub struct GeneralConfig {
#[serde(default)]
pub table: TableConfig,
#[serde(default)]
pub selection: SelectionConfig,
#[serde(default)]
pub search: SearchConfig,
#[serde(default)]
pub default_ui: UiConfig,
@ -350,18 +265,9 @@ pub struct GeneralConfig {
#[serde(default)]
pub start_fifo: Option<String>,
#[serde(default)]
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, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct KeyBindings {
#[serde(default)]
@ -390,7 +296,7 @@ pub struct KeyBindings {
#[serde(default)]
pub default: Option<Action>,
// Checklist for adding new field:
// Checklist for adding field:
// - [ ] Update App::handle_key
// - [ ] Update KeyBindings::sanitized
// - [ ] Update Mode::help_menu
@ -407,37 +313,28 @@ impl KeyBindings {
.filter_map(|(k, a)| a.sanitized(read_only).map(|a| (k, a)))
.collect();
self.on_alphabet = self.on_alphabet.and_then(|a| a.sanitized(read_only));
self.on_number = self.on_number.and_then(|a| a.sanitized(read_only));
self.on_alphabet =
self.on_alphabet.and_then(|a| a.sanitized(read_only));
self.on_number =
self.on_number.and_then(|a| a.sanitized(read_only));
self.on_alphanumeric =
self.on_alphanumeric.and_then(|a| a.sanitized(read_only));
self.on_special_character = self
.on_special_character
.and_then(|a| a.sanitized(read_only));
self.on_character = self.on_character.and_then(|a| a.sanitized(read_only));
self.on_navigation = self.on_navigation.and_then(|a| a.sanitized(read_only));
self.on_function = self.on_function.and_then(|a| a.sanitized(read_only));
self.on_character =
self.on_character.and_then(|a| a.sanitized(read_only));
self.on_navigation =
self.on_navigation.and_then(|a| a.sanitized(read_only));
self.on_function =
self.on_function.and_then(|a| a.sanitized(read_only));
self.default = self.default.and_then(|a| a.sanitized(read_only));
};
self
}
pub fn extend(mut self, other: Self) -> Self {
self.on_key.extend(other.on_key);
self.on_alphabet = other.on_alphabet.or(self.on_alphabet);
self.on_number = other.on_number.or(self.on_number);
self.on_alphanumeric = other.on_alphanumeric.or(self.on_alphanumeric);
self.on_special_character =
other.on_special_character.or(self.on_special_character);
self.on_character = other.on_character.or(self.on_character);
self.on_navigation = other.on_navigation.or(self.on_navigation);
self.on_function = other.on_function.or(self.on_function);
self.default = other.default.or(self.default);
self
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Mode {
#[serde(default)]
@ -454,20 +351,11 @@ pub struct Mode {
#[serde(default)]
pub layout: Option<Layout>,
#[serde(default)]
pub prompt: Option<String>,
}
impl Mode {
pub fn sanitized(
mut self,
read_only: bool,
global_key_bindings: KeyBindings,
) -> Self {
self.key_bindings = global_key_bindings
.sanitized(read_only)
.extend(self.key_bindings.sanitized(read_only));
pub fn sanitized(mut self, read_only: bool) -> Self {
self.key_bindings = self.key_bindings.sanitized(read_only);
self
}
@ -489,32 +377,36 @@ impl Mode {
let lines = extra_help_lines
.unwrap_or_default()
.into_iter()
.chain(self.key_bindings.on_key.iter().filter_map(|(k, a)| {
let remaps = self
.key_bindings
.on_key
.iter()
.filter_map(|(rk, ra)| {
if rk == k {
None
} else if a == ra {
Some(rk.clone())
} else {
None
}
.chain(self.key_bindings.on_key.iter().filter_map(
|(k, a)| {
let remaps = self
.key_bindings
.on_key
.iter()
.filter_map(|(rk, ra)| {
if rk == k {
None
} else if a == ra {
Some(rk.clone())
} else {
None
}
})
.collect::<Vec<String>>();
a.help.clone().map(|h| {
HelpMenuLine::KeyMap(k.into(), remaps, h)
})
.collect::<Vec<String>>();
a.help
.clone()
.map(|h| HelpMenuLine::KeyMap(k.into(), remaps, h))
}))
},
))
.chain(
self.key_bindings
.on_alphabet
.iter()
.map(|a| ("[a-Z]", a.help.clone()))
.filter_map(|(k, mh)| {
mh.map(|h| HelpMenuLine::KeyMap(k.into(), vec![], h))
mh.map(|h| {
HelpMenuLine::KeyMap(k.into(), vec![], h)
})
}),
)
.chain(
@ -523,7 +415,9 @@ impl Mode {
.iter()
.map(|a| ("[0-9]", a.help.clone()))
.filter_map(|(k, mh)| {
mh.map(|h| HelpMenuLine::KeyMap(k.into(), vec![], h))
mh.map(|h| {
HelpMenuLine::KeyMap(k.into(), vec![], h)
})
}),
)
.chain(
@ -532,7 +426,9 @@ impl Mode {
.iter()
.map(|a| ("[0-Z]", a.help.clone()))
.filter_map(|(k, mh)| {
mh.map(|h| HelpMenuLine::KeyMap(k.into(), vec![], h))
mh.map(|h| {
HelpMenuLine::KeyMap(k.into(), vec![], h)
})
}),
)
.chain(
@ -541,7 +437,9 @@ impl Mode {
.iter()
.map(|a| ("[^0-Z]", a.help.clone()))
.filter_map(|(k, mh)| {
mh.map(|h| HelpMenuLine::KeyMap(k.into(), vec![], h))
mh.map(|h| {
HelpMenuLine::KeyMap(k.into(), vec![], h)
})
}),
)
.chain(
@ -550,7 +448,9 @@ impl Mode {
.iter()
.map(|a| ("[*]", a.help.clone()))
.filter_map(|(k, mh)| {
mh.map(|h| HelpMenuLine::KeyMap(k.into(), vec![], h))
mh.map(|h| {
HelpMenuLine::KeyMap(k.into(), vec![], h)
})
}),
)
.chain(
@ -559,7 +459,9 @@ impl Mode {
.iter()
.map(|a| ("[nav]", a.help.clone()))
.filter_map(|(k, mh)| {
mh.map(|h| HelpMenuLine::KeyMap(k.into(), vec![], h))
mh.map(|h| {
HelpMenuLine::KeyMap(k.into(), vec![], h)
})
}),
)
.chain(
@ -568,7 +470,9 @@ impl Mode {
.iter()
.map(|a| ("[f1-f12]", a.help.clone()))
.filter_map(|(k, mh)| {
mh.map(|h| HelpMenuLine::KeyMap(k.into(), vec![], h))
mh.map(|h| {
HelpMenuLine::KeyMap(k.into(), vec![], h)
})
}),
)
.chain(
@ -577,7 +481,9 @@ impl Mode {
.iter()
.map(|a| ("[default]", a.help.clone()))
.filter_map(|(k, mh)| {
mh.map(|h| HelpMenuLine::KeyMap(k.into(), vec![], h))
mh.map(|h| {
HelpMenuLine::KeyMap(k.into(), vec![], h)
})
}),
);
@ -605,11 +511,110 @@ impl Mode {
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct BuiltinModesConfig {
#[serde(default)]
pub default: Mode,
#[serde(default)]
pub recover: Mode,
#[serde(default)]
pub selection_ops: Mode,
#[serde(default)]
pub create: Mode,
#[serde(default)]
pub create_directory: Mode,
#[serde(default)]
pub create_file: Mode,
#[serde(default)]
pub number: Mode,
#[serde(default)]
pub go_to: Mode,
#[serde(default)]
pub rename: Mode,
#[serde(default)]
pub delete: Mode,
#[serde(default)]
pub action: Mode,
#[serde(default)]
pub search: Mode,
#[serde(default)]
pub filter: Mode,
#[serde(default)]
pub relative_path_does_contain: Mode,
#[serde(default)]
pub relative_path_does_not_contain: Mode,
#[serde(default)]
pub sort: Mode,
#[serde(default)]
pub switch_layout: Mode,
#[serde(default)]
pub quit: Mode,
}
impl BuiltinModesConfig {
pub fn get(&self, name: &str) -> Option<&Mode> {
match name {
"default" => Some(&self.default),
"recover" => Some(&self.recover),
"selection ops" => Some(&self.selection_ops),
"selection_ops" => Some(&self.selection_ops),
"create" => Some(&self.create),
"create file" => Some(&self.create_file),
"create_file" => Some(&self.create_file),
"create directory" => Some(&self.create_directory),
"create_directory" => Some(&self.create_directory),
"number" => Some(&self.number),
"go to" => Some(&self.go_to),
"go_to" => Some(&self.go_to),
"rename" => Some(&self.rename),
"delete" => Some(&self.delete),
"action" => Some(&self.action),
"search" => Some(&self.search),
"sort" => Some(&self.sort),
"filter" => Some(&self.filter),
"relative_path_does_contain" => {
Some(&self.relative_path_does_contain)
}
"relative path does contain" => {
Some(&self.relative_path_does_contain)
}
"relative_path_does_not_contain" => {
Some(&self.relative_path_does_not_contain)
}
"relative path does not contain" => {
Some(&self.relative_path_does_not_contain)
}
"switch layout" => Some(&self.switch_layout),
"switch_layout" => Some(&self.switch_layout),
"quit" => Some(&self.quit),
_ => None,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ModesConfig {
#[serde(default)]
pub builtin: HashMap<String, Mode>,
pub builtin: BuiltinModesConfig,
#[serde(default)]
pub custom: HashMap<String, Mode>,
@ -621,41 +626,64 @@ impl ModesConfig {
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct PanelUiConfig {
#[serde(default)]
pub title: UiElement,
#[serde(default)]
pub style: Style,
#[serde(default)]
pub borders: Option<IndexSet<Border>>,
#[serde(default)]
pub border_type: Option<BorderType>,
#[serde(default)]
pub border_style: Style,
pub style: Style,
}
impl PanelUiConfig {
pub fn extend(mut self, other: &Self) -> Self {
self.title = self.title.extend(&other.title);
self.borders = other.borders.to_owned().or(self.borders);
self.style = self.style.extend(&other.style);
self.borders = other.borders.clone().or(self.borders);
self.border_type = other.border_type.or(self.border_type);
self.border_style = self.border_style.extend(&other.border_style);
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct BuiltinLayoutsConfig {
#[serde(default)]
pub default: Layout,
#[serde(default)]
pub no_help: Layout,
#[serde(default)]
pub no_selection: Layout,
#[serde(default)]
pub no_help_no_selection: Layout,
}
impl BuiltinLayoutsConfig {
pub fn get(&self, name: &str) -> Option<&Layout> {
match name {
"default" => Some(&self.default),
"no_help" => Some(&self.no_help),
"no help" => Some(&self.no_help),
"no_selection" => Some(&self.no_selection),
"no selection" => Some(&self.no_selection),
"no_help_no_selection" => Some(&self.no_help_no_selection),
"no help no selection" => Some(&self.no_help_no_selection),
_ => None,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LayoutsConfig {
#[serde(default)]
pub builtin: HashMap<String, Layout>,
pub builtin: BuiltinLayoutsConfig,
#[serde(default)]
pub custom: HashMap<String, Layout>,
@ -678,52 +706,15 @@ impl LayoutsConfig {
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Config {
#[serde(default)]
pub general: GeneralConfig,
#[serde(default)]
pub node_types: NodeTypesConfig,
#[serde(default)]
pub layouts: LayoutsConfig,
#[serde(default)]
pub modes: ModesConfig,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Hooks {
#[serde(default)]
pub on_load: Vec<ExternalMsg>,
#[serde(default)]
pub on_directory_change: Vec<ExternalMsg>,
#[serde(default)]
pub on_focus_change: Vec<ExternalMsg>,
#[serde(default)]
pub on_mode_switch: Vec<ExternalMsg>,
pub general: GeneralConfig,
#[serde(default)]
pub on_layout_switch: Vec<ExternalMsg>,
pub node_types: NodeTypesConfig,
#[serde(default)]
pub on_selection_change: Vec<ExternalMsg>,
// TODO After cleanup or Runner::run
// #[serde(default)]
// pub before_quit: Vec<ExternalMsg>,
}
impl Hooks {
pub fn extend(mut self, other: Self) -> Self {
self.on_load.extend(other.on_load);
self.on_directory_change.extend(other.on_directory_change);
self.on_focus_change.extend(other.on_focus_change);
self.on_mode_switch.extend(other.on_mode_switch);
self.on_layout_switch.extend(other.on_layout_switch);
self.on_selection_change.extend(other.on_selection_change);
self
}
pub modes: ModesConfig,
}

@ -1,37 +0,0 @@
use crate::node::Node;
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct DirectoryBuffer {
pub parent: String,
pub nodes: Vec<Node>,
pub total: usize,
pub focus: usize,
#[serde(skip, default = "now")]
pub explored_at: OffsetDateTime,
}
impl DirectoryBuffer {
pub fn new(parent: String, nodes: Vec<Node>, focus: usize) -> Self {
let total = nodes.len();
Self {
parent,
nodes,
total,
focus,
explored_at: now(),
}
}
pub fn focused_node(&self) -> Option<&Node> {
self.nodes.get(self.focus)
}
}
fn now() -> OffsetDateTime {
OffsetDateTime::now_local()
.ok()
.unwrap_or_else(OffsetDateTime::now_utc)
}

@ -1,26 +0,0 @@
use std::{env, path::PathBuf};
use lazy_static::lazy_static;
use xdg::BaseDirectories;
lazy_static! {
pub static ref BASE_DIRS: Option<BaseDirectories> = BaseDirectories::new().ok();
}
pub fn home_dir() -> Option<PathBuf> {
home::home_dir()
}
pub fn config_dir() -> Option<PathBuf> {
BASE_DIRS.as_ref().map(|base| base.get_config_home())
}
pub fn runtime_dir() -> PathBuf {
let Some(dir) = BASE_DIRS
.as_ref()
.and_then(|base| base.get_runtime_directory().ok())
else {
return env::temp_dir();
};
dir.clone()
}

@ -49,7 +49,8 @@ fn keep_reading(
if rx_stopper.try_recv().unwrap_or(false) {
tx_ack.send(()).unwrap();
break;
} else if event::poll(std::time::Duration::from_millis(150)).unwrap_or_default()
} else if event::poll(std::time::Duration::from_millis(150))
.unwrap_or_default()
{
// NOTE: The poll timeout need to stay low, else spawning sub subshell
// and start typing immediately will cause panic.
@ -81,14 +82,6 @@ fn keep_reading(
tx_msg_in.send(Task::new(msg, None)).map_err(Error::new)
}
Ok(Event::FocusGained) => Ok(()),
Ok(Event::FocusLost) => Ok(()),
Ok(Event::Paste(text)) => {
let msg = MsgIn::External(ExternalMsg::BufferInput(text));
tx_msg_in.send(Task::new(msg, None)).map_err(Error::new)
}
Err(e) => Err(Error::new(e)),
};

@ -1,5 +1,6 @@
use crate::app::{
DirectoryBuffer, ExplorerConfig, ExternalMsg, InternalMsg, MsgIn, Node, Task,
DirectoryBuffer, ExplorerConfig, ExternalMsg, InternalMsg, MsgIn, Node,
Task,
};
use anyhow::{Error, Result};
use std::fs;
@ -7,9 +8,14 @@ use std::path::PathBuf;
use std::sync::mpsc::Sender;
use std::thread;
pub fn explore(parent: &PathBuf, config: &ExplorerConfig) -> Result<Vec<Node>> {
let dirs = fs::read_dir(parent)?;
let nodes = dirs
pub(crate) fn explore_sync(
config: ExplorerConfig,
parent: PathBuf,
focused_path: Option<PathBuf>,
fallback_focus: usize,
) -> Result<DirectoryBuffer> {
let dirs = fs::read_dir(&parent)?;
let mut nodes = dirs
.filter_map(|d| {
d.ok().map(|e| {
e.path()
@ -19,44 +25,19 @@ pub fn explore(parent: &PathBuf, config: &ExplorerConfig) -> Result<Vec<Node>> {
})
})
.map(|name| Node::new(parent.to_string_lossy().to_string(), name))
.filter(|n| config.filter(n));
let mut nodes = if let Some(searcher) = config.searcher.as_ref() {
searcher.search(nodes)
} else {
nodes.collect()
};
let is_ordered_search = config
.searcher
.as_ref()
.map(|s| !s.unordered)
.unwrap_or(false);
.filter(|n| config.filter(n))
.collect::<Vec<Node>>();
if !is_ordered_search {
nodes.sort_by(|a, b| config.sort(a, b));
}
Ok(nodes)
}
nodes.sort_by(|a, b| config.sort(a, b));
pub(crate) fn explore_sync(
config: ExplorerConfig,
parent: PathBuf,
focused_path: Option<PathBuf>,
fallback_focus: usize,
) -> Result<DirectoryBuffer> {
let nodes = explore(&parent, &config)?;
let focus_index = if config.searcher.is_some() {
0
} else if let Some(focus) = focused_path {
let focus_index = if let Some(focus) = focused_path {
let focus_str = focus.to_string_lossy().to_string();
nodes
.iter()
.enumerate()
.find(|(_, n)| n.relative_path == focus_str)
.map(|(i, _)| i)
.unwrap_or_else(|| fallback_focus.min(nodes.len().saturating_sub(1)))
.unwrap_or_else(|| fallback_focus.min(nodes.len().max(1) - 1))
} else {
0
};
@ -128,7 +109,7 @@ mod tests {
#[test]
fn test_explore_sync() {
let config = ExplorerConfig::default();
let path = PathBuf::from(".");
let path = PathBuf::from("/bin");
let r = explore_sync(config, path, None, 0);
@ -166,7 +147,7 @@ mod tests {
#[test]
fn test_explore_async() {
let config = ExplorerConfig::default();
let path = PathBuf::from(".");
let path = PathBuf::from("/usr/bin");
let (tx_msg_in, rx_msg_in) = mpsc::channel();
explore_async(config, path, None, 0, tx_msg_in.clone());
@ -174,7 +155,7 @@ mod tests {
let task = rx_msg_in.recv().unwrap();
let dbuf = extract_dirbuf_from_msg(task.msg);
assert_eq!(dbuf.parent, ".");
assert_eq!(dbuf.parent, "/usr/bin");
drop(tx_msg_in);
assert!(rx_msg_in.recv().is_err());

File diff suppressed because it is too large Load Diff

@ -18,18 +18,6 @@ pub enum Key {
F10,
F11,
F12,
F13,
F14,
F15,
F16,
F17,
F18,
F19,
F20,
F21,
F22,
F23,
F24,
Num0,
Num1,
@ -209,13 +197,13 @@ impl std::fmt::Display for Key {
_ => c.to_string(),
})
.unwrap_or_else(|| {
serde_yaml::to_value(self)
serde_yaml::to_value(&self)
.ok()
.and_then(|v| v.as_str().map(|v| v.to_string()))
.unwrap_or_default()
});
write!(f, "{key_str}")
write!(f, "{}", key_str)
}
}
@ -225,20 +213,19 @@ impl Key {
use Key::*;
match self {
Backspace | CtrlH => Some(DeletePreviousCharacter),
Backspace => Some(DeletePreviousCharacter),
Delete => Some(DeleteNextCharacter),
Tab => Some(InsertCharacter('\t')),
Space => Some(InsertCharacter(' ')),
Left | CtrlB => Some(GoToPreviousCharacter),
Left => Some(GoToPreviousCharacter),
CtrlLeft => Some(GoToPreviousWord),
Right | CtrlF => Some(GoToNextCharacter),
Right => Some(GoToNextCharacter),
CtrlRight => Some(GoToNextWord),
CtrlU => Some(DeleteLine),
CtrlW => Some(DeletePreviousWord),
CtrlDelete => Some(DeleteNextWord),
CtrlA | Home => Some(GoToStart),
CtrlE | End => Some(GoToEnd),
CtrlK => Some(DeleteTillEnd),
CtrlA => Some(GoToStart),
CtrlE => Some(GoToEnd),
key => key.to_char().map(InsertCharacter),
}
}
@ -351,19 +338,7 @@ impl Key {
KeyCode::F(9) => Key::F9,
KeyCode::F(10) => Key::F10,
KeyCode::F(11) => Key::F11,
KeyCode::F(13) => Key::F13,
KeyCode::F(12) => Key::F13,
KeyCode::F(14) => Key::F14,
KeyCode::F(15) => Key::F15,
KeyCode::F(16) => Key::F16,
KeyCode::F(17) => Key::F17,
KeyCode::F(18) => Key::F18,
KeyCode::F(19) => Key::F19,
KeyCode::F(20) => Key::F20,
KeyCode::F(21) => Key::F21,
KeyCode::F(22) => Key::F22,
KeyCode::F(23) => Key::F23,
KeyCode::F(24) => Key::F24,
KeyCode::F(12) => Key::F12,
KeyCode::Backspace => Key::Backspace,
KeyCode::Left => Key::Left,
@ -647,7 +622,7 @@ impl Key {
Self::ShiftZ => Some('Z'),
Self::Space => Some(' '),
Self::Special(c) => Some(*c),
Self::Special(c) => Some(c.to_owned()),
_ => None,
}
@ -731,6 +706,12 @@ impl From<char> for Key {
}
}
impl From<String> for Key {
fn from(string: String) -> Self {
string.into()
}
}
impl From<&str> for Key {
fn from(string: &str) -> Self {
if string.len() == 1 {
@ -774,7 +755,6 @@ pub enum InputOperation {
DeletePreviousWord,
DeleteNextWord,
DeleteLine,
DeleteTillEnd,
}
impl Into<InputRequest> for InputOperation {
@ -793,7 +773,6 @@ impl Into<InputRequest> for InputOperation {
Self::DeletePreviousWord => InputRequest::DeletePrevWord,
Self::DeleteNextWord => InputRequest::DeleteNextWord,
Self::DeleteLine => InputRequest::DeleteLine,
Self::DeleteTillEnd => InputRequest::DeleteTillEnd,
}
}
}

@ -4,24 +4,16 @@
pub mod app;
pub mod cli;
pub mod compat;
pub mod config;
pub mod directory_buffer;
pub mod dirs;
pub mod event_reader;
pub mod explorer;
pub mod input;
pub mod lua;
pub mod msg;
pub mod node;
pub mod path;
pub mod permissions;
pub mod pipe;
pub mod pipe_reader;
pub mod pwd_watcher;
pub mod runner;
pub mod search;
pub mod ui;
pub mod yaml;
#[cfg(test)]
mod tests {

@ -1,43 +1,29 @@
use crate::app::VERSION;
use crate::config::Config;
use crate::config::Hooks;
use anyhow::bail;
use anyhow::Error;
use anyhow::Result;
use mlua::Lua;
use mlua::LuaSerdeExt;
use mlua::SerializeOptions;
use serde::de::DeserializeOwned;
use serde::Serialize;
use serde::Deserialize;
use std::fs;
pub mod util;
const DEFAULT_LUA_SCRIPT: &str = include_str!("../init.lua");
const DEFAULT_LUA_SCRIPT: &str = include_str!("init.lua");
const UPGRADE_GUIDE_LINK: &str = "https://xplr.dev/en/upgrade-guide.html";
pub fn serialize<'lua, T: Serialize + Sized>(
lua: &'lua mlua::Lua,
value: &T,
) -> Result<mlua::Value<'lua>> {
lua.to_value_with(value, SerializeOptions::new().serialize_none_to_null(false))
.map_err(Error::from)
}
fn parse_version(version: &str) -> Result<(u16, u16, u16, Option<u16>)> {
let mut configv = version.split('.');
let major = configv.next().unwrap_or_default().parse::<u16>()?;
let minor = configv.next().unwrap_or_default().parse::<u16>()?;
let patch = configv
let bugfix = configv
.next()
.and_then(|s| s.split('-').next())
.unwrap_or_default()
.parse::<u16>()?;
let pre = configv.next().unwrap_or_default().parse::<u16>().ok();
let beta = configv.next().unwrap_or_default().parse::<u16>().ok();
Ok((major, minor, patch, pre))
Ok((major, minor, bugfix, beta))
}
/// Check the config version and notify users.
@ -46,7 +32,11 @@ pub fn check_version(version: &str, path: &str) -> Result<()> {
let (rmajor, rminor, rbugfix, rbeta) = parse_version(VERSION)?;
let (smajor, sminor, sbugfix, sbeta) = parse_version(version)?;
if rmajor == smajor && rminor == sminor && rbugfix >= sbugfix && rbeta == sbeta {
if rmajor == smajor
&& rminor == sminor
&& rbugfix >= sbugfix
&& rbeta == sbeta
{
Ok(())
} else {
bail!(
@ -59,16 +49,37 @@ pub fn check_version(version: &str, path: &str) -> Result<()> {
}
}
fn resolve_fn_recursive<'lua, 'a>(
table: &mlua::Table<'lua>,
mut path: impl Iterator<Item = &'a str>,
) -> Result<mlua::Function<'lua>> {
if let Some(nxt) = path.next() {
match table.get(nxt)? {
mlua::Value::Table(t) => resolve_fn_recursive(&t, path),
mlua::Value::Function(f) => Ok(f),
t => bail!("{:?} is not a function", t),
}
} else {
bail!("Invalid path")
}
}
/// This function resolves paths like `builtin.func_foo`, `custom.func_bar` into lua functions.
pub fn resolve_fn<'lua>(
globals: &mlua::Table<'lua>,
path: &str,
) -> Result<mlua::Function<'lua>> {
let path = format!("xplr.fn.{}", path);
resolve_fn_recursive(globals, path.split('.'))
}
/// Used to initialize Lua globals
pub fn init(lua: &Lua) -> Result<(Config, Option<Hooks>)> {
pub fn init(lua: &Lua) -> Result<Config> {
let config = Config::default();
let globals = lua.globals();
let util = util::create_table(lua)?;
let lua_xplr = lua.create_table()?;
lua_xplr.set("config", serialize(lua, &config)?)?;
lua_xplr.set("util", util)?;
lua_xplr.set("config", lua.to_value(&config)?)?;
let lua_xplr_fn = lua.create_table()?;
let lua_xplr_fn_builtin = lua.create_table()?;
@ -79,73 +90,43 @@ pub fn init(lua: &Lua) -> Result<(Config, Option<Hooks>)> {
lua_xplr.set("fn", lua_xplr_fn)?;
globals.set("xplr", lua_xplr)?;
let hooks: Option<Hooks> = lua
.load(DEFAULT_LUA_SCRIPT)
.set_name("xplr init")
.call(())
.and_then(|v| lua.from_value(v))?;
lua.load(DEFAULT_LUA_SCRIPT).set_name("init")?.exec()?;
let lua_xplr: mlua::Table = globals.get("xplr")?;
let config: Config = lua.from_value(lua_xplr.get("config")?)?;
Ok((config, hooks))
Ok(config)
}
/// Used to extend Lua globals
pub fn extend(lua: &Lua, path: &str) -> Result<(Config, Option<Hooks>)> {
pub fn extend(lua: &Lua, path: &str) -> Result<Config> {
let globals = lua.globals();
let script = fs::read_to_string(path)?;
let hooks: Option<Hooks> = lua
.load(&script)
.set_name(path)
.call(())
.and_then(|v| lua.from_value(v))?;
lua.load(&script).set_name("init")?.exec()?;
let version: String = match globals.get("version").and_then(|v| lua.from_value(v)) {
Ok(v) => v,
Err(_) => bail!("'version' must be defined globally in {}", path),
};
let version: String =
match globals.get("version").and_then(|v| lua.from_value(v)) {
Ok(v) => v,
Err(_) => bail!("'version' must be defined globally in {}", path),
};
check_version(&version, path)?;
let lua_xplr: mlua::Table = globals.get("xplr")?;
let config: Config = lua.from_value(lua_xplr.get("config")?)?;
Ok((config, hooks))
}
fn resolve_fn_recursive<'lua, 'a>(
table: &mlua::Table<'lua>,
mut path: impl Iterator<Item = &'a str>,
) -> Result<mlua::Function<'lua>> {
if let Some(nxt) = path.next() {
match table.get(nxt)? {
mlua::Value::Table(t) => resolve_fn_recursive(&t, path),
mlua::Value::Function(f) => Ok(f),
t => bail!("{:?} is not a function", t),
}
} else {
bail!("Invalid path")
}
}
/// This function resolves paths like `builtin.func_foo`, `custom.func_bar` into lua functions.
pub fn resolve_fn<'lua>(
globals: &mlua::Table<'lua>,
path: &str,
) -> Result<mlua::Function<'lua>> {
resolve_fn_recursive(globals, path.split('.'))
Ok(config)
}
pub fn call<'lua, R: DeserializeOwned>(
/// Used to call lua functions.
pub fn call<'lua, R: Deserialize<'lua>>(
lua: &'lua Lua,
func: &str,
arg: mlua::Value<'lua>,
args: mlua::Value<'lua>,
) -> Result<R> {
let func = format!("xplr.fn.{func}");
let func = resolve_fn(&lua.globals(), &func)?;
let res: mlua::Value = func.call(arg)?;
let func = resolve_fn(&lua.globals(), func)?;
let res: mlua::Value = func.call(args)?;
let res: R = lua.from_value(res)?;
Ok(res)
}
@ -160,24 +141,24 @@ mod tests {
assert!(check_version(VERSION, "foo path").is_ok());
// Current release if OK
assert!(check_version("0.21.9", "foo path").is_ok());
assert!(check_version("0.16.2", "foo path").is_ok());
// Prev major release is ERR
// - Not yet
// Prev minor release is ERR (Change when we get to v1)
assert!(check_version("0.20.9", "foo path").is_err());
assert!(check_version("0.15.2", "foo path").is_err());
// Prev bugfix release is OK
assert!(check_version("0.21.8", "foo path").is_ok());
assert!(check_version("0.16.1", "foo path").is_ok());
// Next major release is ERR
assert!(check_version("1.20.9", "foo path").is_err());
assert!(check_version("1.16.2", "foo path").is_err());
// Next minor release is ERR
assert!(check_version("0.22.9", "foo path").is_err());
assert!(check_version("0.17.2", "foo path").is_err());
// Next bugfix release is ERR (Change when we get to v1)
assert!(check_version("0.21.10", "foo path").is_err());
assert!(check_version("0.16.3", "foo path").is_err());
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save