An ls replacement written in Rust
LSPlus is a functional Unix ls clone written in Rust. I built it as a Rust
learning project, so some code may still show beginner decisions.
Compatibility
LSPlus supports Linux, macOS, and Windows. Windows output uses native file attributes and recognises junctions separately from symbolic links.
Nerd Fonts
Install a Nerd Font in your terminal to display folder and file icons. The Nerd Fonts website has a broad selection.
My personal favourite is MesoLG Nerd Font. Configure your terminal to use the
font after installing it.
If you DO NOT want to install a Nerd Font, pass the --no-icons switch to
the program.
Installation
Download a Binary
Download the latest Linux, macOS, or Windows archive from the release
page. Unpack it and move
lsp (or lsp.exe on Windows) into a directory on your PATH. On Unix,
make it executable if needed.
These binaries are auto-generated for each release.
Using Cargo
If you have rust installed, you can install the latest release of this package, using the following command:
cargo install lsplus
This will install the lsp binary into:
- Linux and macOS:
~/.cargo/bin - Windows:
%USERPROFILE%\\.cargo\\bin
Make sure this directory is in your PATH environment variable so that you
can run the lsp command from anywhere.
From Source
Install the package from the GitHub repository with:
cargo install --git https://github.com/seapagan/lsplus.git
This uses unreleased commits from the default branch.
Usage
Run this command in your terminal to list files in the current directory:
lsp <options> <path | file>
Options and paths are optional. With no path, lsp lists the current
directory. With no options, it uses defaults similar to ls.
Currently, only a sub-set of the standard ls options are supported. These are:
-a/--all- Show hidden files-A/--almost-all- Show hidden files, but don’t show.and..-p/--slash-dirs- Append a ‘/’ to directories--file-type- Append type indicators except*for executables-F/--classify- Append type indicators, including*for executables--no-indicators- Disable file type indicators-l/--long- Show long format listing--header- Show a title row in long-format output--permissions <MODE>- Select long-format permission display:symbolic,octal,both, ornone-h/--human-readable- Human readable file sizes using powers of 1024--si- Human readable file sizes using powers of 1000-R/--recursive- List subdirectories recursively--tree- Show a long-format directory tree--level <N>- Limit recursive or tree output to visible entry depth--prune-noisy-dirs- Skip descending into common noisy directories--prune-dir <NAME>- Skip descending into matching directory basenames-D/--sort-dirs- Sort directories first-I/--gitignore- Dim entries matched by Git ignore rules-N/--no-color- Disable colored and styled output--no-permission-colors- Disable long-format file type character and permission colors--no-time-gradient- Use the fixed long-format timestamp color--no-size-colors- Disable long-format large-size colors--no-icons- don’t show file or folder icons-V/--version- Print version information and exit-Z/--fuzzy-time- Show fuzzy time for file modification times
You can combine the short options together, e.g. -laph will show a long format
listing with hidden files, append a ‘/’ to directories, and show human-readable
file sizes.
Use the --help option to see the full list of options.
When listing multiple directory operands, or a mix of files and directories,
lsp prints file operands first and labels each directory section with a
path: header. A single non-recursive directory keeps the compact output shape
without a header.
Use -R or --recursive to print GNU-style recursive directory sections.
Recursive output is unlimited unless you pass --level <N>. Use --tree for
long-format tree output. Tree output implies --long, uses a default depth of
2, and can be limited with --level <N>. --tree and --recursive are
mutually exclusive.
Quoted wildcard or filename operands filter matching entries while still walking subdirectories:
lsp -R '*.rs'
lsp -R 'src/*.rs'
Bare filename operands such as main.rs search for that basename below the
current directory. Prefixed literal paths such as src/main.rs remain exact
path operands. When --level is used with a recursive filter, no-match
diagnostics apply to matches visible within that depth limit.
Quote or escape wildcard patterns in shells such as zsh. Otherwise the shell
may expand or reject the pattern before lsp starts, which is the same
limitation GNU ls has for unquoted wildcards.
In both recursive and tree output, --level <N> counts visible entry levels
below each operand. For example, --level 1 shows only entries directly under
the requested directory, while --level 2 also shows grandchildren.
Use --prune-noisy-dirs with recursive or tree output to list common noisy
directories in their parent but skip their descendants. The built-in preset
matches .git, .hg, .svn, node_modules, and __pycache__ by exact
basename:
lsp -R --prune-noisy-dirs project
lsp --tree --prune-noisy-dirs project
Use --prune-dir <NAME> for custom basenames. Repeat the option to add more
names. Custom names also work without the built-in preset:
lsp --tree --prune-dir target --prune-dir dist project
Pruning only controls recursive descent. It does not hide matching directories from their parent listing, and it does not apply to explicit directory operands.
The indicator characters are:
/for directories@for symlinks|for FIFOs=for sockets*for executables, but only with-F/--classify
In long format, native mode omits the symlink @ marker because name -> target and the symlink styling already make the type clear. This also matches
GNU ls, which does not append @ to symlink names in long format.
Styled output is enabled automatically when writing to a terminal. Captured,
piped, and redirected output is plain by default. You can also disable styled
output explicitly with --no-color, no_color = true in the config file, or
the NO_COLOR environment variable.
Long-format output shows symbolic permissions by default. Use
--permissions octal to replace them with octal permission bits,
--permissions both to add octal bits after the symbolic field, or
--permissions none to omit permission fields.
On Windows, long format shows native file attributes for symbolic display.
octal and both are unsupported with long output; use symbolic or none.
Use --header with long-format output to add a title row for the active
columns. It has no effect on short output. In the config file, set
header = true alongside long_format = true or tree = true.
Long-format output colors permission bits, timestamp freshness, and large file
sizes by default. You can adjust those accents independently with
--no-permission-colors, --no-time-gradient, --no-size-colors, or the
matching permission_colors = false, time_gradient = false, and
size_colors = false config options.
Timestamp colors adapt to terminal color capability. Truecolor terminals use a
smooth age gradient and 256-color terminals use a stepped fallback to
distinguish files newer than a day, week, month, and year. Basic ANSI terminals
use named yellow styling. Disabling time_gradient keeps normal timestamps on
the original fixed timestamp color. Future-dated timestamps stay red to make
clock-skewed files stand out.
When -I is enabled, lsp checks the same ignore sources Git normally uses:
merged .gitignore files in the worktree, .git/info/exclude, and the
configured global Git excludes file.
Compatibility Mode
lsp has two CLI modes:
native- the defaultlspluscommand-line interfacegnu- a GNUlscompatibility mode intended for aliases and scripts
You can enable GNU compatibility mode by setting compat_mode = "gnu" in the
config file or by setting LSP_COMPAT_MODE=gnu in the environment. The
environment variable takes precedence over the config file.
At the moment, gnu mode changes the CLI surface and help output only. It does
not yet implement the missing GNU meanings for the conflicting short flags
-D, -I, -N, and -Z; those flags are reserved in gnu mode and will
error until their GNU behavior is implemented.
The current lsplus features behind those four native short flags are still
available in gnu mode through their long forms only:
--group-directories-first(replaces the original--sort-dirs)--gitignore--no-color--fuzzy-time
GNU indicator options are also available in gnu mode:
-p/--indicator-style=slash--file-type/--indicator-style=file-type-F/--indicator-style=classify--indicator-style=none
Fuzzy Time
The -Z option shows file modification times in a human-readable format, e.g.
‘2 hours ago’, ‘yesterday’, etc.
Icons
lsp shows icons for folders, files, and links. The current mappings cover
common names and extensions. Open an issue or PR if you want another icon.
Disable icons with the --no-icons option.
Aliases
Add this line to .bashrc, .zshrc, or a similar file to alias ls to lsp:
alias ls='lsp'
If you want that alias to behave more like GNU ls, enable gnu
compatibility mode in your config file or set LSP_COMPAT_MODE=gnu in your
shell environment.
Restart your shell or source your configuration file to load the alias.
This alias enables several common options:
alias ll='lsp -laph'
This shows a long-format listing with hidden files, appends / to directories,
and shows human-readable file sizes.
On Windows, PowerShell users can add Linux-like commands to their PowerShell
profile ($PROFILE):
Set-Alias -Name ls -Value lsp -Force
function ll { lsp -l @args }
Restart PowerShell or source the profile for the commands to take effect.
Command Prompt users can create equivalent macros with doskey:
doskey ls=lsp $*
doskey ll=lsp -l $*
To make these macros persistent, store them without the doskey prefix in
%USERPROFILE%\doskey.macros:
ls=lsp $*
ll=lsp -l $*
Then configure Command Prompt to load the file automatically:
reg add "HKCU\Software\Microsoft\Command Processor" /v AutoRun /t REG_EXPAND_SZ /d "doskey /macrofile=\"^%USERPROFILE^%\doskey.macros\"" /f
This replaces any existing AutoRun command, so check it first:
reg query "HKCU\Software\Microsoft\Command Processor" /v AutoRun
If an AutoRun value already exists, copy only the command text after its type
(REG_EXPAND_SZ or REG_SZ) from the reg query output, not the full output.
Escape each " in that command as \", then replace existing command below
to chain the commands with &:
reg add "HKCU\Software\Microsoft\Command Processor" /v AutoRun /t REG_EXPAND_SZ /d "doskey /macrofile=\"^%USERPROFILE^%\doskey.macros\" & existing command" /f
Set default options in the configuration file.
Add -D in native mode, or --group-directories-first in GNU mode, to sort
directories first:
Configuration File
Configure lsplus with a TOML file at:
- Linux:
~/.config/lsplus/config.toml - macOS:
~/.config/lsplus/config.toml - Windows:
%APPDATA%\\lsplus\\config.toml
The configuration file is optional. lsplus uses default settings when the file
does not exist.
lsplus also supports an LSP_COMPAT_MODE environment variable. When set, it
overrides the compat_mode value from the config file.
Available Options
The configuration file supports these command-line options:
compat_mode
- Permitted values:
"native"or"gnu" - Default value:
"native"
This option selects which command-line interface lsp uses at startup.
native keeps the standard lsplus CLI, while gnu enables the GNU ls
compatibility surface intended for aliases and scripts.
At the moment, gnu mode changes the CLI surface and help output only. The
conflicting GNU short flags -D, -I, -N, and -Z are reserved in that
mode and will error until their GNU behavior is implemented.
show_all
- Permitted values:
trueorfalse - Default value:
false
This option corresponds to -a or --all and displays hidden files when set to
true.
almost_all
- Permitted values:
trueorfalse - Default value:
false
This option corresponds to -A or --almost-all and displays hidden files
except . and .. when set to true.
indicator_style
- Permitted values:
"none","slash","file-type", or"classify" - Default value:
"none"
This option selects which file type indicators lsp appends to entry names.
In native mode, the related CLI options are -p / --slash-dirs,
--file-type, -F / --classify, and --no-indicators. In gnu
compatibility mode, the equivalent GNU forms are -p,
--indicator-style=slash, --file-type,
--indicator-style=file-type, -F, --indicator-style=classify, and
--indicator-style=none.
The indicator characters are / for directories, @ for symlinks, | for
FIFOs, = for sockets, and * for executables. The * executable marker is
only added by "classify".
In long format, native mode omits the symlink @ marker because name -> target and the symlink styling already make the type clear. This also matches
GNU ls, which does not append @ to symlink names in long format.
For backward compatibility, append_slash = true is still accepted in the
config file and maps to indicator_style = "slash". If both are present,
indicator_style takes precedence.
dirs_first
- Permitted values:
trueorfalse - Default value:
false
This option corresponds to --sort-dirs and sorts directories before files when
set to true. In gnu compatibility mode, the
equivalent long option is --group-directories-first (replacing the original
--sort-dirs).
long_format
- Permitted values:
trueorfalse - Default value:
false
This option corresponds to --long and displays output in long format when set
to true.
header
- Permitted values:
trueorfalse - Default value:
false
This option corresponds to --header and adds a title row to long-format
output when set to true. It only affects long-format output, so use it with
long_format = true or tree = true.
human_readable
- Permitted values:
trueorfalse - Default value:
false
This option corresponds to -h or --human-readable and displays
human-readable file sizes using powers of 1024 when set to true.
si
- Permitted values:
trueorfalse - Default value:
false
This option corresponds to --si and displays human-readable file sizes using
powers of 1000 when set to true. It also enables human-readable size output.
recursive
- Permitted values:
trueorfalse - Default value:
false
This option corresponds to -R or --recursive and lists subdirectories in
separate path: sections.
tree
- Permitted values:
trueorfalse - Default value:
false
This option corresponds to --tree and displays long-format tree output. Tree
output implies long format.
tree_level
- Permitted values: integers greater than or equal to
1 - Default value:
2
This option corresponds to --level and limits recursive or tree output to
visible entry levels below each operand. A value of 1 shows only entries
directly under the requested directory; a value of 2 also shows
grandchildren. Without this option, recursive output is unlimited.
prune_noisy_dirs
- Permitted values:
trueorfalse - Default value:
false
This option enables the built-in traversal prune preset for recursive and tree
output. Matching directories still appear in their parent listing, but lsp
does not descend into them. The preset matches these exact basenames:
.git.hg.svnnode_modules__pycache__
Pruning only applies while traversing children for recursive = true or
tree = true. It does not hide matching entries, does not apply to explicit
directory operands, and is not disabled by show_all = true.
prune_dirs
- Permitted values: an array of strings
- Default value:
[]
This option adds custom exact directory basenames to skip while traversing
recursive and tree output. Custom prune names apply even when
prune_noisy_dirs = false.
For example:
prune_dirs = ["target", "dist"]
no_icons
- Permitted values:
trueorfalse - Default value:
false
This option corresponds to --no-icons and hides icons when set to true.
no_color
- Permitted values:
trueorfalse - Default value:
false
This option corresponds to -N or --no-color and disables colored and styled
output when set to true.
permission_colors
- Permitted values:
trueorfalse - Default value:
true
This option controls long-format colors for the file type character and
permission bits. Set it to false, or pass --no-permission-colors, to render
those fields without accent colors.
permissions
- Permitted values:
symbolic,octal,both, ornone - Default value:
symbolic
This option corresponds to --permissions and controls long-format permission
fields. symbolic shows the default file type character and symbolic
permissions, octal replaces that field with the file type character and
four-digit octal permission bits, both adds an octal permission cell after
the symbolic field, and none omits permission fields.
On Windows, symbolic shows a readable file-attribute column instead. octal
and both are unsupported when long format is active; use symbolic or
none.
time_gradient
- Permitted values:
trueorfalse - Default value:
true
This option controls long-format timestamp freshness colors. Set it to false,
or pass --no-time-gradient, to use the fixed timestamp color instead of
age-based timestamp colors.
Timestamp colors adapt to terminal color capability: truecolor terminals use a
smooth age gradient and 256-color terminals use a stepped fallback to
distinguish day, week, month, and year bands. Basic ANSI terminals use named
yellow styling. Terminals without color support render timestamps plainly.
Future-dated timestamps stay red, even with time_gradient disabled, to make
clock-skewed files stand out.
size_colors
- Permitted values:
trueorfalse - Default value:
true
This option controls long-format large-size colors. Set it to false, or pass
--no-size-colors, to render sizes without large-file accents.
gitignore
- Permitted values:
trueorfalse - Default value:
false
This option corresponds to -I or --gitignore and dims entries matched by the
active Git ignore rules, including merged .gitignore files,
.git/info/exclude, and the configured global Git excludes file.
fuzzy_time
- Permitted values:
trueorfalse - Default value:
false
This option corresponds to -Z or --fuzzy-time and displays timestamps in a
fuzzy format when set to true.
Example Configuration File
This example sets several options. Omitted options use default values:
# compat_mode = "native" # or "gnu" for GNU ls compatibility
show_all = true
indicator_style = "classify"
dirs_first = true
long_format = true
# header = true
human_readable = true
# si = true
# recursive = true
# tree = true
# tree_level = 2
# prune_noisy_dirs = true
# prune_dirs = ["target", "dist"]
no_color = true
permission_colors = false
permissions = "symbolic"
time_gradient = false
size_colors = false
fuzzy_time = true
gitignore = true
Future Plans
This page includes the project TODO list.
Open a GitHub issue to suggest another feature or improvement.
TODO
- migrate docs from
mdbooktozensical - complete code coverage - 83 lines uncovered at last run
- add
zizmorlinting to the GitHub actions - both local and as an action itself. Usehttps://github.com/GitHubSecurityLab/actions-permissionto help. - after hardening the release workflow permissions, switch both local and
CI
zizmorruns to thepedanticpersona - add colorization for different file types, folders and symlinks. Make it customizable and theme-able. Make it default but allow an option to disable it (or vice-versa). Files that have a known extension should all be colored the same way, and different to unknown file tipes.
- Add icons for partials like
TODO.*,LICENSE.*and more - brands like claude, codex, vscode and more where the nerdfont exists - using the config file, allow extending the existing file and folder mapping, or deleting specific maps.
- When adding recursion or tree-style output, revisit whether directory
traversal should move over to the
ignorecrate instead of the current custom walker. - Unify recursive and tree traversal policy behind a shared walker so depth limits, symlink handling, pruning, and error handling cannot drift between output modes.
- Revisit recursive operand error semantics so explicit file-operand stat errors can be reported without aborting later directory walks.
- Avoid duplicate stderr for already-reported recursive traversal errors while still returning a non-zero exit status.
- Add configurable tree rendering styles, including the current compact root display, classic root branch graphics, and an ASCII fallback.
- Consider GNU-style
totallines or another consistent empty-directory marker for long and tree output, rather than special-casing single-root tree output. - Add inode and allocated block size support for long-format output.
- Add explicit long-format header modes, keeping plain
--headeras an alias for per-section headers. Suggested modes:sectionfor every recursive section,oncefor the first long-format table only, andnoneto disable config-enabled headers for one invocation. - Evaluate the Rust crate
uutils-term-gridas a short-format layout alternative before expanding the current custom grid code. - Improve listing performance with focused architecture changes, in this
order:
1. Add a short-format entry model so short output does not build full
long-format
FileInfodata such as owner/group names, permissions, size, mtime, and long symlink target text. 2. Pass a shared buffered stdout writer through render paths so recursive streaming does not pay for many small stdout writes. 3. Cache UID-to-user and GID-to-group lookups during long-format runs. 4. Carry cheapDirEntry::file_type()data through directory filtering and sorting so short mode can avoid extra metadata calls where possible. 5. Avoid per-entrysymlink_metadataon Unix whereDirEntry::file_typeand entry-name visibility rules are sufficient, while preserving the Windows reparse and hidden-attribute classification path. 6. Cache platform-native entry-name sort keys before sorting, so Windows avoids repeated UTF-16 allocation and ordinal comparisons. 7. CarryEntryClassificationfrom directory collection intoFileInfoconstruction so Windows reparse tags are not queried twice per entry. 8. Avoidcurrent_dir()lookup for already-absolute Windows paths before callingFindFirstFileWfor reparse classification. 9. Reuse a singleGitignoreCacheacross recursive and tree traversal so ancestor ignore files are not rediscovered for every directory. 10. Revisitprettytablefor long recursive output; a custom row formatter may be leaner for hot paths. - better handle dotfiles?
- option to list dotfiles (and folders) before non-dotfiles
- Investigate an optional name-shortening mode for very long filenames that preserves extensions without changing the default wrap behavior.
- Consider separating config-loaded values from effective runtime params so
merge behavior is more explicit than the current shared
Paramstype. - Detect terminal color capability once at startup and pass the effective
color mode/level through render paths, instead of re-checking
colored_textcapability while building long-format tables. Refactorlsplusrender tests to inject explicit color levels rather than mutatingTERM,NO_COLOR, orCOLORTERM; leave the env/terminal detection matrix tocolored_text. - Extend human-readable size units beyond petabytes so exabyte-scale values
render as
Einstead of largePmultiples. - Review
src/lib.rsand crate/module visibility. Keep the current out-of-source unit test layout, but reduce the accidental public library API for this app crate where modules/items do not need to be exported. - Improve rustdoc/docstring coverage in a dedicated docs PR. Start by
running
RUSTDOCFLAGS='-D missing-docs' cargo doc --no-deps; current known gaps include the publicstructsmodule export and publicIconenum variants. - Continue shifting tests toward behavior-focused checks at module seams
(
app,settings,render) instead of broad smoke-style coverage. - Evaluate
rstestfor table-driven tests/fixtures andserial_testfor tests that mutate process-global state such as color mode or environment variables. - Consider deriving or implementing
Defaultforcli::Flagsso tests can use struct update syntax instead of hand-rolling every flag field. Keep the parsed default path behavior (".") explicit in tests that need it.