Introduction

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

Features

Hackable

xplr is built with configurability in mind. So it allows you to perform a vast set of operations and make it behave just the way you want.

A few things you can do with the xplr configuration

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. I currently measure the most commonly used operations 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 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.
  • Different quit options:
    • Quit with success without any output (q).
    • Quit with success and the result printed on stdout (enter).
    • Quit with success and the present working directory printed on stdout (: q p).
    • Quit with success and the path under focus printed on stdout (: q f).
    • Quit with success and the selection printed on stdout (: q s).
    • Quit with failure (ctrl-c).

Quickstart

Nice to you have here! Let's quickly start our xplr journey with the following steps:

Install

You can install xplr using one of the following ways. Each has their own advantages and limitations.

For example, the Direct Download, From crates.io, and Build From Source methods allow the users to install the latest possible 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.

Community Maintained Repositories

xplr can be installed from one of the following community maintained repositories:

packaging status

Arch Linux

Official Community Repo

sudo pacman -S xplr

AUR

Binary version:

paru -S xplr-bin

Git version:

paru -S xplr-git

Void Linux

void-templates by shubham

Nix(OS)

Nixpkgs

nix-env -f https://github.com/NixOS/nixpkgs/tarball/master -iA xplr

macOS

MacPorts

sudo port selfupdate
sudo port install xplr

Homebrew

Stable branch:

brew install xplr

HEAD branch:

brew install --head xplr

FreeBSD

ports

pkg install xplr

Or

cd /usr/ports/misc/xplr
make install clean

NetBSD

pkgsrc

pkgin install xplr

Or

cd /usr/pkgsrc/sysutils/xplr
make install

Direct Download

One can directly download the standalone binary from the releases.

Currently, the following options are available for direct download:

Command-line instructions:

platform="linux"  # or "macos"

# Download
wget https://github.com/sayanarijit/xplr/releases/latest/download/xplr-$platform.tar.gz

# Extract
tar xzvf xplr-$platform.tar.gz

# Place in $PATH
sudo mv xplr /usr/local/bin/

From crates.io

Prerequisites:

Command-line instructions:

cargo install --force xplr

Build From Source

Prerequisites:

Command-line instructions:

# Clone the repository
git clone https://github.com/sayanarijit/xplr.git
cd xplr

# Build
cargo build --release --bin xplr

# Place in $PATH
sudo cp target/release/xplr /usr/local/bin/

Android

Termux

xplr-termuxfd3c398d3cf4bcbc.md.jpg

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

    pkg install rustc cargo make
    
  • Install xplr

    cargo install --force xplr
    
  • Setup storage

    termux-setup-storage
    
  • Setup config and runtime dir

    export XDG_CONFIG_HOME="$PWD/storage/.config"
    export XDG_RUNTIME_DIR="$PWD/storage/run"
    
    mkdir -p "$XDG_CONFIG_HOME" "$XDG_RUNTIME_DIR"
    
  • Run

    ~/.cargo/bin/xplr
    

Post Install

Once installed, use the following steps to setup and run xplr.

Create the customizable config file

mkdir -p ~/.config/xplr

version="$(xplr | grep ^version: | cut -d' ' -f 2)"

# When the app loads, press `#`

echo version = '"'${version:?}'"' > ~/.config/xplr/init.lua

Then copy from here 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. Also, the default configuration is meant to be overwritten to suit your workflow.

Run

xplr

Configuration

xplr can be configured using Lua via a special file named init.lua (example), which can be placed in ~/.config/xplr/ (user specific) or /etc/xplr/ (global) depending on the use case.

When a user specific configuration is available, the global configuration file will be ignored.

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.

How Config Is Loaded

When xplr loads, it first executes the built-in init.lua to set the default values, which is then overwritten by another config file, if found using the following lookup order:

--config /path/to/init.lua > ~/.config/xplr/init.lua > /etc/xplr/init.lua

config

The xplr configuration, exposed as xplr.config Lua API contains the following fields:

General Config

This configuration is exposed via the xplr.config.general API. It contains the following fields:

enable_mouse

Type: boolean

Set it to true enable scrolling using mouse.

show_hidden

Type: boolean

Set it to true to show hidden files.

read_only

Type: boolean

Set it to true to use only a subset of selected operations that forbids executing commands or performing write operations on the file-system.

disable_recover_mode

Type: boolean

Set it to true when the special recover mode gets too annoying to appreciate the good intentions. When enabled, typing the wrong keys won't result in any action.

cursor.format

Type: nullable string

This is the shape of the cursor visible when the input buffer contains some string.

cursor.style

Type: Style

Style of the cursor.

initial_layout

Type: string

The name of one of the layout to use when xplr loads.

initial_mode

Type: string

The name of one of the mode to use when xplr loads.

initial_sorting

Type: list of Node Sorter Applicable

Initial group if sorters applied to the nodes list in the table.

table.style

Type: Style

Default style of the table.

table.col_spacing

Type: nullable integer

Default spacing of the columns in the table.

table.col_widths

Type: nullable list of Constraint

Width of each column in the table.

table.header.height

Type: nullable integer

Height of the table header.

table.header.style

Type: Style

Style of table header.

table.header.cols

Type: List of column configuration

Each column config contains format field (string) and style field (Style), that define the content and style of header.

table.row.height

Type: nullable integer

Height of each row in the table.

table.row.style

Type: Style

Style of table rows.

table.row.cols

Type: List of column configuration

Each column config contains format field (string) and style field (Style).

However, unlike table.header.cols, the format field here points to a column renderer function.

table.tree

Type: List of tree configuration

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.

Each item requires the format field which is a string, and the style field, which is the Style object.

Example:

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 } },
}

TODO: Continue documentation.

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.

The modes can be configured using the xplr.config.modes Lua API.

It contains the following fields:

builtin

Type: mapping of string and Mode

This is exposed by the xplr.config.modes.builtin API.

xplr by default provides the following builtin modes:

  • 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

Visit the Default Key Bindings to see what each mode does.

custom

Type: mapping of string and Mode

This is exposed by the xplr.config.modes.custom API.

It allows the users to define custom modes.

Example:

xplr.config.modes.custom.example = {
  name = "example",
  key_bindings = {
    on_key = {
      enter = {
        help = "default mode",
        messages = {
          "PopMode",
          { SwitchModeBuiltin = "default" }
        }
      }
    }
  }
}

xplr.config.general.initial_mode = "example"

-- when you load xplr, you should be in the "example" mode,
-- pressing "enter" should take you to the "default" mode.

Mode

A mode contains the following information:

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

The key bindings available in that mode.

Key Bindings

Key bindings define how each keyboard input will be handled in a specific mode.

See the default key bindings for example.

Key bindings contains the following information:

on_key

Type: mapping of Key to nullable Action

Defines what to do when a specific key is pressed.

on_alphabet

Type: nullable Action

An action to perform if the keyboard input is an alphabet and is not mapped via the on_key field.

on_number

Type: nullable Action

An action to perform if the keyboard input is a number and is not mapped via the on_key field.

on_special_character

Type: nullable Action

An action to perform if the keyboard input is a special character and is not mapped via the on_key field.

default

Type: nullable Action

Default action to perform in case of a keyboard input not mapped via any of the on_key, on_alphabet, on_number or on_special_character field.

Key

A key can be one of the following:

  • 0, 1, ... 9
  • a, b, ... z
  • A, B, ... Z
  • f1, f2, ... f12
  • ctrl-a, ctrl-b, ... ctrl-z
  • alt-a, alt-b, ... alt-z
  • backspace
  • left
  • right
  • up
  • down
  • home
  • end
  • page-up
  • page-down
  • back-tab
  • delete
  • insert
  • enter
  • tab
  • esc

And finally, the special characters - including space (" ").

Action

An action contains the following information:

help

Type: nullable string

Description of what it does. If unspecified, it will be excluded from the help menu.

messages

Type: A list of Message to send.

The list of messages to send when a key is pressed.

Tutorial: Adding a New Mode

Assuming xplr is installed and setup, let's add our own mode to integrate xplr with fzf.

We'll call it fzxplr mode.

First, let's add a custom mode called fzxplr, and map the key F to an action that will call fzf to search and focus on a file or enter into a directory.

xplr.config.modes.custom.fzxplr = {
  name = "fzxplr",
  key_bindings = {
    on_key = {
      F = {
        help = "search",
        messages = {
          {
            BashExec = [===[
            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",
        },
      },
    },
    default = {
      messages = {
        "PopMode",
      },
    },
  },
}

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, $XPLR_PIPE_MSG_IN, $XPLR_PIPE_DIRECTORY_NODES_OUT are environment variables exported by xplr before executing the command. They contain the path to the input and output 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.

xplr.config.modes.builtin.default.key_bindings.on_key["F"] = {
  help = "fzf mode",
  messages = {
    { SwitchModeCustom = "fzxplr" },
  },
}

Now let's try out the new xplr-fzf integration.

xplr-fzf.gif


Visit Awesome Plugins for more integration options.

Message

You can think of xplr as a server. Just like web servers listen to HTTP requests, xplr listens to messages.

You can send these messages to an xplr session in the following ways:

Format

To send messages using the key bindings or Lua function calls, messages are represented in Lua syntax. For example:

  • "Quit"
  • { FocusPath = "/path/to/file" }
  • { Call = { command = "bash", args = { "-c", "read -p test" } } }

However, to send messages using the input pipe, they need to be represented using YAML (or JSON) syntax. For example:

  • Quit
  • FocusPath: "/path/to/file"
  • Call: { command: bash, args: ["-c", "read -p test"] }

Full List of Messages

"ExplorePwd"

YAML: ExplorePwd

Explore the present working directory and register the filtered nodes. This operation is expensive. So, try to avoid using it too often.

"ExplorePwdAsync"

YAML: ExplorePwdAsync

Explore the present working directory and register the filtered nodes asynchronously. This operation happens asynchronously. That means, the xplr directory buffers won't be updated immediately. Hence, it needs to be used with care and probably with special checks in place. To explore $PWD synchronously, use ExplorePwd instead.

"ExploreParentsAsync"

YAML: ExploreParentsAsync

Explore the present working directory along with its parents and register the filtered nodes. This operation happens asynchronously. That means, the xplr directory buffers won't be updated immediately. Hence, it needs to be used with care and probably with special checks in place. To explore just the $PWD synchronously, use ExplorePwd instead.

"Refresh"

YAML: Refresh

Refresh the UI. But it will not re-explore the directory if the working directory is the same. If there is some change in the working directory and you want to re-explore it, use the Explore message instead. Also, it will not clear the screen. Use ClearScreen for that.

"ClearScreen"

YAML: ClearScreen

Clears the screen.

"FocusNext"

YAML: FocusNext

Focus next node.

{ FocusNextByRelativeIndex = int }

YAML: FocusNextByRelativeIndex: int

Focus on the nth node relative to the current focus where n is a given value.

YAML Example: FocusNextByRelativeIndex: 2

Lua Example: { FocusNextByRelativeIndex = 2 }

"FocusNextByRelativeIndexFromInput"

YAML: FocusNextByRelativeIndexFromInput

Focus on the nth node relative to the current focus where n is read from the input buffer.

"FocusPrevious"

YAML: FocusPrevious

Focus on the previous item.

{ FocusPreviousByRelativeIndex = int }

YAML: FocusPreviousByRelativeIndex: int

Focus on the -nth node relative to the current focus where n is a given value.

YAML Example: FocusPreviousByRelativeIndex: 2

Lua Example: { FocusPreviousByRelativeIndex = 2 }

"FocusPreviousByRelativeIndexFromInput"

YAML: FocusPreviousByRelativeIndexFromInput

Focus on the -nth node relative to the current focus where n is read from the input buffer.

"FocusFirst"

YAML: FocusFirst

Focus on the first node.

"FocusLast"

YAML: FocusLast

Focus on the last node.

{ FocusPath = "string" }

YAML: FocusPath: string

Focus on the given path.

YAML Example: FocusPath: /path/to/file

Lua Example: { FocusPath = "/path/to/file" }

"FocusPathFromInput"

YAML: FocusPathFromInput

Focus on the path read from input buffer.

{ FocusByIndex = int }

YAML: FocusByIndex: int

Focus on the absolute nth node where n is a given value.

YAML Example: FocusByIndex: 2

Lua Example: { FocusByIndex = 2 }

"FocusByIndexFromInput"

YAML: FocusByIndexFromInput

Focus on the absolute nth node where n is read from the input buffer.

{ FocusByFileName = "string" }

YAML: FocusByFileName: string

Focus on the file by name from the present working directory.

YAML Example: FocusByFileName: filename.ext

Lua Example: { FocusByFileName = "filename.ext" }

{ ChangeDirectory = "string" }

YAML: ChangeDirectory: string

Change the present working directory ($PWD)

YAML Example: ChangeDirectory: /path/to/directory

Lua Example: { ChangeDirectory = "/path/to/directory" }

"Enter"

YAML: Enter

Enter into the currently focused path if it's a directory.

"Back"

YAML: Back

Go back to the parent directory.

"LastVisitedPath"

YAML: LastVisitedPath

Go to the last path visited.

"NextVisitedPath"

YAML: NextVisitedPath

Go to the next path visited.

YAML: FollowSymlink

Follow the symlink under focus to its actual location.

{ BufferInput = "string" }

YAML: BufferInput(String)

Append/buffer the given string into the input buffer.

YAML Example: BufferInput: foo

Lua Example: { BufferInput = "foo" }

"BufferInputFromKey"

YAML: BufferInputFromKey

Append/buffer the characted read from a keyboard input into the input buffer.

{ SetInputBuffer = "string" }

YAML: SetInputBuffer: string

Set/rewrite the input buffer with the given string. When the input buffer is not-null (even if empty string) it will show in the UI.

YAML Example: SetInputBuffer: foo

Lua Example: { SetInputBuffer = "foo" }

"RemoveInputBufferLastCharacter"

YAML: RemoveInputBufferLastCharacter

Remove input buffer's last character.

"RemoveInputBufferLastWord"

YAML: RemoveInputBufferLastWord

Remove input buffer's last word.

"ResetInputBuffer"

YAML: ResetInputBuffer

Reset the input buffer back to null. It will not show in the UI.

{ SwitchMode = "string" }

YAML: SwitchMode: string

Switch input mode.

NOTE: To be specific about which mode to switch to, use SwitchModeBuiltin or SwitchModeCustom instead.

YAML Example: SwitchMode: default

Lua Example: { SwitchMode = "default" }

{ SwitchModeBuiltin = "string" }

YAML: SwitchModeBuiltin: string

Switch to a builtin mode.

YAML Example: SwitchModeBuiltin: default

Lua Example: { SwitchModeBuiltin = "default" }

{ SwitchModeCustom = "string" }

YAML: SwitchModeCustom: string

Switch to a custom mode.

YAML Example: SwitchModeCustom: my_custom_mode

Lua Example: { SwitchModeCustom = "my_custom_mode" }

"PopMode"

YAML: PopMode

Pop the last mode from the history and switch to it.

{ SwitchLayout = "string" }

YAML: SwitchLayout: string

Switch layout.

NOTE: To be specific about which layout to switch to, use SwitchLayoutBuiltin or SwitchLayoutCustom instead.

YAML Example: SwitchLayout: default

Lua Example: { SwitchLayout = "default" }

{ SwitchLayoutBuiltin = "string" }

YAML: SwitchLayoutBuiltin: string

Switch to a builtin layout.

YAML Example: SwitchLayoutBuiltin: default

Lua Example: { SwitchLayoutBuiltin = "default" }

{ SwitchLayoutCustom = "string" }

YAML: SwitchLayoutCustom: string

Switch to a custom layout.

YAML Example: SwitchLayoutCustom: my_custom_layout

Lua Example: { SwitchLayoutCustom = "my_custom_layout" }

{ Call = "string" }

YAML: Call: string

Call a shell command with the given arguments. Note that the arguments will be shell-escaped. So to read the variables, the -c option of the shell can be used. You may need to pass ExplorePwd depening on the expectation.

YAML Example: Call: { command: bash, args: ["-c", "read -p test"] }

Lua Example: { Call = { command = "bash", args = { "-c", "read -p test" } } }

{ CallSilently = "string" }

YAML: CallSilently: string

Like Call but without the flicker. The stdin, stdout stderr will be piped to null. So it's non-interactive.

YAML Example: CallSilently: { command: tput, args: ["bell"] }

Lua Example: { CallSilently = { command = "tput", args = { "bell" } } }

{ CallLua = "string" }

YAML: CallLua: string

Call a Lua function. A CallLuaArg object will be passed to the function as argument. The function can optionally return a list of messages for xplr to handle after the executing the function.

YAML Example: CallLua: custom.some_custom_funtion

Lua Example: { CallLua = "custom.some_custom_funtion" }

{ CallLuaSilently = "string" }

YAML: CallLuaSilently: string

Like CallLua but without the flicker. The stdin, stdout stderr will be piped to null. So it's non-interactive.

YAML Example: CallLuaSilently: custom.some_custom_function

Lua Example: { CallLuaSilently = "custom.some_custom_function" }

{ BashExec = "string" }

YAML: BashExec: string

An alias to Call: {command: bash, args: ["-c", "{string}"], silent: false} where {string} is the given value.

YAML Example: BashExec: "read -p test"

Lua Example: { BashExec = "read -p test" }

{ BashExecSilently = "string" }

YAML: BashExecSilently(String)

Like BashExec but without the flicker. The stdin, stdout stderr will be piped to null. So it's non-interactive.

YAML Example: BashExecSilently: "tput bell"

Lua Example: { BashExecSilently = "tput bell" }

"Select"

YAML: Select

Select the focused node.

"SelectAll"

YAML: SelectAll

Select all the visible nodes.

{ SelectPath = "string" }

YAML: SelectPath: string

Select the given path.

YAML Example: SelectPath: /path/to/file

Lua Example: { SelectPath = "/path/to/file" }

"UnSelect"

YAML: UnSelect

Unselect the focused node.

"UnSelectAll"

YAML: UnSelectAll

Unselect all the visible nodes.

{ UnSelectPath = "string)" }

YAML: UnSelectPath: string

UnSelect the given path.

YAML Example: UnSelectPath: /path/to/file

Lua Example: { UnSelectPath = "/path/to/file" }

"ToggleSelection"

YAML: ToggleSelection

Toggle selection on the focused node.

"ToggleSelectAll"

YAML: ToggleSelectAll

Toggle between select all and unselect all.

{ ToggleSelectionByPath = "string" }

YAML: ToggleSelectionByPath: string

Toggle selection by file path.

YAML Example: ToggleSelectionByPath: /path/to/file

Lua Example: { ToggleSelectionByPath = "/path/to/file" }

"ClearSelection"

YAML: ClearSelection

Clear the selection.

{ AddNodeFilter = { filter = Filter, input = "string" }

YAML: AddNodeFilter: { filter = Filter, input = string }

Add a filter to exclude nodes while exploring directories.

YAML Example: AddNodeFilter: { filter: RelativePathDoesStartWith, input: foo }

Lua Example: { AddNodeFilter = { filter = "RelativePathDoesStartWith", input = "foo" } }

{ RemoveNodeFilter = { filter = Filter, input = "string" }

YAML: RemoveNodeFilter: { filter = Filter, input = string

Remove an existing filter.

YAML Example: RemoveNodeFilter: { filter: RelativePathDoesStartWith, input: foo }

Lua Example: { RemoveNodeFilter: { filter: "RelativePathDoesStartWith", input: "foo" } }

{ ToggleNodeFilter = { filter = Filter, input = "string" }

YAML: ToggleNodeFilter: { filter = Filter, input = string }

Remove a filter if it exists, else, add a it.

YAML Example: ToggleNodeFilter: { filter: RelativePathDoesStartWith, input: foo }

Lua Example: { ToggleNodeFilter = { filter = "RelativePathDoesStartWith", input = "foo" } }

{ AddNodeFilterFromInput = Filter }

YAML: AddNodeFilterFromInput: Filter

Add a node filter reading the input from the buffer.

YAML Example: AddNodeFilterFromInput: RelativePathDoesStartWith

Lua Example: { AddNodeFilterFromInput = "RelativePathDoesStartWith" }

{ RemoveNodeFilterFromInput = Filter }

YAML: RemoveNodeFilterFromInput: Filter

Remove a node filter reading the input from the buffer.

YAML Example: RemoveNodeFilterFromInput: RelativePathDoesStartWith

Lua Example: { RemoveNodeFilterFromInput = "RelativePathDoesStartWith" }

"RemoveLastNodeFilter"

YAML: RemoveLastNodeFilter

Remove the last node filter.

"ResetNodeFilters"

YAML: ResetNodeFilters

Reset the node filters back to the default configuration.

"ClearNodeFilters"

YAML: ClearNodeFilters

Clear all the node filters.

{ AddNodeSorter = { sorter = Sorter, reverse = bool } }

YAML: AddNodeSorter: { sorter: Sorter, reverse = bool }

Add a sorter to sort nodes while exploring directories.

YAML Example: AddNodeSorter: { sorter: ByRelativePath, reverse: false }

YAML Example: { AddNodeSorter = { sorter = "ByRelativePath", reverse = false } }

{ RemoveNodeSorter = Sorter }

YAML: RemoveNodeSorter: Sorter

Remove an existing sorter.

YAML Example: RemoveNodeSorter: ByRelativePath

Lua Example: { RemoveNodeSorter = "ByRelativePath" }

{ ReverseNodeSorter = Sorter }

YAML: ReverseNodeSorter: Sorter

Reverse a node sorter.

YAML Example: ReverseNodeSorter: ByRelativePath

Lua Example: { ReverseNodeSorter = "ByRelativePath" }

{ ToggleNodeSorter = { sorter = Sorter, reverse = bool } }

YAML: ToggleNodeSorter: { sorter: Sorter, reverse = bool }

Remove a sorter if it exists, else, add a it.

YAML Example: ToggleSorterSorter: {sorter: ByRelativePath, reverse: false }

Lua Example: { ToggleSorterSorter: { sorter = "ByRelativePath", reverse = false } }

"ReverseNodeSorters"

YAML: ReverseNodeSorters

Reverse the node sorters.

"RemoveLastNodeSorter"

YAML: RemoveLastNodeSorter

Remove the last node sorter.

"ResetNodeSorters"

YAML: ResetNodeSorters

Reset the node sorters back to the default configuration.

"ClearNodeSorters"

YAML: ClearNodeSorters

Clear all the node sorters.

"EnableMouse"

YAML: EnableMouse

Enable mouse

"DisableMouse"

YAML: DisableMouse

Disable mouse

"ToggleMouse"

YAML: ToggleMouse

Toggle mouse

{ StartFifo = "string" }

YAML: StartFifo: string

Start piping the focused path to the given fifo path

YAML Example: StartFifo: /tmp/xplr.fifo

Lua Example: { StartFifo = "/tmp/xplr.fifo }

"StopFifo"

YAML: StopFifo

Close the active fifo and stop piping.

{ ToggleFifo = "string" }

YAML: ToggleFifo: string

Toggle betwen {Start|Stop}Fifo

YAML Example: ToggleFifo: /path/to/fifo

Lua Example: { ToggleFifo = "/path/to/fifo" }

{ LogInfo = "string" }

YAML: LogInfo: string

Log information message.

YAML Example: LogInfo: launching satellite

Lua Example: { LogInfo = "launching satellite" }

{ LogSuccess = "String" }

YAML: LogSuccess: string

Log a success message.

YAML Example: LogSuccess: satellite reached destination.

Lua Example: { LogSuccess = "satellite reached destination" }.

{ LogWarning = "string" }

YAML: LogWarning: string

Log an warning message.

YAML Example: LogWarning: satellite is heating

Lua Example: { LogWarning = "satellite is heating" }

{ LogError = "string" }

YAML: LogError: string

Log an error message.

YAML Example: LogError: satellite crashed

Lua Example: { LogError = "satellite crashed" }

"Quit"

YAML: Quit

Quit with returncode zero (success).

"PrintPwdAndQuit"

YAML: PrintPwdAndQuit

Print $PWD and quit.

"PrintFocusPathAndQuit"

YAML: PrintFocusPathAndQuit

Print the path under focus and quit. It can be empty string if there's nothing to focus.

"PrintSelectionAndQuit"

YAML: PrintSelectionAndQuit

Print the selected paths and quit. It can be empty is no path is selected.

"PrintResultAndQuit"

YAML: PrintResultAndQuit

Print the selected paths if it's not empty, else, print the focused node's path.

"PrintAppStateAndQuit"

YAML: PrintAppStateAndQuit

Print the state of application in YAML format. Helpful for debugging or generating the default configuration file.

{ Debug = "string" }

YAML: Debug: string

Write the application state to a file, without quitting. Also helpful for debugging.

YAML Example: Debug: /path/to/file

Lua Example: { Debug = "/path/to/file" }

"Terminate"

YAML: Terminate

Terminate the application with a non-zero return code.

Lua Function Calls

xplr allows users 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 that contains some useful information. The function can optionally return a list of messages which will be handled by xplr.

CallLua Argument

This is a special argument passed to the lua functions when called using the CallLua, CallLuaSilently messages.

It contains the following information:

version

Type: string

xplr version. Can be used to test compatibility.

config

Type: Config

pwd

Type: string

The present working directory/

focused_node

Type: nullable Node

The node under focus.

directory_buffer

TODO

selection

Type: list of selected Nodes

The selected nodes.

mode

Type: Mode

Current mode.

layout

Type: Layout

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

TODO

history

TODO

last_modes

Type: list of Mode

Last modes, not popped yet.

Node

A node contains the following fields:

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.

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

The permissions applied to the node.

canonical

Type: nullable Resolved Node Metadata

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.

Type: nullable Resolved Node Metadata

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.

Example: Using Lua Function Calls

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

Environment Variables and Pipes

Alternative to CallLua, CallLuaSilently messages that call Lua functions, there are Call, CallSilently, BashExec, BashExecSilently messages that call shell commands.

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.

Visit the fzf integration tutorial for example.

Environment variables

To see the environment variables, invoke the shell by typing :! in default mode and run the following command:

env | grep ^XPLR_

You will see something like:

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.

Input pipe

Current there is only one input pipe.

Output pipes

XPLR_PIPE_*_OUT are the output pipes that contain data which cannot be exposed directly via environment variables, like multi-line string.

XPLR_PIPE_MSG_IN

Append new-line delimited messages to this pipe in YAML (or JSON) syntax. These messages will be read and handled by xplr after the command execution.

XPLR_PIPE_SELECTION_OUT

New-line delimited list of selected paths.

XPLR_PIPE_GLOBAL_HELP_MENU_OUT

The full help menu.

XPLR_PIPE_LOGS_OUT

New-line delimited list of logs.

XPLR_PIPE_RESULT_OUT

New-line delimited result (selected paths if any, else the focused path)

XPLR_PIPE_HISTORY_OUT

New-line delimited list of last visited paths (similar to jump list in vim).

XPLR_PIPE_DIRECTORY_NODES_OUT

New-line delimited list of paths, filtered and sorted as displayed in the files table.

Example: Using Environment Variables and Pipes

xplr.config.modes.builtin.default.key_bindings.on_key.space = {
  help = "ask name and greet",
  messages = {
    {
      BashExec = [===[
      echo "What's your name?"

      read name
      greeting="Hello $name!"
      message="$greeting You are inside $PWD"
    
      echo LogSuccess: '"'$message'"' >> "${XPLR_PIPE_MSG_IN:?}"
      ]===]
    }
  }
}

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

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. It contains the following fields:

The users can switch between these layouts at run-time.

builtin

Type: mapping of string and Layout

This is exposed by the xplr.config.layouts.builtin API.

xplr by default provides the following builtin layouts:

default

Type: Layout

This is the default layout we see when we run xplr.

no_help

Type: Layout

This layout hides the help menu.

no_selection

Type: Layout

This layout hides the selection panel.

no_help_no_selection

Type: Layout

This layout hides both the help menu and the selection panel.

custom

Type: mapping of string and Layout

This is exposed by the xplr.config.layouts.custom API.

It allows the users to define any custom layout.

Example:

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

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:

Vertical

This is a special layout that splits the panel into two vertical parts.

It contains the following information:

Layout Config

A layout config contains the following information:

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

vertical_Margin

Type: nullable integer

The width of the vertical margins. Overwrites the margin value.

constraints

Type: nullable list of Constraint

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 }
  • { MaxthLessThanLayoutWidth = int }
  • { Min = int }
  • { MinLessThanScreenHeight = int }
  • { MinLessThanScreenWidth = int }
  • { MinLessThanLayoutHeight = int }
  • { MinLessThanLayoutWidth = int }

TODO: document each constraint.

splits

Type: list of Layout

The list of child layouts to fit into the parent layout.

Example

layout.png

xplr.config.layouts.builtin.default = {
  Horizontal = {
    config = {
      margin = 1,
      horizontal_margin = 2,
      vertical_margin = 3,
      constraints = {
        { Percentage = 50 },
        { Percentage = 50 },
      }
    },
    splits = {
      "Table",
      "HelpMenu",
    }
  }
}

Node Types

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.

It contains the following fields:

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.

The priority is:

special > extension > mime_essence > symlink > file > directory

directory

Type: NodeType Config

Properties related to directories are defined here.

Contains the following fields:

Example:

xplr.config.node_types.directory.meta.icon = ""
xplr.config.node_types.directory.style.add_modifiers = { "Bold" }

file

Type: NodeType Config

Properties related to regular files are defined here.

Contains the following fields:

Example:

xplr.config.node_types.file.meta.icon = ""
xplr.config.node_types.file.style.fg = "White"

Type: NodeType Config

Properties related to symlink are defined here.

Example:

xplr.config.node_types.symlink.meta.icon = ""
xplr.config.node_types.symlink.style.add_modifiers = { "Italic" }

mime_essence

Type: mapping of mime-type and mapping of mime-subtype and NodeType Config

Properties related to files with specific mime types are defined here.

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:

xplr.config.node_types.mime_essence = {
  application = {
    -- application/*
    ["*"] = { meta = { icon = "a" } }
    
    -- application/pdf
    pdf = { meta = { icon = "" } },

    -- application/zip
    zip = { meta = { icon = ""} },
  },
}

extension

Type: mapping of extension and NodeType Config

Properties related to files with specific extension are defined here.

Example:

xplr.config.node_types.extension.md = { meta = { icon = "" } }
xplr.config.node_types.extension.rs = { meta = { icon = "🦀" } }

special

Type: mapping of name and NodeType Config

Properties related to files and directories with special names are defined here.

Example:

xplr.config.node_types.special["Cargo.toml"] = { meta = { icon = "" } }
xplr.config.node_types.special["Downloads"] = { meta = { icon = "" } }

NodeType Config

A node-type config contains the following fields:

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, file, and symlink nodes.

Example:

xplr.config.node_types.file = {
  meta = {
    icon = "f",
    foo = "bar",
  }
}

Style

A style object contains the following information:

fg

Type: nullable Color

The foreground color.

bg

Type: nullable Color

The background color.

add_modifiers

Type: nullable list of Modifier

Modifiers to add.

sub_modifiers

Type: nullable list of Modifier

Modifiers to remove.

Color

Color can be one of the following:

  • "Reset"
  • "Black"
  • "Red"
  • "Green"
  • "Yellow"
  • "Blue"
  • "Magenta"
  • "Cyan"
  • "Gray"
  • "DarkGray"
  • "LightRed"
  • "LightGreen"
  • "LightYellow"
  • "LightBlue"
  • "LightMagenta"
  • "LightCyan"
  • "White"
  • { Rgb = { int, int, int } }
  • { Indexed = int }

Modifier

Modifier can be one of the following:

  • "Bold"
  • "Dim"
  • "Italic"
  • "Underlined"
  • "SlowBlink"
  • "RapidBlink"
  • "Reversed"
  • "Hidden"
  • "CrossedOut"

Example

xplr.config.general.cursor.style.fg = "Red"
xplr.config.general.cursor.style.bg = { Rgb = { 100, 150, 200 } }
xplr.config.general.cursor.style.add_modifiers = { "Bold", "Italic" }
xplr.config.general.cursor.style.sub_modifiers = { "Hidden" }

Sorting

xplr supports sorting paths by different properties. The sorting mechanism works like a pipeline, which in visible in the Sort & filter panel.

Example:

size↑ › [i]rel↓ › [c]dir↑ › [c]file↑ › sym↑

This line means that the nodes visible in the table will be first sorted by it's size, then by case insensitive relative path, then by the canonical (symlink resolved) type of the node, and finally by whether or not the node is a symlink.

The arrows denote the order.

Each part of this pipeline is called Node Sorter Applicable.

Node Sorter Applicable

It contains the following information:

sorter

A sorter can be one of the following:

  • "ByRelativePath"
  • "ByIRelativePath"
  • "ByExtension"
  • "ByIsDir"
  • "ByIsFile"
  • "ByIsSymlink"
  • "ByIsBroken"
  • "ByIsReadonly"
  • "ByMimeEssence"
  • "BySize"
  • "ByCanonicalAbsolutePath"
  • "ByICanonicalAbsolutePath"
  • "ByCanonicalExtension"
  • "ByCanonicalIsDir"
  • "ByCanonicalIsFile"
  • "ByCanonicalIsReadonly"
  • "ByCanonicalMimeEssence"
  • "ByCanonicalSize"
  • "BySymlinkAbsolutePath"
  • "ByISymlinkAbsolutePath"
  • "BySymlinkExtension"
  • "BySymlinkIsDir"
  • "BySymlinkIsFile"
  • "BySymlinkIsReadonly"
  • "BySymlinkMimeEssence"
  • "BySymlinkSize"

TODO: document each

reverse

Type: boolean

It defined the direction of the order.

Example

xplr.config.general.initial_sorting = {
    { sorter = "ByCanonicalIsDir", reverse = true },
    { sorter = "ByIRelativePath", reverse = false },
}

This snippet defines the initial sorting logic to be applied when xplr loads.

Filtering

xplr supports filtering paths by different properties. The filtering mechanism works like a pipeline, which in visible in the Sort & filter panel.

Example:

rel!^. › [i]abs=~abc › [i]rel!~xyz

This line means that the nodes visible on the table will first be filtered by the condition: relative path does not start with ., then by the condition: absolute path contains abc (case insensitive), and finally by the condition: relative path does not contain xyz (case insensitive).

Each part of this pipeline is called Node Filter Applicable.

Node Filter Applicable

It contains the following information:

filter

A filter can be one of the following:

  • "RelativePathIs"
  • "RelativePathIsNot"
  • "IRelativePathIs"
  • "IRelativePathIsNot"
  • "RelativePathDoesStartWith"
  • "RelativePathDoesNotStartWith"
  • "IRelativePathDoesStartWith"
  • "IRelativePathDoesNotStartWith"
  • "RelativePathDoesContain"
  • "RelativePathDoesNotContain"
  • "IRelativePathDoesContain"
  • "IRelativePathDoesNotContain"
  • "RelativePathDoesEndWith"
  • "RelativePathDoesNotEndWith"
  • "IRelativePathDoesEndWith"
  • "IRelativePathDoesNotEndWith"
  • "AbsolutePathIs"
  • "AbsolutePathIsNot"
  • "IAbsolutePathIs"
  • "IAbsolutePathIsNot"
  • "AbsolutePathDoesStartWith"
  • "AbsolutePathDoesNotStartWith"
  • "IAbsolutePathDoesStartWith"
  • "IAbsolutePathDoesNotStartWith"
  • "AbsolutePathDoesContain"
  • "AbsolutePathDoesNotContain"
  • "IAbsolutePathDoesContain"
  • "IAbsolutePathDoesNotContain"
  • "AbsolutePathDoesEndWith"
  • "AbsolutePathDoesNotEndWith"
  • "IAbsolutePathDoesEndWith"
  • "IAbsolutePathDoesNotEndWith"

TODO: document each

input

Type: string

The input for the condition.

Example:

ToggleNodeFilter = {
  filter = "RelativePathDoesNotStartWith",
  input = "."
}

Here, ToggleNodeFilter is a message that adds or removes (toggles) the filter applied.

Column Renderer

A column renderer is a Lua function that receives a special argument and returns a string that will be displayed in each specific field of the files table.

xplr by default provides the following column renderers:

  • xplr.fn.builtin.fmt_general_table_row_cols_0
  • xplr.fn.builtin.fmt_general_table_row_cols_1
  • xplr.fn.builtin.fmt_general_table_row_cols_2
  • xplr.fn.builtin.fmt_general_table_row_cols_3
  • xplr.fn.builtin.fmt_general_table_row_cols_4

You can either overwrite these functions, or create new functions in xplr.fn.custom and point to them.

Terminal colors are supported.

Table Renderer Argument

The special argument contains the following fields

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.

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

The permissions applied to the node.

canonical

Type: nullable Resolved Node Metadata

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.

Type: nullable Resolved Node Metadata

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.

index

Type: integer

Index (starting from 0) of the node.

relative_index

Type: integer

Relative index from the focused node (i.e. 0th node).

is_before_focus

Type: boolean

true if the node is before the focused node.

is_after_focus

Type: boolean

true if the node is after the focused node.

tree

Type: string

The tree component based on the node's index.

prefix

Type: string

The prefix applicable for the node.

suffix

Type: string

The suffix applicable for the node.

is_selected

Type: boolean

true if the node is selected.

is_focused

Type: boolean

true if the node is under focus.

total

Type: integer

The total number of the nodes.

meta

Type: mapping of string and string

The applicable meta object for the node.

Permission

Permission contains the following fields:

  • user_read
  • user_write
  • user_execute
  • group_read
  • group_write
  • group_execute
  • other_read
  • other_write
  • other_execute
  • sticky
  • setgid
  • setuid

Each field holds a boolean value.

Resolved Node Metadata

It contains the following fields.

Example: Customizing Table Renderer

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.

Default Key Bindings

The default key binding is inspired by vim and slightly overlaps with nnn, but it's supposed to be customized as per user requirements.

When you press ? in default mode, you can see the complete list of modes and the key mappings for each mode.

default

keyremapsaction
.show hidden
/ctrl-fsearch
:action
?global help menu
Ggo to bottom
Vctrl-aselect/unselect all
ctrl-cterminate
ctrl-itabnext visited path
ctrl-olast visited path
ctrl-rrefresh screen
ctrl-uclear selection
ctrl-wswitch layout
ddelete
downjdown
enterquit with result
ffilter
ggo to
hleftback
kupup
lrightenter
qquit
rrename
ssort
spacevtoggle selection
~go home
[0-9]input

recover

keyremapsaction
ctrl-cterminate
escescape

filter

keyremapsaction
Rrelative does not contain
backspaceremove last filter
ctrl-cterminate
ctrl-rreset filters
ctrl-uclear filters
enterescdone
rrelative does contain

number

keyremapsaction
backspaceremove last character
ctrl-cterminate
ctrl-uremove line
ctrl-wremove last word
downjto down
enterto index
esccancel
kupto up
[0-9]input

go to

keyremapsaction
ctrl-cterminate
esccancel
ffollow symlink
gtop
xopen in gui
keyremapsaction
backspaceremove last character
ctrl-cterminate
ctrl-ndowndown
ctrl-pupup
ctrl-uremove line
ctrl-wremove last word
enterescfocus
leftback
rightenter
tabtoggle selection

selection ops

keyremapsaction
ccopy here
ctrl-cterminate
esccancel
mmove here
xopen in gui

action to

keyremapsaction
!shell
ccreate
ctrl-cterminate
eopen in editor
esccancel
llogs
mtoggle mouse
qquit options
sselection operations
[0-9]go to index

create

keyremapsaction
ctrl-cterminate
dcreate directory
esccancel
fcreate file

create file

keyremapsaction
backspaceremove last character
ctrl-cterminate
ctrl-uremove line
ctrl-wremove last word
entercreate file
esccancel

create directory

keyremapsaction
backspaceremove last character
ctrl-cterminate
ctrl-uremove line
ctrl-wremove last word
entercreate directory
esccancel

rename

keyremapsaction
backspaceremove last character
ctrl-cterminate
ctrl-uremove line
ctrl-wremove last word
enterrename
esccancel

delete

keyremapsaction
Dforce delete
ctrl-cterminate
ddelete
esccancel

sort

keyremapsaction
!reverse sorters
Eby canonical extension reverse
Mby canonical mime essence reverse
Nby node type reverse
Rby relative path reverse
Sby size reverse
backspaceremove last sorter
ctrl-cterminate
ctrl-rreset sorters
ctrl-uclear sorters
eby canonical extension
enterescdone
mby canonical mime essence
nby node type
rby relative path
sby size

filter

keyremapsaction
Rrelative does not contain
backspaceremove last filter
ctrl-cterminate
ctrl-rreset filters
ctrl-uclear filters
enterescdone
rrelative does contain

relative path does contain

keyremapsaction
backspaceremove last character
ctrl-cterminate
ctrl-uremove line
ctrl-wremove last word
enterapply filter
esccancel

relative path does not contain

keyremapsaction
backspaceremove last character
ctrl-cterminate
ctrl-uremove line
ctrl-wremove last word
enterapply filter
esccancel

switch layout

keyremapsaction
1default
2no help menu
3no selection panel
4no help or selection
ctrl-cterminate
esccancel

Plugin

xplr supports pluggable Lua modules that can be used to easily configure or extend xplr UI and functionalities.

Installing Plugins

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

    package.path = os.getenv("HOME") .. '/.config/xplr/plugins/?/src/init.lua'
    
  • Clone the plugin

    mkdir -p ~/.config/xplr/plugins
    
    git clone https://github.com/sayanarijit/material-landscape2.xplr ~/.config/xplr/plugins/material-landscape2
    
  • Require the module in ~/.config/xplr/init.lua

    require("material-landscape2").setup()
    
    -- The setup arguments might differ for different plugins.
    -- Visit the project README for setup instructions.
    

Writing Plugins

Anyone who can write Lua code, can write xplr plugins.

Just follow the instructions and best practices:

Naming

xplr plugins are named using hiphen (-) separated words that may also include integers. They will be plugged using the require() function in Lua.

Structure

A minimal plugin should confirm to the following structure:

plugin-name
├── README.md
└── src
    └── init.lua

You can also use this template.

README.md

This is where you document what the plugin does, how to use it, etc.

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.

Example:

local function setup(args)
  local xplr = xplr
  -- do stuff with xplr
end

return { setup = setup }

Publishing

When publishing plugins on GitHub or other repositories, it's a best practice to append .xplr to the name to make them distinguishable. Similar to the *.nvim naming convention for Neovim plugins.

Finally, after publishing, don't hesitate to let us know.

Examples

Visit Awesome Plugins for xplr plugin examples.

Also See

Awesome Plugins

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.

Categories

Extension

Integration

Theme

Integration

xplr is designed to integrate well with other tools and commands. It can be used as a file picker or a pluggable file manager.

Awesome Integrations

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.

Categories

Editor

  • vim-floaterm xplr integrated in vim-floaterm (Neo)vim plugin.
  • xplr.nvim Opens xplr inside nvim, and hosts a msgpack client inside xplr.
  • xplr.vim Pick files in Vim using xplr.

Shell

Security Tools

  • gpg-tui Import GPG certificates using xplr.

TODO

  • Saner key bindings.
  • Pipes.
  • Native search & filter.
  • Create, copy, move, delete files directly.
  • logging support.
  • Version compatibility instructions.
  • Implement CLI arguments.
  • ~Add support for tabs and/or panes (non native)~ hacked | discussion
  • ~Implement bookmarks.~ hacked
  • Add sorting support.
  • Add filter support.
  • File previews.
  • Implement plugins support (or some way to easily share configuration).
  • Bigger (and better) help menu.
  • Offline docs.
  • Support for background services.
  • ~Customize~ switch UI in run-time.
  • More tests and benchmarks.
  • Measure code coverage.
  • Improve the vim plugin.
  • Cleanup, refactor, optimize.

add more

Like this project so far? Please consider contributing.

Alternatives

These are the alternative TUI/CLI file managers/explorers you might want to check out (in no particular order).

add more

Upgrade Guide

When you upgrade xplr, you might see an error like this

Incompatible script version in: /home/sayanarijit/.config/xplr/init.lua. The script version is: 0.9.0, the required version is: 0.10.1. Visit https://github.com/sayanarijit/xplr/wiki/Upgrade-Guide

All you need to do is follow the instructions starting from your config version, all the way to the required version.

Expand for more information

With every update, we either implement a major breaking change (e.g. deprecating or replacing messages), or a minor feature addition (e.g. adding new messages) or patch, fixes, and optimization (e.g. performance optimization).

Knowing that we use the {major}.{minor}.{patch} versioning format,

  • Major version mismatch are generally incompatible. xplr will fail with error.
  • Minor version upgrades (not downgrades) and patch fixes are backwards compatible. You might get notified by log a message which you can disable by updating the version in your config file.
  • However, if the config file has a higher value for the minor version than the app, then also xplr will fail with error, suggesting you to visit this page. Though in that case, you will be downgrading your config file based on your app version.

e.g.

  • 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} fix number as {minor} to determine compatibility.

Instructions

v0.13.7 -> v0.14.7

  • macOS users need to place their config file (init.lua) in $HOME/.config/xplr/ or /etc/xplr/.
  • Library users please refer to the latest API docs.
  • Check out the new messages: {Start|Stop|Toggle}Fifo. These enable support for FIFO based file previews.
  • 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 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 related data.
  • Since version v0.14.6, the -C or --extra-config CLI argument is available.

Like this project so far? Please consider contributing.

v0.12.1 -> v0.13.7

  • Lua functions called using CallLua and CallLuaSilently messages will receive CallLuaArg object as the function argument (instead of the App object).
  • 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 -> v0.12.1

  • xplr.config.node_types.mime_essence has split into type and subtype. Hence, instead of xplr.config.node_types.mime_essence["text/plain"] = .. use xplr.config.node_types.mime_essence["text"] = { plain = .. }.
  • You can also define xplr.config.node_types.mime_essence["text"]["*"] that will match all text types (text/*).

v0.10.2 -> v0.11.1

  • remaps: has been removed to avoid confusion. Use lua assignments instead. For e.g.
    xplr.config.modes.builtin.default.key_bindings.on_key["v"] = xplr.config.modes.builtin.default.key_bindings.on_key.space
    

v0.9.1 -> v0.10.2

  • config.yml has been fully replaced with init.lua. If you have a lot of customization in your config.yml, xplr-yml2lua can help you with migrating it to init.lua.
  • Handlebars templates has been replaced with Lua functions. You can either remove the customizations or overwrite the functions accordingly.
  • Added new messages CallLua and CallLuaSilently to call lua functions. The app state will be passed as input to the functions, and the returned messages will be handled by xplr. CallLua and CallLuaSilently are more flexible (and probably faster) alternatives to Call, CallSilently, BashExec and BashExecSilently. e.g.

v0.9.0 -> v0.9.1

  • You can now set remaps: {key: null} to un-map a key.
  • gx will open the item under focus.
  • New key map :sx will open the selected items.

v0.8.0 -> v0.9.0

Your previous config should mostly work fine. However, in case you are using SwitchMode heavily in your custom config, follow along.

  • Introduced new message PopMode. You might want to use this message instead of SwitchMode* when returning back to the previous mode.
  • After using (the group of) PopMode and SwitchMode* messages, you are now required to Refresh manually to avoid the UI lag.
  • Pressing any invalid key will now lead you to the recover mode and will protect you from typing further invalid keys. Press esc to escape the recover mode.
  • Introduced new message LogWarning, similar to other Log* messages.
  • Creating files and directories has been optimized for batch creation.

v0.7.2 -> v0.8.0

If you have made changes to the config file,

  • Replace message Explore with ExplorePwd or ExplorePwdAsync or probably ExploreParentsAsync.
  • Pipe $XPLR_PIPE_FOCUS_OUT has been removed. Use $XPLR_FOCUS_PATH env var instead.
  • You might want to review your path escaping logics. For e.g. use echo FocusPath: "'"$PWD"'" >> $PIPE instead of echo "FocusPath: $PWD" >> $PIPE.

v0.7.0 -> v0.7.2

  • Just update the version in your config file.
  • For version >= v0.7.1, you might want to free up or remap the tab key in search mode to enable easy selection during search.

v0.6.0 -> v0.7.0

If you haven't made any changes in the config file, you should be fine just updating the version number. Else,

  • You can make the Table: ..., InputAndLogs: ... layout values null and define the common properties in the general.panel_ui instead.

v0.5.13 -> v0.6.0

If you haven't made any changes in the config file, you should be fine just updating the version number. Else,

  • Rename add_modifier: {bits: 1} to add_modifiers: [Bold], sub_modifier: {bits: 1} to sub_modifiers: [Bold] and so on.
  • Rename percentage: 10 to Percentage: 10, ratio: 1 to Ratio: 1 and so on.
  • You might want to free up or remap the ctrl-w key binding in default mode to enable layout switching.

Optionally, checkout this new theme to learn more about what's new.

v0.5.0 -> v0.5.13

  • Just update the version in your config file.
  • For versions >= v0.5.8, you can set $OPENER env var to declare a global GUI file opener (to open files using keys gx).
  • You might also want to update other mappings to handle files with names starting with - (hiphen). For example, instead of rm ${filename} use rm -- ${filename}. Same goes for cp, mv, cat, touch etc.
  • For version >= v0.5.13, you might want to use the more specific SwitchModeBuiltin and SwitchModeCustom messages instead of the general SwitchMode message.

v0.4.3 -> v0.5.0

If you haven't have any changes in the config file, you should be fine just updating the version number.

Else do the following

  • Replace {RelativePathIs, case_sensitive: true} with RelativePathIs.
  • Replace {RelativePathIs, case_sensitive: false} with IRelativePathIs.
  • Do the same with other filters you are using.
  • You might want to update your backspace handling to use the RemoveInputBufferLastCharacter message.
  • You might want to free-up f, s, ctrl-r and ctrl-u key bindings in the default mode, or remap them.
  • You might want to use the new UI variables.
  • Update your config version to v0.5.0.

v0.4.2 -> v0.4.3

If you have customized general.table.row.cols, you might want to update it to use the new variables with better symlink support.

v0.4.1 -> v0.4.2

In case you have mapped the keys q, ctrl-i and ctrl-o, you may want to revisit the default mode key bindings and remap accordingly to use the new functionalities.

v0.3.13 -> v0.4.1

A lot has changed (apologies). But I promise from now on, upgrading will be much less painful (thanks to @maximbaz's valuable inputs and code reviews).

So, to start with the upgrade, let's remove everything from your config file except the version field and your custom modifications. If version is the only thing remaining, update it to v0.4.1 and you are done.

Else, do the following

  • Rename general.focused_ui to general.focus_ui (see here).
  • Rename filetypes to node_types. (see here)
  • Rename custom field to meta. (see here)
  • Move icon to meta.icon. (see here)
  • Rename normal_ui to default_ui. (see here)
  • Split modes into modes.builtin and modes.custom (see here). Migrate your custom modes to modes.custom. And copy only the changes in the in-built modes in modes.builtin.
  • Finally, update the version to v0.4.1.

v0.3.8 -> v0.3.13

Your current config should work fine. However, you might want to replace some Call and BashExec messages with CallSilently and BashExecSilently to remove the flickering of the screen.

If you haven't made any changes to the configuration, you can delete and regenerate it.

Else, do the following

  • Check the new default config by temporarily removing your current config (with backup) and dumping the new config.
  • Search for Call and BashExec in the new config.
  • Compare and probably replace the associated actions in your current config

v0.3.0 -> v0.3.8

Your current config should work fine. However, you might want to replace some ResetNodeFilters messages with RemoveNodeFilter and RemoveNodeFilterFromInput to get a better search and filter experience.

If you haven't made any changes to the configuration, you can delete and regenerate it.

Else, do the following

  • Check the new default config by temporarily removing your current config (with backup) and dumping the new config.
  • Search for RemoveNodeFilterFromInput in the new config.
  • Compare and probably replace the associated actions in your current config.

v0.2.14 -> v0.3.0

If you haven't made any changes to the configuration, you can delete and regenerate it.

Else do the following:

  • $XPLR_APP_YAML has been removed. You can use Debug to export the app state.
  • $XPLR_RESULT has been ported to file $XPLR_PIPE_RESULT_OUT. Use cat instead of echo, < instead of <<< etc.
  • $XPLR_GLOBAL_HELP_MENU has been ported to file $XPLR_PIPE_GLOBAL_HELP_MENU_OUT. Use cat instead of echo, < instead of <<< etc.
  • $XPLR_DIRECTORY_NODES has been ported to file $XPLR_PIPE_DIRECTORY_NODES_OUT. Use cat instead of echo, < instead of <<< etc.
  • $XPLR_LOGS has been ported to file $XPLR_PIPE_LOGS_OUT. Use cat instead of echo, < instead of <<< etc.
  • $XPLR_PIPE_RESULT has been ported to file $XPLR_PIPE_RESULT_OUT. Use cat instead of echo, < instead of <<< etc.
  • Finally, update the version in your config file.

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.

BTW I like Miyazaki and Shinkai works.

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

For further queries or concern related to xplr, just ask us.

Backers