Merge remote-tracking branch 'upstream/main'

This commit is contained in:
fwt 2024-07-10 17:21:43 +08:00
commit a0534c0900
268 changed files with 6562 additions and 3075 deletions

View file

@ -13,6 +13,8 @@ body:
- macOS - macOS
- Windows - Windows
- Windows WSL - Windows WSL
- FreeBSD X11
- FreeBSD Wayland
validations: validations:
required: true required: true
- type: input - type: input
@ -22,31 +24,32 @@ body:
placeholder: "ex: kitty v0.32.2" placeholder: "ex: kitty v0.32.2"
validations: validations:
required: true required: true
- type: dropdown
id: tried_main
attributes:
label: Did you try the latest code to see if this problem got fixed?
options:
- Tried, but the problem still
- Not tried, and I'll explain why below
validations:
required: true
- type: textarea - type: textarea
id: debug id: debug
attributes: attributes:
label: "`yazi --debug` output" label: "`yazi --debug` output"
description: Please do a `yazi --debug` and paste the output here. description: Please run `yazi --debug` and paste the debug information here.
value: | value: |
<details> <details>
<!-- Paste the output between the backticks below: -->
```sh ```sh
##### ↓↓↓ Paste the output here: ↓↓↓ #####
``` ```
</details> </details>
validations: validations:
required: true required: true
- type: dropdown
id: tried_main
attributes:
label: Did you try the latest nightly build to see if the problem got fixed?
options:
- Yes, and I updated the debug information above (`yazi --debug`) to the nightly that I tried
- No, and I'll explain why below
validations:
required: true
- type: textarea - type: textarea
id: description id: description
attributes: attributes:

View file

@ -39,3 +39,13 @@ jobs:
- name: Rustfmt - name: Rustfmt
run: cargo +nightly fmt --all -- --check run: cargo +nightly fmt --all -- --check
stylua:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: JohnnyMorganz/stylua-action@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
version: latest
args: --color always --check .

View file

@ -24,7 +24,10 @@ jobs:
- name: Install gcc-aarch64-linux-gnu - name: Install gcc-aarch64-linux-gnu
if: matrix.target == 'aarch64-unknown-linux-gnu' if: matrix.target == 'aarch64-unknown-linux-gnu'
run: sudo apt-get update && sudo apt-get install -yq gcc-aarch64-linux-gnu run: |
sudo apt-get update && sudo apt-get install -yq gcc-aarch64-linux-gnu
echo "JEMALLOC_SYS_WITH_LG_PAGE=16" >> $GITHUB_ENV
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=/usr/bin/aarch64-linux-gnu-gcc" >> $GITHUB_ENV
- name: Build - name: Build
run: ./scripts/build.sh ${{ matrix.target }} run: ./scripts/build.sh ${{ matrix.target }}

View file

@ -28,4 +28,4 @@ jobs:
run: cargo build --verbose run: cargo build --verbose
- name: Test - name: Test
run: cargo test --verbose run: cargo test --all --verbose

2
.styluaignore Normal file
View file

@ -0,0 +1,2 @@
# this file caused some issues with the build system
yazi-plugin/preset/plugins/mime.lua

146
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,146 @@
# Contributing to Yazi
Thank you for your interest in contributing to Yazi! We welcome contributions in the form of bug reports, feature requests, documentation improvements, and code changes.
This guide will help you understand how to contribute to the project.
## Table of Contents
1. [Getting Started](#getting-started)
2. [Project Structure](#project-structure)
3. [Development Setup](#development-setup)
4. [How to Contribute](#how-to-contribute)
5. [Pull Request Process](#pull-request-process)
## Getting Started
### Prerequisites
Before you begin, ensure you have met the following requirements:
- Rust installed on your machine. You can download it from [rustup.rs](https://rustup.rs).
- Familiarity with Git and GitHub.
### Fork the Repository
1. Fork the [Yazi repository](https://github.com/sxyazi/yazi) to your GitHub account.
2. Clone your fork to your local machine:
```sh
git clone https://github.com/<your-username>/yazi.git
```
3. Set up the upstream remote:
```sh
git remote add upstream https://github.com/sxyazi/yazi.git
```
## Project Structure
A brief overview of the project's structure:
```sh
.
├── assets/ # Assets like images and fonts
├── nix/ # Nix-related configurations
├── scripts/ # Helper scripts used by CI/CD
├── snap/ # Snapcraft configuration
├── yazi-adapter/ # Yazi image adapter
├── yazi-boot/ # Yazi bootstrapper
├── yazi-cli/ # Yazi command-line interface
├── yazi-config/ # Yazi configuration file parser
├── yazi-core/ # Yazi core logic
├── yazi-dds/ # Yazi data distribution service
├── yazi-fm/ # Yazi file manager
├── yazi-plugin/ # Yazi plugin system
├── yazi-proxy/ # Yazi event proxy
├── yazi-scheduler/ # Yazi task scheduler
├── yazi-shared/ # Yazi shared library
├── .github/ # GitHub-specific files and workflows
├── Cargo.toml # Rust workflow configuration
└── README.md # Project overview
```
## Development Setup
1. Ensure the latest stable Rust is installed:
```sh
rustc --version
cargo --version
```
2. Build the project:
```sh
cargo build
```
3. Run the tests:
```sh
cargo test
```
4. Format the code (requires `rustfmt` nightly):
```sh
rustup component add rustfmt --toolchain nightly
rustfmt +nightly **/*.rs
```
## How to Contribute
### Reporting Bugs
If you find a bug, please file an issue.
### Suggesting Features
If you have a feature request, please file an issue.
### Improving Documentation
Yazi's documentation placed at [yazi-rs/yazi-rs.github.io](https://github.com/yazi-rs/yazi-rs.github.io), contributions related to documentation need to be made within this repository.
### Submitting Code Changes
1. Create a new branch for your changes:
```sh
git checkout -b your-branch-name
```
2. Make your changes. Ensure that your code follows the project's [coding style](https://github.com/sxyazi/yazi/blob/main/rustfmt.toml) and passes all tests.
3. Commit your changes with a descriptive commit message:
```sh
git commit -m "feat: an awesome feature"
```
4. Push your changes to your fork:
```sh
git push origin your-branch-name
```
## Pull Request Process
1. Ensure your fork is up-to-date with the upstream repository:
```sh
git fetch upstream
git checkout main
git merge upstream/main
```
2. Rebase your feature branch onto the main branch:
```sh
git checkout your-branch-name
git rebase main
```
3. Create a pull request to the `main` branch of the upstream repository. Follow the pull request template and ensure that:
- Your code passes all tests and lints.
- Your pull request description clearly explains the changes and why they are needed.
4. Address any review comments. Make sure to push updates to the same branch on your fork.

662
Cargo.lock generated

File diff suppressed because it is too large Load diff

21
LICENSE-ICONS Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 nvim-tree
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -12,13 +12,14 @@ Yazi (means "duck") is a terminal file manager written in Rust, based on non-blo
- 💪 **Powerful Async Task Scheduling and Management**: Provides real-time progress updates, task cancellation, and internal task priority assignment. - 💪 **Powerful Async Task Scheduling and Management**: Provides real-time progress updates, task cancellation, and internal task priority assignment.
- 🖼️ **Built-in Support for Multiple Image Protocols**: Also integrated with Überzug++, covering almost all terminals. - 🖼️ **Built-in Support for Multiple Image Protocols**: Also integrated with Überzug++, covering almost all terminals.
- 🌟 **Built-in Code Highlighting and Image Decoding**: Combined with the pre-loading mechanism, greatly accelerates image and normal file loading. - 🌟 **Built-in Code Highlighting and Image Decoding**: Combined with the pre-loading mechanism, greatly accelerates image and normal file loading.
- 🔌 **Concurrent Plugin System**: UI plugins (rewriting most of the UI), functional plugins, custom previewer, and custom preloader; Just some pieces of Lua. - 🔌 **Concurrent Plugin System**: UI plugins (rewriting most of the UI), functional plugins, custom previewer/preloader/fetcher; Just some pieces of Lua.
- 📡 **Data Distribution Service**: Built on a client-server architecture (no additional server process required), integrated with a Lua-based publish-subscribe model, achieving cross-instance communication and state persistence. - 📡 **Data Distribution Service**: Built on a client-server architecture (no additional server process required), integrated with a Lua-based publish-subscribe model, achieving cross-instance communication and state persistence.
- 📦 **Package Manager**: Install plugins and themes with one command, keeping them always up to date, or pin them to a specific version.
- 🧰 Integration with fd, rg, fzf, zoxide - 🧰 Integration with fd, rg, fzf, zoxide
- 💫 Vim-like input/select/which/notify component, auto-completion for cd paths - 💫 Vim-like input/select/which/notify component, auto-completion for cd paths
- 🏷️ Multi-Tab Support, Cross-directory selection, Scrollable Preview (for videos, PDFs, archives, directories, code, etc.) - 🏷️ Multi-Tab Support, Cross-directory selection, Scrollable Preview (for videos, PDFs, archives, directories, code, etc.)
- 🔄 Bulk Renaming, Visual Mode, File Chooser - 🔄 Bulk Renaming, Visual Mode, File Chooser
- 🎨 Theme System, Custom Layouts, Trash Bin, CSI u - 🎨 Theme System, Mouse Support, Trash Bin, Custom Layouts, CSI u
- ... and more! - ... and more!
https://github.com/sxyazi/yazi/assets/17523360/92ff23fa-0cd5-4f04-b387-894c12265cc7 https://github.com/sxyazi/yazi/assets/17523360/92ff23fa-0cd5-4f04-b387-894c12265cc7
@ -37,21 +38,21 @@ https://github.com/sxyazi/yazi/assets/17523360/92ff23fa-0cd5-4f04-b387-894c12265
## Image Preview ## Image Preview
| Platform | Protocol | Support | | Platform | Protocol | Support |
| ----------------- | ----------------------------------------------------------------------------------------------------- | --------------------- | | ----------------- | ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| kitty | [Kitty unicode placeholders](https://sw.kovidgoyal.net/kitty/graphics-protocol/#unicode-placeholders) | ✅ Built-in | | kitty | [Kitty unicode placeholders](https://sw.kovidgoyal.net/kitty/graphics-protocol/#unicode-placeholders) | ✅ Built-in |
| Konsole | [Kitty old protocol](https://github.com/sxyazi/yazi/blob/main/yazi-adaptor/src/kitty_old.rs) | ✅ Built-in | | Konsole | [Kitty old protocol](https://github.com/sxyazi/yazi/blob/main/yazi-adapter/src/kitty_old.rs) | ✅ Built-in |
| iTerm2 | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in | | iTerm2 | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in |
| WezTerm | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in | | WezTerm | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in |
| Mintty (Git Bash) | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in | | Mintty (Git Bash) | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in |
| foot | [Sixel graphics format](https://www.vt100.net/docs/vt3xx-gp/chapter14.html) | ✅ Built-in | | foot | [Sixel graphics format](https://www.vt100.net/docs/vt3xx-gp/chapter14.html) | ✅ Built-in |
| Ghostty | [Kitty old protocol](https://github.com/sxyazi/yazi/blob/main/yazi-adaptor/src/kitty_old.rs) | ✅ Built-in | | Ghostty | [Kitty old protocol](https://github.com/sxyazi/yazi/blob/main/yazi-adapter/src/kitty_old.rs) | ✅ Built-in |
| Black Box | [Sixel graphics format](https://www.vt100.net/docs/vt3xx-gp/chapter14.html) | ✅ Built-in | | Black Box | [Sixel graphics format](https://www.vt100.net/docs/vt3xx-gp/chapter14.html) | ✅ Built-in |
| VSCode | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in | | VSCode | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in |
| Tabby | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in | | Tabby | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in |
| Hyper | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in | | Hyper | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in |
| X11 / Wayland | Window system protocol | ☑️ Überzug++ required | | X11 / Wayland | Window system protocol | ☑️ [Überzug++](https://github.com/jstkdng/ueberzugpp) required |
| Fallback | [Chafa](https://hpjansson.org/chafa/) | ☑️ Überzug++ required | | Fallback | [ASCII art (Unicode block)](https://en.wikipedia.org/wiki/ASCII_art) | ☑️ [Chafa](https://hpjansson.org/chafa/) required |
See https://yazi-rs.github.io/docs/image-preview for details. See https://yazi-rs.github.io/docs/image-preview for details.

View file

@ -1 +1 @@
{"version":"0.2","flagWords":[],"language":"en","words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp"," Überzug"," Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","imagesize","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime"]} {"flagWords":[],"language":"en","words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp"," Überzug"," Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","imagesize","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking"],"version":"0.2"}

12
flake.lock generated
View file

@ -20,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1713805509, "lastModified": 1716097317,
"narHash": "sha256-YgSEan4CcrjivCNO5ZNzhg7/8ViLkZ4CB/GrGBVSudo=", "narHash": "sha256-1UMrLtgzielG/Sop6gl6oTSM4pDt7rF9j9VuxhDWDlY=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "1e1dc66fe68972a76679644a5577828b6a7e8be4", "rev": "8535fb92661f37ff9f0da3007fbc942f7d134b41",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -51,11 +51,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1713924823, "lastModified": 1716085073,
"narHash": "sha256-kOeyS3GFwgnKvzuBMmFqEAX0xwZ7Nj4/5tXuvpZ0d4U=", "narHash": "sha256-3+9gI93XxszWA2+9S2xZfws1QArPX/MC6nahOGpcMB4=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "8a2edac3ae926a2a6ce60f4595dcc4540fc8cad4", "rev": "cfc8776011bd83508324115d353222475e1601c0",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -34,7 +34,7 @@
# Resize logo # Resize logo
for RES in 16 24 32 48 64 128 256; do for RES in 16 24 32 48 64 128 256; do
mkdir -p $out/share/icons/hicolor/"$RES"x"$RES"/apps mkdir -p $out/share/icons/hicolor/"$RES"x"$RES"/apps
convert assets/logo.png -resize "$RES"x"$RES" $out/share/icons/hicolor/"$RES"x"$RES"/apps/yazi.png magick assets/logo.png -resize "$RES"x"$RES" $out/share/icons/hicolor/"$RES"x"$RES"/apps/yazi.png
done done
mkdir -p $out/share/applications mkdir -p $out/share/applications

View file

@ -3,7 +3,6 @@ set -euo pipefail
export ARTIFACT_NAME="yazi-$1" export ARTIFACT_NAME="yazi-$1"
export YAZI_GEN_COMPLETIONS=1 export YAZI_GEN_COMPLETIONS=1
export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=/usr/bin/aarch64-linux-gnu-gcc
# Setup Rust toolchain # Setup Rust toolchain
if [[ "$1" == *-musl ]]; then if [[ "$1" == *-musl ]]; then

View file

@ -0,0 +1,36 @@
local dark = require("icons-default")
local light = require("icons-light")
function rearrange(by)
local map = {}
local source = by == "exts" and "icons_by_file_extension" or "icons_by_filename"
for k, v in pairs(dark[source]) do
map[k] = map[k] or {}
map[k].icon = v.icon
map[k].fg_dark = v.color:lower()
end
for k, v in pairs(light[source]) do
map[k].fg_light = v.color:lower()
end
return map
end
function dump(map)
local list = {}
for k, v in pairs(map) do
list[#list + 1] = { name = k, text = v.icon, fg_dark = v.fg_dark, fg_light = v.fg_light }
end
table.sort(list, function(a, b) return a.name:lower() < b.name:lower() end)
for _, v in ipairs(list) do
-- stylua: ignore
print(string.format('\t{ name = "%s", text = "%s", fg_dark = "%s", fg_light = "%s" },', v.name, v.text, v.fg_dark, v.fg_light))
end
end
print("files = [")
dump(rearrange("files"))
print("]")
print("exts = [")
dump(rearrange("exts"))
print("]")

View file

@ -1,9 +1,11 @@
cargo publish -p yazi-shared cargo publish -p yazi-shared
cargo publish -p yazi-config cargo publish -p yazi-config
cargo publish -p yazi-proxy cargo publish -p yazi-proxy
cargo publish -p yazi-adaptor cargo publish -p yazi-adapter
cargo publish -p yazi-boot cargo publish -p yazi-boot
cargo publish -p yazi-dds
cargo publish -p yazi-scheduler cargo publish -p yazi-scheduler
cargo publish -p yazi-plugin cargo publish -p yazi-plugin
cargo publish -p yazi-core cargo publish -p yazi-core
cargo publish -p yazi-fm cargo publish -p yazi-fm
cargo publish -p yazi-cli

6
stylua.toml Normal file
View file

@ -0,0 +1,6 @@
indent_width = 2
call_parentheses = "NoSingleTable"
collapse_simple_statement = "FunctionOnly"
[sort_requires]
enabled = true

View file

@ -1,10 +1,10 @@
[package] [package]
name = "yazi-adaptor" name = "yazi-adapter"
version = "0.2.5" version = "0.2.5"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
authors = [ "sxyazi <sxyazi@gmail.com>" ] authors = [ "sxyazi <sxyazi@gmail.com>" ]
description = "Yazi image adaptor" description = "Yazi image adapter"
homepage = "https://yazi-rs.github.io" homepage = "https://yazi-rs.github.io"
repository = "https://github.com/sxyazi/yazi" repository = "https://github.com/sxyazi/yazi"
@ -13,17 +13,19 @@ yazi-config = { path = "../yazi-config", version = "0.2.5" }
yazi-shared = { path = "../yazi-shared", version = "0.2.5" } yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies # External dependencies
anyhow = "1.0.82" ansi-to-tui = "=3.1.0"
anyhow = "1.0.86"
arc-swap = "1.7.1" arc-swap = "1.7.1"
base64 = "0.22.0" base64 = "0.22.1"
color_quant = "1.1.0" color_quant = "1.1.0"
crossterm = "0.27.0" crossterm = "0.27.0"
futures = "0.3.30" futures = "0.3.30"
image = "0.24.9" image = "=0.24.9"
imagesize = "0.12.0" imagesize = "0.13.0"
kamadak-exif = "0.5.5" kamadak-exif = "0.5.5"
ratatui = "=0.26.1" ratatui = "0.27.0"
tokio = { version = "1.37.0", features = [ "full" ] } scopeguard = "1.2.0"
tokio = { version = "1.38.0", features = [ "full" ] }
# Logging # Logging
tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] } tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] }

View file

@ -3,13 +3,13 @@ use std::{env, fmt::Display, path::Path, sync::Arc};
use anyhow::Result; use anyhow::Result;
use ratatui::layout::Rect; use ratatui::layout::Rect;
use tracing::warn; use tracing::warn;
use yazi_shared::{env_exists, term::Term}; use yazi_shared::env_exists;
use super::{Iterm2, Kitty, KittyOld}; use super::{Iterm2, Kitty, KittyOld};
use crate::{ueberzug::Ueberzug, Emulator, Sixel, SHOWN, TMUX}; use crate::{Chafa, Emulator, Sixel, Ueberzug, SHOWN, TMUX};
#[derive(Clone, Copy, PartialEq, Eq, Debug)] #[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Adaptor { pub enum Adapter {
Kitty, Kitty,
KittyOld, KittyOld,
Iterm2, Iterm2,
@ -21,7 +21,7 @@ pub enum Adaptor {
Chafa, Chafa,
} }
impl Display for Adaptor { impl Display for Adapter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::Kitty => write!(f, "kitty"), Self::Kitty => write!(f, "kitty"),
@ -35,49 +35,44 @@ impl Display for Adaptor {
} }
} }
impl Adaptor { impl Adapter {
pub async fn image_show(self, path: &Path, rect: Rect) -> Result<(u32, u32)> { pub async fn image_show(self, path: &Path, max: Rect) -> Result<Rect> {
if max.is_empty() {
return Ok(Rect::default());
}
match self { match self {
Self::Kitty => Kitty::image_show(path, rect).await, Self::Kitty => Kitty::image_show(path, max).await,
Self::KittyOld => KittyOld::image_show(path, rect).await, Self::KittyOld => KittyOld::image_show(path, max).await,
Self::Iterm2 => Iterm2::image_show(path, rect).await, Self::Iterm2 => Iterm2::image_show(path, max).await,
Self::Sixel => Sixel::image_show(path, rect).await, Self::Sixel => Sixel::image_show(path, max).await,
_ => Ueberzug::image_show(path, rect).await, Self::X11 | Self::Wayland => Ueberzug::image_show(path, max).await,
Self::Chafa => Chafa::image_show(path, max).await,
} }
} }
pub fn image_hide(self) -> Result<()> { pub fn image_hide(self) -> Result<()> {
if let Some(rect) = SHOWN.swap(None) { self.image_erase(*rect) } else { Ok(()) } if let Some(area) = SHOWN.swap(None) { self.image_erase(*area) } else { Ok(()) }
} }
pub fn image_erase(self, rect: Rect) -> Result<()> { pub fn image_erase(self, area: Rect) -> Result<()> {
match self { match self {
Self::Kitty => Kitty::image_erase(rect), Self::Kitty => Kitty::image_erase(area),
Self::Iterm2 => Iterm2::image_erase(rect), Self::Iterm2 => Iterm2::image_erase(area),
Self::KittyOld => KittyOld::image_erase(), Self::KittyOld => KittyOld::image_erase(area),
Self::Sixel => Sixel::image_erase(rect), Self::Sixel => Sixel::image_erase(area),
_ => Ueberzug::image_erase(rect), Self::X11 | Self::Wayland => Ueberzug::image_erase(area),
Self::Chafa => Chafa::image_erase(area),
} }
} }
#[inline] #[inline]
pub fn shown_load(self) -> Option<Rect> { SHOWN.load_full().map(|r| *r) } pub fn shown_load(self) -> Option<Rect> { SHOWN.load_full().map(|r| *r) }
pub(super) fn start(self) { Ueberzug::start(self); }
#[inline] #[inline]
pub(super) fn shown_store(rect: Rect, size: (u32, u32)) { pub(super) fn shown_store(area: Rect) { SHOWN.store(Some(Arc::new(area))); }
SHOWN.store(Some(Arc::new(
Term::ratio() pub(super) fn start(self) { Ueberzug::start(self); }
.map(|(r1, r2)| Rect {
x: rect.x,
y: rect.y,
width: (size.0 as f64 / r1).ceil() as u16,
height: (size.1 as f64 / r2).ceil() as u16,
})
.unwrap_or(rect),
)));
}
#[inline] #[inline]
pub(super) fn needs_ueberzug(self) -> bool { pub(super) fn needs_ueberzug(self) -> bool {
@ -85,7 +80,7 @@ impl Adaptor {
} }
} }
impl Adaptor { impl Adapter {
pub fn matches() -> Self { pub fn matches() -> Self {
let mut protocols = Emulator::detect().adapters(); let mut protocols = Emulator::detect().adapters();
@ -93,8 +88,7 @@ impl Adaptor {
protocols.retain(|p| *p == Self::Iterm2); protocols.retain(|p| *p == Self::Iterm2);
if env_exists("ZELLIJ_SESSION_NAME") { if env_exists("ZELLIJ_SESSION_NAME") {
protocols.retain(|p| *p == Self::Sixel); protocols.retain(|p| *p == Self::Sixel);
} } else if *TMUX {
if *TMUX && protocols.len() > 1 {
protocols.retain(|p| *p != Self::KittyOld); protocols.retain(|p| *p != Self::KittyOld);
} }
if let Some(p) = protocols.first() { if let Some(p) = protocols.first() {
@ -104,7 +98,7 @@ impl Adaptor {
match env::var("XDG_SESSION_TYPE").unwrap_or_default().as_str() { match env::var("XDG_SESSION_TYPE").unwrap_or_default().as_str() {
"x11" => return Self::X11, "x11" => return Self::X11,
"wayland" => return Self::Wayland, "wayland" => return Self::Wayland,
_ => warn!("[Adaptor] Could not identify XDG_SESSION_TYPE"), _ => warn!("[Adapter] Could not identify XDG_SESSION_TYPE"),
} }
if env_exists("WAYLAND_DISPLAY") { if env_exists("WAYLAND_DISPLAY") {
return Self::Wayland; return Self::Wayland;
@ -116,7 +110,7 @@ impl Adaptor {
return Self::KittyOld; return Self::KittyOld;
} }
warn!("[Adaptor] Falling back to chafa"); warn!("[Adapter] Falling back to chafa");
Self::Chafa Self::Chafa
} }
} }

77
yazi-adapter/src/chafa.rs Normal file
View file

@ -0,0 +1,77 @@
use std::{io::Write, path::Path, process::Stdio};
use ansi_to_tui::IntoText;
use anyhow::{bail, Result};
use crossterm::{cursor::MoveTo, queue};
use ratatui::layout::Rect;
use tokio::process::Command;
use crate::{Adapter, Emulator};
pub(super) struct Chafa;
impl Chafa {
pub(super) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
let output = Command::new("chafa")
.args([
"-f",
"symbols",
"--relative",
"off",
"--polite",
"on",
"--passthrough",
"none",
"--animate",
"off",
"--view-size",
])
.arg(format!("{}x{}", max.width, max.height))
.arg(path)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.kill_on_drop(true)
.output()
.await?;
if !output.status.success() {
bail!("chafa failed with status: {}", output.status);
} else if output.stdout.is_empty() {
bail!("chafa returned no output");
}
let lines: Vec<_> = output.stdout.split(|&b| b == b'\n').collect();
let Ok(Some(first)) = lines[0].into_text().map(|mut t| t.lines.pop()) else {
bail!("failed to parse chafa output");
};
let area = Rect {
x: max.x,
y: max.y,
width: first.spans.into_iter().map(|s| s.content.chars().count() as u16).sum(),
height: lines.len() as u16,
};
Adapter::Chafa.image_hide()?;
Adapter::shown_store(area);
Emulator::move_lock((max.x, max.y), |stderr| {
for (i, line) in lines.into_iter().enumerate() {
stderr.write_all(line)?;
queue!(stderr, MoveTo(max.x, max.y + i as u16 + 1))?;
}
Ok(area)
})
}
pub(super) fn image_erase(area: Rect) -> Result<()> {
let s = " ".repeat(area.width as usize);
Emulator::move_lock((0, 0), |stderr| {
for y in area.top()..area.bottom() {
queue!(stderr, MoveTo(area.x, y))?;
write!(stderr, "{s}")?;
}
Ok(())
})
}
}

View file

@ -0,0 +1,35 @@
use std::mem;
use crossterm::terminal::WindowSize;
pub struct Dimension;
impl Dimension {
pub fn available() -> WindowSize {
let mut size = WindowSize { rows: 0, columns: 0, width: 0, height: 0 };
if let Ok(s) = crossterm::terminal::window_size() {
_ = mem::replace(&mut size, s);
}
if size.rows == 0 || size.columns == 0 {
if let Ok(s) = crossterm::terminal::size() {
size.columns = s.0;
size.rows = s.1;
}
}
// TODO: Use `CSI 14 t` to get the actual size of the terminal
// if size.width == 0 || size.height == 0 {}
size
}
#[inline]
pub fn ratio() -> Option<(f64, f64)> {
let s = Self::available();
if s.width == 0 || s.height == 0 {
return None;
}
Some((f64::from(s.width) / f64::from(s.columns), f64::from(s.height) / f64::from(s.rows)))
}
}

View file

@ -0,0 +1,216 @@
use std::{env, io::{stderr, LineWriter}, time::Duration};
use anyhow::{anyhow, bail, Result};
use crossterm::{cursor::{RestorePosition, SavePosition}, execute, style::Print, terminal::{disable_raw_mode, enable_raw_mode}};
use scopeguard::defer;
use tokio::{io::{AsyncReadExt, BufReader}, time::timeout};
use tracing::{error, warn};
use yazi_shared::env_exists;
use crate::{Adapter, CLOSE, ESCAPE, START, TMUX};
#[derive(Clone, Debug)]
pub enum Emulator {
Unknown(Vec<Adapter>),
Kitty,
Konsole,
Iterm2,
WezTerm,
Foot,
Ghostty,
BlackBox,
VSCode,
Tabby,
Hyper,
Mintty,
Neovim,
Apple,
Urxvt,
}
impl Emulator {
pub fn adapters(self) -> Vec<Adapter> {
match self {
Self::Unknown(adapters) => adapters,
Self::Kitty => vec![Adapter::Kitty],
Self::Konsole => vec![Adapter::KittyOld],
Self::Iterm2 => vec![Adapter::Iterm2, Adapter::Sixel],
Self::WezTerm => vec![Adapter::Iterm2, Adapter::Sixel],
Self::Foot => vec![Adapter::Sixel],
Self::Ghostty => vec![Adapter::KittyOld],
Self::BlackBox => vec![Adapter::Sixel],
Self::VSCode => vec![Adapter::Iterm2, Adapter::Sixel],
Self::Tabby => vec![Adapter::Iterm2, Adapter::Sixel],
Self::Hyper => vec![Adapter::Iterm2, Adapter::Sixel],
Self::Mintty => vec![Adapter::Iterm2],
Self::Neovim => vec![],
Self::Apple => vec![],
Self::Urxvt => vec![],
}
}
}
impl Emulator {
pub fn detect() -> Self {
if env_exists("NVIM_LOG_FILE") && env_exists("NVIM") {
return Self::Neovim;
}
let vars = [
("KITTY_WINDOW_ID", Self::Kitty),
("KONSOLE_VERSION", Self::Konsole),
("ITERM_SESSION_ID", Self::Iterm2),
("WEZTERM_EXECUTABLE", Self::WezTerm),
("GHOSTTY_RESOURCES_DIR", Self::Ghostty),
("VSCODE_INJECTION", Self::VSCode),
("TABBY_CONFIG_DIRECTORY", Self::Tabby),
];
match vars.into_iter().find(|v| env_exists(v.0)) {
Some(var) => return var.1,
None => warn!("[Adapter] No special environment variables detected"),
}
let (term, program) = Self::via_env();
match program.as_str() {
"iTerm.app" => return Self::Iterm2,
"WezTerm" => return Self::WezTerm,
"ghostty" => return Self::Ghostty,
"BlackBox" => return Self::BlackBox,
"vscode" => return Self::VSCode,
"Tabby" => return Self::Tabby,
"Hyper" => return Self::Hyper,
"mintty" => return Self::Mintty,
"Apple_Terminal" => return Self::Apple,
_ => warn!("[Adapter] Unknown TERM_PROGRAM: {program}"),
}
match term.as_str() {
"xterm-kitty" => return Self::Kitty,
"foot" => return Self::Foot,
"foot-extra" => return Self::Foot,
"xterm-ghostty" => return Self::Ghostty,
"rxvt-unicode-256color" => return Self::Urxvt,
_ => warn!("[Adapter] Unknown TERM: {term}"),
}
Self::via_csi().unwrap_or(Self::Unknown(vec![]))
}
pub fn via_env() -> (String, String) {
fn tmux_env(name: &str) -> Result<String> {
let output = std::process::Command::new("tmux").args(["show-environment", name]).output()?;
String::from_utf8(output.stdout)?
.trim()
.strip_prefix(&format!("{name}="))
.map_or_else(|| Err(anyhow!("")), |s| Ok(s.to_string()))
}
let mut term = env::var("TERM").unwrap_or_default();
let mut program = env::var("TERM_PROGRAM").unwrap_or_default();
if *TMUX {
term = tmux_env("TERM").unwrap_or(term);
program = tmux_env("TERM_PROGRAM").unwrap_or(program);
}
(term, program)
}
pub fn via_csi() -> Result<Self> {
defer! { disable_raw_mode().ok(); }
enable_raw_mode()?;
execute!(
LineWriter::new(stderr()),
SavePosition,
Print(format!(
"{}[>q{}_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA{}\\{}[c{}",
START, ESCAPE, ESCAPE, ESCAPE, CLOSE
)),
RestorePosition
)?;
let resp = futures::executor::block_on(Self::read_until_da1())?;
let names = [
("kitty", Self::Kitty),
("Konsole", Self::Konsole),
("iTerm2", Self::Iterm2),
("WezTerm", Self::WezTerm),
("foot", Self::Foot),
("ghostty", Self::Ghostty),
];
for (name, emulator) in names.iter() {
if resp.contains(name) {
return Ok(emulator.clone());
}
}
let mut adapters = Vec::with_capacity(2);
if resp.contains("\x1b_Gi=31;OK") {
adapters.push(Adapter::KittyOld);
}
if ["?4;", "?4c", ";4;", ";4c"].iter().any(|s| resp.contains(s)) {
adapters.push(Adapter::Sixel);
}
Ok(Self::Unknown(adapters))
}
pub fn move_lock<F, T>((x, y): (u16, u16), cb: F) -> Result<T>
where
F: FnOnce(&mut std::io::BufWriter<std::io::StderrLock>) -> Result<T>,
{
use std::{io::Write, thread, time::Duration};
use crossterm::{cursor::{Hide, MoveTo, RestorePosition, SavePosition, Show}, queue};
let mut buf = std::io::BufWriter::new(stderr().lock());
// I really don't want to add this,
// But tmux and ConPTY sometimes cause the cursor position to get out of sync.
if *TMUX || cfg!(windows) {
execute!(buf, SavePosition, MoveTo(x, y), Show)?;
execute!(buf, MoveTo(x, y), Show)?;
execute!(buf, MoveTo(x, y), Show)?;
thread::sleep(Duration::from_millis(1));
} else {
queue!(buf, SavePosition, MoveTo(x, y))?;
}
let result = cb(&mut buf);
if *TMUX || cfg!(windows) {
queue!(buf, Hide, RestorePosition)?;
} else {
queue!(buf, RestorePosition)?;
}
buf.flush()?;
result
}
pub async fn read_until_da1() -> Result<String> {
let read = async {
let mut stdin = BufReader::new(tokio::io::stdin());
let mut buf = String::with_capacity(200);
loop {
let mut c = [0; 1];
if stdin.read(&mut c).await? == 0 {
bail!("unexpected EOF");
}
buf.push(c[0] as char);
if c[0] == b'c' && buf.contains("\x1b[?") {
break;
}
}
Ok(buf)
};
let timeout = timeout(Duration::from_secs(10), read).await;
if let Err(ref e) = timeout {
error!("read_until_da1: {e:?}");
}
timeout?
}
}

View file

@ -5,7 +5,8 @@ use exif::{In, Tag};
use image::{codecs::jpeg::JpegEncoder, imageops::{self, FilterType}, io::Limits, DynamicImage}; use image::{codecs::jpeg::JpegEncoder, imageops::{self, FilterType}, io::Limits, DynamicImage};
use ratatui::layout::Rect; use ratatui::layout::Rect;
use yazi_config::{PREVIEW, TASKS}; use yazi_config::{PREVIEW, TASKS};
use yazi_shared::term::Term;
use crate::Dimension;
pub struct Image; pub struct Image;
@ -57,7 +58,7 @@ impl Image {
}) })
.await??; .await??;
let (mut w, mut h) = Self::max_size(rect); let (mut w, mut h) = Self::max_pixel(rect);
if (5..=8).contains(&orientation) { if (5..=8).contains(&orientation) {
(w, h) = (h, w); (w, h) = (h, w);
} }
@ -76,8 +77,8 @@ impl Image {
.await? .await?
} }
pub(super) fn max_size(rect: Rect) -> (u32, u32) { pub(super) fn max_pixel(rect: Rect) -> (u32, u32) {
Term::ratio() Dimension::ratio()
.map(|(r1, r2)| { .map(|(r1, r2)| {
let (w, h) = ((rect.width as f64 * r1) as u32, (rect.height as f64 * r2) as u32); let (w, h) = ((rect.width as f64 * r1) as u32, (rect.height as f64 * r2) as u32);
(w.min(PREVIEW.max_width), h.min(PREVIEW.max_height)) (w.min(PREVIEW.max_width), h.min(PREVIEW.max_height))
@ -85,6 +86,17 @@ impl Image {
.unwrap_or((PREVIEW.max_width, PREVIEW.max_height)) .unwrap_or((PREVIEW.max_width, PREVIEW.max_height))
} }
pub(super) fn pixel_area(size: (u32, u32), rect: Rect) -> Rect {
Dimension::ratio()
.map(|(r1, r2)| Rect {
x: rect.x,
y: rect.y,
width: (size.0 as f64 / r1).ceil() as u16,
height: (size.1 as f64 / r2).ceil() as u16,
})
.unwrap_or(rect)
}
#[inline] #[inline]
fn filter() -> FilterType { fn filter() -> FilterType {
match PREVIEW.image_filter.as_str() { match PREVIEW.image_filter.as_str() {

View file

@ -0,0 +1,61 @@
use std::{io::Write, path::Path};
use anyhow::Result;
use base64::{engine::{general_purpose::STANDARD, Config}, Engine};
use crossterm::{cursor::MoveTo, queue};
use image::{codecs::jpeg::JpegEncoder, DynamicImage};
use ratatui::layout::Rect;
use super::image::Image;
use crate::{adapter::Adapter, Emulator, CLOSE, START};
pub(super) struct Iterm2;
impl Iterm2 {
pub(super) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
let img = Image::downscale(path, max).await?;
let area = Image::pixel_area((img.width(), img.height()), max);
let b = Self::encode(img).await?;
Adapter::Iterm2.image_hide()?;
Adapter::shown_store(area);
Emulator::move_lock((max.x, max.y), |stderr| {
stderr.write_all(&b)?;
Ok(area)
})
}
pub(super) fn image_erase(area: Rect) -> Result<()> {
let s = " ".repeat(area.width as usize);
Emulator::move_lock((0, 0), |stderr| {
for y in area.top()..area.bottom() {
queue!(stderr, MoveTo(area.x, y))?;
write!(stderr, "{s}")?;
}
Ok(())
})
}
async fn encode(img: DynamicImage) -> Result<Vec<u8>> {
tokio::task::spawn_blocking(move || {
let mut jpg = vec![];
JpegEncoder::new_with_quality(&mut jpg, 75).encode_image(&img)?;
let len = base64::encoded_len(jpg.len(), STANDARD.config().encode_padding());
let mut buf = Vec::with_capacity(200 + len.unwrap_or(1 << 16));
write!(
buf,
"{}]1337;File=inline=1;size={};width={}px;height={}px;doNotMoveCursor=1:{}\x07{}",
START,
jpg.len(),
img.width(),
img.height(),
STANDARD.encode(&jpg),
CLOSE
)?;
Ok(buf)
})
.await?
}
}

View file

@ -3,12 +3,12 @@ use std::{io::Write, path::Path};
use anyhow::Result; use anyhow::Result;
use base64::{engine::general_purpose, Engine}; use base64::{engine::general_purpose, Engine};
use crossterm::{cursor::MoveTo, queue};
use image::DynamicImage; use image::DynamicImage;
use ratatui::layout::Rect; use ratatui::layout::Rect;
use yazi_shared::term::Term;
use super::image::Image; use super::image::Image;
use crate::{adaptor::Adaptor, CLOSE, ESCAPE, START}; use crate::{adapter::Adapter, Emulator, CLOSE, ESCAPE, START};
static DIACRITICS: [char; 297] = [ static DIACRITICS: [char; 297] = [
'\u{0305}', '\u{0305}',
@ -313,31 +313,31 @@ static DIACRITICS: [char; 297] = [
pub(super) struct Kitty; pub(super) struct Kitty;
impl Kitty { impl Kitty {
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<(u32, u32)> { pub(super) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
let img = Image::downscale(path, rect).await?; let img = Image::downscale(path, max).await?;
let size = (img.width(), img.height()); let area = Image::pixel_area((img.width(), img.height()), max);
let b1 = Self::encode(img).await?; let b1 = Self::encode(img).await?;
let b2 = Self::place(&rect)?; let b2 = Self::place(&area)?;
Adaptor::Kitty.image_hide()?; Adapter::Kitty.image_hide()?;
Adaptor::shown_store(rect, size); Adapter::shown_store(area);
Term::move_lock((rect.x, rect.y), |stderr| { Emulator::move_lock((area.x, area.y), |stderr| {
stderr.write_all(&b1)?; stderr.write_all(&b1)?;
stderr.write_all(&b2)?; stderr.write_all(&b2)?;
Ok(size) Ok(area)
}) })
} }
pub(super) fn image_erase(rect: Rect) -> Result<()> { pub(super) fn image_erase(area: Rect) -> Result<()> {
let s = " ".repeat(rect.width as usize); let s = " ".repeat(area.width as usize);
Term::move_lock((0, 0), |stderr| { Emulator::move_lock((0, 0), |stderr| {
for y in rect.top()..rect.bottom() { for y in area.top()..area.bottom() {
Term::move_to(stderr, rect.x, y)?; queue!(stderr, MoveTo(area.x, y))?;
write!(stderr, "{s}")?; write!(stderr, "{s}")?;
} }
write!(stderr, "{}_Gq=1,a=d,d=A{}\\{}", START, ESCAPE, CLOSE)?; write!(stderr, "{}_Gq=2,a=d,d=A{}\\{}", START, ESCAPE, CLOSE)?;
Ok(()) Ok(())
}) })
} }
@ -351,7 +351,7 @@ impl Kitty {
if let Some(first) = it.next() { if let Some(first) = it.next() {
write!( write!(
buf, buf,
"{}_Gq=1,a=T,i=1,C=1,U=1,f={},s={},v={},m={};{}{}\\{}", "{}_Gq=2,a=T,i=1,C=1,U=1,f={},s={},v={},m={};{}{}\\{}",
START, START,
format, format,
size.0, size.0,
@ -388,11 +388,11 @@ impl Kitty {
.await? .await?
} }
fn place(rect: &Rect) -> Result<Vec<u8>> { fn place(area: &Rect) -> Result<Vec<u8>> {
let mut buf = Vec::with_capacity(rect.width as usize * rect.height as usize * 3 + 50); let mut buf = Vec::with_capacity(area.width as usize * area.height as usize * 3 + 50);
for y in 0..rect.height { for y in 0..area.height {
write!(buf, "\x1b[{};{}H\x1b[38;5;1m", rect.y + y + 1, rect.x + 1)?; write!(buf, "\x1b[{};{}H\x1b[38;5;1m", area.y + y + 1, area.x + 1)?;
for x in 0..rect.width { for x in 0..area.width {
write!(buf, "\u{10EEEE}")?; write!(buf, "\u{10EEEE}")?;
write!(buf, "{}", *DIACRITICS.get(y as usize).unwrap_or(&DIACRITICS[0]))?; write!(buf, "{}", *DIACRITICS.get(y as usize).unwrap_or(&DIACRITICS[0]))?;
write!(buf, "{}", *DIACRITICS.get(x as usize).unwrap_or(&DIACRITICS[0]))?; write!(buf, "{}", *DIACRITICS.get(x as usize).unwrap_or(&DIACRITICS[0]))?;

View file

@ -5,31 +5,30 @@ use anyhow::Result;
use base64::{engine::general_purpose, Engine}; use base64::{engine::general_purpose, Engine};
use image::DynamicImage; use image::DynamicImage;
use ratatui::layout::Rect; use ratatui::layout::Rect;
use yazi_shared::term::Term;
use super::image::Image; use super::image::Image;
use crate::{adaptor::Adaptor, CLOSE, ESCAPE, START}; use crate::{adapter::Adapter, Emulator, CLOSE, ESCAPE, START};
pub(super) struct KittyOld; pub(super) struct KittyOld;
impl KittyOld { impl KittyOld {
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<(u32, u32)> { pub(super) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
let img = Image::downscale(path, rect).await?; let img = Image::downscale(path, max).await?;
let size = (img.width(), img.height()); let area = Image::pixel_area((img.width(), img.height()), max);
let b = Self::encode(img).await?; let b = Self::encode(img).await?;
Adaptor::KittyOld.image_hide()?; Adapter::KittyOld.image_hide()?;
Adaptor::shown_store(rect, size); Adapter::shown_store(area);
Term::move_lock((rect.x, rect.y), |stderr| { Emulator::move_lock((area.x, area.y), |stderr| {
stderr.write_all(&b)?; stderr.write_all(&b)?;
Ok(size) Ok(area)
}) })
} }
#[inline] #[inline]
pub(super) fn image_erase() -> Result<()> { pub(super) fn image_erase(_: Rect) -> Result<()> {
let mut stderr = LineWriter::new(stderr()); let mut stderr = LineWriter::new(stderr());
write!(stderr, "{}_Gq=1,a=d,d=A{}\\{}", START, ESCAPE, CLOSE)?; write!(stderr, "{}_Gq=2,a=d,d=A{}\\{}", START, ESCAPE, CLOSE)?;
stderr.flush()?; stderr.flush()?;
Ok(()) Ok(())
} }
@ -43,7 +42,7 @@ impl KittyOld {
if let Some(first) = it.next() { if let Some(first) = it.next() {
write!( write!(
buf, buf,
"{}_Gq=1,a=T,z=-1,C=1,f={},s={},v={},m={};{}{}\\{}", "{}_Gq=2,a=T,z=-1,C=1,f={},s={},v={},m={};{}{}\\{}",
START, START,
format, format,
size.0, size.0,

View file

@ -1,6 +1,8 @@
#![allow(clippy::unit_arg)] #![allow(clippy::unit_arg)]
mod adaptor; mod adapter;
mod chafa;
mod dimension;
mod emulator; mod emulator;
mod image; mod image;
mod iterm2; mod iterm2;
@ -9,17 +11,20 @@ mod kitty_old;
mod sixel; mod sixel;
mod ueberzug; mod ueberzug;
pub use adaptor::*; pub use adapter::*;
use chafa::*;
pub use dimension::*;
pub use emulator::*; pub use emulator::*;
use iterm2::*; use iterm2::*;
use kitty::*; use kitty::*;
use kitty_old::*; use kitty_old::*;
use sixel::*; use sixel::*;
use ueberzug::*;
use yazi_shared::{env_exists, RoCell}; use yazi_shared::{env_exists, RoCell};
pub use crate::image::*; pub use crate::image::*;
pub static ADAPTOR: RoCell<Adaptor> = RoCell::new(); pub static ADAPTOR: RoCell<Adapter> = RoCell::new();
// Tmux support // Tmux support
pub static TMUX: RoCell<bool> = RoCell::new(); pub static TMUX: RoCell<bool> = RoCell::new();
@ -31,22 +36,22 @@ static CLOSE: RoCell<&'static str> = RoCell::new();
static SHOWN: RoCell<arc_swap::ArcSwapOption<ratatui::layout::Rect>> = RoCell::new(); static SHOWN: RoCell<arc_swap::ArcSwapOption<ratatui::layout::Rect>> = RoCell::new();
pub fn init() { pub fn init() {
TMUX.init(env_exists("TMUX")); TMUX.init(env_exists("TMUX") && env_exists("TMUX_PANE"));
START.init(if *TMUX { "\x1bPtmux;\x1b\x1b" } else { "\x1b" }); START.init(if *TMUX { "\x1bPtmux;\x1b\x1b" } else { "\x1b" });
CLOSE.init(if *TMUX { "\x1b\\" } else { "" }); CLOSE.init(if *TMUX { "\x1b\\" } else { "" });
ESCAPE.init(if *TMUX { "\x1b\x1b" } else { "\x1b" }); ESCAPE.init(if *TMUX { "\x1b\x1b" } else { "\x1b" });
SHOWN.with(Default::default);
ADAPTOR.init(Adaptor::matches());
ADAPTOR.start();
if *TMUX { if *TMUX {
_ = std::process::Command::new("tmux") _ = std::process::Command::new("tmux")
.args(["set", "-p", "allow-passthrough", "on"]) .args(["set", "-p", "allow-passthrough", "on"])
.stdin(std::process::Stdio::null()) .stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null()) .stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null()) .stderr(std::process::Stdio::null())
.spawn(); .status();
} }
SHOWN.with(Default::default);
ADAPTOR.init(Adapter::matches());
ADAPTOR.start();
} }

View file

@ -2,34 +2,34 @@ use std::{io::Write, path::Path};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use color_quant::NeuQuant; use color_quant::NeuQuant;
use crossterm::{cursor::MoveTo, queue};
use image::DynamicImage; use image::DynamicImage;
use ratatui::layout::Rect; use ratatui::layout::Rect;
use yazi_config::PREVIEW; use yazi_config::PREVIEW;
use yazi_shared::term::Term;
use crate::{adaptor::Adaptor, Image, CLOSE, ESCAPE, START}; use crate::{adapter::Adapter, Emulator, Image, CLOSE, ESCAPE, START};
pub(super) struct Sixel; pub(super) struct Sixel;
impl Sixel { impl Sixel {
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<(u32, u32)> { pub(super) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
let img = Image::downscale(path, rect).await?; let img = Image::downscale(path, max).await?;
let size = (img.width(), img.height()); let area = Image::pixel_area((img.width(), img.height()), max);
let b = Self::encode(img).await?; let b = Self::encode(img).await?;
Adaptor::Sixel.image_hide()?; Adapter::Sixel.image_hide()?;
Adaptor::shown_store(rect, size); Adapter::shown_store(area);
Term::move_lock((rect.x, rect.y), |stderr| { Emulator::move_lock((area.x, area.y), |stderr| {
stderr.write_all(&b)?; stderr.write_all(&b)?;
Ok(size) Ok(area)
}) })
} }
pub(super) fn image_erase(rect: Rect) -> Result<()> { pub(super) fn image_erase(area: Rect) -> Result<()> {
let s = " ".repeat(rect.width as usize); let s = " ".repeat(area.width as usize);
Term::move_lock((0, 0), |stderr| { Emulator::move_lock((0, 0), |stderr| {
for y in rect.top()..rect.bottom() { for y in area.top()..area.bottom() {
Term::move_to(stderr, rect.x, y)?; queue!(stderr, MoveTo(area.x, y))?;
write!(stderr, "{s}")?; write!(stderr, "{s}")?;
} }
Ok(()) Ok(())

View file

@ -8,7 +8,7 @@ use tracing::{debug, warn};
use yazi_config::PREVIEW; use yazi_config::PREVIEW;
use yazi_shared::RoCell; use yazi_shared::RoCell;
use crate::{Adaptor, Image}; use crate::{Adapter, Dimension};
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
static DEMON: RoCell<Option<UnboundedSender<Option<(PathBuf, Rect)>>>> = RoCell::new(); static DEMON: RoCell<Option<UnboundedSender<Option<(PathBuf, Rect)>>>> = RoCell::new();
@ -16,12 +16,12 @@ static DEMON: RoCell<Option<UnboundedSender<Option<(PathBuf, Rect)>>>> = RoCell:
pub(super) struct Ueberzug; pub(super) struct Ueberzug;
impl Ueberzug { impl Ueberzug {
pub(super) fn start(adaptor: Adaptor) { pub(super) fn start(adapter: Adapter) {
if !adaptor.needs_ueberzug() { if !adapter.needs_ueberzug() {
return DEMON.init(None); return DEMON.init(None);
} }
let mut child = Self::create_demon(adaptor).ok(); let mut child = Self::create_demon(adapter).ok();
let (tx, mut rx) = mpsc::unbounded_channel(); let (tx, mut rx) = mpsc::unbounded_channel();
tokio::spawn(async move { tokio::spawn(async move {
@ -31,7 +31,7 @@ impl Ueberzug {
child = None; child = None;
} }
if child.is_none() { if child.is_none() {
child = Self::create_demon(adaptor).ok(); child = Self::create_demon(adapter).ok();
} }
if let Some(c) = &mut child { if let Some(c) = &mut child {
Self::send_command(c, cmd).await.ok(); Self::send_command(c, cmd).await.ok();
@ -41,25 +41,27 @@ impl Ueberzug {
DEMON.init(Some(tx)) DEMON.init(Some(tx))
} }
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<(u32, u32)> { pub(super) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
if let Some(tx) = &*DEMON { let Some(tx) = &*DEMON else {
tx.send(Some((path.to_path_buf(), rect)))?;
Adaptor::shown_store(rect, (0, 0));
} else {
bail!("uninitialized ueberzugpp"); bail!("uninitialized ueberzugpp");
} };
let path = path.to_owned(); let p = path.to_owned();
let ImageSize { width: w, height: h } = let ImageSize { width: w, height: h } =
tokio::task::spawn_blocking(move || imagesize::size(path)).await??; tokio::task::spawn_blocking(move || imagesize::size(p)).await??;
let (max_w, max_h) = Image::max_size(rect); let area = Dimension::ratio()
if w <= max_w as usize && h <= max_h as usize { .map(|(r1, r2)| Rect {
return Ok((w as u32, h as u32)); x: max.x,
} y: max.y,
width: max.width.min((w.min(PREVIEW.max_width as _) as f64 / r1).ceil() as _),
height: max.height.min((h.min(PREVIEW.max_height as _) as f64 / r2).ceil() as _),
})
.unwrap_or(max);
let ratio = f64::min(max_w as f64 / w as f64, max_h as f64 / h as f64); tx.send(Some((path.to_owned(), area)))?;
Ok(((w as f64 * ratio).round() as u32, (h as f64 * ratio).round() as u32)) Adapter::shown_store(area);
Ok(area)
} }
pub(super) fn image_erase(_: Rect) -> Result<()> { pub(super) fn image_erase(_: Rect) -> Result<()> {
@ -70,13 +72,14 @@ impl Ueberzug {
} }
} }
fn create_demon(adaptor: Adaptor) -> Result<Child> { fn create_demon(adapter: Adapter) -> Result<Child> {
// TODO: demon // TODO: demon
let result = Command::new("ueberzugpp") let result = Command::new("ueberzugpp")
.args(["layer", "-so", &adaptor.to_string()]) .args(["layer", "-so", &adapter.to_string()])
.env("SPDLOG_LEVEL", if cfg!(debug_assertions) { "debug" } else { "" }) .env("SPDLOG_LEVEL", if cfg!(debug_assertions) { "debug" } else { "" })
.kill_on_drop(true) .kill_on_drop(true)
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stdout(Stdio::null())
.stderr(Stdio::null()) .stderr(Stdio::null())
.spawn(); .spawn();

View file

@ -1,151 +0,0 @@
use std::{env, io::{stderr, LineWriter}};
use anyhow::{anyhow, Result};
use crossterm::{cursor::{RestorePosition, SavePosition}, execute, style::Print, terminal::{disable_raw_mode, enable_raw_mode}};
use tracing::warn;
use yazi_shared::{env_exists, term::Term};
use crate::{Adaptor, TMUX};
#[derive(Clone, Debug)]
pub enum Emulator {
Unknown(Vec<Adaptor>),
Kitty,
Konsole,
Iterm2,
WezTerm,
Foot,
Ghostty,
BlackBox,
VSCode,
Tabby,
Hyper,
Mintty,
Neovim,
Apple,
}
impl Emulator {
pub fn adapters(self) -> Vec<Adaptor> {
match self {
Self::Unknown(adapters) => adapters,
Self::Kitty => vec![Adaptor::Kitty],
Self::Konsole => vec![Adaptor::KittyOld, Adaptor::Iterm2, Adaptor::Sixel],
Self::Iterm2 => vec![Adaptor::Iterm2, Adaptor::Sixel],
Self::WezTerm => vec![Adaptor::Iterm2, Adaptor::Sixel],
Self::Foot => vec![Adaptor::Sixel],
Self::Ghostty => vec![Adaptor::KittyOld],
Self::BlackBox => vec![Adaptor::Sixel],
Self::VSCode => vec![Adaptor::Iterm2, Adaptor::Sixel],
Self::Tabby => vec![Adaptor::Iterm2, Adaptor::Sixel],
Self::Hyper => vec![Adaptor::Iterm2, Adaptor::Sixel],
Self::Mintty => vec![Adaptor::Iterm2],
Self::Neovim => vec![],
Self::Apple => vec![],
}
}
}
impl Emulator {
pub fn detect() -> Self {
if env_exists("NVIM_LOG_FILE") && env_exists("NVIM") {
return Self::Neovim;
}
let vars = [
("KITTY_WINDOW_ID", Self::Kitty),
("KONSOLE_VERSION", Self::Konsole),
("ITERM_SESSION_ID", Self::Iterm2),
("WEZTERM_EXECUTABLE", Self::WezTerm),
("GHOSTTY_RESOURCES_DIR", Self::Ghostty),
("VSCODE_INJECTION", Self::VSCode),
("TABBY_CONFIG_DIRECTORY", Self::Tabby),
];
match vars.into_iter().find(|v| env_exists(v.0)) {
Some(var) => return var.1,
None => warn!("[Adaptor] No special environment variables detected"),
}
let (term, program) = Self::via_env();
match program.as_str() {
"iTerm.app" => return Self::Iterm2,
"WezTerm" => return Self::WezTerm,
"ghostty" => return Self::Ghostty,
"BlackBox" => return Self::BlackBox,
"vscode" => return Self::VSCode,
"Tabby" => return Self::Tabby,
"Hyper" => return Self::Hyper,
"mintty" => return Self::Mintty,
"Apple_Terminal" => return Self::Apple,
_ => warn!("[Adaptor] Unknown TERM_PROGRAM: {program}"),
}
match term.as_str() {
"xterm-kitty" => return Self::Kitty,
"foot" => return Self::Foot,
"foot-extra" => return Self::Foot,
"xterm-ghostty" => return Self::Ghostty,
_ => warn!("[Adaptor] Unknown TERM: {term}"),
}
Self::via_csi().unwrap_or(Self::Unknown(vec![]))
}
pub fn via_env() -> (String, String) {
fn tmux_env(name: &str) -> Result<String> {
let output = std::process::Command::new("tmux").args(["show-environment", name]).output()?;
String::from_utf8(output.stdout)?
.trim()
.strip_prefix(&format!("{name}="))
.map_or_else(|| Err(anyhow!("")), |s| Ok(s.to_string()))
}
let mut term = env::var("TERM").unwrap_or_default();
let mut program = env::var("TERM_PROGRAM").unwrap_or_default();
if *TMUX {
term = tmux_env("TERM").unwrap_or(term);
program = tmux_env("TERM_PROGRAM").unwrap_or(program);
}
(term, program)
}
pub fn via_csi() -> Result<Self> {
enable_raw_mode()?;
execute!(
LineWriter::new(stderr()),
SavePosition,
Print("\x1b[>q\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\\x1b[c"),
RestorePosition
)?;
let resp = futures::executor::block_on(Term::read_until_da1())?;
disable_raw_mode().ok();
let names = [
("kitty", Self::Kitty),
("Konsole", Self::Konsole),
("iTerm2", Self::Iterm2),
("WezTerm", Self::WezTerm),
("foot", Self::Foot),
("ghostty", Self::Ghostty),
];
for (name, emulator) in names.iter() {
if resp.contains(name) {
return Ok(emulator.clone());
}
}
let mut adapters = Vec::with_capacity(2);
if resp.contains("\x1b_Gi=31;OK") {
adapters.push(Adaptor::KittyOld);
}
if ["?4;", "?4c", ";4;", ";4c"].iter().any(|s| resp.contains(s)) {
adapters.push(Adaptor::Sixel);
}
Ok(Self::Unknown(adapters))
}
}

View file

@ -1,61 +0,0 @@
use std::{io::Write, path::Path};
use anyhow::Result;
use base64::{engine::general_purpose, Engine};
use image::{codecs::jpeg::JpegEncoder, DynamicImage};
use ratatui::layout::Rect;
use yazi_shared::term::Term;
use super::image::Image;
use crate::{adaptor::Adaptor, CLOSE, START};
pub(super) struct Iterm2;
impl Iterm2 {
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<(u32, u32)> {
let img = Image::downscale(path, rect).await?;
let size = (img.width(), img.height());
let b = Self::encode(img).await?;
Adaptor::Iterm2.image_hide()?;
Adaptor::shown_store(rect, size);
Term::move_lock((rect.x, rect.y), |stderr| {
stderr.write_all(&b)?;
Ok(size)
})
}
pub(super) fn image_erase(rect: Rect) -> Result<()> {
let s = " ".repeat(rect.width as usize);
Term::move_lock((0, 0), |stderr| {
for y in rect.top()..rect.bottom() {
Term::move_to(stderr, rect.x, y)?;
write!(stderr, "{s}")?;
}
Ok(())
})
}
async fn encode(img: DynamicImage) -> Result<Vec<u8>> {
tokio::task::spawn_blocking(move || {
let size = (img.width(), img.height());
let mut jpg = vec![];
JpegEncoder::new_with_quality(&mut jpg, 75).encode_image(&img)?;
let mut buf = vec![];
write!(
buf,
"{}]1337;File=inline=1;size={};width={}px;height={}px;doNotMoveCursor=1:{}\x07{}",
START,
jpg.len(),
size.0,
size.1,
general_purpose::STANDARD.encode(&jpg),
CLOSE
)?;
Ok(buf)
})
.await?
}
}

View file

@ -9,17 +9,18 @@ homepage = "https://yazi-rs.github.io"
repository = "https://github.com/sxyazi/yazi" repository = "https://github.com/sxyazi/yazi"
[dependencies] [dependencies]
yazi-adaptor = { path = "../yazi-adaptor", version = "0.2.5" } regex = "1.10.5"
yazi-adapter = { path = "../yazi-adapter", version = "0.2.5" }
yazi-config = { path = "../yazi-config", version = "0.2.5" } yazi-config = { path = "../yazi-config", version = "0.2.5" }
yazi-shared = { path = "../yazi-shared", version = "0.2.5" } yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies # External dependencies
clap = { version = "4.5.4", features = [ "derive" ] } clap = { version = "4.5.7", features = [ "derive" ] }
serde = { version = "1.0.198", features = [ "derive" ] } serde = { version = "1.0.203", features = [ "derive" ] }
[build-dependencies] [build-dependencies]
clap = { version = "4.5.4", features = [ "derive" ] } clap = { version = "4.5.7", features = [ "derive" ] }
clap_complete = "4.5.2" clap_complete = "4.5.6"
clap_complete_nushell = "4.5.1" clap_complete_nushell = "4.5.2"
clap_complete_fig = "4.5.0" clap_complete_fig = "4.5.1"
vergen = { version = "8.3.1", features = [ "build", "git", "gitcl" ] } vergen = { version = "8.3.1", features = [ "build", "git", "gitcl" ] }

View file

@ -1,6 +1,7 @@
use std::{collections::HashSet, env, ffi::OsString, fmt::Write, path::{Path, PathBuf}, process}; use std::{collections::HashSet, env, ffi::{OsStr, OsString}, fmt::Write, path::{Path, PathBuf}, process};
use clap::Parser; use clap::Parser;
use regex::Regex;
use serde::Serialize; use serde::Serialize;
use yazi_config::PREVIEW; use yazi_config::PREVIEW;
use yazi_shared::{fs::{current_cwd, expand_path}, Xdg}; use yazi_shared::{fs::{current_cwd, expand_path}, Xdg};
@ -37,6 +38,22 @@ impl Boot {
(parent.unwrap().to_owned(), Some(entry.file_name().unwrap().to_owned())) (parent.unwrap().to_owned(), Some(entry.file_name().unwrap().to_owned()))
} }
fn process_output(name: impl AsRef<OsStr>, arg: impl AsRef<OsStr>) -> String {
match std::process::Command::new(name.as_ref()).arg(arg).output() {
Ok(out) if out.status.success() => {
let line =
String::from_utf8_lossy(&out.stdout).trim().lines().next().unwrap_or_default().to_owned();
Regex::new(r"\d+\.\d+(\.\d+-\d+|\.\d+|\b)")
.unwrap()
.find(&line)
.map(|m| m.as_str().to_owned())
.unwrap_or(line)
}
Ok(out) => format!("{:?}, {:?}", out.status, String::from_utf8_lossy(&out.stderr)),
Err(e) => format!("{e}"),
}
}
fn action_version() -> String { fn action_version() -> String {
format!( format!(
"{} ({} {})", "{} ({} {})",
@ -47,26 +64,29 @@ impl Boot {
} }
fn action_debug() -> Result<String, std::fmt::Error> { fn action_debug() -> Result<String, std::fmt::Error> {
use std::{env::consts::{ARCH, FAMILY, OS}, process::Command}; use std::env::consts::{ARCH, FAMILY, OS};
let mut s = String::new(); let mut s = String::new();
writeln!(s, "\nYazi")?; writeln!(s, "\nYazi")?;
writeln!(s, " Version: {}", Self::action_version())?; writeln!(s, " Version: {}", Self::action_version())?;
writeln!(s, " OS: {}-{} ({})", OS, ARCH, FAMILY)?; writeln!(s, " Debug : {}", cfg!(debug_assertions))?;
writeln!(s, " Debug: {}", cfg!(debug_assertions))?; writeln!(s, " OS : {}-{} ({})", OS, ARCH, FAMILY)?;
writeln!(s, "\nYa")?;
writeln!(s, " Version: {}", Self::process_output("ya", "--version"))?;
writeln!(s, "\nEmulator")?; writeln!(s, "\nEmulator")?;
writeln!(s, " Emulator.via_env: {:?}", yazi_adaptor::Emulator::via_env())?; writeln!(s, " Emulator.via_env: {:?}", yazi_adapter::Emulator::via_env())?;
writeln!(s, " Emulator.via_csi: {:?}", yazi_adaptor::Emulator::via_csi())?; writeln!(s, " Emulator.via_csi: {:?}", yazi_adapter::Emulator::via_csi())?;
writeln!(s, " Emulator.detect: {:?}", yazi_adaptor::Emulator::detect())?; writeln!(s, " Emulator.detect : {:?}", yazi_adapter::Emulator::detect())?;
writeln!(s, "\nAdaptor")?; writeln!(s, "\nAdapter")?;
writeln!(s, " Adaptor.matches: {:?}", yazi_adaptor::Adaptor::matches())?; writeln!(s, " Adapter.matches: {:?}", yazi_adapter::Adapter::matches())?;
writeln!(s, "\nDesktop")?; writeln!(s, "\nDesktop")?;
writeln!(s, " XDG_SESSION_TYPE: {:?}", env::var_os("XDG_SESSION_TYPE"))?; writeln!(s, " XDG_SESSION_TYPE: {:?}", env::var_os("XDG_SESSION_TYPE"))?;
writeln!(s, " WAYLAND_DISPLAY: {:?}", env::var_os("WAYLAND_DISPLAY"))?; writeln!(s, " WAYLAND_DISPLAY : {:?}", env::var_os("WAYLAND_DISPLAY"))?;
writeln!(s, " DISPLAY: {:?}", env::var_os("DISPLAY"))?; writeln!(s, " DISPLAY : {:?}", env::var_os("DISPLAY"))?;
writeln!(s, "\nSSH")?; writeln!(s, "\nSSH")?;
writeln!(s, " shared.in_ssh_connection: {:?}", yazi_shared::in_ssh_connection())?; writeln!(s, " shared.in_ssh_connection: {:?}", yazi_shared::in_ssh_connection())?;
@ -79,18 +99,11 @@ impl Boot {
)?; )?;
writeln!(s, "\nVariables")?; writeln!(s, "\nVariables")?;
writeln!(s, " SHELL: {:?}", env::var_os("SHELL"))?; writeln!(s, " SHELL : {:?}", env::var_os("SHELL"))?;
writeln!(s, " EDITOR: {:?}", env::var_os("EDITOR"))?; writeln!(s, " EDITOR : {:?}", env::var_os("EDITOR"))?;
writeln!(s, " ZELLIJ_SESSION_NAME: {:?}", env::var_os("ZELLIJ_SESSION_NAME"))?; writeln!(s, " ZELLIJ_SESSION_NAME: {:?}", env::var_os("ZELLIJ_SESSION_NAME"))?;
writeln!(s, " YAZI_FILE_ONE: {:?}", env::var_os("YAZI_FILE_ONE"))?; writeln!(s, " YAZI_FILE_ONE : {:?}", env::var_os("YAZI_FILE_ONE"))?;
writeln!(s, " YAZI_CONFIG_HOME: {:?}", env::var_os("YAZI_CONFIG_HOME"))?; writeln!(s, " YAZI_CONFIG_HOME : {:?}", env::var_os("YAZI_CONFIG_HOME"))?;
writeln!(s, "\nfile(1)")?;
writeln!(
s,
" Version: {:?}",
Command::new(env::var_os("YAZI_FILE_ONE").unwrap_or("file".into())).arg("--version").output()
)?;
writeln!(s, "\nText Opener")?; writeln!(s, "\nText Opener")?;
writeln!( writeln!(
@ -98,13 +111,28 @@ impl Boot {
" default: {:?}", " default: {:?}",
yazi_config::OPEN.openers("f75a.txt", "text/plain").and_then(|a| a.first().cloned()) yazi_config::OPEN.openers("f75a.txt", "text/plain").and_then(|a| a.first().cloned())
)?; )?;
writeln!(s, " block: {:?}", yazi_config::OPEN.block_opener("bulk.txt", "text/plain"))?; writeln!(s, " block : {:?}", yazi_config::OPEN.block_opener("bulk.txt", "text/plain"))?;
writeln!(s, "\ntmux")?; writeln!(s, "\ntmux")?;
writeln!(s, " TMUX: {:?}", *yazi_adaptor::TMUX)?; writeln!(s, " TMUX : {:?}", *yazi_adapter::TMUX)?;
writeln!(s, " Version: {}", Self::process_output("tmux", "-V"))?;
writeln!(s, "\nUeberzug++")?; writeln!(s, "\nDependencies")?;
writeln!(s, " Version: {:?}", Command::new("ueberzugpp").arg("--version").output())?; writeln!(
s,
" file : {}",
Self::process_output(env::var_os("YAZI_FILE_ONE").unwrap_or("file".into()), "--version")
)?;
writeln!(s, " ueberzugpp : {}", Self::process_output("ueberzugpp", "--version"))?;
writeln!(s, " ffmpegthumbnailer: {}", Self::process_output("ffmpegthumbnailer", "-v"))?;
writeln!(s, " magick : {}", Self::process_output("magick", "--version"))?;
writeln!(s, " fzf : {}", Self::process_output("fzf", "--version"))?;
writeln!(s, " fd : {}", Self::process_output("fd", "--version"))?;
writeln!(s, " rg : {}", Self::process_output("rg", "--version"))?;
writeln!(s, " chafa : {}", Self::process_output("chafa", "--version"))?;
writeln!(s, " zoxide : {}", Self::process_output("zoxide", "--version"))?;
writeln!(s, " unar : {}", Self::process_output("unar", "--version"))?;
writeln!(s, " jq : {}", Self::process_output("jq", "--version"))?;
writeln!(s, "\n\n--------------------------------------------------")?; writeln!(s, "\n\n--------------------------------------------------")?;
writeln!( writeln!(
@ -145,7 +173,7 @@ impl Default for Boot {
.map(|s| s.split(',').map(|s| s.to_owned()).collect()) .map(|s| s.split(',').map(|s| s.to_owned()).collect())
.unwrap_or_default(); .unwrap_or_default();
let boot = Self { Self {
cwd, cwd,
file, file,
@ -156,11 +184,7 @@ impl Default for Boot {
plugin_dir: config_dir.join("plugins"), plugin_dir: config_dir.join("plugins"),
config_dir, config_dir,
state_dir: Xdg::state_dir(), state_dir: Xdg::state_dir(),
}; }
std::fs::create_dir_all(&boot.flavor_dir).ok();
std::fs::create_dir_all(&boot.plugin_dir).ok();
boot
} }
} }

View file

@ -9,22 +9,31 @@ homepage = "https://yazi-rs.github.io"
repository = "https://github.com/sxyazi/yazi" repository = "https://github.com/sxyazi/yazi"
[dependencies] [dependencies]
yazi-dds = { path = "../yazi-dds", version = "0.2.5" } yazi-dds = { path = "../yazi-dds", version = "0.2.5" }
yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies # External dependencies
anyhow = "1.0.82" anyhow = "1.0.86"
clap = { version = "4.5.4", features = [ "derive" ] } clap = { version = "4.5.7", features = [ "derive" ] }
serde_json = "1.0.116" crossterm = "0.27.0"
tokio = { version = "1.37.0", features = [ "full" ] } md-5 = "0.10.6"
serde_json = "1.0.117"
tokio = { version = "1.38.0", features = [ "full" ] }
toml_edit = "0.22.14"
[build-dependencies] [build-dependencies]
anyhow = "1.0.82" anyhow = "1.0.86"
clap = { version = "4.5.4", features = [ "derive" ] } clap = { version = "4.5.7", features = [ "derive" ] }
clap_complete = "4.5.2" clap_complete = "4.5.6"
clap_complete_fig = "4.5.0" clap_complete_fig = "4.5.1"
clap_complete_nushell = "4.5.1" clap_complete_nushell = "4.5.2"
serde_json = "1.0.116" serde_json = "1.0.117"
vergen = { version = "8.3.1", features = [ "build", "git", "gitcl" ] }
[[bin]] [[bin]]
name = "ya" name = "ya"
path = "src/main.rs" path = "src/main.rs"
[package.metadata.binstall]
pkg-url = "{ repo }/releases/download/v{ version }/yazi-{ target }{ archive-suffix }"
bin-dir = "yazi-{ target }/{ bin }{ binary-ext }"

View file

@ -5,8 +5,11 @@ use std::{env, error::Error};
use clap::CommandFactory; use clap::CommandFactory;
use clap_complete::{generate_to, Shell}; use clap_complete::{generate_to, Shell};
use vergen::EmitBuilder;
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
EmitBuilder::builder().build_date().git_sha(true).emit()?;
if env::var_os("YAZI_GEN_COMPLETIONS").is_none() { if env::var_os("YAZI_GEN_COMPLETIONS").is_none() {
return Ok(()); return Ok(());
} }

View file

@ -4,29 +4,34 @@ use anyhow::{bail, Result};
use clap::{command, Parser, Subcommand}; use clap::{command, Parser, Subcommand};
#[derive(Parser)] #[derive(Parser)]
#[command(name = "ya", version, about, long_about = None)] #[command(name = "Ya", about, long_about = None)]
#[command(propagate_version = true)]
pub(super) struct Args { pub(super) struct Args {
#[command(subcommand)] #[command(subcommand)]
pub(super) command: Command, pub(super) command: Command,
/// Print version
#[arg(short = 'V', long)]
pub(super) version: bool,
} }
#[derive(Subcommand)] #[derive(Subcommand)]
pub(super) enum Command { pub(super) enum Command {
/// Publish a message to remote instance(s). /// Publish a message to remote instance(s).
Pub(CommandPub), Pub(CommandPub),
/// Publish a static message to all remote instances. /// Manage packages.
PubStatic(CommandPubStatic), Pack(CommandPack),
/// Subscribe to messages from all remote instances.
Sub(CommandSub),
} }
#[derive(clap::Args)] #[derive(clap::Args)]
pub(super) struct CommandPub { pub(super) struct CommandPub {
/// The receiver ID.
#[arg(index = 1)]
pub(super) receiver: u64,
/// The kind of message. /// The kind of message.
#[arg(index = 2)] #[arg(index = 1)]
pub(super) kind: String, pub(super) kind: String,
/// The receiver ID.
#[arg(index = 2)]
pub(super) receiver: Option<u64>,
/// Send the message with a string body. /// Send the message with a string body.
#[arg(long)] #[arg(long)]
pub(super) str: Option<String>, pub(super) str: Option<String>,
@ -37,34 +42,16 @@ pub(super) struct CommandPub {
impl CommandPub { impl CommandPub {
#[allow(dead_code)] #[allow(dead_code)]
pub(super) fn body(&self) -> Result<Cow<str>> { pub(super) fn receiver(&self) -> Result<u64> {
if let Some(json) = &self.json { if let Some(receiver) = self.receiver {
Ok(json.into()) Ok(receiver)
} else if let Some(str) = &self.str { } else if let Some(s) = std::env::var("YAZI_PID").ok().filter(|s| !s.is_empty()) {
Ok(serde_json::to_string(str)?.into()) Ok(s.parse()?)
} else { } else {
bail!("No body provided"); bail!("No receiver ID provided, also no YAZI_ID environment variable found.")
} }
} }
}
#[derive(clap::Args)]
pub(super) struct CommandPubStatic {
/// The severity of the message.
#[arg(index = 1)]
pub(super) severity: u16,
/// The kind of message.
#[arg(index = 2)]
pub(super) kind: String,
/// Send the message with a string body.
#[arg(long)]
pub(super) str: Option<String>,
/// Send the message with a JSON body.
#[arg(long)]
pub(super) json: Option<String>,
}
impl CommandPubStatic {
#[allow(dead_code)] #[allow(dead_code)]
pub(super) fn body(&self) -> Result<Cow<str>> { pub(super) fn body(&self) -> Result<Cow<str>> {
if let Some(json) = &self.json { if let Some(json) = &self.json {
@ -72,7 +59,31 @@ impl CommandPubStatic {
} else if let Some(str) = &self.str { } else if let Some(str) = &self.str {
Ok(serde_json::to_string(str)?.into()) Ok(serde_json::to_string(str)?.into())
} else { } else {
bail!("No body provided"); Ok("".into())
} }
} }
} }
#[derive(clap::Args)]
#[command(arg_required_else_help = true)]
pub(super) struct CommandPack {
/// Add a package.
#[arg(short = 'a', long)]
pub(super) add: Option<String>,
/// Install all packages.
#[arg(short = 'i', long)]
pub(super) install: bool,
/// List all packages.
#[arg(short = 'l', long)]
pub(super) list: bool,
/// Upgrade all packages.
#[arg(short = 'u', long)]
pub(super) upgrade: bool,
}
#[derive(clap::Args)]
pub(super) struct CommandSub {
/// The kind of messages to subscribe to, separated by commas if multiple.
#[arg(index = 1)]
pub(super) kinds: String,
}

View file

@ -1,27 +1,51 @@
mod args; mod args;
mod package;
use args::*; use args::*;
use clap::Parser; use clap::Parser;
#[tokio::main] #[tokio::main]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
let args = Args::parse(); if std::env::args_os().nth(1).is_some_and(|s| s == "-V" || s == "--version") {
println!(
"Ya {} ({} {})",
env!("CARGO_PKG_VERSION"),
env!("VERGEN_GIT_SHA"),
env!("VERGEN_BUILD_DATE")
);
return Ok(());
}
match &args.command { match Args::parse().command {
Command::Pub(cmd) => { Command::Pub(cmd) => {
yazi_dds::init(); yazi_dds::init();
if let Err(e) = yazi_dds::Client::shot(&cmd.kind, cmd.receiver, None, &cmd.body()?).await { if let Err(e) = yazi_dds::Client::shot(&cmd.kind, cmd.receiver()?, &cmd.body()?).await {
eprintln!("Cannot send message: {e}"); eprintln!("Cannot send message: {e}");
std::process::exit(1); std::process::exit(1);
} }
} }
Command::PubStatic(cmd) => { Command::Pack(cmd) => {
yazi_dds::init(); package::init();
if let Err(e) = yazi_dds::Client::shot(&cmd.kind, 0, Some(cmd.severity), &cmd.body()?).await { if cmd.install {
eprintln!("Cannot send message: {e}"); package::Package::install_from_config("plugin", false).await?;
std::process::exit(1); package::Package::install_from_config("flavor", false).await?;
} else if cmd.list {
package::Package::list_from_config("plugin").await?;
package::Package::list_from_config("flavor").await?;
} else if cmd.upgrade {
package::Package::install_from_config("plugin", true).await?;
package::Package::install_from_config("flavor", true).await?;
} else if let Some(repo) = &cmd.add {
package::Package::add_to_config(repo).await?;
} }
} }
Command::Sub(cmd) => {
yazi_dds::init();
yazi_dds::Client::draw(cmd.kinds.split(',').collect()).await?;
tokio::signal::ctrl_c().await?;
}
} }
Ok(()) Ok(())

View file

@ -0,0 +1,20 @@
use anyhow::Result;
use yazi_shared::fs::must_exists;
use super::{Git, Package};
impl Package {
pub(super) async fn add(&mut self) -> Result<()> {
self.output("Upgrading package `{name}`")?;
let path = self.local();
if !must_exists(&path).await {
Git::clone(&self.remote(), &path).await?;
} else {
Git::pull(&path).await?;
};
self.commit = Git::hash(&path).await?;
self.deploy().await
}
}

View file

@ -0,0 +1,50 @@
use anyhow::{bail, Context, Result};
use tokio::fs;
use yazi_shared::{fs::{maybe_exists, must_exists}, Xdg};
use super::Package;
const TRACKER: &str = "DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY";
impl Package {
pub(super) async fn deploy(&mut self) -> Result<()> {
let Some(name) = self.name().map(ToOwned::to_owned) else { bail!("Invalid package url") };
let from = self.local().join(&self.child);
self.output("Deploying package `{name}`")?;
self.is_flavor = maybe_exists(&from.join("flavor.toml")).await;
let to = if self.is_flavor {
Xdg::config_dir().join(format!("flavors/{name}"))
} else {
Xdg::config_dir().join(format!("plugins/{name}"))
};
let tracker = to.join(TRACKER);
if maybe_exists(&to).await && !must_exists(&tracker).await {
bail!(
"A user package with the same name `{name}` already exists.
For safety, please manually delete it from your plugin/flavor directory and re-run the command."
);
}
fs::create_dir_all(&to).await?;
fs::write(tracker, []).await?;
let files = if self.is_flavor {
&["flavor.toml", "tmtheme.xml", "README.md", "preview.png", "LICENSE", "LICENSE-tmtheme"][..]
} else {
&["init.lua", "README.md", "LICENSE"][..]
};
for file in files {
let (from, to) = (from.join(file), to.join(file));
fs::copy(&from, &to)
.await
.with_context(|| format!("Failed to copy `{}` to `{}`", from.display(), to.display()))?;
}
println!("Done!");
Ok(())
}
}

View file

@ -0,0 +1,55 @@
use std::path::Path;
use anyhow::{bail, Context, Result};
use tokio::process::Command;
use yazi_shared::strip_trailing_newline;
pub(super) struct Git;
impl Git {
pub(super) async fn clone(url: &str, path: &Path) -> Result<()> {
Self::exec(|c| c.args(["clone", url]).arg(path)).await
}
pub(super) async fn fetch(path: &Path) -> Result<()> {
Self::exec(|c| c.current_dir(path).arg("fetch")).await
}
pub(super) async fn checkout(path: &Path, commit: &str) -> Result<()> {
Self::exec(|c| c.current_dir(path).args(["checkout", commit])).await
}
pub(super) async fn pull(path: &Path) -> Result<()> {
Self::fetch(path).await?;
Self::checkout(path, "origin/HEAD").await?;
Ok(())
}
pub(super) async fn hash(path: &Path) -> Result<String> {
let output = Command::new("git")
.current_dir(path)
.args(["rev-parse", "--short", "HEAD"])
.output()
.await
.context("Failed to get current commit hash")?;
if !output.status.success() {
bail!("Getting commit hash failed: {}", output.status);
}
Ok(strip_trailing_newline(
String::from_utf8(output.stdout).context("Failed to parse commit hash")?,
))
}
async fn exec(f: impl FnOnce(&mut Command) -> &mut Command) -> Result<()> {
let status =
f(&mut Command::new("git")).status().await.context("Failed to execute `git` command")?;
if !status.success() {
bail!("`git` command failed: {status}");
}
Ok(())
}
}

View file

@ -0,0 +1,25 @@
use anyhow::Result;
use yazi_shared::fs::must_exists;
use super::{Git, Package};
impl Package {
pub(super) async fn install(&mut self) -> Result<()> {
self.output("Installing package `{name}`")?;
let path = self.local();
if !must_exists(&path).await {
Git::clone(&self.remote(), &path).await?;
} else {
Git::fetch(&path).await?;
};
if self.commit.is_empty() {
self.commit = Git::hash(&path).await?;
} else {
Git::checkout(&path, self.commit.trim_start_matches('=')).await?;
}
self.deploy().await
}
}

View file

@ -0,0 +1,17 @@
#![allow(clippy::module_inception)]
mod add;
mod deploy;
mod git;
mod install;
mod package;
mod parser;
mod upgrade;
use git::*;
pub(super) use package::*;
pub(super) fn init() {
let root = yazi_shared::Xdg::state_dir().join("packages");
std::fs::create_dir_all(root).expect("Failed to create packages directory");
}

View file

@ -0,0 +1,79 @@
use std::{borrow::Cow, io::BufWriter, path::PathBuf};
use anyhow::Result;
use md5::{Digest, Md5};
use yazi_shared::Xdg;
pub(crate) struct Package {
pub(crate) repo: String,
pub(crate) child: String,
pub(crate) commit: String,
pub(super) is_flavor: bool,
}
impl Package {
pub(super) fn new(url: &str, commit: Option<&str>) -> Self {
let mut parts = url.splitn(2, '#');
let mut repo = parts.next().unwrap_or_default().to_owned();
let child = if let Some(s) = parts.next() {
format!("{s}.yazi")
} else {
repo.push_str(".yazi");
String::new()
};
Self { repo, child, commit: commit.unwrap_or_default().to_owned(), is_flavor: false }
}
#[inline]
pub(super) fn use_(&self) -> Cow<str> {
if self.child.is_empty() {
self.repo.trim_end_matches(".yazi").into()
} else {
format!("{}#{}", self.repo, self.child.trim_end_matches(".yazi")).into()
}
}
#[inline]
pub(super) fn name(&self) -> Option<&str> {
let s = if self.child.is_empty() {
self.repo.split('/').last().filter(|s| !s.is_empty())
} else {
Some(self.child.as_str())
};
s.filter(|s| s.bytes().all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'z' | b'-' | b'.')))
}
#[inline]
pub(super) fn local(&self) -> PathBuf {
Xdg::state_dir()
.join("packages")
.join(format!("{:x}", Md5::new_with_prefix(self.remote()).finalize()))
}
#[inline]
pub(super) fn remote(&self) -> String {
// Support more Git hosting services in the future
format!("https://github.com/{}.git", self.repo)
}
pub(super) fn output(&self, s: &str) -> Result<()> {
use crossterm::style::{Attribute, Print, SetAttributes};
crossterm::execute!(
BufWriter::new(std::io::stdout()),
Print("\n"),
SetAttributes(Attribute::Reverse.into()),
SetAttributes(Attribute::Bold.into()),
Print(" "),
Print(s.replacen("{name}", self.name().unwrap_or_default(), 1)),
Print(" "),
SetAttributes(Attribute::NoBold.into()),
SetAttributes(Attribute::NoReverse.into()),
Print("\n\n"),
)?;
Ok(())
}
}

View file

@ -0,0 +1,135 @@
use anyhow::{bail, Context, Result};
use tokio::fs;
use toml_edit::{Array, DocumentMut, InlineTable, Item, Value};
use yazi_shared::Xdg;
use super::Package;
impl Package {
pub(crate) async fn add_to_config(use_: &str) -> Result<()> {
let mut package = Self::new(use_, None);
let Some(name) = package.name() else { bail!("Invalid package `use`") };
let path = Xdg::config_dir().join("package.toml");
let mut doc = Self::ensure_config(&fs::read_to_string(&path).await.unwrap_or_default())?;
Self::ensure_unique(&doc, name)?;
package.add().await?;
let mut table = InlineTable::new();
table.insert("use", package.use_().as_ref().into());
if !package.commit.is_empty() {
table.insert("commit", package.commit.into());
}
if package.is_flavor {
doc["flavor"]["deps"].as_array_mut().unwrap().push(table);
} else {
doc["plugin"]["deps"].as_array_mut().unwrap().push(table);
}
fs::write(path, doc.to_string()).await?;
Ok(())
}
pub(crate) async fn install_from_config(section: &str, upgrade: bool) -> Result<()> {
let path = Xdg::config_dir().join("package.toml");
let Ok(s) = fs::read_to_string(&path).await else {
return Ok(());
};
let mut doc = s.parse::<DocumentMut>().context("Failed to parse package.toml")?;
let Some(deps) = doc.get_mut(section).and_then(|d| d.get_mut("deps")) else {
return Ok(());
};
let deps = deps.as_array_mut().context("`deps` must be an array")?;
for dep in deps.iter_mut() {
let dep = dep.as_inline_table_mut().context("Dependency must be an inline table")?;
let use_ = dep.get("use").and_then(|d| d.as_str()).context("Missing `use` field")?;
let commit = dep.get("commit").and_then(|d| d.as_str());
let mut package = Package::new(use_, commit);
if upgrade {
package.upgrade().await?;
} else {
package.install().await?;
}
if package.commit.is_empty() {
dep.remove("commit");
} else {
dep.insert("commit", package.commit.into());
}
}
fs::write(path, doc.to_string()).await.context("Failed to write package.toml")
}
pub(crate) async fn list_from_config(section: &str) -> Result<()> {
let path = Xdg::config_dir().join("package.toml");
let Ok(s) = fs::read_to_string(&path).await else {
return Ok(());
};
let doc = s.parse::<DocumentMut>().context("Failed to parse package.toml")?;
let Some(deps) = doc.get(section).and_then(|d| d.get("deps")) else {
return Ok(());
};
let deps = deps.as_array().context("`deps` must be an array")?;
println!("{section}s:");
for dep in deps {
if let Some(Value::String(use_)) = dep.as_inline_table().and_then(|t| t.get("use")) {
println!("\t{}", use_.value());
}
}
Ok(())
}
fn ensure_config(s: &str) -> Result<DocumentMut> {
let mut doc = s.parse::<DocumentMut>().context("Failed to parse package.toml")?;
doc
.entry("plugin")
.or_insert(toml_edit::table())
.as_table_mut()
.context("Failed to get `plugin` table")?
.entry("deps")
.or_insert(Item::Value(Array::new().into()))
.as_array()
.context("Failed to get `deps` array")?;
doc
.entry("flavor")
.or_insert(toml_edit::table())
.as_table_mut()
.context("Failed to get `flavor` table")?
.entry("deps")
.or_insert(Item::Value(Array::new().into()))
.as_array()
.context("Failed to get `deps` array")?;
Ok(doc)
}
fn ensure_unique(doc: &DocumentMut, name: &str) -> Result<()> {
#[inline]
fn same(v: &Value, name: &str) -> bool {
v.as_inline_table()
.and_then(|t| t.get("use"))
.and_then(|v| v.as_str())
.is_some_and(|s| Package::new(s, None).name() == Some(name))
}
if doc["plugin"]["deps"].as_array().unwrap().into_iter().any(|v| same(v, name)) {
bail!("Plugin `{name}` already exists in package.toml");
}
if doc["flavor"]["deps"].as_array().unwrap().into_iter().any(|v| same(v, name)) {
bail!("Flavor `{name}` already exists in package.toml");
}
Ok(())
}
}

View file

@ -0,0 +1,9 @@
use anyhow::Result;
use super::Package;
impl Package {
pub(super) async fn upgrade(&mut self) -> Result<()> {
if self.commit.starts_with('=') { Ok(()) } else { self.add().await }
}
}

View file

@ -12,13 +12,13 @@ repository = "https://github.com/sxyazi/yazi"
yazi-shared = { path = "../yazi-shared", version = "0.2.5" } yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies # External dependencies
anyhow = "1.0.82" anyhow = "1.0.86"
arc-swap = "1.7.1" arc-swap = "1.7.1"
bitflags = "2.5.0"
crossterm = "0.27.0" crossterm = "0.27.0"
globset = "0.4.14" globset = "0.4.14"
indexmap = "2.2.6" indexmap = "2.2.6"
ratatui = "=0.26.1" ratatui = "0.27.0"
serde = { version = "1.0.198", features = [ "derive" ] } serde = { version = "1.0.203", features = [ "derive" ] }
shell-words = "1.1.0" toml = { version = "0.8.14", features = [ "preserve_order" ] }
toml = { version = "0.8.12", features = [ "preserve_order" ] }
validator = { version = "0.18.1", features = [ "derive" ] } validator = { version = "0.18.1", features = [ "derive" ] }

View file

@ -5,305 +5,305 @@
[manager] [manager]
keymap = [ keymap = [
{ on = [ "<Esc>" ], run = "escape", desc = "Exit visual mode, clear selected, or cancel search" }, { on = "<Esc>", run = "escape", desc = "Exit visual mode, clear selected, or cancel search" },
{ on = [ "<C-[>" ], run = "escape", desc = "Exit visual mode, clear selected, or cancel search" }, { on = "<C-[>", run = "escape", desc = "Exit visual mode, clear selected, or cancel search" },
{ on = [ "q" ], run = "quit", desc = "Exit the process" }, { on = "q", run = "quit", desc = "Exit the process" },
{ on = [ "Q" ], run = "quit --no-cwd-file", desc = "Exit the process without writing cwd-file" }, { on = "Q", run = "quit --no-cwd-file", desc = "Exit the process without writing cwd-file" },
{ on = [ "<C-q>" ], run = "close", desc = "Close the current tab, or quit if it is last tab" }, { on = "<C-c>", run = "close", desc = "Close the current tab, or quit if it is last tab" },
{ on = [ "<C-z>" ], run = "suspend", desc = "Suspend the process" }, { on = "<C-z>", run = "suspend", desc = "Suspend the process" },
# Navigation # Navigation
{ on = [ "k" ], run = "arrow -1", desc = "Move cursor up" }, { on = "k", run = "arrow -1", desc = "Move cursor up" },
{ on = [ "j" ], run = "arrow 1", desc = "Move cursor down" }, { on = "j", run = "arrow 1", desc = "Move cursor down" },
{ on = [ "K" ], run = "arrow -5", desc = "Move cursor up 5 lines" }, { on = "K", run = "arrow -5", desc = "Move cursor up 5 lines" },
{ on = [ "J" ], run = "arrow 5", desc = "Move cursor down 5 lines" }, { on = "J", run = "arrow 5", desc = "Move cursor down 5 lines" },
{ on = [ "<S-Up>" ], run = "arrow -5", desc = "Move cursor up 5 lines" }, { on = "<S-Up>", run = "arrow -5", desc = "Move cursor up 5 lines" },
{ on = [ "<S-Down>" ], run = "arrow 5", desc = "Move cursor down 5 lines" }, { on = "<S-Down>", run = "arrow 5", desc = "Move cursor down 5 lines" },
{ on = [ "<C-u>" ], run = "arrow -50%", desc = "Move cursor up half page" }, { on = "<C-u>", run = "arrow -50%", desc = "Move cursor up half page" },
{ on = [ "<C-d>" ], run = "arrow 50%", desc = "Move cursor down half page" }, { on = "<C-d>", run = "arrow 50%", desc = "Move cursor down half page" },
{ on = [ "<C-b>" ], run = "arrow -100%", desc = "Move cursor up one page" }, { on = "<C-b>", run = "arrow -100%", desc = "Move cursor up one page" },
{ on = [ "<C-f>" ], run = "arrow 100%", desc = "Move cursor down one page" }, { on = "<C-f>", run = "arrow 100%", desc = "Move cursor down one page" },
{ on = [ "<C-PageUp>" ], run = "arrow -50%", desc = "Move cursor up half page" }, { on = "<C-PageUp>", run = "arrow -50%", desc = "Move cursor up half page" },
{ on = [ "<C-PageDown>" ], run = "arrow 50%", desc = "Move cursor down half page" }, { on = "<C-PageDown>", run = "arrow 50%", desc = "Move cursor down half page" },
{ on = [ "<PageUp>" ], run = "arrow -100%", desc = "Move cursor up one page" }, { on = "<PageUp>", run = "arrow -100%", desc = "Move cursor up one page" },
{ on = [ "<PageDown>" ], run = "arrow 100%", desc = "Move cursor down one page" }, { on = "<PageDown>", run = "arrow 100%", desc = "Move cursor down one page" },
{ on = [ "h" ], run = "leave", desc = "Go back to the parent directory" }, { on = "h", run = "leave", desc = "Go back to the parent directory" },
{ on = [ "l" ], run = "enter", desc = "Enter the child directory" }, { on = "l", run = "enter", desc = "Enter the child directory" },
{ on = [ "H" ], run = "back", desc = "Go back to the previous directory" }, { on = "H", run = "back", desc = "Go back to the previous directory" },
{ on = [ "L" ], run = "forward", desc = "Go forward to the next directory" }, { on = "L", run = "forward", desc = "Go forward to the next directory" },
{ on = [ "<A-k>" ], run = "seek -5", desc = "Seek up 5 units in the preview" }, { on = "<A-k>", run = "seek -5", desc = "Seek up 5 units in the preview" },
{ on = [ "<A-j>" ], run = "seek 5", desc = "Seek down 5 units in the preview" }, { on = "<A-j>", run = "seek 5", desc = "Seek down 5 units in the preview" },
{ on = [ "<A-PageUp>" ], run = "seek -5", desc = "Seek up 5 units in the preview" }, { on = "<A-PageUp>", run = "seek -5", desc = "Seek up 5 units in the preview" },
{ on = [ "<A-PageDown>" ], run = "seek 5", desc = "Seek down 5 units in the preview" }, { on = "<A-PageDown>", run = "seek 5", desc = "Seek down 5 units in the preview" },
{ on = [ "<Up>" ], run = "arrow -1", desc = "Move cursor up" }, { on = "<Up>", run = "arrow -1", desc = "Move cursor up" },
{ on = [ "<Down>" ], run = "arrow 1", desc = "Move cursor down" }, { on = "<Down>", run = "arrow 1", desc = "Move cursor down" },
{ on = [ "<Left>" ], run = "leave", desc = "Go back to the parent directory" }, { on = "<Left>", run = "leave", desc = "Go back to the parent directory" },
{ on = [ "<Right>" ], run = "enter", desc = "Enter the child directory" }, { on = "<Right>", run = "enter", desc = "Enter the child directory" },
{ on = [ "g", "g" ], run = "arrow -99999999", desc = "Move cursor to the top" }, { on = [ "g", "g" ], run = "arrow -99999999", desc = "Move cursor to the top" },
{ on = [ "G" ], run = "arrow 99999999", desc = "Move cursor to the bottom" }, { on = "G", run = "arrow 99999999", desc = "Move cursor to the bottom" },
# Selection # Selection
{ on = [ "<Space>" ], run = [ "select --state=none", "arrow 1" ], desc = "Toggle the current selection state" }, { on = "<Space>", run = [ "select --state=none", "arrow 1" ], desc = "Toggle the current selection state" },
{ on = [ "v" ], run = "visual_mode", desc = "Enter visual mode (selection mode)" }, { on = "v", run = "visual_mode", desc = "Enter visual mode (selection mode)" },
{ on = [ "V" ], run = "visual_mode --unset", desc = "Enter visual mode (unset mode)" }, { on = "V", run = "visual_mode --unset", desc = "Enter visual mode (unset mode)" },
{ on = [ "<C-a>" ], run = "select_all --state=true", desc = "Select all files" }, { on = "<C-a>", run = "select_all --state=true", desc = "Select all files" },
{ on = [ "<C-r>" ], run = "select_all --state=none", desc = "Inverse selection of all files" }, { on = "<C-r>", run = "select_all --state=none", desc = "Inverse selection of all files" },
# Operation # Operation
{ on = [ "o" ], run = "open", desc = "Open the selected files" }, { on = "o", run = "open", desc = "Open selected files" },
{ on = [ "O" ], run = "open --interactive", desc = "Open the selected files interactively" }, { on = "O", run = "open --interactive", desc = "Open selected files interactively" },
{ on = [ "<Enter>" ], run = "open", desc = "Open the selected files" }, { on = "<Enter>", run = "open", desc = "Open selected files" },
{ on = [ "<C-Enter>" ], run = "open --interactive", desc = "Open the selected files interactively" }, { on = "<S-Enter>", run = "open --interactive", desc = "Open selected files interactively" },
{ on = [ "y" ], run = "yank", desc = "Copy the selected files" }, { on = "y", run = "yank", desc = "Copy selected files" },
{ on = [ "Y" ], run = "unyank", desc = "Cancel the yank status of files" }, { on = "x", run = "yank --cut", desc = "Cut the selected files" },
{ on = [ "x" ], run = "yank --cut", desc = "Cut the selected files" }, { on = "Y", run = "unyank", desc = "Cancel the yank status" },
{ on = [ "X" ], run = "unyank", desc = "Cancel the yank status of files" }, { on = "X", run = "unyank", desc = "Cancel the yank status" },
{ on = [ "p" ], run = "paste", desc = "Paste the files" }, { on = "p", run = "paste", desc = "Paste yanked files" },
{ on = [ "P" ], run = "paste --force", desc = "Paste the files (overwrite if the destination exists)" }, { on = "P", run = "paste --force", desc = "Paste yanked files (overwrite if the destination exists)" },
{ on = [ "-" ], run = "link", desc = "Symlink the absolute path of files" }, { on = "-", run = "link", desc = "Symlink the absolute path of yanked files" },
{ on = [ "_" ], run = "link --relative", desc = "Symlink the relative path of files" }, { on = "_", run = "link --relative", desc = "Symlink the relative path of yanked files" },
{ on = [ "d" ], run = "remove", desc = "Move the files to the trash" }, { on = "d", run = "remove", desc = "Trash selected files" },
{ on = [ "D" ], run = "remove --permanently", desc = "Permanently delete the files" }, { on = "D", run = "remove --permanently", desc = "Permanently delete selected files" },
{ on = [ "a" ], run = "create", desc = "Create a file or directory (ends with / for directories)" }, { on = "a", run = "create", desc = "Create a file (ends with / for directories)" },
{ on = [ "r" ], run = "rename --cursor=before_ext", desc = "Rename a file or directory" }, { on = "r", run = "rename --cursor=before_ext", desc = "Rename selected file(s)" },
{ on = [ ";" ], run = "shell", desc = "Run a shell command" }, { on = ";", run = "shell --interactive", desc = "Run a shell command" },
{ on = [ ":" ], run = "shell --block", desc = "Run a shell command (block the UI until the command finishes)" }, { on = ":", run = "shell --block --interactive", desc = "Run a shell command (block until finishes)" },
{ on = [ "." ], run = "hidden toggle", desc = "Toggle the visibility of hidden files" }, { on = ".", run = "hidden toggle", desc = "Toggle the visibility of hidden files" },
{ on = [ "s" ], run = "search fd", desc = "Search files by name using fd" }, { on = "s", run = "search fd", desc = "Search files by name using fd" },
{ on = [ "S" ], run = "search rg", desc = "Search files by content using ripgrep" }, { on = "S", run = "search rg", desc = "Search files by content using ripgrep" },
{ on = [ "<C-s>" ], run = "search none", desc = "Cancel the ongoing search" }, { on = "<C-s>", run = "search none", desc = "Cancel the ongoing search" },
{ on = [ "z" ], run = "plugin zoxide", desc = "Jump to a directory using zoxide" }, { on = "z", run = "plugin zoxide", desc = "Jump to a directory using zoxide" },
{ on = [ "Z" ], run = "plugin fzf", desc = "Jump to a directory, or reveal a file using fzf" }, { on = "Z", run = "plugin fzf", desc = "Jump to a directory or reveal a file using fzf" },
# Linemode # Linemode
{ on = [ "m", "s" ], run = "linemode size", desc = "Set linemode to size" }, { on = [ "m", "s" ], run = "linemode size", desc = "Set linemode to size" },
{ on = [ "m", "p" ], run = "linemode permissions", desc = "Set linemode to permissions" }, { on = [ "m", "p" ], run = "linemode permissions", desc = "Set linemode to permissions" },
{ on = [ "m", "m" ], run = "linemode mtime", desc = "Set linemode to mtime" }, { on = [ "m", "m" ], run = "linemode mtime", desc = "Set linemode to mtime" },
{ on = [ "m", "o" ], run = "linemode owner", desc = "Set linemode to owner" },
{ on = [ "m", "n" ], run = "linemode none", desc = "Set linemode to none" }, { on = [ "m", "n" ], run = "linemode none", desc = "Set linemode to none" },
# Copy # Copy
{ on = [ "c", "c" ], run = "copy path", desc = "Copy the absolute path" }, { on = [ "c", "c" ], run = "copy path", desc = "Copy the file path" },
{ on = [ "c", "d" ], run = "copy dirname", desc = "Copy the path of the parent directory" }, { on = [ "c", "d" ], run = "copy dirname", desc = "Copy the directory path" },
{ on = [ "c", "f" ], run = "copy filename", desc = "Copy the name of the file" }, { on = [ "c", "f" ], run = "copy filename", desc = "Copy the filename" },
{ on = [ "c", "n" ], run = "copy name_without_ext", desc = "Copy the name of the file without the extension" }, { on = [ "c", "n" ], run = "copy name_without_ext", desc = "Copy the filename without extension" },
# Filter # Filter
{ on = [ "f" ], run = "filter --smart", desc = "Filter the files" }, { on = "f", run = "filter --smart", desc = "Filter files" },
# Find # Find
{ on = [ "/" ], run = "find --smart", desc = "Find next file" }, { on = "/", run = "find --smart", desc = "Find next file" },
{ on = [ "?" ], run = "find --previous --smart", desc = "Find previous file" }, { on = "?", run = "find --previous --smart", desc = "Find previous file" },
{ on = [ "n" ], run = "find_arrow", desc = "Go to next found file" }, { on = "n", run = "find_arrow", desc = "Go to the next found" },
{ on = [ "N" ], run = "find_arrow --previous", desc = "Go to previous found file" }, { on = "N", run = "find_arrow --previous", desc = "Go to the previous found" },
# Sorting # Sorting
{ on = [ ",", "m" ], run = "sort modified --dir-first", desc = "Sort by modified time" }, { on = [ ",", "m" ], run = "sort modified --reverse=no", desc = "Sort by modified time" },
{ on = [ ",", "M" ], run = "sort modified --reverse --dir-first", desc = "Sort by modified time (reverse)" }, { on = [ ",", "M" ], run = "sort modified --reverse", desc = "Sort by modified time (reverse)" },
{ on = [ ",", "c" ], run = "sort created --dir-first", desc = "Sort by created time" }, { on = [ ",", "c" ], run = "sort created --reverse=no", desc = "Sort by created time" },
{ on = [ ",", "C" ], run = "sort created --reverse --dir-first", desc = "Sort by created time (reverse)" }, { on = [ ",", "C" ], run = "sort created --reverse", desc = "Sort by created time (reverse)" },
{ on = [ ",", "e" ], run = "sort extension --dir-first", desc = "Sort by extension" }, { on = [ ",", "e" ], run = "sort extension --reverse=no", desc = "Sort by extension" },
{ on = [ ",", "E" ], run = "sort extension --reverse --dir-first", desc = "Sort by extension (reverse)" }, { on = [ ",", "E" ], run = "sort extension --reverse", desc = "Sort by extension (reverse)" },
{ on = [ ",", "a" ], run = "sort alphabetical --dir-first", desc = "Sort alphabetically" }, { on = [ ",", "a" ], run = "sort alphabetical --reverse=no", desc = "Sort alphabetically" },
{ on = [ ",", "A" ], run = "sort alphabetical --reverse --dir-first", desc = "Sort alphabetically (reverse)" }, { on = [ ",", "A" ], run = "sort alphabetical --reverse", desc = "Sort alphabetically (reverse)" },
{ on = [ ",", "n" ], run = "sort natural --dir-first", desc = "Sort naturally" }, { on = [ ",", "n" ], run = "sort natural --reverse=no", desc = "Sort naturally" },
{ on = [ ",", "N" ], run = "sort natural --reverse --dir-first", desc = "Sort naturally (reverse)" }, { on = [ ",", "N" ], run = "sort natural --reverse", desc = "Sort naturally (reverse)" },
{ on = [ ",", "s" ], run = "sort size --dir-first", desc = "Sort by size" }, { on = [ ",", "s" ], run = "sort size --reverse=no", desc = "Sort by size" },
{ on = [ ",", "S" ], run = "sort size --reverse --dir-first", desc = "Sort by size (reverse)" }, { on = [ ",", "S" ], run = "sort size --reverse", desc = "Sort by size (reverse)" },
# Tabs # Tabs
{ on = [ "t" ], run = "tab_create --current", desc = "Create a new tab using the current path" }, { on = "t", run = "tab_create --current", desc = "Create a new tab with CWD" },
{ on = [ "1" ], run = "tab_switch 0", desc = "Switch to the first tab" }, { on = "1", run = "tab_switch 0", desc = "Switch to the first tab" },
{ on = [ "2" ], run = "tab_switch 1", desc = "Switch to the second tab" }, { on = "2", run = "tab_switch 1", desc = "Switch to the second tab" },
{ on = [ "3" ], run = "tab_switch 2", desc = "Switch to the third tab" }, { on = "3", run = "tab_switch 2", desc = "Switch to the third tab" },
{ on = [ "4" ], run = "tab_switch 3", desc = "Switch to the fourth tab" }, { on = "4", run = "tab_switch 3", desc = "Switch to the fourth tab" },
{ on = [ "5" ], run = "tab_switch 4", desc = "Switch to the fifth tab" }, { on = "5", run = "tab_switch 4", desc = "Switch to the fifth tab" },
{ on = [ "6" ], run = "tab_switch 5", desc = "Switch to the sixth tab" }, { on = "6", run = "tab_switch 5", desc = "Switch to the sixth tab" },
{ on = [ "7" ], run = "tab_switch 6", desc = "Switch to the seventh tab" }, { on = "7", run = "tab_switch 6", desc = "Switch to the seventh tab" },
{ on = [ "8" ], run = "tab_switch 7", desc = "Switch to the eighth tab" }, { on = "8", run = "tab_switch 7", desc = "Switch to the eighth tab" },
{ on = [ "9" ], run = "tab_switch 8", desc = "Switch to the ninth tab" }, { on = "9", run = "tab_switch 8", desc = "Switch to the ninth tab" },
{ on = [ "[" ], run = "tab_switch -1 --relative", desc = "Switch to the previous tab" }, { on = "[", run = "tab_switch -1 --relative", desc = "Switch to the previous tab" },
{ on = [ "]" ], run = "tab_switch 1 --relative", desc = "Switch to the next tab" }, { on = "]", run = "tab_switch 1 --relative", desc = "Switch to the next tab" },
{ on = [ "{" ], run = "tab_swap -1", desc = "Swap the current tab with the previous tab" }, { on = "{", run = "tab_swap -1", desc = "Swap current tab with previous tab" },
{ on = [ "}" ], run = "tab_swap 1", desc = "Swap the current tab with the next tab" }, { on = "}", run = "tab_swap 1", desc = "Swap current tab with next tab" },
# Tasks # Tasks
{ on = [ "w" ], run = "tasks_show", desc = "Show the tasks manager" }, { on = "w", run = "tasks_show", desc = "Show task manager" },
# Goto # Goto
{ on = [ "g", "h" ], run = "cd ~", desc = "Go to the home directory" }, { on = [ "g", "h" ], run = "cd ~", desc = "Go to the home directory" },
{ on = [ "g", "c" ], run = "cd ~/.config", desc = "Go to the config directory" }, { on = [ "g", "c" ], run = "cd ~/.config", desc = "Go to the config directory" },
{ on = [ "g", "d" ], run = "cd ~/Downloads", desc = "Go to the downloads directory" }, { on = [ "g", "d" ], run = "cd ~/Downloads", desc = "Go to the downloads directory" },
{ on = [ "g", "t" ], run = "cd /tmp", desc = "Go to the temporary directory" },
{ on = [ "g", "<Space>" ], run = "cd --interactive", desc = "Go to a directory interactively" }, { on = [ "g", "<Space>" ], run = "cd --interactive", desc = "Go to a directory interactively" },
# Help # Help
{ on = [ "~" ], run = "help", desc = "Open help" }, { on = "~", run = "help", desc = "Open help" },
] ]
[tasks] [tasks]
keymap = [ keymap = [
{ on = [ "<Esc>" ], run = "close", desc = "Hide the task manager" }, { on = "<Esc>", run = "close", desc = "Close task manager" },
{ on = [ "<C-[>" ], run = "close", desc = "Hide the task manager" }, { on = "<C-[>", run = "close", desc = "Close task manager" },
{ on = [ "<C-q>" ], run = "close", desc = "Hide the task manager" }, { on = "<C-c>", run = "close", desc = "Close task manager" },
{ on = [ "w" ], run = "close", desc = "Hide the task manager" }, { on = "w", run = "close", desc = "Close task manager" },
{ on = [ "k" ], run = "arrow -1", desc = "Move cursor up" }, { on = "k", run = "arrow -1", desc = "Move cursor up" },
{ on = [ "j" ], run = "arrow 1", desc = "Move cursor down" }, { on = "j", run = "arrow 1", desc = "Move cursor down" },
{ on = [ "<Up>" ], run = "arrow -1", desc = "Move cursor up" }, { on = "<Up>", run = "arrow -1", desc = "Move cursor up" },
{ on = [ "<Down>" ], run = "arrow 1", desc = "Move cursor down" }, { on = "<Down>", run = "arrow 1", desc = "Move cursor down" },
{ on = [ "<Enter>" ], run = "inspect", desc = "Inspect the task" }, { on = "<Enter>", run = "inspect", desc = "Inspect the task" },
{ on = [ "x" ], run = "cancel", desc = "Cancel the task" }, { on = "x", run = "cancel", desc = "Cancel the task" },
{ on = [ "~" ], run = "help", desc = "Open help" } { on = "~", run = "help", desc = "Open help" }
] ]
[select] [select]
keymap = [ keymap = [
{ on = [ "<Esc>" ], run = "close", desc = "Cancel selection" }, { on = "<Esc>", run = "close", desc = "Cancel selection" },
{ on = [ "<C-[>" ], run = "close", desc = "Cancel selection" }, { on = "<C-[>", run = "close", desc = "Cancel selection" },
{ on = [ "<C-q>" ], run = "close", desc = "Cancel selection" }, { on = "<C-c>", run = "close", desc = "Cancel selection" },
{ on = [ "<Enter>" ], run = "close --submit", desc = "Submit the selection" }, { on = "<Enter>", run = "close --submit", desc = "Submit the selection" },
{ on = [ "k" ], run = "arrow -1", desc = "Move cursor up" }, { on = "k", run = "arrow -1", desc = "Move cursor up" },
{ on = [ "j" ], run = "arrow 1", desc = "Move cursor down" }, { on = "j", run = "arrow 1", desc = "Move cursor down" },
{ on = [ "K" ], run = "arrow -5", desc = "Move cursor up 5 lines" }, { on = "K", run = "arrow -5", desc = "Move cursor up 5 lines" },
{ on = [ "J" ], run = "arrow 5", desc = "Move cursor down 5 lines" }, { on = "J", run = "arrow 5", desc = "Move cursor down 5 lines" },
{ on = [ "<Up>" ], run = "arrow -1", desc = "Move cursor up" }, { on = "<Up>", run = "arrow -1", desc = "Move cursor up" },
{ on = [ "<Down>" ], run = "arrow 1", desc = "Move cursor down" }, { on = "<Down>", run = "arrow 1", desc = "Move cursor down" },
{ on = [ "<S-Up>" ], run = "arrow -5", desc = "Move cursor up 5 lines" }, { on = "<S-Up>", run = "arrow -5", desc = "Move cursor up 5 lines" },
{ on = [ "<S-Down>" ], run = "arrow 5", desc = "Move cursor down 5 lines" }, { on = "<S-Down>", run = "arrow 5", desc = "Move cursor down 5 lines" },
{ on = [ "~" ], run = "help", desc = "Open help" } { on = "~", run = "help", desc = "Open help" }
] ]
[input] [input]
keymap = [ keymap = [
{ on = [ "<C-q>" ], run = "close", desc = "Cancel input" }, { on = "<C-c>", run = "close", desc = "Cancel input" },
{ on = [ "<Enter>" ], run = "close --submit", desc = "Submit the input" }, { on = "<Enter>", run = "close --submit", desc = "Submit the input" },
{ on = [ "<Esc>" ], run = "escape", desc = "Go back the normal mode, or cancel input" }, { on = "<Esc>", run = "escape", desc = "Go back the normal mode, or cancel input" },
{ on = [ "<C-[>" ], run = "escape", desc = "Go back the normal mode, or cancel input" }, { on = "<C-[>", run = "escape", desc = "Go back the normal mode, or cancel input" },
# Mode # Mode
{ on = [ "i" ], run = "insert", desc = "Enter insert mode" }, { on = "i", run = "insert", desc = "Enter insert mode" },
{ on = [ "a" ], run = "insert --append", desc = "Enter append mode" }, { on = "a", run = "insert --append", desc = "Enter append mode" },
{ on = [ "I" ], run = [ "move -999", "insert" ], desc = "Move to the BOL, and enter insert mode" }, { on = "I", run = [ "move -999", "insert" ], desc = "Move to the BOL, and enter insert mode" },
{ on = [ "A" ], run = [ "move 999", "insert --append" ], desc = "Move to the EOL, and enter append mode" }, { on = "A", run = [ "move 999", "insert --append" ], desc = "Move to the EOL, and enter append mode" },
{ on = [ "v" ], run = "visual", desc = "Enter visual mode" }, { on = "v", run = "visual", desc = "Enter visual mode" },
{ on = [ "V" ], run = [ "move -999", "visual", "move 999" ], desc = "Enter visual mode and select all" }, { on = "V", run = [ "move -999", "visual", "move 999" ], desc = "Enter visual mode and select all" },
# Character-wise movement # Character-wise movement
{ on = [ "h" ], run = "move -1", desc = "Move back a character" }, { on = "h", run = "move -1", desc = "Move back a character" },
{ on = [ "l" ], run = "move 1", desc = "Move forward a character" }, { on = "l", run = "move 1", desc = "Move forward a character" },
{ on = [ "<Left>" ], run = "move -1", desc = "Move back a character" }, { on = "<Left>", run = "move -1", desc = "Move back a character" },
{ on = [ "<Right>" ], run = "move 1", desc = "Move forward a character" }, { on = "<Right>", run = "move 1", desc = "Move forward a character" },
{ on = [ "<C-b>" ], run = "move -1", desc = "Move back a character" }, { on = "<C-b>", run = "move -1", desc = "Move back a character" },
{ on = [ "<C-f>" ], run = "move 1", desc = "Move forward a character" }, { on = "<C-f>", run = "move 1", desc = "Move forward a character" },
# Word-wise movement # Word-wise movement
{ on = [ "b" ], run = "backward", desc = "Move back to the start of the current or previous word" }, { on = "b", run = "backward", desc = "Move back to the start of the current or previous word" },
{ on = [ "w" ], run = "forward", desc = "Move forward to the start of the next word" }, { on = "w", run = "forward", desc = "Move forward to the start of the next word" },
{ on = [ "e" ], run = "forward --end-of-word", desc = "Move forward to the end of the current or next word" }, { on = "e", run = "forward --end-of-word", desc = "Move forward to the end of the current or next word" },
{ on = [ "<A-b>" ], run = "backward", desc = "Move back to the start of the current or previous word" }, { on = "<A-b>", run = "backward", desc = "Move back to the start of the current or previous word" },
{ on = [ "<A-f>" ], run = "forward --end-of-word", desc = "Move forward to the end of the current or next word" }, { on = "<A-f>", run = "forward --end-of-word", desc = "Move forward to the end of the current or next word" },
# Line-wise movement # Line-wise movement
{ on = [ "0" ], run = "move -999", desc = "Move to the BOL" }, { on = "0", run = "move -999", desc = "Move to the BOL" },
{ on = [ "$" ], run = "move 999", desc = "Move to the EOL" }, { on = "$", run = "move 999", desc = "Move to the EOL" },
{ on = [ "<C-a>" ], run = "move -999", desc = "Move to the BOL" }, { on = "<C-a>", run = "move -999", desc = "Move to the BOL" },
{ on = [ "<C-e>" ], run = "move 999", desc = "Move to the EOL" }, { on = "<C-e>", run = "move 999", desc = "Move to the EOL" },
{ on = [ "<Home>" ], run = "move -999", desc = "Move to the BOL" }, { on = "<Home>", run = "move -999", desc = "Move to the BOL" },
{ on = [ "<End>" ], run = "move 999", desc = "Move to the EOL" }, { on = "<End>", run = "move 999", desc = "Move to the EOL" },
# Delete # Delete
{ on = [ "<Backspace>" ], run = "backspace", desc = "Delete the character before the cursor" }, { on = "<Backspace>", run = "backspace", desc = "Delete the character before the cursor" },
{ on = [ "<Delete>" ], run = "backspace --under", desc = "Delete the character under the cursor" }, { on = "<Delete>", run = "backspace --under", desc = "Delete the character under the cursor" },
{ on = [ "<C-h>" ], run = "backspace", desc = "Delete the character before the cursor" }, { on = "<C-h>", run = "backspace", desc = "Delete the character before the cursor" },
{ on = [ "<C-d>" ], run = "backspace --under", desc = "Delete the character under the cursor" }, { on = "<C-d>", run = "backspace --under", desc = "Delete the character under the cursor" },
# Kill # Kill
{ on = [ "<C-u>" ], run = "kill bol", desc = "Kill backwards to the BOL" }, { on = "<C-u>", run = "kill bol", desc = "Kill backwards to the BOL" },
{ on = [ "<C-k>" ], run = "kill eol", desc = "Kill forwards to the EOL" }, { on = "<C-k>", run = "kill eol", desc = "Kill forwards to the EOL" },
{ on = [ "<C-w>" ], run = "kill backward", desc = "Kill backwards to the start of the current word" }, { on = "<C-w>", run = "kill backward", desc = "Kill backwards to the start of the current word" },
{ on = [ "<A-d>" ], run = "kill forward", desc = "Kill forwards to the end of the current word" }, { on = "<A-d>", run = "kill forward", desc = "Kill forwards to the end of the current word" },
# Cut/Yank/Paste # Cut/Yank/Paste
{ on = [ "d" ], run = "delete --cut", desc = "Cut the selected characters" }, { on = "d", run = "delete --cut", desc = "Cut the selected characters" },
{ on = [ "D" ], run = [ "delete --cut", "move 999" ], desc = "Cut until the EOL" }, { on = "D", run = [ "delete --cut", "move 999" ], desc = "Cut until the EOL" },
{ on = [ "c" ], run = "delete --cut --insert", desc = "Cut the selected characters, and enter insert mode" }, { on = "c", run = "delete --cut --insert", desc = "Cut the selected characters, and enter insert mode" },
{ on = [ "C" ], run = [ "delete --cut --insert", "move 999" ], desc = "Cut until the EOL, and enter insert mode" }, { on = "C", run = [ "delete --cut --insert", "move 999" ], desc = "Cut until the EOL, and enter insert mode" },
{ on = [ "x" ], run = [ "delete --cut", "move 1 --in-operating" ], desc = "Cut the current character" }, { on = "x", run = [ "delete --cut", "move 1 --in-operating" ], desc = "Cut the current character" },
{ on = [ "y" ], run = "yank", desc = "Copy the selected characters" }, { on = "y", run = "yank", desc = "Copy the selected characters" },
{ on = [ "p" ], run = "paste", desc = "Paste the copied characters after the cursor" }, { on = "p", run = "paste", desc = "Paste the copied characters after the cursor" },
{ on = [ "P" ], run = "paste --before", desc = "Paste the copied characters before the cursor" }, { on = "P", run = "paste --before", desc = "Paste the copied characters before the cursor" },
# Undo/Redo # Undo/Redo
{ on = [ "u" ], run = "undo", desc = "Undo the last operation" }, { on = "u", run = "undo", desc = "Undo the last operation" },
{ on = [ "<C-r>" ], run = "redo", desc = "Redo the last operation" }, { on = "<C-r>", run = "redo", desc = "Redo the last operation" },
# Help # Help
{ on = [ "~" ], run = "help", desc = "Open help" } { on = "~", run = "help", desc = "Open help" }
] ]
[completion] [completion]
keymap = [ keymap = [
{ on = [ "<C-q>" ], run = "close", desc = "Cancel completion" }, { on = "<C-c>", run = "close", desc = "Cancel completion" },
{ on = [ "<Tab>" ], run = "close --submit", desc = "Submit the completion" }, { on = "<Tab>", run = "close --submit", desc = "Submit the completion" },
{ on = [ "<Enter>" ], run = [ "close --submit", "close_input --submit" ], desc = "Submit the completion and input" }, { on = "<Enter>", run = [ "close --submit", "close_input --submit" ], desc = "Submit the completion and input" },
{ on = [ "<A-k>" ], run = "arrow -1", desc = "Move cursor up" }, { on = "<A-k>", run = "arrow -1", desc = "Move cursor up" },
{ on = [ "<A-j>" ], run = "arrow 1", desc = "Move cursor down" }, { on = "<A-j>", run = "arrow 1", desc = "Move cursor down" },
{ on = [ "<Up>" ], run = "arrow -1", desc = "Move cursor up" }, { on = "<Up>", run = "arrow -1", desc = "Move cursor up" },
{ on = [ "<Down>" ], run = "arrow 1", desc = "Move cursor down" }, { on = "<Down>", run = "arrow 1", desc = "Move cursor down" },
{ on = [ "<C-p>" ], run = "arrow -1", desc = "Move cursor up" }, { on = "<C-p>", run = "arrow -1", desc = "Move cursor up" },
{ on = [ "<C-n>" ], run = "arrow 1", desc = "Move cursor down" }, { on = "<C-n>", run = "arrow 1", desc = "Move cursor down" },
{ on = [ "~" ], run = "help", desc = "Open help" } { on = "~", run = "help", desc = "Open help" }
] ]
[help] [help]
keymap = [ keymap = [
{ on = [ "<Esc>" ], run = "escape", desc = "Clear the filter, or hide the help" }, { on = "<Esc>", run = "escape", desc = "Clear the filter, or hide the help" },
{ on = [ "<C-[>" ], run = "escape", desc = "Clear the filter, or hide the help" }, { on = "<C-[>", run = "escape", desc = "Clear the filter, or hide the help" },
{ on = [ "q" ], run = "close", desc = "Exit the process" }, { on = "q", run = "close", desc = "Exit the process" },
{ on = [ "<C-q>" ], run = "close", desc = "Hide the help" }, { on = "<C-c>", run = "close", desc = "Hide the help" },
# Navigation # Navigation
{ on = [ "k" ], run = "arrow -1", desc = "Move cursor up" }, { on = "k", run = "arrow -1", desc = "Move cursor up" },
{ on = [ "j" ], run = "arrow 1", desc = "Move cursor down" }, { on = "j", run = "arrow 1", desc = "Move cursor down" },
{ on = [ "K" ], run = "arrow -5", desc = "Move cursor up 5 lines" }, { on = "K", run = "arrow -5", desc = "Move cursor up 5 lines" },
{ on = [ "J" ], run = "arrow 5", desc = "Move cursor down 5 lines" }, { on = "J", run = "arrow 5", desc = "Move cursor down 5 lines" },
{ on = [ "<Up>" ], run = "arrow -1", desc = "Move cursor up" }, { on = "<Up>", run = "arrow -1", desc = "Move cursor up" },
{ on = [ "<Down>" ], run = "arrow 1", desc = "Move cursor down" }, { on = "<Down>", run = "arrow 1", desc = "Move cursor down" },
{ on = [ "<S-Up>" ], run = "arrow -5", desc = "Move cursor up 5 lines" }, { on = "<S-Up>", run = "arrow -5", desc = "Move cursor up 5 lines" },
{ on = [ "<S-Down>" ], run = "arrow 5", desc = "Move cursor down 5 lines" }, { on = "<S-Down>", run = "arrow 5", desc = "Move cursor down 5 lines" },
# Filtering # Filtering
{ on = [ "/" ], run = "filter", desc = "Apply a filter for the help items" }, { on = "/", run = "filter", desc = "Apply a filter for the help items" },
] ]

View file

@ -170,17 +170,24 @@ icon_error = ""
rules = [ rules = [
# Images # Images
{ mime = "image/*", fg = "cyan" }, { mime = "image/*", fg = "yellow" },
# Media # Media
{ mime = "{audio,video}/*", fg = "yellow" }, { mime = "{audio,video}/*", fg = "magenta" },
# Archives # Archives
{ mime = "application/*zip", fg = "magenta" }, { mime = "application/{,g}zip", fg = "red" },
{ mime = "application/x-{tar,bzip*,7z-compressed,xz,rar}", fg = "magenta" }, { mime = "application/x-{tar,bzip*,7z-compressed,xz,rar}", fg = "red" },
# Documents # Documents
{ mime = "application/{pdf,doc,rtf,vnd.*}", fg = "green" }, { mime = "application/{pdf,doc,rtf,vnd.*}", fg = "cyan" },
# Empty files
# { mime = "inode/x-empty", fg = "red" },
# Special files
{ name = "*", is = "orphan", bg = "red" },
{ name = "*", is = "exec" , fg = "green" },
# Fallback # Fallback
# { name = "*", fg = "white" }, # { name = "*", fg = "white" },
@ -189,155 +196,586 @@ rules = [
[icon] [icon]
rules = [ globs = []
# Programming dirs = [
{ name = "*.c" , text = "", fg = "#599eff" }, { name = ".config", text = "" },
{ name = "*.cpp" , text = "", fg = "#519aba" }, { name = ".git", text = "" },
{ name = "*.class", text = "", fg = "#cc3e44" }, { name = "Desktop", text = "" },
{ name = "*.cs" , text = "󰌛", fg = "#596706" }, { name = "Development", text = "" },
{ name = "*.css" , text = "", fg = "#42a5f5" }, { name = "Documents", text = "" },
{ name = "*.elm" , text = "", fg = "#4391d2" }, { name = "Downloads", text = "" },
{ name = "*.fish" , text = "", fg = "#4d5a5e" }, { name = "Library", text = "" },
{ name = "*.go" , text = "", fg = "#519aba" }, { name = "Movies", text = "" },
{ name = "*.h" , text = "", fg = "#a074c4" }, { name = "Music", text = "" },
{ name = "*.hpp" , text = "", fg = "#a074c4" }, { name = "Pictures", text = "" },
{ name = "*.html" , text = "", fg = "#e44d26" }, { name = "Public", text = "" },
{ name = "*.jar" , text = "", fg = "#cc3e44" }, { name = "Videos", text = "" },
{ name = "*.java" , text = "", fg = "#cc3e44" }, ]
{ name = "*.js" , text = "", fg = "#F1F134" }, files = [
{ name = "*.jsx" , text = "", fg = "#20c2e3" }, { name = ".babelrc", text = "", fg_dark = "#cbcb41", fg_light = "#666620" },
{ name = "*.lua" , text = "", fg = "#51a0cf" }, { name = ".bash_profile", text = "", fg_dark = "#89e051", fg_light = "#447028" },
{ name = "*.nix" , text = "", fg = "#7ebae4" }, { name = ".bashrc", text = "", fg_dark = "#89e051", fg_light = "#447028" },
{ name = "*.nu" , text = ">", fg = "#3aa675" }, { name = ".dockerignore", text = "󰡨", fg_dark = "#458ee6", fg_light = "#2e5f99" },
{ name = "*.php" , text = "", fg = "#a074c4" }, { name = ".ds_store", text = "", fg_dark = "#41535b", fg_light = "#41535b" },
{ name = "*.py" , text = "", fg = "#ffbc03" }, { name = ".editorconfig", text = "", fg_dark = "#fff2f2", fg_light = "#333030" },
{ name = "*.rb" , text = "", fg = "#701516" }, { name = ".env", text = "", fg_dark = "#faf743", fg_light = "#32310d" },
{ name = "*.rs" , text = "", fg = "#dea584" }, { name = ".eslintignore", text = "", fg_dark = "#4b32c3", fg_light = "#4b32c3" },
{ name = "*.sbt" , text = "", fg = "#4d5a5e" }, { name = ".eslintrc", text = "", fg_dark = "#4b32c3", fg_light = "#4b32c3" },
{ name = "*.scala", text = "", fg = "#cc463e" }, { name = ".gitattributes", text = "", fg_dark = "#f54d27", fg_light = "#b83a1d" },
{ name = "*.scss" , text = "", fg = "#f55385" }, { name = ".gitconfig", text = "", fg_dark = "#f54d27", fg_light = "#b83a1d" },
{ name = "*.sh" , text = "", fg = "#4d5a5e" }, { name = ".gitignore", text = "", fg_dark = "#f54d27", fg_light = "#b83a1d" },
{ name = "*.swift", text = "", fg = "#e37933" }, { name = ".gitlab-ci.yml", text = "", fg_dark = "#e24329", fg_light = "#aa321f" },
{ name = "*.ts" , text = "", fg = "#519aba" }, { name = ".gitmodules", text = "", fg_dark = "#f54d27", fg_light = "#b83a1d" },
{ name = "*.tsx" , text = "", fg = "#1354bf" }, { name = ".gtkrc-2.0", text = "", fg_dark = "#ffffff", fg_light = "#333333" },
{ name = "*.vim" , text = "", fg = "#019833" }, { name = ".gvimrc", text = "", fg_dark = "#019833", fg_light = "#017226" },
{ name = "*.vue" , text = "󰡄", fg = "#8dc149" }, { name = ".luaurc", text = "", fg_dark = "#00a2ff", fg_light = "#007abf" },
{ name = ".mailmap", text = "󰊢", fg_dark = "#41535b", fg_light = "#41535b" },
{ name = ".npmignore", text = "", fg_dark = "#e8274b", fg_light = "#ae1d38" },
{ name = ".npmrc", text = "", fg_dark = "#e8274b", fg_light = "#ae1d38" },
{ name = ".prettierrc", text = "", fg_dark = "#4285f4", fg_light = "#3264b7" },
{ name = ".settings.json", text = "", fg_dark = "#854cc7", fg_light = "#643995" },
{ name = ".SRCINFO", text = "󰣇", fg_dark = "#0f94d2", fg_light = "#0b6f9e" },
{ name = ".vimrc", text = "", fg_dark = "#019833", fg_light = "#017226" },
{ name = ".Xauthority", text = "", fg_dark = "#e54d18", fg_light = "#ac3a12" },
{ name = ".xinitrc", text = "", fg_dark = "#e54d18", fg_light = "#ac3a12" },
{ name = ".Xresources", text = "", fg_dark = "#e54d18", fg_light = "#ac3a12" },
{ name = ".xsession", text = "", fg_dark = "#e54d18", fg_light = "#ac3a12" },
{ name = ".zprofile", text = "", fg_dark = "#89e051", fg_light = "#447028" },
{ name = ".zshenv", text = "", fg_dark = "#89e051", fg_light = "#447028" },
{ name = ".zshrc", text = "", fg_dark = "#89e051", fg_light = "#447028" },
{ name = "_gvimrc", text = "", fg_dark = "#019833", fg_light = "#017226" },
{ name = "_vimrc", text = "", fg_dark = "#019833", fg_light = "#017226" },
{ name = "avif", text = "", fg_dark = "#a074c4", fg_light = "#6b4d83" },
{ name = "brewfile", text = "", fg_dark = "#701516", fg_light = "#701516" },
{ name = "bspwmrc", text = "", fg_dark = "#2f2f2f", fg_light = "#2f2f2f" },
{ name = "build", text = "", fg_dark = "#89e051", fg_light = "#447028" },
{ name = "build.gradle", text = "", fg_dark = "#005f87", fg_light = "#005f87" },
{ name = "build.zig.zon", text = "", fg_dark = "#f69a1b", fg_light = "#7b4d0e" },
{ name = "cantorrc", text = "", fg_dark = "#1c99f3", fg_light = "#1573b6" },
{ name = "checkhealth", text = "󰓙", fg_dark = "#75b4fb", fg_light = "#3a5a7e" },
{ name = "cmakelists.txt", text = "", fg_dark = "#6d8086", fg_light = "#526064" },
{ name = "commit_editmsg", text = "", fg_dark = "#f54d27", fg_light = "#b83a1d" },
{ name = "compose.yaml", text = "󰡨", fg_dark = "#458ee6", fg_light = "#2e5f99" },
{ name = "compose.yml", text = "󰡨", fg_dark = "#458ee6", fg_light = "#2e5f99" },
{ name = "config", text = "", fg_dark = "#6d8086", fg_light = "#526064" },
{ name = "containerfile", text = "󰡨", fg_dark = "#458ee6", fg_light = "#2e5f99" },
{ name = "copying", text = "", fg_dark = "#cbcb41", fg_light = "#666620" },
{ name = "copying.lesser", text = "", fg_dark = "#cbcb41", fg_light = "#666620" },
{ name = "docker-compose.yaml", text = "󰡨", fg_dark = "#458ee6", fg_light = "#2e5f99" },
{ name = "docker-compose.yml", text = "󰡨", fg_dark = "#458ee6", fg_light = "#2e5f99" },
{ name = "dockerfile", text = "󰡨", fg_dark = "#458ee6", fg_light = "#2e5f99" },
{ name = "ext_typoscript_setup.txt", text = "", fg_dark = "#ff8700", fg_light = "#aa5a00" },
{ name = "favicon.ico", text = "", fg_dark = "#cbcb41", fg_light = "#666620" },
{ name = "fp-info-cache", text = "", fg_dark = "#ffffff", fg_light = "#333333" },
{ name = "fp-lib-table", text = "", fg_dark = "#ffffff", fg_light = "#333333" },
{ name = "FreeCAD.conf", text = "", fg_dark = "#cb0d0d", fg_light = "#cb0d0d" },
{ name = "gemfile$", text = "", fg_dark = "#701516", fg_light = "#701516" },
{ name = "gnumakefile", text = "", fg_dark = "#6d8086", fg_light = "#526064" },
{ name = "gradle-wrapper.properties", text = "", fg_dark = "#005f87", fg_light = "#005f87" },
{ name = "gradle.properties", text = "", fg_dark = "#005f87", fg_light = "#005f87" },
{ name = "gradlew", text = "", fg_dark = "#005f87", fg_light = "#005f87" },
{ name = "groovy", text = "", fg_dark = "#4a687c", fg_light = "#384e5d" },
{ name = "gruntfile.babel.js", text = "", fg_dark = "#e37933", fg_light = "#975122" },
{ name = "gruntfile.coffee", text = "", fg_dark = "#e37933", fg_light = "#975122" },
{ name = "gruntfile.js", text = "", fg_dark = "#e37933", fg_light = "#975122" },
{ name = "gruntfile.ts", text = "", fg_dark = "#e37933", fg_light = "#975122" },
{ name = "gtkrc", text = "", fg_dark = "#ffffff", fg_light = "#333333" },
{ name = "gulpfile.babel.js", text = "", fg_dark = "#cc3e44", fg_light = "#992e33" },
{ name = "gulpfile.coffee", text = "", fg_dark = "#cc3e44", fg_light = "#992e33" },
{ name = "gulpfile.js", text = "", fg_dark = "#cc3e44", fg_light = "#992e33" },
{ name = "gulpfile.ts", text = "", fg_dark = "#cc3e44", fg_light = "#992e33" },
{ name = "hyprland.conf", text = "", fg_dark = "#00aaae", fg_light = "#008082" },
{ name = "i3blocks.conf", text = "", fg_dark = "#e8ebee", fg_light = "#2e2f30" },
{ name = "i3status.conf", text = "", fg_dark = "#e8ebee", fg_light = "#2e2f30" },
{ name = "kalgebrarc", text = "", fg_dark = "#1c99f3", fg_light = "#1573b6" },
{ name = "kdeglobals", text = "", fg_dark = "#1c99f3", fg_light = "#1573b6" },
{ name = "kdenlive-layoutsrc", text = "", fg_dark = "#83b8f2", fg_light = "#425c79" },
{ name = "kdenliverc", text = "", fg_dark = "#83b8f2", fg_light = "#425c79" },
{ name = "kritadisplayrc", text = "", fg_dark = "#f245fb", fg_light = "#a12ea7" },
{ name = "kritarc", text = "", fg_dark = "#f245fb", fg_light = "#a12ea7" },
{ name = "license", text = "", fg_dark = "#d0bf41", fg_light = "#686020" },
{ name = "lxde-rc.xml", text = "", fg_dark = "#909090", fg_light = "#606060" },
{ name = "lxqt.conf", text = "", fg_dark = "#0192d3", fg_light = "#016e9e" },
{ name = "makefile", text = "", fg_dark = "#6d8086", fg_light = "#526064" },
{ name = "mix.lock", text = "", fg_dark = "#a074c4", fg_light = "#6b4d83" },
{ name = "mpv.conf", text = "", fg_dark = "#3b1342", fg_light = "#3b1342" },
{ name = "node_modules", text = "", fg_dark = "#e8274b", fg_light = "#ae1d38" },
{ name = "package-lock.json", text = "", fg_dark = "#7a0d21", fg_light = "#7a0d21" },
{ name = "package.json", text = "", fg_dark = "#e8274b", fg_light = "#ae1d38" },
{ name = "PKGBUILD", text = "", fg_dark = "#0f94d2", fg_light = "#0b6f9e" },
{ name = "platformio.ini", text = "", fg_dark = "#f6822b", fg_light = "#a4571d" },
{ name = "pom.xml", text = "", fg_dark = "#7a0d21", fg_light = "#7a0d21" },
{ name = "procfile", text = "", fg_dark = "#a074c4", fg_light = "#6b4d83" },
{ name = "PrusaSlicer.ini", text = "", fg_dark = "#ec6b23", fg_light = "#9d4717" },
{ name = "PrusaSlicerGcodeViewer.ini", text = "", fg_dark = "#ec6b23", fg_light = "#9d4717" },
{ name = "py.typed", text = "", fg_dark = "#ffbc03", fg_light = "#805e02" },
{ name = "QtProject.conf", text = "", fg_dark = "#40cd52", fg_light = "#2b8937" },
{ name = "R", text = "󰟔", fg_dark = "#2266ba", fg_light = "#1a4c8c" },
{ name = "r", text = "󰟔", fg_dark = "#2266ba", fg_light = "#1a4c8c" },
{ name = "rakefile", text = "", fg_dark = "#701516", fg_light = "#701516" },
{ name = "rmd", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "settings.gradle", text = "", fg_dark = "#005f87", fg_light = "#005f87" },
{ name = "svelte.config.js", text = "", fg_dark = "#ff3e00", fg_light = "#bf2e00" },
{ name = "sxhkdrc", text = "", fg_dark = "#2f2f2f", fg_light = "#2f2f2f" },
{ name = "sym-lib-table", text = "", fg_dark = "#ffffff", fg_light = "#333333" },
{ name = "tailwind.config.js", text = "󱏿", fg_dark = "#20c2e3", fg_light = "#158197" },
{ name = "tailwind.config.mjs", text = "󱏿", fg_dark = "#20c2e3", fg_light = "#158197" },
{ name = "tailwind.config.ts", text = "󱏿", fg_dark = "#20c2e3", fg_light = "#158197" },
{ name = "tmux.conf", text = "", fg_dark = "#14ba19", fg_light = "#0f8c13" },
{ name = "tmux.conf.local", text = "", fg_dark = "#14ba19", fg_light = "#0f8c13" },
{ name = "tsconfig.json", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "unlicense", text = "", fg_dark = "#d0bf41", fg_light = "#686020" },
{ name = "vagrantfile$", text = "", fg_dark = "#1563ff", fg_light = "#104abf" },
{ name = "vlcrc", text = "󰕼", fg_dark = "#ee7a00", fg_light = "#9f5100" },
{ name = "webpack", text = "󰜫", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "weston.ini", text = "", fg_dark = "#ffbb01", fg_light = "#805e00" },
{ name = "workspace", text = "", fg_dark = "#89e051", fg_light = "#447028" },
{ name = "xmobarrc", text = "", fg_dark = "#fd4d5d", fg_light = "#a9333e" },
{ name = "xmobarrc.hs", text = "", fg_dark = "#fd4d5d", fg_light = "#a9333e" },
{ name = "xmonad.hs", text = "", fg_dark = "#fd4d5d", fg_light = "#a9333e" },
{ name = "xorg.conf", text = "", fg_dark = "#e54d18", fg_light = "#ac3a12" },
{ name = "xsettingsd.conf", text = "", fg_dark = "#e54d18", fg_light = "#ac3a12" },
]
exts = [
{ name = "3gp", text = "", fg_dark = "#fd971f", fg_light = "#7e4c10" },
{ name = "3mf", text = "󰆧", fg_dark = "#888888", fg_light = "#5b5b5b" },
{ name = "7z", text = "", fg_dark = "#eca517", fg_light = "#76520c" },
{ name = "a", text = "", fg_dark = "#dcddd6", fg_light = "#494a47" },
{ name = "aac", text = "", fg_dark = "#00afff", fg_light = "#0075aa" },
{ name = "ai", text = "", fg_dark = "#cbcb41", fg_light = "#666620" },
{ name = "aif", text = "", fg_dark = "#00afff", fg_light = "#0075aa" },
{ name = "aiff", text = "", fg_dark = "#00afff", fg_light = "#0075aa" },
{ name = "android", text = "", fg_dark = "#34a853", fg_light = "#277e3e" },
{ name = "ape", text = "", fg_dark = "#00afff", fg_light = "#0075aa" },
{ name = "apk", text = "", fg_dark = "#34a853", fg_light = "#277e3e" },
{ name = "app", text = "", fg_dark = "#9f0500", fg_light = "#9f0500" },
{ name = "applescript", text = "", fg_dark = "#6d8085", fg_light = "#526064" },
{ name = "asc", text = "󰦝", fg_dark = "#576d7f", fg_light = "#41525f" },
{ name = "ass", text = "󰨖", fg_dark = "#ffb713", fg_light = "#805c0a" },
{ name = "astro", text = "", fg_dark = "#e23f67", fg_light = "#aa2f4d" },
{ name = "awk", text = "", fg_dark = "#4d5a5e", fg_light = "#3a4446" },
{ name = "azcli", text = "", fg_dark = "#0078d4", fg_light = "#005a9f" },
{ name = "bak", text = "󰁯", fg_dark = "#6d8086", fg_light = "#526064" },
{ name = "bash", text = "", fg_dark = "#89e051", fg_light = "#447028" },
{ name = "bat", text = "", fg_dark = "#c1f12e", fg_light = "#40500f" },
{ name = "bazel", text = "", fg_dark = "#89e051", fg_light = "#447028" },
{ name = "bib", text = "󱉟", fg_dark = "#cbcb41", fg_light = "#666620" },
{ name = "bicep", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "bicepparam", text = "", fg_dark = "#9f74b3", fg_light = "#6a4d77" },
{ name = "bin", text = "", fg_dark = "#9f0500", fg_light = "#9f0500" },
{ name = "blade.php", text = "", fg_dark = "#f05340", fg_light = "#a0372b" },
{ name = "blend", text = "󰂫", fg_dark = "#ea7600", fg_light = "#9c4f00" },
{ name = "blp", text = "󰺾", fg_dark = "#5796e2", fg_light = "#3a6497" },
{ name = "bmp", text = "", fg_dark = "#a074c4", fg_light = "#6b4d83" },
{ name = "brep", text = "󰻫", fg_dark = "#839463", fg_light = "#576342" },
{ name = "bz", text = "", fg_dark = "#eca517", fg_light = "#76520c" },
{ name = "bz2", text = "", fg_dark = "#eca517", fg_light = "#76520c" },
{ name = "bz3", text = "", fg_dark = "#eca517", fg_light = "#76520c" },
{ name = "bzl", text = "", fg_dark = "#89e051", fg_light = "#447028" },
{ name = "c", text = "", fg_dark = "#599eff", fg_light = "#3b69aa" },
{ name = "c++", text = "", fg_dark = "#f34b7d", fg_light = "#a23253" },
{ name = "cache", text = "", fg_dark = "#ffffff", fg_light = "#333333" },
{ name = "cast", text = "", fg_dark = "#fd971f", fg_light = "#7e4c10" },
{ name = "cbl", text = "⚙", fg_dark = "#005ca5", fg_light = "#005ca5" },
{ name = "cc", text = "", fg_dark = "#f34b7d", fg_light = "#a23253" },
{ name = "ccm", text = "", fg_dark = "#f34b7d", fg_light = "#a23253" },
{ name = "cfg", text = "", fg_dark = "#6d8086", fg_light = "#526064" },
{ name = "cjs", text = "", fg_dark = "#cbcb41", fg_light = "#666620" },
{ name = "clj", text = "", fg_dark = "#8dc149", fg_light = "#466024" },
{ name = "cljc", text = "", fg_dark = "#8dc149", fg_light = "#466024" },
{ name = "cljd", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "cljs", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "cmake", text = "", fg_dark = "#6d8086", fg_light = "#526064" },
{ name = "cob", text = "⚙", fg_dark = "#005ca5", fg_light = "#005ca5" },
{ name = "cobol", text = "⚙", fg_dark = "#005ca5", fg_light = "#005ca5" },
{ name = "coffee", text = "", fg_dark = "#cbcb41", fg_light = "#666620" },
{ name = "conf", text = "", fg_dark = "#6d8086", fg_light = "#526064" },
{ name = "config.ru", text = "", fg_dark = "#701516", fg_light = "#701516" },
{ name = "cp", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "cpp", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "cppm", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "cpy", text = "⚙", fg_dark = "#005ca5", fg_light = "#005ca5" },
{ name = "cr", text = "", fg_dark = "#c8c8c8", fg_light = "#434343" },
{ name = "crdownload", text = "", fg_dark = "#44cda8", fg_light = "#226654" },
{ name = "cs", text = "󰌛", fg_dark = "#596706", fg_light = "#434d04" },
{ name = "csh", text = "", fg_dark = "#4d5a5e", fg_light = "#3a4446" },
{ name = "cshtml", text = "󱦗", fg_dark = "#512bd4", fg_light = "#512bd4" },
{ name = "cson", text = "", fg_dark = "#cbcb41", fg_light = "#666620" },
{ name = "csproj", text = "󰪮", fg_dark = "#512bd4", fg_light = "#512bd4" },
{ name = "css", text = "", fg_dark = "#42a5f5", fg_light = "#2c6ea3" },
{ name = "csv", text = "", fg_dark = "#89e051", fg_light = "#447028" },
{ name = "cts", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "cu", text = "", fg_dark = "#89e051", fg_light = "#447028" },
{ name = "cue", text = "󰲹", fg_dark = "#ed95ae", fg_light = "#764a57" },
{ name = "cuh", text = "", fg_dark = "#a074c4", fg_light = "#6b4d83" },
{ name = "cxx", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "cxxm", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "d", text = "", fg_dark = "#427819", fg_light = "#325a13" },
{ name = "d.ts", text = "", fg_dark = "#d59855", fg_light = "#6a4c2a" },
{ name = "dart", text = "", fg_dark = "#03589c", fg_light = "#03589c" },
{ name = "db", text = "", fg_dark = "#dad8d8", fg_light = "#494848" },
{ name = "dconf", text = "", fg_dark = "#ffffff", fg_light = "#333333" },
{ name = "desktop", text = "", fg_dark = "#563d7c", fg_light = "#563d7c" },
{ name = "diff", text = "", fg_dark = "#41535b", fg_light = "#41535b" },
{ name = "dll", text = "", fg_dark = "#4d2c0b", fg_light = "#4d2c0b" },
{ name = "doc", text = "󰈬", fg_dark = "#185abd", fg_light = "#185abd" },
{ name = "Dockerfile", text = "󰡨", fg_dark = "#458ee6", fg_light = "#2e5f99" },
{ name = "docx", text = "󰈬", fg_dark = "#185abd", fg_light = "#185abd" },
{ name = "dot", text = "󱁉", fg_dark = "#30638e", fg_light = "#244a6a" },
{ name = "download", text = "", fg_dark = "#44cda8", fg_light = "#226654" },
{ name = "drl", text = "", fg_dark = "#ffafaf", fg_light = "#553a3a" },
{ name = "dropbox", text = "", fg_dark = "#0061fe", fg_light = "#0049be" },
{ name = "dump", text = "", fg_dark = "#dad8d8", fg_light = "#494848" },
{ name = "dwg", text = "󰻫", fg_dark = "#839463", fg_light = "#576342" },
{ name = "dxf", text = "󰻫", fg_dark = "#839463", fg_light = "#576342" },
{ name = "ebook", text = "", fg_dark = "#eab16d", fg_light = "#755836" },
{ name = "edn", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "eex", text = "", fg_dark = "#a074c4", fg_light = "#6b4d83" },
{ name = "ejs", text = "", fg_dark = "#cbcb41", fg_light = "#666620" },
{ name = "el", text = "", fg_dark = "#8172be", fg_light = "#61568e" },
{ name = "elc", text = "", fg_dark = "#8172be", fg_light = "#61568e" },
{ name = "elf", text = "", fg_dark = "#9f0500", fg_light = "#9f0500" },
{ name = "elm", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "eln", text = "", fg_dark = "#8172be", fg_light = "#61568e" },
{ name = "env", text = "", fg_dark = "#faf743", fg_light = "#32310d" },
{ name = "eot", text = "", fg_dark = "#ececec", fg_light = "#2f2f2f" },
{ name = "epp", text = "", fg_dark = "#ffa61a", fg_light = "#80530d" },
{ name = "epub", text = "", fg_dark = "#eab16d", fg_light = "#755836" },
{ name = "erb", text = "", fg_dark = "#701516", fg_light = "#701516" },
{ name = "erl", text = "", fg_dark = "#b83998", fg_light = "#8a2b72" },
{ name = "ex", text = "", fg_dark = "#a074c4", fg_light = "#6b4d83" },
{ name = "exe", text = "", fg_dark = "#9f0500", fg_light = "#9f0500" },
{ name = "exs", text = "", fg_dark = "#a074c4", fg_light = "#6b4d83" },
{ name = "f#", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "f3d", text = "󰻫", fg_dark = "#839463", fg_light = "#576342" },
{ name = "f90", text = "󱈚", fg_dark = "#734f96", fg_light = "#563b70" },
{ name = "fbx", text = "󰆧", fg_dark = "#888888", fg_light = "#5b5b5b" },
{ name = "fcbak", text = "", fg_dark = "#cb0d0d", fg_light = "#cb0d0d" },
{ name = "fcmacro", text = "", fg_dark = "#cb0d0d", fg_light = "#cb0d0d" },
{ name = "fcmat", text = "", fg_dark = "#cb0d0d", fg_light = "#cb0d0d" },
{ name = "fcparam", text = "", fg_dark = "#cb0d0d", fg_light = "#cb0d0d" },
{ name = "fcscript", text = "", fg_dark = "#cb0d0d", fg_light = "#cb0d0d" },
{ name = "fcstd", text = "", fg_dark = "#cb0d0d", fg_light = "#cb0d0d" },
{ name = "fcstd1", text = "", fg_dark = "#cb0d0d", fg_light = "#cb0d0d" },
{ name = "fctb", text = "", fg_dark = "#cb0d0d", fg_light = "#cb0d0d" },
{ name = "fctl", text = "", fg_dark = "#cb0d0d", fg_light = "#cb0d0d" },
{ name = "fdmdownload", text = "", fg_dark = "#44cda8", fg_light = "#226654" },
{ name = "fish", text = "", fg_dark = "#4d5a5e", fg_light = "#3a4446" },
{ name = "flac", text = "", fg_dark = "#0075aa", fg_light = "#005880" },
{ name = "flc", text = "", fg_dark = "#ececec", fg_light = "#2f2f2f" },
{ name = "flf", text = "", fg_dark = "#ececec", fg_light = "#2f2f2f" },
{ name = "fnl", text = "", fg_dark = "#fff3d7", fg_light = "#33312b" },
{ name = "fs", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "fsi", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "fsscript", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "fsx", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "gcode", text = "󰐫", fg_dark = "#1471ad", fg_light = "#0f5582" },
{ name = "gd", text = "", fg_dark = "#6d8086", fg_light = "#526064" },
{ name = "gemspec", text = "", fg_dark = "#701516", fg_light = "#701516" },
{ name = "gif", text = "", fg_dark = "#a074c4", fg_light = "#6b4d83" },
{ name = "git", text = "", fg_dark = "#f14c28", fg_light = "#b5391e" },
{ name = "glb", text = "", fg_dark = "#ffb13b", fg_light = "#80581e" },
{ name = "gnumakefile", text = "", fg_dark = "#6d8086", fg_light = "#526064" },
{ name = "go", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "godot", text = "", fg_dark = "#6d8086", fg_light = "#526064" },
{ name = "gql", text = "", fg_dark = "#e535ab", fg_light = "#ac2880" },
{ name = "graphql", text = "", fg_dark = "#e535ab", fg_light = "#ac2880" },
{ name = "gresource", text = "", fg_dark = "#ffffff", fg_light = "#333333" },
{ name = "gv", text = "󱁉", fg_dark = "#30638e", fg_light = "#244a6a" },
{ name = "gz", text = "", fg_dark = "#eca517", fg_light = "#76520c" },
{ name = "h", text = "", fg_dark = "#a074c4", fg_light = "#6b4d83" },
{ name = "haml", text = "", fg_dark = "#eaeae1", fg_light = "#2f2f2d" },
{ name = "hbs", text = "", fg_dark = "#f0772b", fg_light = "#a04f1d" },
{ name = "heex", text = "", fg_dark = "#a074c4", fg_light = "#6b4d83" },
{ name = "hex", text = "", fg_dark = "#2e63ff", fg_light = "#224abf" },
{ name = "hh", text = "", fg_dark = "#a074c4", fg_light = "#6b4d83" },
{ name = "hpp", text = "", fg_dark = "#a074c4", fg_light = "#6b4d83" },
{ name = "hrl", text = "", fg_dark = "#b83998", fg_light = "#8a2b72" },
{ name = "hs", text = "", fg_dark = "#a074c4", fg_light = "#6b4d83" },
{ name = "htm", text = "", fg_dark = "#e34c26", fg_light = "#aa391c" },
{ name = "html", text = "", fg_dark = "#e44d26", fg_light = "#ab3a1c" },
{ name = "huff", text = "󰡘", fg_dark = "#4242c7", fg_light = "#4242c7" },
{ name = "hurl", text = "", fg_dark = "#ff0288", fg_light = "#bf0266" },
{ name = "hx", text = "", fg_dark = "#ea8220", fg_light = "#9c5715" },
{ name = "hxx", text = "", fg_dark = "#a074c4", fg_light = "#6b4d83" },
{ name = "ical", text = "", fg_dark = "#2b2e83", fg_light = "#2b2e83" },
{ name = "icalendar", text = "", fg_dark = "#2b2e83", fg_light = "#2b2e83" },
{ name = "ico", text = "", fg_dark = "#cbcb41", fg_light = "#666620" },
{ name = "ics", text = "", fg_dark = "#2b2e83", fg_light = "#2b2e83" },
{ name = "ifb", text = "", fg_dark = "#2b2e83", fg_light = "#2b2e83" },
{ name = "ifc", text = "󰻫", fg_dark = "#839463", fg_light = "#576342" },
{ name = "ige", text = "󰻫", fg_dark = "#839463", fg_light = "#576342" },
{ name = "iges", text = "󰻫", fg_dark = "#839463", fg_light = "#576342" },
{ name = "igs", text = "󰻫", fg_dark = "#839463", fg_light = "#576342" },
{ name = "image", text = "", fg_dark = "#d0bec8", fg_light = "#453f43" },
{ name = "img", text = "", fg_dark = "#d0bec8", fg_light = "#453f43" },
{ name = "import", text = "", fg_dark = "#ececec", fg_light = "#2f2f2f" },
{ name = "info", text = "", fg_dark = "#ffffcd", fg_light = "#333329" },
{ name = "ini", text = "", fg_dark = "#6d8086", fg_light = "#526064" },
{ name = "ino", text = "", fg_dark = "#56b6c2", fg_light = "#397981" },
{ name = "ipynb", text = "", fg_dark = "#51a0cf", fg_light = "#366b8a" },
{ name = "iso", text = "", fg_dark = "#d0bec8", fg_light = "#453f43" },
{ name = "ixx", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "java", text = "", fg_dark = "#cc3e44", fg_light = "#992e33" },
{ name = "jl", text = "", fg_dark = "#a270ba", fg_light = "#6c4b7c" },
{ name = "jpeg", text = "", fg_dark = "#a074c4", fg_light = "#6b4d83" },
{ name = "jpg", text = "", fg_dark = "#a074c4", fg_light = "#6b4d83" },
{ name = "js", text = "", fg_dark = "#cbcb41", fg_light = "#666620" },
{ name = "json", text = "", fg_dark = "#cbcb41", fg_light = "#666620" },
{ name = "json5", text = "", fg_dark = "#cbcb41", fg_light = "#666620" },
{ name = "jsonc", text = "", fg_dark = "#cbcb41", fg_light = "#666620" },
{ name = "jsx", text = "", fg_dark = "#20c2e3", fg_light = "#158197" },
{ name = "jwmrc", text = "", fg_dark = "#0078cd", fg_light = "#005a9a" },
{ name = "jxl", text = "", fg_dark = "#a074c4", fg_light = "#6b4d83" },
{ name = "kbx", text = "󰯄", fg_dark = "#737672", fg_light = "#565856" },
{ name = "kdb", text = "", fg_dark = "#529b34", fg_light = "#3e7427" },
{ name = "kdbx", text = "", fg_dark = "#529b34", fg_light = "#3e7427" },
{ name = "kdenlive", text = "", fg_dark = "#83b8f2", fg_light = "#425c79" },
{ name = "kdenlivetitle", text = "", fg_dark = "#83b8f2", fg_light = "#425c79" },
{ name = "kicad_dru", text = "", fg_dark = "#ffffff", fg_light = "#333333" },
{ name = "kicad_mod", text = "", fg_dark = "#ffffff", fg_light = "#333333" },
{ name = "kicad_pcb", text = "", fg_dark = "#ffffff", fg_light = "#333333" },
{ name = "kicad_prl", text = "", fg_dark = "#ffffff", fg_light = "#333333" },
{ name = "kicad_pro", text = "", fg_dark = "#ffffff", fg_light = "#333333" },
{ name = "kicad_sch", text = "", fg_dark = "#ffffff", fg_light = "#333333" },
{ name = "kicad_sym", text = "", fg_dark = "#ffffff", fg_light = "#333333" },
{ name = "kicad_wks", text = "", fg_dark = "#ffffff", fg_light = "#333333" },
{ name = "ko", text = "", fg_dark = "#dcddd6", fg_light = "#494a47" },
{ name = "kpp", text = "", fg_dark = "#f245fb", fg_light = "#a12ea7" },
{ name = "kra", text = "", fg_dark = "#f245fb", fg_light = "#a12ea7" },
{ name = "krz", text = "", fg_dark = "#f245fb", fg_light = "#a12ea7" },
{ name = "ksh", text = "", fg_dark = "#4d5a5e", fg_light = "#3a4446" },
{ name = "kt", text = "", fg_dark = "#7f52ff", fg_light = "#5f3ebf" },
{ name = "kts", text = "", fg_dark = "#7f52ff", fg_light = "#5f3ebf" },
{ name = "lck", text = "", fg_dark = "#bbbbbb", fg_light = "#5e5e5e" },
{ name = "leex", text = "", fg_dark = "#a074c4", fg_light = "#6b4d83" },
{ name = "less", text = "", fg_dark = "#563d7c", fg_light = "#563d7c" },
{ name = "lff", text = "", fg_dark = "#ececec", fg_light = "#2f2f2f" },
{ name = "lhs", text = "", fg_dark = "#a074c4", fg_light = "#6b4d83" },
{ name = "lib", text = "", fg_dark = "#4d2c0b", fg_light = "#4d2c0b" },
{ name = "license", text = "", fg_dark = "#cbcb41", fg_light = "#666620" },
{ name = "liquid", text = "", fg_dark = "#95bf47", fg_light = "#4a6024" },
{ name = "lock", text = "", fg_dark = "#bbbbbb", fg_light = "#5e5e5e" },
{ name = "log", text = "󰌱", fg_dark = "#dddddd", fg_light = "#4a4a4a" },
{ name = "lrc", text = "󰨖", fg_dark = "#ffb713", fg_light = "#805c0a" },
{ name = "lua", text = "", fg_dark = "#51a0cf", fg_light = "#366b8a" },
{ name = "luac", text = "", fg_dark = "#51a0cf", fg_light = "#366b8a" },
{ name = "luau", text = "", fg_dark = "#00a2ff", fg_light = "#007abf" },
{ name = "m", text = "", fg_dark = "#599eff", fg_light = "#3b69aa" },
{ name = "m3u", text = "󰲹", fg_dark = "#ed95ae", fg_light = "#764a57" },
{ name = "m3u8", text = "󰲹", fg_dark = "#ed95ae", fg_light = "#764a57" },
{ name = "m4a", text = "", fg_dark = "#00afff", fg_light = "#0075aa" },
{ name = "m4v", text = "", fg_dark = "#fd971f", fg_light = "#7e4c10" },
{ name = "magnet", text = "", fg_dark = "#a51b16", fg_light = "#a51b16" },
{ name = "makefile", text = "", fg_dark = "#6d8086", fg_light = "#526064" },
{ name = "markdown", text = "", fg_dark = "#dddddd", fg_light = "#4a4a4a" },
{ name = "material", text = "󰔉", fg_dark = "#b83998", fg_light = "#8a2b72" },
{ name = "md", text = "", fg_dark = "#dddddd", fg_light = "#4a4a4a" },
{ name = "md5", text = "󰕥", fg_dark = "#8c86af", fg_light = "#5d5975" },
{ name = "mdx", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "mint", text = "󰌪", fg_dark = "#87c095", fg_light = "#44604a" },
{ name = "mjs", text = "", fg_dark = "#f1e05a", fg_light = "#504b1e" },
{ name = "mk", text = "", fg_dark = "#6d8086", fg_light = "#526064" },
{ name = "mkv", text = "", fg_dark = "#fd971f", fg_light = "#7e4c10" },
{ name = "ml", text = "", fg_dark = "#e37933", fg_light = "#975122" },
{ name = "mli", text = "", fg_dark = "#e37933", fg_light = "#975122" },
{ name = "mm", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "mo", text = "∞", fg_dark = "#9772fb", fg_light = "#654ca7" },
{ name = "mobi", text = "", fg_dark = "#eab16d", fg_light = "#755836" },
{ name = "mov", text = "", fg_dark = "#fd971f", fg_light = "#7e4c10" },
{ name = "mp3", text = "", fg_dark = "#00afff", fg_light = "#0075aa" },
{ name = "mp4", text = "", fg_dark = "#fd971f", fg_light = "#7e4c10" },
{ name = "mpp", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "msf", text = "", fg_dark = "#137be1", fg_light = "#0e5ca9" },
{ name = "mts", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "mustache", text = "", fg_dark = "#e37933", fg_light = "#975122" },
{ name = "nfo", text = "", fg_dark = "#ffffcd", fg_light = "#333329" },
{ name = "nim", text = "", fg_dark = "#f3d400", fg_light = "#514700" },
{ name = "nix", text = "", fg_dark = "#7ebae4", fg_light = "#3f5d72" },
{ name = "nswag", text = "", fg_dark = "#85ea2d", fg_light = "#427516" },
{ name = "nu", text = ">", fg_dark = "#3aa675", fg_light = "#276f4e" },
{ name = "o", text = "", fg_dark = "#9f0500", fg_light = "#9f0500" },
{ name = "obj", text = "󰆧", fg_dark = "#888888", fg_light = "#5b5b5b" },
{ name = "ogg", text = "", fg_dark = "#0075aa", fg_light = "#005880" },
{ name = "opus", text = "", fg_dark = "#0075aa", fg_light = "#005880" },
{ name = "org", text = "", fg_dark = "#77aa99", fg_light = "#4f7166" },
{ name = "otf", text = "", fg_dark = "#ececec", fg_light = "#2f2f2f" },
{ name = "out", text = "", fg_dark = "#9f0500", fg_light = "#9f0500" },
{ name = "part", text = "", fg_dark = "#44cda8", fg_light = "#226654" },
{ name = "patch", text = "", fg_dark = "#41535b", fg_light = "#41535b" },
{ name = "pck", text = "", fg_dark = "#6d8086", fg_light = "#526064" },
{ name = "pcm", text = "", fg_dark = "#0075aa", fg_light = "#005880" },
{ name = "pdf", text = "", fg_dark = "#b30b00", fg_light = "#b30b00" },
{ name = "php", text = "", fg_dark = "#a074c4", fg_light = "#6b4d83" },
{ name = "pl", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "pls", text = "󰲹", fg_dark = "#ed95ae", fg_light = "#764a57" },
{ name = "ply", text = "󰆧", fg_dark = "#888888", fg_light = "#5b5b5b" },
{ name = "pm", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "png", text = "", fg_dark = "#a074c4", fg_light = "#6b4d83" },
{ name = "po", text = "", fg_dark = "#2596be", fg_light = "#1c708e" },
{ name = "pot", text = "", fg_dark = "#2596be", fg_light = "#1c708e" },
{ name = "pp", text = "", fg_dark = "#ffa61a", fg_light = "#80530d" },
{ name = "ppt", text = "󰈧", fg_dark = "#cb4a32", fg_light = "#983826" },
{ name = "prisma", text = "", fg_dark = "#5a67d8", fg_light = "#444da2" },
{ name = "pro", text = "", fg_dark = "#e4b854", fg_light = "#725c2a" },
{ name = "ps1", text = "󰨊", fg_dark = "#4273ca", fg_light = "#325698" },
{ name = "psb", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "psd", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "psd1", text = "󰨊", fg_dark = "#6975c4", fg_light = "#4f5893" },
{ name = "psm1", text = "󰨊", fg_dark = "#6975c4", fg_light = "#4f5893" },
{ name = "pub", text = "󰷖", fg_dark = "#e3c58e", fg_light = "#4c422f" },
{ name = "pxd", text = "", fg_dark = "#5aa7e4", fg_light = "#3c6f98" },
{ name = "pxi", text = "", fg_dark = "#5aa7e4", fg_light = "#3c6f98" },
{ name = "py", text = "", fg_dark = "#ffbc03", fg_light = "#805e02" },
{ name = "pyc", text = "", fg_dark = "#ffe291", fg_light = "#332d1d" },
{ name = "pyd", text = "", fg_dark = "#ffe291", fg_light = "#332d1d" },
{ name = "pyi", text = "", fg_dark = "#ffbc03", fg_light = "#805e02" },
{ name = "pyo", text = "", fg_dark = "#ffe291", fg_light = "#332d1d" },
{ name = "pyx", text = "", fg_dark = "#5aa7e4", fg_light = "#3c6f98" },
{ name = "qm", text = "", fg_dark = "#2596be", fg_light = "#1c708e" },
{ name = "qml", text = "", fg_dark = "#40cd52", fg_light = "#2b8937" },
{ name = "qrc", text = "", fg_dark = "#40cd52", fg_light = "#2b8937" },
{ name = "qss", text = "", fg_dark = "#40cd52", fg_light = "#2b8937" },
{ name = "query", text = "", fg_dark = "#90a850", fg_light = "#607035" },
{ name = "r", text = "󰟔", fg_dark = "#2266ba", fg_light = "#1a4c8c" },
{ name = "rake", text = "", fg_dark = "#701516", fg_light = "#701516" },
{ name = "rar", text = "", fg_dark = "#eca517", fg_light = "#76520c" },
{ name = "razor", text = "󱦘", fg_dark = "#512bd4", fg_light = "#512bd4" },
{ name = "rb", text = "", fg_dark = "#701516", fg_light = "#701516" },
{ name = "res", text = "", fg_dark = "#cc3e44", fg_light = "#992e33" },
{ name = "resi", text = "", fg_dark = "#f55385", fg_light = "#a33759" },
{ name = "rlib", text = "", fg_dark = "#dea584", fg_light = "#6f5242" },
{ name = "rmd", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "rproj", text = "󰗆", fg_dark = "#358a5b", fg_light = "#286844" },
{ name = "rs", text = "", fg_dark = "#dea584", fg_light = "#6f5242" },
{ name = "rss", text = "", fg_dark = "#fb9d3b", fg_light = "#7e4e1e" },
{ name = "sass", text = "", fg_dark = "#f55385", fg_light = "#a33759" },
{ name = "sbt", text = "", fg_dark = "#cc3e44", fg_light = "#992e33" },
{ name = "sc", text = "", fg_dark = "#cc3e44", fg_light = "#992e33" },
{ name = "scad", text = "", fg_dark = "#f9d72c", fg_light = "#53480f" },
{ name = "scala", text = "", fg_dark = "#cc3e44", fg_light = "#992e33" },
{ name = "scm", text = "󰘧", fg_dark = "#eeeeee", fg_light = "#303030" },
{ name = "scss", text = "", fg_dark = "#f55385", fg_light = "#a33759" },
{ name = "sh", text = "", fg_dark = "#4d5a5e", fg_light = "#3a4446" },
{ name = "sha1", text = "󰕥", fg_dark = "#8c86af", fg_light = "#5d5975" },
{ name = "sha224", text = "󰕥", fg_dark = "#8c86af", fg_light = "#5d5975" },
{ name = "sha256", text = "󰕥", fg_dark = "#8c86af", fg_light = "#5d5975" },
{ name = "sha384", text = "󰕥", fg_dark = "#8c86af", fg_light = "#5d5975" },
{ name = "sha512", text = "󰕥", fg_dark = "#8c86af", fg_light = "#5d5975" },
{ name = "sig", text = "λ", fg_dark = "#e37933", fg_light = "#975122" },
{ name = "signature", text = "λ", fg_dark = "#e37933", fg_light = "#975122" },
{ name = "skp", text = "󰻫", fg_dark = "#839463", fg_light = "#576342" },
{ name = "sldasm", text = "󰻫", fg_dark = "#839463", fg_light = "#576342" },
{ name = "sldprt", text = "󰻫", fg_dark = "#839463", fg_light = "#576342" },
{ name = "slim", text = "", fg_dark = "#e34c26", fg_light = "#aa391c" },
{ name = "sln", text = "", fg_dark = "#854cc7", fg_light = "#643995" },
{ name = "slvs", text = "󰻫", fg_dark = "#839463", fg_light = "#576342" },
{ name = "sml", text = "λ", fg_dark = "#e37933", fg_light = "#975122" },
{ name = "so", text = "", fg_dark = "#dcddd6", fg_light = "#494a47" },
{ name = "sol", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "spec.js", text = "", fg_dark = "#cbcb41", fg_light = "#666620" },
{ name = "spec.jsx", text = "", fg_dark = "#20c2e3", fg_light = "#158197" },
{ name = "spec.ts", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "spec.tsx", text = "", fg_dark = "#1354bf", fg_light = "#1354bf" },
{ name = "sql", text = "", fg_dark = "#dad8d8", fg_light = "#494848" },
{ name = "sqlite", text = "", fg_dark = "#dad8d8", fg_light = "#494848" },
{ name = "sqlite3", text = "", fg_dark = "#dad8d8", fg_light = "#494848" },
{ name = "srt", text = "󰨖", fg_dark = "#ffb713", fg_light = "#805c0a" },
{ name = "ssa", text = "󰨖", fg_dark = "#ffb713", fg_light = "#805c0a" },
{ name = "ste", text = "󰻫", fg_dark = "#839463", fg_light = "#576342" },
{ name = "step", text = "󰻫", fg_dark = "#839463", fg_light = "#576342" },
{ name = "stl", text = "󰆧", fg_dark = "#888888", fg_light = "#5b5b5b" },
{ name = "stp", text = "󰻫", fg_dark = "#839463", fg_light = "#576342" },
{ name = "strings", text = "", fg_dark = "#2596be", fg_light = "#1c708e" },
{ name = "styl", text = "", fg_dark = "#8dc149", fg_light = "#466024" },
{ name = "sub", text = "󰨖", fg_dark = "#ffb713", fg_light = "#805c0a" },
{ name = "sublime", text = "", fg_dark = "#e37933", fg_light = "#975122" },
{ name = "suo", text = "", fg_dark = "#854cc7", fg_light = "#643995" },
{ name = "sv", text = "󰍛", fg_dark = "#019833", fg_light = "#017226" },
{ name = "svelte", text = "", fg_dark = "#ff3e00", fg_light = "#bf2e00" },
{ name = "svg", text = "󰜡", fg_dark = "#ffb13b", fg_light = "#80581e" },
{ name = "svh", text = "󰍛", fg_dark = "#019833", fg_light = "#017226" },
{ name = "swift", text = "", fg_dark = "#e37933", fg_light = "#975122" },
{ name = "t", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "tbc", text = "󰛓", fg_dark = "#1e5cb3", fg_light = "#1e5cb3" },
{ name = "tcl", text = "󰛓", fg_dark = "#1e5cb3", fg_light = "#1e5cb3" },
{ name = "templ", text = "", fg_dark = "#dbbd30", fg_light = "#6e5e18" },
{ name = "terminal", text = "", fg_dark = "#31b53e", fg_light = "#217929" },
{ name = "test.js", text = "", fg_dark = "#cbcb41", fg_light = "#666620" },
{ name = "test.jsx", text = "", fg_dark = "#20c2e3", fg_light = "#158197" },
{ name = "test.ts", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "test.tsx", text = "", fg_dark = "#1354bf", fg_light = "#1354bf" },
{ name = "tex", text = "", fg_dark = "#3d6117", fg_light = "#3d6117" },
{ name = "tf", text = "", fg_dark = "#5f43e9", fg_light = "#4732af" },
{ name = "tfvars", text = "", fg_dark = "#5f43e9", fg_light = "#4732af" },
{ name = "tgz", text = "", fg_dark = "#eca517", fg_light = "#76520c" },
{ name = "tmux", text = "", fg_dark = "#14ba19", fg_light = "#0f8c13" },
{ name = "toml", text = "", fg_dark = "#9c4221", fg_light = "#753219" },
{ name = "torrent", text = "", fg_dark = "#44cda8", fg_light = "#226654" },
{ name = "tres", text = "", fg_dark = "#6d8086", fg_light = "#526064" },
{ name = "ts", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "tscn", text = "", fg_dark = "#6d8086", fg_light = "#526064" },
{ name = "tsconfig", text = "", fg_dark = "#ff8700", fg_light = "#aa5a00" },
{ name = "tsx", text = "", fg_dark = "#1354bf", fg_light = "#1354bf" },
{ name = "ttf", text = "", fg_dark = "#ececec", fg_light = "#2f2f2f" },
{ name = "twig", text = "", fg_dark = "#8dc149", fg_light = "#466024" },
{ name = "txt", text = "󰈙", fg_dark = "#89e051", fg_light = "#447028" },
{ name = "txz", text = "", fg_dark = "#eca517", fg_light = "#76520c" },
{ name = "typoscript", text = "", fg_dark = "#ff8700", fg_light = "#aa5a00" },
{ name = "ui", text = "", fg_dark = "#0c306e", fg_light = "#0c306e" },
{ name = "v", text = "󰍛", fg_dark = "#019833", fg_light = "#017226" },
{ name = "vala", text = "", fg_dark = "#7239b3", fg_light = "#562b86" },
{ name = "vh", text = "󰍛", fg_dark = "#019833", fg_light = "#017226" },
{ name = "vhd", text = "󰍛", fg_dark = "#019833", fg_light = "#017226" },
{ name = "vhdl", text = "󰍛", fg_dark = "#019833", fg_light = "#017226" },
{ name = "vim", text = "", fg_dark = "#019833", fg_light = "#017226" },
{ name = "vsh", text = "", fg_dark = "#5d87bf", fg_light = "#3e5a7f" },
{ name = "vsix", text = "", fg_dark = "#854cc7", fg_light = "#643995" },
{ name = "vue", text = "", fg_dark = "#8dc149", fg_light = "#466024" },
{ name = "wasm", text = "", fg_dark = "#5c4cdb", fg_light = "#4539a4" },
{ name = "wav", text = "", fg_dark = "#00afff", fg_light = "#0075aa" },
{ name = "webm", text = "", fg_dark = "#fd971f", fg_light = "#7e4c10" },
{ name = "webmanifest", text = "", fg_dark = "#f1e05a", fg_light = "#504b1e" },
{ name = "webp", text = "", fg_dark = "#a074c4", fg_light = "#6b4d83" },
{ name = "webpack", text = "󰜫", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "wma", text = "", fg_dark = "#00afff", fg_light = "#0075aa" },
{ name = "woff", text = "", fg_dark = "#ececec", fg_light = "#2f2f2f" },
{ name = "woff2", text = "", fg_dark = "#ececec", fg_light = "#2f2f2f" },
{ name = "wrl", text = "󰆧", fg_dark = "#888888", fg_light = "#5b5b5b" },
{ name = "wrz", text = "󰆧", fg_dark = "#888888", fg_light = "#5b5b5b" },
{ name = "x", text = "", fg_dark = "#599eff", fg_light = "#3b69aa" },
{ name = "xaml", text = "󰙳", fg_dark = "#512bd4", fg_light = "#512bd4" },
{ name = "xcf", text = "", fg_dark = "#635b46", fg_light = "#4a4434" },
{ name = "xcplayground", text = "", fg_dark = "#e37933", fg_light = "#975122" },
{ name = "xcstrings", text = "", fg_dark = "#2596be", fg_light = "#1c708e" },
{ name = "xls", text = "󰈛", fg_dark = "#207245", fg_light = "#207245" },
{ name = "xlsx", text = "󰈛", fg_dark = "#207245", fg_light = "#207245" },
{ name = "xm", text = "", fg_dark = "#519aba", fg_light = "#36677c" },
{ name = "xml", text = "󰗀", fg_dark = "#e37933", fg_light = "#975122" },
{ name = "xpi", text = "", fg_dark = "#ff1b01", fg_light = "#bf1401" },
{ name = "xul", text = "", fg_dark = "#e37933", fg_light = "#975122" },
{ name = "xz", text = "", fg_dark = "#eca517", fg_light = "#76520c" },
{ name = "yaml", text = "", fg_dark = "#6d8086", fg_light = "#526064" },
{ name = "yml", text = "", fg_dark = "#6d8086", fg_light = "#526064" },
{ name = "zig", text = "", fg_dark = "#f69a1b", fg_light = "#7b4d0e" },
{ name = "zip", text = "", fg_dark = "#eca517", fg_light = "#76520c" },
{ name = "zsh", text = "", fg_dark = "#89e051", fg_light = "#447028" },
{ name = "zst", text = "", fg_dark = "#eca517", fg_light = "#76520c" },
]
conds = [
# Special files
{ if = "orphan", text = "" },
{ if = "link" , text = "" },
{ if = "block" , text = "" },
{ if = "char" , text = "" },
{ if = "fifo" , text = "" },
{ if = "sock" , text = "" },
{ if = "sticky", text = "" },
# Text # Fallback
{ name = "*.conf", text = "", fg = "#6d8086" }, { if = "dir", text = "" },
{ name = "*.ini" , text = "", fg = "#6d8086" }, { if = "exec", text = "" },
{ name = "*.json", text = "", fg = "#cbcb41" }, { if = "!dir", text = "" },
{ name = "*.kdl" , text = "", fg = "#6d8086" },
{ name = "*.md" , text = "", fg = "#ffffff" },
{ name = "*.toml", text = "", fg = "#ffffff" },
{ name = "*.txt" , text = "", fg = "#89e051" },
{ name = "*.yaml", text = "", fg = "#6d8086" },
{ name = "*.yml" , text = "", fg = "#6d8086" },
# Archives
{ name = "*.7z" , text = "" },
{ name = "*.bz2", text = "" },
{ name = "*.gz" , text = "" },
{ name = "*.rar", text = "" },
{ name = "*.tar", text = "" },
{ name = "*.xz" , text = "" },
{ name = "*.zip", text = "" },
# Images
{ name = "*.HEIC", text = "", fg = "#a074c4" },
{ name = "*.avif", text = "", fg = "#a074c4" },
{ name = "*.bmp" , text = "", fg = "#a074c4" },
{ name = "*.gif" , text = "", fg = "#a074c4" },
{ name = "*.ico" , text = "", fg = "#cbcb41" },
{ name = "*.jpeg", text = "", fg = "#a074c4" },
{ name = "*.jpg" , text = "", fg = "#a074c4" },
{ name = "*.png" , text = "", fg = "#a074c4" },
{ name = "*.svg" , text = "", fg = "#FFB13B" },
{ name = "*.webp", text = "", fg = "#a074c4" },
# Movies
{ name = "*.avi" , text = "", fg = "#FD971F" },
{ name = "*.mkv" , text = "", fg = "#FD971F" },
{ name = "*.mov" , text = "", fg = "#FD971F" },
{ name = "*.mp4" , text = "", fg = "#FD971F" },
{ name = "*.webm", text = "", fg = "#FD971F" },
# Audio
{ name = "*.aac" , text = "", fg = "#66D8EF" },
{ name = "*.flac", text = "", fg = "#66D8EF" },
{ name = "*.m4a" , text = "", fg = "#66D8EF" },
{ name = "*.mp3" , text = "", fg = "#66D8EF" },
{ name = "*.ogg" , text = "", fg = "#66D8EF" },
{ name = "*.wav" , text = "", fg = "#66D8EF" },
# Documents
{ name = "*.csv" , text = "", fg = "#89e051" },
{ name = "*.doc" , text = "", fg = "#185abd" },
{ name = "*.doct", text = "", fg = "#185abd" },
{ name = "*.docx", text = "", fg = "#185abd" },
{ name = "*.dot" , text = "", fg = "#185abd" },
{ name = "*.ods" , text = "", fg = "#207245" },
{ name = "*.ots" , text = "", fg = "#207245" },
{ name = "*.pdf" , text = "", fg = "#b30b00" },
{ name = "*.pom" , text = "", fg = "#cc3e44" },
{ name = "*.pot" , text = "", fg = "#cb4a32" },
{ name = "*.potx", text = "", fg = "#cb4a32" },
{ name = "*.ppm" , text = "", fg = "#a074c4" },
{ name = "*.ppmx", text = "", fg = "#cb4a32" },
{ name = "*.pps" , text = "", fg = "#cb4a32" },
{ name = "*.ppsx", text = "", fg = "#cb4a32" },
{ name = "*.ppt" , text = "", fg = "#cb4a32" },
{ name = "*.pptx", text = "", fg = "#cb4a32" },
{ name = "*.xlc" , text = "", fg = "#207245" },
{ name = "*.xlm" , text = "", fg = "#207245" },
{ name = "*.xls" , text = "", fg = "#207245" },
{ name = "*.xlsm", text = "", fg = "#207245" },
{ name = "*.xlsx", text = "", fg = "#207245" },
{ name = "*.xlt" , text = "", fg = "#207245" },
# Lockfiles
{ name = "*.lock", text = "", fg = "#bbbbbb" },
# Misc
{ name = "*.bin", text = "", fg = "#9F0500" },
{ name = "*.exe", text = "", fg = "#9F0500" },
{ name = "*.pkg", text = "", fg = "#9F0500" },
# Dotfiles
{ name = ".DS_Store" , text = "", fg = "#41535b" },
{ name = ".bashprofile" , text = "", fg = "#89e051" },
{ name = ".bashrc" , text = "", fg = "#89e051" },
{ name = ".gitattributes", text = "", fg = "#41535b" },
{ name = ".gitignore" , text = "", fg = "#41535b" },
{ name = ".gitmodules" , text = "", fg = "#41535b" },
{ name = ".vimrc" , text = "", fg = "#019833" },
{ name = ".zprofile" , text = "", fg = "#89e051" },
{ name = ".zshenv" , text = "", fg = "#89e051" },
{ name = ".zshrc" , text = "", fg = "#89e051" },
# Named files
{ name = "COPYING" , text = "󰿃", fg = "#cbcb41" },
{ name = "Containerfile", text = "󰡨", fg = "#458ee6" },
{ name = "Dockerfile" , text = "󰡨", fg = "#458ee6" },
{ name = "LICENSE" , text = "󰿃", fg = "#d0bf41" },
# Directories
{ name = ".config/" , text = "" },
{ name = ".git/" , text = "" },
{ name = "Desktop/" , text = "" },
{ name = "Development/", text = "" },
{ name = "Documents/" , text = "" },
{ name = "Downloads/" , text = "" },
{ name = "Library/" , text = "" },
{ name = "Movies/" , text = "" },
{ name = "Music/" , text = "" },
{ name = "Pictures/" , text = "" },
{ name = "Public/" , text = "" },
{ name = "Videos/" , text = "" },
# Default
{ name = "*" , text = "" },
{ name = "*/", text = "" },
] ]
# : }}} # : }}}

View file

@ -6,12 +6,14 @@
ratio = [ 1, 4, 3 ] ratio = [ 1, 4, 3 ]
sort_by = "alphabetical" sort_by = "alphabetical"
sort_sensitive = false sort_sensitive = false
sort_reverse = false sort_reverse = false
sort_dir_first = true sort_dir_first = true
sort_translit = false
linemode = "none" linemode = "none"
show_hidden = false show_hidden = false
show_symlink = true show_symlink = true
scrolloff = 5 scrolloff = 5
mouse_events = [ "click", "scroll" ]
[preview] [preview]
tab_size = 2 tab_size = 2
@ -27,16 +29,16 @@ ueberzug_offset = [ 0, 0, 0, 0 ]
[opener] [opener]
edit = [ edit = [
{ run = '${EDITOR:=vi} "$@"', desc = "$EDITOR", block = true, for = "unix" }, { run = '${EDITOR:=vi} "$@"', desc = "$EDITOR", block = true, for = "unix" },
{ run = 'code "%*"', orphan = true, desc = "code", for = "windows" }, { run = 'code %*', orphan = true, desc = "code", for = "windows" },
{ run = 'code -w "%*"', block = true, desc = "code (block)", for = "windows" }, { run = 'code -w %*', block = true, desc = "code (block)", for = "windows" },
] ]
open = [ open = [
{ run = 'xdg-open "$@"', desc = "Open", for = "linux" }, { run = 'xdg-open "$1"', desc = "Open", for = "linux" },
{ run = 'open "$@"', desc = "Open", for = "macos" }, { run = 'open "$@"', desc = "Open", for = "macos" },
{ run = 'start "" "%1"', orphan = true, desc = "Open", for = "windows" }, { run = 'start "" "%1"', orphan = true, desc = "Open", for = "windows" },
] ]
reveal = [ reveal = [
{ run = 'xdg-open "$(dirname "$0")"', desc = "Reveal", for = "linux" }, { run = 'xdg-open "$(dirname "$1")"', desc = "Reveal", for = "linux" },
{ run = 'open -R "$1"', desc = "Reveal", for = "macos" }, { run = 'open -R "$1"', desc = "Reveal", for = "macos" },
{ run = 'explorer /select, "%1"', orphan = true, desc = "Reveal", for = "windows" }, { run = 'explorer /select, "%1"', orphan = true, desc = "Reveal", for = "windows" },
{ run = '''exiftool "$1"; echo "Press enter to exit"; read _''', block = true, desc = "Show EXIF", for = "unix" }, { run = '''exiftool "$1"; echo "Press enter to exit"; read _''', block = true, desc = "Show EXIF", for = "unix" },
@ -46,8 +48,8 @@ extract = [
{ run = 'unar "%1"', desc = "Extract here", for = "windows" }, { run = 'unar "%1"', desc = "Extract here", for = "windows" },
] ]
play = [ play = [
{ run = 'mpv "$@"', orphan = true, for = "unix" }, { run = 'mpv --force-window "$@"', orphan = true, for = "unix" },
{ run = 'mpv "%1"', orphan = true, for = "windows" }, { run = 'mpv --force-window "%1"', orphan = true, for = "windows" },
{ run = '''mediainfo "$1"; echo "Press enter to exit"; read _''', block = true, desc = "Show media info", for = "unix" }, { run = '''mediainfo "$1"; echo "Press enter to exit"; read _''', block = true, desc = "Show media info", for = "unix" },
] ]
@ -60,13 +62,13 @@ rules = [
{ mime = "{audio,video}/*", use = [ "play", "reveal" ] }, { mime = "{audio,video}/*", use = [ "play", "reveal" ] },
{ mime = "inode/x-empty", use = [ "edit", "reveal" ] }, { mime = "inode/x-empty", use = [ "edit", "reveal" ] },
{ mime = "application/*zip", use = [ "extract", "reveal" ] }, { mime = "application/{,g}zip", use = [ "extract", "reveal" ] },
{ mime = "application/x-{tar,bzip*,7z-compressed,xz,rar}", use = [ "extract", "reveal" ] }, { mime = "application/x-{tar,bzip*,7z-compressed,xz,rar}", use = [ "extract", "reveal" ] },
{ mime = "application/json", use = [ "edit", "reveal" ] }, { mime = "application/{json,x-ndjson}", use = [ "edit", "reveal" ] },
{ mime = "*/javascript", use = [ "edit", "reveal" ] }, { mime = "*/javascript", use = [ "edit", "reveal" ] },
{ mime = "*", use = [ "open", "reveal" ] }, { name = "*", use = [ "open", "reveal" ] },
] ]
[tasks] [tasks]
@ -79,14 +81,21 @@ suppress_preload = false
[plugin] [plugin]
fetchers = [
# Mimetype
{ id = "mime", name = "*", run = "mime", if = "!mime", prio = "high" },
]
preloaders = [ preloaders = [
{ name = "*", cond = "!mime", run = "mime", multi = true, prio = "high" },
# Image # Image
{ mime = "image/{avif,heic,jxl,svg+xml}", run = "magick" },
{ mime = "image/*", run = "image" }, { mime = "image/*", run = "image" },
# Video # Video
{ mime = "video/*", run = "video" }, { mime = "video/*", run = "video" },
# PDF # PDF
{ mime = "application/pdf", run = "pdf" }, { mime = "application/pdf", run = "pdf" },
# Font
{ mime = "font/*", run = "font" },
{ mime = "application/vnd.ms-opentype", run = "font" },
] ]
previewers = [ previewers = [
{ name = "*/", run = "folder", sync = true }, { name = "*/", run = "folder", sync = true },
@ -94,22 +103,27 @@ previewers = [
{ mime = "text/*", run = "code" }, { mime = "text/*", run = "code" },
{ mime = "*/{xml,javascript,x-wine-extension-ini}", run = "code" }, { mime = "*/{xml,javascript,x-wine-extension-ini}", run = "code" },
# JSON # JSON
{ mime = "application/json", run = "json" }, { mime = "application/{json,x-ndjson}", run = "json" },
# Image # Image
{ mime = "image/vnd.djvu", run = "noop" }, { mime = "image/{avif,heic,jxl,svg+xml}", run = "magick" },
{ mime = "image/*", run = "image" }, { mime = "image/*", run = "image" },
# Video # Video
{ mime = "video/*", run = "video" }, { mime = "video/*", run = "video" },
# PDF # PDF
{ mime = "application/pdf", run = "pdf" }, { mime = "application/pdf", run = "pdf" },
# Archive # Archive
{ mime = "application/*zip", run = "archive" }, { mime = "application/{,g}zip", run = "archive" },
{ mime = "application/x-{tar,bzip*,7z-compressed,xz,rar}", run = "archive" }, { mime = "application/x-{tar,bzip*,7z-compressed,xz,rar,iso9660-image}", run = "archive" },
# Font
{ mime = "font/*", run = "font" },
{ mime = "application/vnd.ms-opentype", run = "font" },
# Fallback # Fallback
{ name = "*", run = "file" }, { name = "*", run = "file" },
] ]
[input] [input]
cursor_blink = true
# cd # cd
cd_title = "Change directory:" cd_title = "Change directory:"
cd_origin = "top-center" cd_origin = "top-center"
@ -171,11 +185,10 @@ open_origin = "hovered"
open_offset = [ 0, 1, 50, 7 ] open_offset = [ 0, 1, 50, 7 ]
[which] [which]
sort_by = "none" sort_by = "none"
sort_sensitive = false sort_sensitive = false
sort_reverse = false sort_reverse = false
sort_translit = false
[log] [log]
enabled = false enabled = false
[headsup]

View file

@ -1,34 +0,0 @@
use serde::{Deserialize, Deserializer};
use crate::MERGED_YAZI;
#[derive(Debug)]
pub struct Headsup {
// TODO: remove this once Yazi 0.3 is released --
pub disable_exec_warn: bool,
}
impl Default for Headsup {
fn default() -> Self { toml::from_str(&MERGED_YAZI).unwrap() }
}
impl<'de> Deserialize<'de> for Headsup {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Outer {
headsup: Shadow,
}
#[derive(Deserialize)]
struct Shadow {
#[serde(default)]
disable_exec_warn: bool,
}
let outer = Outer::deserialize(deserializer)?;
Ok(Self { disable_exec_warn: outer.headsup.disable_exec_warn })
}
}

View file

@ -1,3 +0,0 @@
mod headsup;
pub use headsup::*;

View file

@ -1,14 +1,15 @@
use std::{borrow::Cow, collections::VecDeque, sync::atomic::Ordering}; use std::{borrow::Cow, collections::VecDeque};
use serde::{Deserialize, Deserializer}; use serde::Deserialize;
use yazi_shared::event::Cmd; use yazi_shared::event::Cmd;
use super::Key; use super::Key;
use crate::DEPRECATED_EXEC;
#[derive(Debug, Default)] #[derive(Debug, Default, Deserialize)]
pub struct Control { pub struct Control {
#[serde(deserialize_with = "super::deserialize_on")]
pub on: Vec<Key>, pub on: Vec<Key>,
#[serde(deserialize_with = "super::deserialize_run")]
pub run: Vec<Cmd>, pub run: Vec<Cmd>,
pub desc: Option<String>, pub desc: Option<String>,
} }
@ -24,7 +25,7 @@ impl Control {
#[inline] #[inline]
pub fn run(&self) -> String { pub fn run(&self) -> String {
self.run.iter().map(|e| e.to_string()).collect::<Vec<_>>().join("; ") self.run.iter().map(|c| c.to_string()).collect::<Vec<_>>().join("; ")
} }
#[inline] #[inline]
@ -40,33 +41,3 @@ impl Control {
|| self.on().to_lowercase().contains(&s) || self.on().to_lowercase().contains(&s)
} }
} }
// TODO: remove this once Yazi 0.3 is released
impl<'de> Deserialize<'de> for Control {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
pub struct Shadow {
pub on: Vec<Key>,
pub run: Option<VecCmd>,
pub exec: Option<VecCmd>,
pub desc: Option<String>,
}
let shadow = Shadow::deserialize(deserializer)?;
#[derive(Deserialize)]
struct VecCmd(#[serde(deserialize_with = "super::run_deserialize")] Vec<Cmd>);
if shadow.exec.is_some() {
DEPRECATED_EXEC.store(true, Ordering::Relaxed);
}
let Some(run) = shadow.run.or(shadow.exec) else {
return Err(serde::de::Error::custom("missing field `run` within `[keymap]`"));
};
Ok(Self { on: shadow.on, run: run.0, desc: shadow.desc })
}
}

View file

@ -0,0 +1,83 @@
use std::{fmt, str::FromStr};
use anyhow::Result;
use serde::{de::{self, Visitor}, Deserializer};
use yazi_shared::event::Cmd;
use crate::keymap::Key;
pub(super) fn deserialize_on<'de, D>(deserializer: D) -> Result<Vec<Key>, D::Error>
where
D: Deserializer<'de>,
{
struct OnVisitor;
impl<'de> Visitor<'de> for OnVisitor {
type Value = Vec<Key>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a `on` string or array of strings within keymap.toml")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: de::SeqAccess<'de>,
{
let mut cmds = vec![];
while let Some(value) = &seq.next_element::<String>()? {
cmds.push(Key::from_str(value).map_err(de::Error::custom)?);
}
if cmds.is_empty() {
return Err(de::Error::custom("`on` within keymap.toml cannot be empty"));
}
Ok(cmds)
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(vec![Key::from_str(value).map_err(de::Error::custom)?])
}
}
deserializer.deserialize_any(OnVisitor)
}
pub(super) fn deserialize_run<'de, D>(deserializer: D) -> Result<Vec<Cmd>, D::Error>
where
D: Deserializer<'de>,
{
struct RunVisitor;
impl<'de> Visitor<'de> for RunVisitor {
type Value = Vec<Cmd>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a `run` string or array of strings within keymap.toml")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: de::SeqAccess<'de>,
{
let mut cmds = vec![];
while let Some(value) = &seq.next_element::<String>()? {
cmds.push(Cmd::from_str(value).map_err(de::Error::custom)?);
}
if cmds.is_empty() {
return Err(de::Error::custom("`run` within keymap.toml cannot be empty"));
}
Ok(cmds)
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(vec![Cmd::from_str(value).map_err(de::Error::custom)?])
}
}
deserializer.deserialize_any(RunVisitor)
}

View file

@ -2,34 +2,30 @@ use std::{fmt::{Display, Write}, str::FromStr};
use anyhow::bail; use anyhow::bail;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use serde::Deserialize;
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[serde(try_from = "String")]
pub struct Key { pub struct Key {
pub code: KeyCode, pub code: KeyCode,
pub shift: bool, pub shift: bool,
pub ctrl: bool, pub ctrl: bool,
pub alt: bool, pub alt: bool,
pub super_: bool,
} }
impl Key { impl Key {
#[inline] #[inline]
pub fn plain(&self) -> Option<char> { pub fn plain(&self) -> Option<char> {
match self.code { match self.code {
KeyCode::Char(c) if !self.ctrl && !self.alt => Some(c), KeyCode::Char(c) if !self.ctrl && !self.alt && !self.super_ => Some(c),
_ => None, _ => None,
} }
} }
#[inline]
pub fn is_enter(&self) -> bool {
matches!(self, Key { code: KeyCode::Enter, shift: false, ctrl: false, alt: false })
}
} }
impl Default for Key { impl Default for Key {
fn default() -> Self { Self { code: KeyCode::Null, shift: false, ctrl: false, alt: false } } fn default() -> Self {
Self { code: KeyCode::Null, shift: false, ctrl: false, alt: false, super_: false }
}
} }
impl From<KeyEvent> for Key { impl From<KeyEvent> for Key {
@ -56,6 +52,7 @@ impl From<KeyEvent> for Key {
shift, shift,
ctrl: value.modifiers.contains(KeyModifiers::CONTROL), ctrl: value.modifiers.contains(KeyModifiers::CONTROL),
alt: value.modifiers.contains(KeyModifiers::ALT), alt: value.modifiers.contains(KeyModifiers::ALT),
super_: value.modifiers.contains(KeyModifiers::SUPER),
} }
} }
} }
@ -77,45 +74,46 @@ impl FromStr for Key {
} }
let mut it = s[1..s.len() - 1].split_inclusive('-').peekable(); let mut it = s[1..s.len() - 1].split_inclusive('-').peekable();
while let Some(x) = it.next() { while let Some(next) = it.next() {
match x { match next.to_ascii_lowercase().as_str() {
"S-" => key.shift = true, "s-" => key.shift = true,
"C-" => key.ctrl = true, "c-" => key.ctrl = true,
"A-" => key.alt = true, "a-" => key.alt = true,
"d-" => key.super_ = true,
"Space" => key.code = KeyCode::Char(' '), "space" => key.code = KeyCode::Char(' '),
"Backspace" => key.code = KeyCode::Backspace, "backspace" => key.code = KeyCode::Backspace,
"Enter" => key.code = KeyCode::Enter, "enter" => key.code = KeyCode::Enter,
"Left" => key.code = KeyCode::Left, "left" => key.code = KeyCode::Left,
"Right" => key.code = KeyCode::Right, "right" => key.code = KeyCode::Right,
"Up" => key.code = KeyCode::Up, "up" => key.code = KeyCode::Up,
"Down" => key.code = KeyCode::Down, "down" => key.code = KeyCode::Down,
"Home" => key.code = KeyCode::Home, "home" => key.code = KeyCode::Home,
"End" => key.code = KeyCode::End, "end" => key.code = KeyCode::End,
"PageUp" => key.code = KeyCode::PageUp, "pageup" => key.code = KeyCode::PageUp,
"PageDown" => key.code = KeyCode::PageDown, "pagedown" => key.code = KeyCode::PageDown,
"Tab" => key.code = KeyCode::Tab, "tab" => key.code = KeyCode::Tab,
"BackTab" => key.code = KeyCode::BackTab, "backtab" => key.code = KeyCode::BackTab,
"Delete" => key.code = KeyCode::Delete, "delete" => key.code = KeyCode::Delete,
"Insert" => key.code = KeyCode::Insert, "insert" => key.code = KeyCode::Insert,
"F1" => key.code = KeyCode::F(1), "f1" => key.code = KeyCode::F(1),
"F2" => key.code = KeyCode::F(2), "f2" => key.code = KeyCode::F(2),
"F3" => key.code = KeyCode::F(3), "f3" => key.code = KeyCode::F(3),
"F4" => key.code = KeyCode::F(4), "f4" => key.code = KeyCode::F(4),
"F5" => key.code = KeyCode::F(5), "f5" => key.code = KeyCode::F(5),
"F6" => key.code = KeyCode::F(6), "f6" => key.code = KeyCode::F(6),
"F7" => key.code = KeyCode::F(7), "f7" => key.code = KeyCode::F(7),
"F8" => key.code = KeyCode::F(8), "f8" => key.code = KeyCode::F(8),
"F9" => key.code = KeyCode::F(9), "f9" => key.code = KeyCode::F(9),
"F10" => key.code = KeyCode::F(10), "f10" => key.code = KeyCode::F(10),
"F11" => key.code = KeyCode::F(11), "f11" => key.code = KeyCode::F(11),
"F12" => key.code = KeyCode::F(12), "f12" => key.code = KeyCode::F(12),
"Esc" => key.code = KeyCode::Esc, "esc" => key.code = KeyCode::Esc,
c if it.peek().is_none() => { _ => match next {
key.code = KeyCode::Char(c.chars().next().unwrap()); s if it.peek().is_none() => key.code = KeyCode::Char(s.chars().next().unwrap()),
} s => bail!("unknown key: {s}"),
k => bail!("unknown key: {k}"), },
} }
} }
@ -126,12 +124,6 @@ impl FromStr for Key {
} }
} }
impl TryFrom<String> for Key {
type Error = anyhow::Error;
fn try_from(s: String) -> Result<Self, Self::Error> { Self::from_str(&s) }
}
impl Display for Key { impl Display for Key {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(c) = self.plain() { if let Some(c) = self.plain() {
@ -140,6 +132,9 @@ impl Display for Key {
} }
write!(f, "<")?; write!(f, "<")?;
if self.super_ {
write!(f, "D-")?;
}
if self.ctrl { if self.ctrl {
write!(f, "C-")?; write!(f, "C-")?;
} }

View file

@ -1,8 +1,10 @@
use std::str::FromStr;
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
use yazi_shared::Layer; use yazi_shared::Layer;
use super::Control; use super::Control;
use crate::{Preset, MERGED_KEYMAP}; use crate::Preset;
#[derive(Debug)] #[derive(Debug)]
pub struct Keymap { pub struct Keymap {
@ -14,6 +16,28 @@ pub struct Keymap {
pub completion: Vec<Control>, pub completion: Vec<Control>,
} }
impl Keymap {
#[inline]
pub fn get(&self, layer: Layer) -> &Vec<Control> {
match layer {
Layer::App => unreachable!(),
Layer::Manager => &self.manager,
Layer::Tasks => &self.tasks,
Layer::Select => &self.select,
Layer::Input => &self.input,
Layer::Help => &self.help,
Layer::Completion => &self.completion,
Layer::Which => unreachable!(),
}
}
}
impl FromStr for Keymap {
type Err = toml::de::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { toml::from_str(s) }
}
impl<'de> Deserialize<'de> for Keymap { impl<'de> Deserialize<'de> for Keymap {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where where
@ -62,23 +86,3 @@ impl<'de> Deserialize<'de> for Keymap {
}) })
} }
} }
impl Default for Keymap {
fn default() -> Self { toml::from_str(&MERGED_KEYMAP).unwrap() }
}
impl Keymap {
#[inline]
pub fn get(&self, layer: Layer) -> &Vec<Control> {
match layer {
Layer::App => unreachable!(),
Layer::Manager => &self.manager,
Layer::Tasks => &self.tasks,
Layer::Select => &self.select,
Layer::Input => &self.input,
Layer::Help => &self.help,
Layer::Completion => &self.completion,
Layer::Which => unreachable!(),
}
}
}

View file

@ -1,12 +1,11 @@
mod control; mod control;
mod cow; mod cow;
mod deserializers;
mod key; mod key;
mod keymap; mod keymap;
mod run;
pub use control::*; pub use control::*;
pub use cow::*; pub use cow::*;
use deserializers::*;
pub use key::*; pub use key::*;
pub use keymap::*; pub use keymap::*;
#[allow(unused_imports)]
pub use run::*;

View file

@ -1,70 +0,0 @@
use std::{fmt, mem};
use anyhow::{bail, Result};
use serde::{de::{self, Visitor}, Deserializer};
use yazi_shared::event::{Cmd, Data};
pub(super) fn run_deserialize<'de, D>(deserializer: D) -> Result<Vec<Cmd>, D::Error>
where
D: Deserializer<'de>,
{
struct RunVisitor;
#[allow(clippy::explicit_counter_loop)]
fn parse(s: &str) -> Result<Cmd> {
let mut args = shell_words::split(s)?;
let mut cmd = Cmd { name: mem::take(&mut args[0]), ..Default::default() };
let mut i = 0usize;
for arg in args.into_iter().skip(1) {
let Some(arg) = arg.strip_prefix("--") else {
cmd.args.insert(i.to_string(), Data::String(arg));
i += 1;
continue;
};
let mut parts = arg.splitn(2, '=');
let Some(key) = parts.next().map(|s| s.to_owned()) else {
bail!("invalid argument: {arg}");
};
if let Some(val) = parts.next() {
cmd.args.insert(key, Data::String(val.to_owned()));
} else {
cmd.args.insert(key, Data::Boolean(true));
}
}
Ok(cmd)
}
impl<'de> Visitor<'de> for RunVisitor {
type Value = Vec<Cmd>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a `run` string or array of strings within keymap.toml")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: de::SeqAccess<'de>,
{
let mut cmds = vec![];
while let Some(value) = &seq.next_element::<String>()? {
cmds.push(parse(value).map_err(de::Error::custom)?);
}
if cmds.is_empty() {
return Err(de::Error::custom("`run` within keymap.toml cannot be empty"));
}
Ok(cmds)
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(vec![parse(value).map_err(de::Error::custom)?])
}
}
deserializer.deserialize_any(RunVisitor)
}

View file

@ -1,8 +1,9 @@
#![allow(clippy::module_inception)] #![allow(clippy::module_inception)]
use std::str::FromStr;
use yazi_shared::{RoCell, Xdg}; use yazi_shared::{RoCell, Xdg};
pub mod headsup;
pub mod keymap; pub mod keymap;
mod layout; mod layout;
mod log; mod log;
@ -16,7 +17,6 @@ pub mod preview;
mod priority; mod priority;
mod tasks; mod tasks;
pub mod theme; pub mod theme;
mod validation;
pub mod which; pub mod which;
pub use layout::*; pub use layout::*;
@ -24,17 +24,8 @@ pub(crate) use pattern::*;
pub(crate) use preset::*; pub(crate) use preset::*;
pub use priority::*; pub use priority::*;
// TODO: remove this once Yazi 0.3 is released --
pub static DEPRECATED_EXEC: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(false);
static MERGED_YAZI: RoCell<String> = RoCell::new();
static MERGED_KEYMAP: RoCell<String> = RoCell::new();
static MERGED_THEME: RoCell<String> = RoCell::new();
pub static LAYOUT: RoCell<arc_swap::ArcSwap<Layout>> = RoCell::new(); pub static LAYOUT: RoCell<arc_swap::ArcSwap<Layout>> = RoCell::new();
pub static HEADSUP: RoCell<headsup::Headsup> = RoCell::new();
pub static KEYMAP: RoCell<keymap::Keymap> = RoCell::new(); pub static KEYMAP: RoCell<keymap::Keymap> = RoCell::new();
pub static LOG: RoCell<log::Log> = RoCell::new(); pub static LOG: RoCell<log::Log> = RoCell::new();
pub static MANAGER: RoCell<manager::Manager> = RoCell::new(); pub static MANAGER: RoCell<manager::Manager> = RoCell::new();
@ -49,37 +40,37 @@ pub static WHICH: RoCell<which::Which> = RoCell::new();
pub fn init() -> anyhow::Result<()> { pub fn init() -> anyhow::Result<()> {
let config_dir = Xdg::config_dir(); let config_dir = Xdg::config_dir();
MERGED_YAZI.init(Preset::yazi(&config_dir)?); let yazi_toml = &Preset::yazi(&config_dir)?;
MERGED_KEYMAP.init(Preset::keymap(&config_dir)?); let keymap_toml = &Preset::keymap(&config_dir)?;
MERGED_THEME.init(Preset::theme(&config_dir)?); let theme_toml = &Preset::theme(&config_dir)?;
LAYOUT.with(Default::default); LAYOUT.with(Default::default);
HEADSUP.with(Default::default); KEYMAP.init(<_>::from_str(keymap_toml)?);
KEYMAP.with(Default::default); LOG.init(<_>::from_str(yazi_toml)?);
LOG.with(Default::default); MANAGER.init(<_>::from_str(yazi_toml)?);
MANAGER.with(Default::default); OPEN.init(<_>::from_str(yazi_toml)?);
OPEN.with(Default::default); PLUGIN.init(<_>::from_str(yazi_toml)?);
PLUGIN.with(Default::default); PREVIEW.init(<_>::from_str(yazi_toml)?);
PREVIEW.with(Default::default); TASKS.init(<_>::from_str(yazi_toml)?);
TASKS.with(Default::default); THEME.init(<_>::from_str(theme_toml)?);
THEME.with(Default::default); INPUT.init(<_>::from_str(yazi_toml)?);
INPUT.with(Default::default); SELECT.init(<_>::from_str(yazi_toml)?);
SELECT.with(Default::default); WHICH.init(<_>::from_str(yazi_toml)?);
WHICH.with(Default::default);
// TODO: remove this once Yazi 0.3 is released -- // TODO: Remove in v0.3.2
if !HEADSUP.disable_exec_warn && DEPRECATED_EXEC.load(std::sync::atomic::Ordering::Relaxed) { for c in &KEYMAP.manager {
eprintln!( for r in &c.run {
r#" if r.name == "shell" && !r.bool("confirm") && !r.bool("interactive") {
WARNING: `exec` will be deprecated in the next major version v0.3 and replaced by `run`. eprintln!(
r#"WARNING: In Yazi v0.3, the behavior of the interactive `shell` (i.e., shell templates) must be explicitly specified with `--interactive`.
Please replace all `exec = ...` with `run = ...`, in your `yazi.toml` and `keymap.toml`. Please replace e.g. `shell` with `shell --interactive`, `shell "my-template"` with `shell "my-template" --interactive`, in your keymap.toml"#
);
--- return Ok(());
Add `disable_exec_warn = true` to your `yazi.toml` under `[headsup]` to suppress this warning. }
"# }
);
} }
Ok(()) Ok(())
} }

View file

@ -1,14 +1,16 @@
use serde::{Deserialize, Deserializer}; use std::str::FromStr;
use crate::MERGED_YAZI; use serde::{Deserialize, Deserializer};
#[derive(Debug)] #[derive(Debug)]
pub struct Log { pub struct Log {
pub enabled: bool, pub enabled: bool,
} }
impl Default for Log { impl FromStr for Log {
fn default() -> Self { toml::from_str(&MERGED_YAZI).unwrap() } type Err = toml::de::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { toml::from_str(s) }
} }
impl<'de> Deserialize<'de> for Log { impl<'de> Deserialize<'de> for Log {

View file

@ -1,8 +1,9 @@
use std::str::FromStr;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use validator::Validate; use validator::Validate;
use super::{ManagerRatio, SortBy}; use super::{ManagerRatio, MouseEvents, SortBy};
use crate::{validation::check_validation, MERGED_YAZI};
#[derive(Debug, Deserialize, Serialize, Validate)] #[derive(Debug, Deserialize, Serialize, Validate)]
pub struct Manager { pub struct Manager {
@ -13,6 +14,7 @@ pub struct Manager {
pub sort_sensitive: bool, pub sort_sensitive: bool,
pub sort_reverse: bool, pub sort_reverse: bool,
pub sort_dir_first: bool, pub sort_dir_first: bool,
pub sort_translit: bool,
// Display // Display
#[validate(length(min = 1, max = 20, message = "must be between 1 and 20 characters"))] #[validate(length(min = 1, max = 20, message = "must be between 1 and 20 characters"))]
@ -20,18 +22,21 @@ pub struct Manager {
pub show_hidden: bool, pub show_hidden: bool,
pub show_symlink: bool, pub show_symlink: bool,
pub scrolloff: u8, pub scrolloff: u8,
pub mouse_events: MouseEvents,
} }
impl Default for Manager { impl FromStr for Manager {
fn default() -> Self { type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)] #[derive(Deserialize)]
struct Outer { struct Outer {
manager: Manager, manager: Manager,
} }
let manager = toml::from_str::<Outer>(&MERGED_YAZI).unwrap().manager; let manager = toml::from_str::<Outer>(s)?.manager;
manager.validate()?;
check_validation(manager.validate()); Ok(manager)
manager
} }
} }

View file

@ -1,7 +1,9 @@
mod manager; mod manager;
mod mouse;
mod ratio; mod ratio;
mod sorting; mod sorting;
pub use manager::*; pub use manager::*;
pub use mouse::*;
pub use ratio::*; pub use ratio::*;
pub use sorting::*; pub use sorting::*;

View file

@ -0,0 +1,62 @@
use anyhow::{bail, Result};
use bitflags::bitflags;
use crossterm::event::MouseEventKind;
use serde::{Deserialize, Serialize};
bitflags! {
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
#[serde(try_from = "Vec<String>", into = "Vec<String>")]
pub struct MouseEvents: u8 {
const CLICK = 0b00001;
const SCROLL = 0b00010;
const TOUCH = 0b00100;
const MOVE = 0b01000;
const DRAG = 0b10000;
}
}
impl MouseEvents {
pub const fn draggable(self) -> bool { self.contains(Self::DRAG) }
}
impl TryFrom<Vec<String>> for MouseEvents {
type Error = anyhow::Error;
fn try_from(value: Vec<String>) -> Result<Self, Self::Error> {
value.into_iter().try_fold(Self::empty(), |aac, s| {
Ok(match s.as_str() {
"click" => aac | Self::CLICK,
"scroll" => aac | Self::SCROLL,
"touch" => aac | Self::TOUCH,
"move" => aac | Self::MOVE,
"drag" => aac | Self::DRAG,
_ => bail!("Invalid mouse event: {s}"),
})
})
}
}
impl From<MouseEvents> for Vec<String> {
fn from(value: MouseEvents) -> Self {
let events = [
(MouseEvents::CLICK, "click"),
(MouseEvents::SCROLL, "scroll"),
(MouseEvents::TOUCH, "touch"),
(MouseEvents::MOVE, "move"),
(MouseEvents::DRAG, "drag"),
];
events.into_iter().filter(|v| value.contains(v.0)).map(|v| v.1.to_owned()).collect()
}
}
impl From<crossterm::event::MouseEventKind> for MouseEvents {
fn from(value: crossterm::event::MouseEventKind) -> Self {
match value {
MouseEventKind::Down(_) | MouseEventKind::Up(_) => Self::CLICK,
MouseEventKind::ScrollDown | MouseEventKind::ScrollUp => Self::SCROLL,
MouseEventKind::ScrollLeft | MouseEventKind::ScrollRight => Self::TOUCH,
MouseEventKind::Moved => Self::MOVE,
MouseEventKind::Drag(_) => Self::DRAG,
}
}
}

View file

@ -1,11 +1,11 @@
use std::{collections::HashMap, path::Path}; use std::{collections::HashMap, path::Path, str::FromStr};
use indexmap::IndexSet; use indexmap::IndexSet;
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
use yazi_shared::MIME_DIR; use yazi_shared::MIME_DIR;
use super::Opener; use super::Opener;
use crate::{open::OpenRule, Preset, MERGED_YAZI}; use crate::{open::OpenRule, Preset};
#[derive(Debug)] #[derive(Debug)]
pub struct Open { pub struct Open {
@ -13,20 +13,16 @@ pub struct Open {
openers: HashMap<String, IndexSet<Opener>>, openers: HashMap<String, IndexSet<Opener>>,
} }
impl Default for Open {
fn default() -> Self { toml::from_str(&MERGED_YAZI).unwrap() }
}
impl Open { impl Open {
pub fn openers<P, M>(&self, path: P, mime: M) -> Option<IndexSet<&Opener>> pub fn openers<P, M>(&self, path: P, mime: M) -> Option<IndexSet<&Opener>>
where where
P: AsRef<Path>, P: AsRef<Path>,
M: AsRef<str>, M: AsRef<str>,
{ {
let is_folder = mime.as_ref() == MIME_DIR; let is_dir = mime.as_ref() == MIME_DIR;
self.rules.iter().find_map(|rule| { self.rules.iter().find_map(|rule| {
if rule.mime.as_ref().is_some_and(|p| p.match_mime(&mime)) if rule.mime.as_ref().is_some_and(|p| p.match_mime(&mime))
|| rule.name.as_ref().is_some_and(|p| p.match_path(&path, is_folder)) || rule.name.as_ref().is_some_and(|p| p.match_path(&path, is_dir))
{ {
let openers = rule let openers = rule
.use_ .use_
@ -58,6 +54,12 @@ impl Open {
} }
} }
impl FromStr for Open {
type Err = toml::de::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { toml::from_str(s) }
}
impl<'de> Deserialize<'de> for Open { impl<'de> Deserialize<'de> for Open {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where where
@ -78,6 +80,13 @@ impl<'de> Deserialize<'de> for Open {
} }
let mut outer = Outer::deserialize(deserializer)?; let mut outer = Outer::deserialize(deserializer)?;
if outer.open.append_rules.iter().any(|r| r.any_file()) {
outer.open.rules.retain(|r| !r.any_file());
}
if outer.open.append_rules.iter().any(|r| r.any_dir()) {
outer.open.rules.retain(|r| !r.any_dir());
}
Preset::mix(&mut outer.open.rules, outer.open.prepend_rules, outer.open.append_rules); Preset::mix(&mut outer.open.rules, outer.open.prepend_rules, outer.open.append_rules);
let openers = outer let openers = outer

View file

@ -1,9 +1,5 @@
use std::sync::atomic::Ordering;
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
use crate::DEPRECATED_EXEC;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Opener { pub struct Opener {
pub run: String, pub run: String,
@ -36,9 +32,7 @@ impl<'de> Deserialize<'de> for Opener {
{ {
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Shadow { pub struct Shadow {
run: Option<String>, run: String,
// TODO: remove this once Yazi 0.3 is released --
exec: Option<String>,
#[serde(default)] #[serde(default)]
block: bool, block: bool,
#[serde(default)] #[serde(default)]
@ -50,13 +44,7 @@ impl<'de> Deserialize<'de> for Opener {
let shadow = Shadow::deserialize(deserializer)?; let shadow = Shadow::deserialize(deserializer)?;
// TODO: remove this once Yazi 0.3 is released -- let run = shadow.run;
if shadow.exec.is_some() {
DEPRECATED_EXEC.store(true, Ordering::Relaxed);
}
let run = shadow.run.or(shadow.exec).unwrap_or_default();
// TODO: -- remove this once Yazi 0.3 is released
if run.is_empty() { if run.is_empty() {
return Err(serde::de::Error::custom("`run` cannot be empty")); return Err(serde::de::Error::custom("`run` cannot be empty"));
} }

View file

@ -13,6 +13,14 @@ pub(super) struct OpenRule {
pub(super) use_: Vec<String>, pub(super) use_: Vec<String>,
} }
impl OpenRule {
#[inline]
pub fn any_file(&self) -> bool { self.name.as_ref().is_some_and(|p| p.any_file()) }
#[inline]
pub fn any_dir(&self) -> bool { self.name.as_ref().is_some_and(|p| p.any_dir()) }
}
impl OpenRule { impl OpenRule {
fn deserialize<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error> fn deserialize<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
where where

View file

@ -6,25 +6,27 @@ use serde::Deserialize;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(try_from = "String")] #[serde(try_from = "String")]
pub struct Pattern { pub struct Pattern {
inner: globset::GlobMatcher, inner: globset::GlobMatcher,
is_star: bool, is_dir: bool,
is_folder: bool, is_star: bool,
} }
impl Pattern { impl Pattern {
#[inline] #[inline]
pub fn match_mime(&self, str: impl AsRef<str>) -> bool { self.inner.is_match(str.as_ref()) } pub fn match_mime(&self, str: impl AsRef<str>) -> bool {
self.is_star || self.inner.is_match(str.as_ref())
#[inline]
pub fn match_path(&self, path: impl AsRef<Path>, is_folder: bool) -> bool {
is_folder == self.is_folder && (self.is_star || self.inner.is_match(path))
} }
#[inline] #[inline]
pub fn any_file(&self) -> bool { self.is_star && !self.is_folder } pub fn match_path(&self, path: impl AsRef<Path>, is_dir: bool) -> bool {
is_dir == self.is_dir && (self.is_star || self.inner.is_match(path))
}
#[inline] #[inline]
pub fn any_dir(&self) -> bool { self.is_star && self.is_folder } pub fn any_file(&self) -> bool { self.is_star && !self.is_dir }
#[inline]
pub fn any_dir(&self) -> bool { self.is_star && self.is_dir }
} }
impl TryFrom<&str> for Pattern { impl TryFrom<&str> for Pattern {
@ -36,13 +38,13 @@ impl TryFrom<&str> for Pattern {
let inner = GlobBuilder::new(b) let inner = GlobBuilder::new(b)
.case_insensitive(a.len() == s.len()) .case_insensitive(a.len() == s.len())
.literal_separator(b.contains('/')) .literal_separator(false)
.backslash_escape(false) .backslash_escape(false)
.empty_alternates(false) .empty_alternates(true)
.build()? .build()?
.compile_matcher(); .compile_matcher();
Ok(Self { inner, is_star: b == "*", is_folder: b.len() < a.len() }) Ok(Self { inner, is_dir: b.len() < a.len(), is_star: b == "*" })
} }
} }

View file

@ -0,0 +1,32 @@
use serde::Deserialize;
use yazi_shared::{event::Cmd, Condition};
use crate::{Pattern, Priority};
#[derive(Debug, Deserialize)]
pub struct Fetcher {
#[serde(skip)]
pub idx: u8,
pub id: String,
#[serde(rename = "if")]
pub if_: Option<Condition>,
pub name: Option<Pattern>,
pub mime: Option<Pattern>,
pub run: Cmd,
#[serde(default)]
pub prio: Priority,
}
#[derive(Debug, Clone)]
pub struct FetcherProps {
pub id: u8,
pub name: String,
pub prio: Priority,
}
impl From<&Fetcher> for FetcherProps {
fn from(fetcher: &Fetcher) -> Self {
Self { id: fetcher.idx, name: fetcher.run.name.to_owned(), prio: fetcher.prio }
}
}

View file

@ -1,12 +1,11 @@
mod fetcher;
mod plugin; mod plugin;
mod props; mod preloader;
mod rule; mod previewer;
mod run;
pub use fetcher::*;
pub use plugin::*; pub use plugin::*;
pub use props::*; pub use preloader::*;
pub use rule::*; pub use previewer::*;
#[allow(unused_imports)]
pub use run::*;
pub const MAX_PRELOADERS: u8 = 32; pub const MAX_PREWORKERS: u8 = 32;

View file

@ -1,19 +1,68 @@
use std::path::Path; use std::{path::Path, str::FromStr};
use serde::Deserialize; use serde::Deserialize;
use yazi_shared::MIME_DIR; use yazi_shared::MIME_DIR;
use super::PluginRule; use super::{Fetcher, Preloader, Previewer};
use crate::{plugin::MAX_PRELOADERS, Preset, MERGED_YAZI}; use crate::{plugin::MAX_PREWORKERS, Preset};
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Plugin { pub struct Plugin {
pub preloaders: Vec<PluginRule>, pub fetchers: Vec<Fetcher>,
pub previewers: Vec<PluginRule>, pub preloaders: Vec<Preloader>,
pub previewers: Vec<Previewer>,
} }
impl Default for Plugin { impl Plugin {
fn default() -> Self { pub fn fetchers(
&self,
path: &Path,
mime: Option<&str>,
f: impl Fn(&str) -> bool + Copy,
) -> Vec<&Fetcher> {
let is_dir = mime == Some(MIME_DIR);
self
.fetchers
.iter()
.filter(|&p| {
p.if_.as_ref().and_then(|c| c.eval(f)) != Some(false)
&& (p.mime.as_ref().zip(mime).map_or(false, |(p, m)| p.match_mime(m))
|| p.name.as_ref().is_some_and(|p| p.match_path(path, is_dir)))
})
.collect()
}
pub fn preloaders(&self, path: &Path, mime: Option<&str>) -> Vec<&Preloader> {
let is_dir = mime == Some(MIME_DIR);
let mut preloaders = Vec::with_capacity(1);
for p in &self.preloaders {
if !p.mime.as_ref().zip(mime).map_or(false, |(p, m)| p.match_mime(m))
&& !p.name.as_ref().is_some_and(|p| p.match_path(path, is_dir))
{
continue;
}
preloaders.push(p);
if !p.next {
break;
}
}
preloaders
}
pub fn previewer(&self, path: &Path, mime: &str) -> Option<&Previewer> {
let is_dir = mime == MIME_DIR;
self.previewers.iter().find(|&p| {
p.mime.as_ref().is_some_and(|p| p.match_mime(mime))
|| p.name.as_ref().is_some_and(|p| p.match_path(path, is_dir))
})
}
}
impl FromStr for Plugin {
type Err = toml::de::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)] #[derive(Deserialize)]
struct Outer { struct Outer {
plugin: Shadow, plugin: Shadow,
@ -21,20 +70,26 @@ impl Default for Plugin {
#[derive(Deserialize)] #[derive(Deserialize)]
struct Shadow { struct Shadow {
preloaders: Vec<PluginRule>, fetchers: Vec<Fetcher>,
#[serde(default)] #[serde(default)]
prepend_preloaders: Vec<PluginRule>, prepend_fetchers: Vec<Fetcher>,
#[serde(default)] #[serde(default)]
append_preloaders: Vec<PluginRule>, append_fetchers: Vec<Fetcher>,
previewers: Vec<PluginRule>, preloaders: Vec<Preloader>,
#[serde(default)] #[serde(default)]
prepend_previewers: Vec<PluginRule>, prepend_preloaders: Vec<Preloader>,
#[serde(default)] #[serde(default)]
append_previewers: Vec<PluginRule>, append_preloaders: Vec<Preloader>,
previewers: Vec<Previewer>,
#[serde(default)]
prepend_previewers: Vec<Previewer>,
#[serde(default)]
append_previewers: Vec<Previewer>,
} }
let mut shadow = toml::from_str::<Outer>(&MERGED_YAZI).unwrap().plugin; let mut shadow = toml::from_str::<Outer>(s)?.plugin;
if shadow.append_previewers.iter().any(|r| r.any_file()) { if shadow.append_previewers.iter().any(|r| r.any_file()) {
shadow.previewers.retain(|r| !r.any_file()); shadow.previewers.retain(|r| !r.any_file());
} }
@ -42,48 +97,25 @@ impl Default for Plugin {
shadow.previewers.retain(|r| !r.any_dir()); shadow.previewers.retain(|r| !r.any_dir());
} }
Preset::mix(&mut shadow.fetchers, shadow.prepend_fetchers, shadow.append_fetchers);
Preset::mix(&mut shadow.preloaders, shadow.prepend_preloaders, shadow.append_preloaders); Preset::mix(&mut shadow.preloaders, shadow.prepend_preloaders, shadow.append_preloaders);
Preset::mix(&mut shadow.previewers, shadow.prepend_previewers, shadow.append_previewers); Preset::mix(&mut shadow.previewers, shadow.prepend_previewers, shadow.append_previewers);
if shadow.preloaders.len() > MAX_PRELOADERS as usize { if shadow.fetchers.len() + shadow.preloaders.len() > MAX_PREWORKERS as usize {
panic!("Too many preloaders"); panic!("Fetchers and preloaders exceed the limit of {MAX_PREWORKERS}");
} }
for (i, preloader) in shadow.preloaders.iter_mut().enumerate() { for (i, p) in shadow.fetchers.iter_mut().enumerate() {
if preloader.sync { p.idx = i as u8;
panic!("Preloaders cannot be synchronous"); }
} for (i, p) in shadow.preloaders.iter_mut().enumerate() {
preloader.id = i as u8; p.idx = shadow.fetchers.len() as u8 + i as u8;
} }
Self { preloaders: shadow.preloaders, previewers: shadow.previewers } Ok(Self {
} fetchers: shadow.fetchers,
} preloaders: shadow.preloaders,
previewers: shadow.previewers,
impl Plugin {
pub fn preloaders(
&self,
path: &Path,
mime: Option<&str>,
f: impl Fn(&str) -> bool + Copy,
) -> Vec<&PluginRule> {
let is_folder = mime == Some(MIME_DIR);
self
.preloaders
.iter()
.filter(|&rule| {
rule.cond.as_ref().and_then(|c| c.eval(f)) != Some(false)
&& (rule.mime.as_ref().zip(mime).map_or(false, |(p, m)| p.match_mime(m))
|| rule.name.as_ref().is_some_and(|p| p.match_path(path, is_folder)))
})
.collect()
}
pub fn previewer(&self, path: &Path, mime: &str) -> Option<&PluginRule> {
let is_folder = mime == MIME_DIR;
self.previewers.iter().find(|&rule| {
rule.mime.as_ref().is_some_and(|p| p.match_mime(mime))
|| rule.name.as_ref().is_some_and(|p| p.match_path(path, is_folder))
}) })
} }
} }

View file

@ -0,0 +1,31 @@
use serde::Deserialize;
use yazi_shared::event::Cmd;
use crate::{Pattern, Priority};
#[derive(Debug, Deserialize)]
pub struct Preloader {
#[serde(skip)]
pub idx: u8,
pub name: Option<Pattern>,
pub mime: Option<Pattern>,
pub run: Cmd,
#[serde(default)]
pub next: bool,
#[serde(default)]
pub prio: Priority,
}
#[derive(Debug, Clone)]
pub struct PreloaderProps {
pub id: u8,
pub name: String,
pub prio: Priority,
}
impl From<&Preloader> for PreloaderProps {
fn from(preloader: &Preloader) -> Self {
Self { id: preloader.idx, name: preloader.run.name.to_owned(), prio: preloader.prio }
}
}

View file

@ -0,0 +1,21 @@
use serde::Deserialize;
use yazi_shared::event::Cmd;
use crate::Pattern;
#[derive(Debug, Deserialize)]
pub struct Previewer {
pub name: Option<Pattern>,
pub mime: Option<Pattern>,
pub run: Cmd,
#[serde(default)]
pub sync: bool,
}
impl Previewer {
#[inline]
pub fn any_file(&self) -> bool { self.name.as_ref().is_some_and(|p| p.any_file()) }
#[inline]
pub fn any_dir(&self) -> bool { self.name.as_ref().is_some_and(|p| p.any_dir()) }
}

View file

@ -1,16 +0,0 @@
use super::PluginRule;
use crate::Priority;
#[derive(Debug, Clone)]
pub struct PluginProps {
pub id: u8,
pub name: String,
pub multi: bool,
pub prio: Priority,
}
impl From<&PluginRule> for PluginProps {
fn from(rule: &PluginRule) -> Self {
Self { id: rule.id, name: rule.cmd.name.to_owned(), multi: rule.multi, prio: rule.prio }
}
}

View file

@ -1,74 +0,0 @@
use std::sync::atomic::Ordering;
use serde::{Deserialize, Deserializer};
use yazi_shared::{event::Cmd, Condition};
use crate::{Pattern, Priority, DEPRECATED_EXEC};
#[derive(Debug)]
pub struct PluginRule {
pub id: u8,
pub cond: Option<Condition>,
pub name: Option<Pattern>,
pub mime: Option<Pattern>,
pub cmd: Cmd,
pub sync: bool,
pub multi: bool,
pub prio: Priority,
}
impl PluginRule {
#[inline]
pub fn any_file(&self) -> bool { self.name.as_ref().is_some_and(|p| p.any_file()) }
#[inline]
pub fn any_dir(&self) -> bool { self.name.as_ref().is_some_and(|p| p.any_dir()) }
}
// TODO: remove this once Yazi 0.3 is released
impl<'de> Deserialize<'de> for PluginRule {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
pub struct Shadow {
#[serde(default)]
pub id: u8,
pub cond: Option<Condition>,
pub name: Option<Pattern>,
pub mime: Option<Pattern>,
pub run: Option<WrappedCmd>,
pub exec: Option<WrappedCmd>,
#[serde(default)]
pub sync: bool,
#[serde(default)]
pub multi: bool,
#[serde(default)]
pub prio: Priority,
}
let shadow = Shadow::deserialize(deserializer)?;
#[derive(Deserialize)]
struct WrappedCmd(#[serde(deserialize_with = "super::run_deserialize")] Cmd);
if shadow.exec.is_some() {
DEPRECATED_EXEC.store(true, Ordering::Relaxed);
}
let Some(run) = shadow.run.or(shadow.exec) else {
return Err(serde::de::Error::custom("missing field `run` within `[plugin]`"));
};
Ok(Self {
id: shadow.id,
cond: shadow.cond,
name: shadow.name,
mime: shadow.mime,
cmd: run.0,
sync: shadow.sync,
multi: shadow.multi,
prio: shadow.prio,
})
}
}

View file

@ -1,39 +0,0 @@
use std::fmt;
use anyhow::Result;
use serde::{de::{self, Visitor}, Deserializer};
use yazi_shared::event::Cmd;
pub(super) fn run_deserialize<'de, D>(deserializer: D) -> Result<Cmd, D::Error>
where
D: Deserializer<'de>,
{
struct RunVisitor;
impl<'de> Visitor<'de> for RunVisitor {
type Value = Cmd;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a `run` string or array of strings")
}
fn visit_seq<A>(self, _: A) -> Result<Self::Value, A::Error>
where
A: de::SeqAccess<'de>,
{
Err(de::Error::custom("`run` within [plugin] must be a string"))
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
if value.is_empty() {
return Err(de::Error::custom("`run` within [plugin] cannot be empty"));
}
Ok(Cmd { name: value.to_owned(), ..Default::default() })
}
}
deserializer.deserialize_any(RunVisitor)
}

View file

@ -1,10 +1,13 @@
use std::str::FromStr;
use serde::Deserialize; use serde::Deserialize;
use super::{Offset, Origin}; use super::{Offset, Origin};
use crate::MERGED_YAZI;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Input { pub struct Input {
pub cursor_blink: bool,
// cd // cd
pub cd_title: String, pub cd_title: String,
pub cd_origin: Origin, pub cd_origin: Origin,
@ -61,18 +64,19 @@ pub struct Input {
pub quit_offset: Offset, pub quit_offset: Offset,
} }
impl Default for Input { impl Input {
fn default() -> Self { pub const fn border(&self) -> u16 { 2 }
}
impl FromStr for Input {
type Err = toml::de::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)] #[derive(Deserialize)]
struct Outer { struct Outer {
input: Input, input: Input,
} }
toml::from_str::<Outer>(&MERGED_YAZI).unwrap().input Ok(toml::from_str::<Outer>(s)?.input)
} }
} }
impl Input {
#[inline]
pub const fn border(&self) -> u16 { 2 }
}

View file

@ -1,6 +1,5 @@
use crossterm::terminal::WindowSize; use crossterm::terminal::WindowSize;
use ratatui::layout::Rect; use ratatui::layout::Rect;
use yazi_shared::term::Term;
use super::{Offset, Origin}; use super::{Offset, Origin};
@ -14,10 +13,9 @@ impl Position {
#[inline] #[inline]
pub fn new(origin: Origin, offset: Offset) -> Self { Self { origin, offset } } pub fn new(origin: Origin, offset: Offset) -> Self { Self { origin, offset } }
pub fn rect(&self) -> Rect { pub fn rect(&self, WindowSize { columns, rows, .. }: WindowSize) -> Rect {
use Origin::*; use Origin::*;
let Offset { x, y, width, height } = self.offset; let Offset { x, y, width, height } = self.offset;
let WindowSize { columns, rows, .. } = Term::size();
let max_x = columns.saturating_sub(width); let max_x = columns.saturating_sub(width);
let new_x = match self.origin { let new_x = match self.origin {
@ -45,9 +43,8 @@ impl Position {
} }
} }
pub fn sticky(base: Rect, offset: Offset) -> Rect { pub fn sticky(WindowSize { columns, rows, .. }: WindowSize, base: Rect, offset: Offset) -> Rect {
let Offset { x, y, width, height } = offset; let Offset { x, y, width, height } = offset;
let WindowSize { columns, rows, .. } = Term::size();
let above = let above =
base.y.saturating_add(base.height).saturating_add(height).saturating_add_signed(y) > rows; base.y.saturating_add(base.height).saturating_add(height).saturating_add_signed(y) > rows;

View file

@ -1,7 +1,8 @@
use std::str::FromStr;
use serde::Deserialize; use serde::Deserialize;
use super::{Offset, Origin}; use super::{Offset, Origin};
use crate::MERGED_YAZI;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Select { pub struct Select {
@ -11,18 +12,19 @@ pub struct Select {
pub open_offset: Offset, pub open_offset: Offset,
} }
impl Default for Select { impl Select {
fn default() -> Self { pub const fn border(&self) -> u16 { 2 }
}
impl FromStr for Select {
type Err = toml::de::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)] #[derive(Deserialize)]
struct Outer { struct Outer {
select: Select, select: Select,
} }
toml::from_str::<Outer>(&MERGED_YAZI).unwrap().select Ok(toml::from_str::<Outer>(s)?.select)
} }
} }
impl Select {
#[inline]
pub const fn border(&self) -> u16 { 2 }
}

View file

@ -1,4 +1,4 @@
use std::{mem, path::{Path, PathBuf}}; use std::{borrow::Cow, mem, path::{Path, PathBuf}};
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use toml::{Table, Value}; use toml::{Table, Value};
@ -8,17 +8,17 @@ use crate::theme::Flavor;
pub(crate) struct Preset; pub(crate) struct Preset;
impl Preset { impl Preset {
pub(crate) fn yazi(p: &Path) -> Result<String> { pub(crate) fn yazi(p: &Path) -> Result<Cow<str>> {
Self::merge_path(p.join("yazi.toml"), include_str!("../preset/yazi.toml")) Self::merge_path(p.join("yazi.toml"), include_str!("../preset/yazi.toml"))
} }
pub(crate) fn keymap(p: &Path) -> Result<String> { pub(crate) fn keymap(p: &Path) -> Result<Cow<str>> {
Self::merge_path(p.join("keymap.toml"), include_str!("../preset/keymap.toml")) Self::merge_path(p.join("keymap.toml"), include_str!("../preset/keymap.toml"))
} }
pub(crate) fn theme(p: &Path) -> Result<String> { pub(crate) fn theme(p: &Path) -> Result<Cow<str>> {
let Ok(user) = std::fs::read_to_string(p.join("theme.toml")) else { let Ok(user) = std::fs::read_to_string(p.join("theme.toml")) else {
return Ok(include_str!("../preset/theme.toml").to_owned()); return Ok(include_str!("../preset/theme.toml").into());
}; };
let Some(use_) = Flavor::parse_use(&user) else { let Some(use_) = Flavor::parse_use(&user) else {
return Self::merge_str(&user, include_str!("../preset/theme.toml")); return Self::merge_str(&user, include_str!("../preset/theme.toml"));
@ -37,18 +37,18 @@ impl Preset {
} }
#[inline] #[inline]
pub(crate) fn merge_str(user: &str, base: &str) -> Result<String> { pub(crate) fn merge_str(user: &str, base: &str) -> Result<Cow<'static, str>> {
let mut t = user.parse()?; let mut t = user.parse()?;
Self::merge(&mut t, base.parse()?, 2); Self::merge(&mut t, base.parse()?, 2);
Ok(t.to_string()) Ok(t.to_string().into())
} }
#[inline] #[inline]
fn merge_path(user: PathBuf, base: &str) -> Result<String> { fn merge_path(user: PathBuf, base: &str) -> Result<Cow<str>> {
let s = std::fs::read_to_string(&user).unwrap_or_default(); let s = std::fs::read_to_string(&user).unwrap_or_default();
if s.is_empty() { if s.is_empty() {
return Ok(base.to_string()); return Ok(base.into());
} }
Self::merge_str(&s, base).with_context(|| anyhow!("Loading {user:?}")) Self::merge_str(&s, base).with_context(|| anyhow!("Loading {user:?}"))

View file

@ -1,10 +1,11 @@
use std::{path::PathBuf, time::{self, SystemTime}}; use std::{path::PathBuf, str::FromStr, time::{self, SystemTime}};
use anyhow::Context;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use validator::Validate; use validator::Validate;
use yazi_shared::fs::expand_path; use yazi_shared::fs::expand_path;
use crate::{validation::check_validation, Xdg, MERGED_YAZI}; use crate::Xdg;
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
pub struct Preview { pub struct Preview {
@ -22,8 +23,18 @@ pub struct Preview {
pub ueberzug_offset: (f32, f32, f32, f32), pub ueberzug_offset: (f32, f32, f32, f32),
} }
impl Default for Preview { impl Preview {
fn default() -> Self { #[inline]
pub fn tmpfile(&self, prefix: &str) -> PathBuf {
let nanos = SystemTime::now().duration_since(time::UNIX_EPOCH).unwrap().as_nanos();
self.cache_dir.join(format!("{prefix}-{}", nanos / 1000))
}
}
impl FromStr for Preview {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)] #[derive(Deserialize)]
struct Outer { struct Outer {
preview: Shadow, preview: Shadow,
@ -46,17 +57,14 @@ impl Default for Preview {
ueberzug_offset: (f32, f32, f32, f32), ueberzug_offset: (f32, f32, f32, f32),
} }
let preview = toml::from_str::<Outer>(&MERGED_YAZI).unwrap().preview; let preview = toml::from_str::<Outer>(s)?.preview;
check_validation(preview.validate()); preview.validate()?;
let cache_dir = let cache_dir =
preview.cache_dir.filter(|p| !p.is_empty()).map_or_else(Xdg::cache_dir, expand_path); preview.cache_dir.filter(|p| !p.is_empty()).map_or_else(Xdg::cache_dir, expand_path);
std::fs::create_dir_all(&cache_dir).context("Failed to create cache directory")?;
if !cache_dir.is_dir() { Ok(Preview {
std::fs::create_dir(&cache_dir).expect("Failed to create cache directory");
}
Preview {
tab_size: preview.tab_size, tab_size: preview.tab_size,
max_width: preview.max_width, max_width: preview.max_width,
max_height: preview.max_height, max_height: preview.max_height,
@ -69,14 +77,6 @@ impl Default for Preview {
ueberzug_scale: preview.ueberzug_scale, ueberzug_scale: preview.ueberzug_scale,
ueberzug_offset: preview.ueberzug_offset, ueberzug_offset: preview.ueberzug_offset,
} })
}
}
impl Preview {
#[inline]
pub fn tmpfile(&self, prefix: &str) -> PathBuf {
let nanos = SystemTime::now().duration_since(time::UNIX_EPOCH).unwrap().as_nanos();
self.cache_dir.join(format!("{prefix}-{}", nanos / 1000))
} }
} }

View file

@ -1,8 +1,8 @@
use std::str::FromStr;
use serde::Deserialize; use serde::Deserialize;
use validator::Validate; use validator::Validate;
use crate::{validation::check_validation, MERGED_YAZI};
#[derive(Debug, Deserialize, Validate)] #[derive(Debug, Deserialize, Validate)]
pub struct Tasks { pub struct Tasks {
#[validate(range(min = 1, message = "Cannot be less than 1"))] #[validate(range(min = 1, message = "Cannot be less than 1"))]
@ -18,16 +18,18 @@ pub struct Tasks {
pub suppress_preload: bool, pub suppress_preload: bool,
} }
impl Default for Tasks { impl FromStr for Tasks {
fn default() -> Self { type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)] #[derive(Deserialize)]
struct Outer { struct Outer {
tasks: Tasks, tasks: Tasks,
} }
let tasks = toml::from_str::<Outer>(&MERGED_YAZI).unwrap().tasks; let tasks = toml::from_str::<Outer>(s)?.tasks;
check_validation(tasks.validate()); tasks.validate()?;
tasks Ok(tasks)
} }
} }

View file

@ -1,78 +0,0 @@
use std::ops::Deref;
use serde::{Deserialize, Deserializer};
use yazi_shared::{fs::File, theme::{Color, StyleShadow}};
use crate::{preset::Preset, theme::Is, Pattern};
pub struct Icon {
is: Is,
name: Pattern,
inner: yazi_shared::theme::Icon,
}
impl Deref for Icon {
type Target = yazi_shared::theme::Icon;
fn deref(&self) -> &Self::Target { &self.inner }
}
impl Icon {
pub fn matches(&self, file: &File) -> bool {
if !self.is.check(&file.cha) {
return false;
}
self.name.match_path(&file.url, file.is_dir())
}
}
impl Icon {
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Icon>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct IconOuter {
rules: Vec<IconRule>,
#[serde(default)]
prepend_rules: Vec<IconRule>,
#[serde(default)]
append_rules: Vec<IconRule>,
}
#[derive(Deserialize)]
struct IconRule {
#[serde(default)]
is: Is,
name: Pattern,
text: String,
fg: Option<Color>,
}
let mut outer = IconOuter::deserialize(deserializer)?;
if outer.append_rules.iter().any(|r| r.name.any_file()) {
outer.rules.retain(|r| !r.name.any_file());
}
if outer.append_rules.iter().any(|r| r.name.any_dir()) {
outer.rules.retain(|r| !r.name.any_dir());
}
Preset::mix(&mut outer.rules, outer.prepend_rules, outer.append_rules);
Ok(
outer
.rules
.into_iter()
.map(|r| Icon {
is: r.is,
name: r.name,
inner: yazi_shared::theme::Icon {
text: r.text,
style: StyleShadow { fg: r.fg, ..Default::default() }.into(),
},
})
.collect(),
)
}
}

View file

@ -0,0 +1,169 @@
use std::collections::HashMap;
use anyhow::Result;
use serde::{Deserialize, Deserializer};
use yazi_shared::{fs::File, theme::{Color, Icon, Style}, Condition};
use crate::{Pattern, Preset};
pub struct Icons {
globs: Vec<(Pattern, Icon)>,
dirs: HashMap<String, Icon>,
files: HashMap<String, Icon>,
exts: HashMap<String, Icon>,
conds: Vec<(Condition, Icon)>,
}
impl Icons {
pub fn matches(&self, file: &File) -> Option<&Icon> {
if let Some(i) = self.match_by_glob(file) {
return Some(i);
}
if let Some(i) = self.match_by_name(file) {
return Some(i);
}
let f = |s: &str| match s {
"dir" => file.is_dir(),
"hidden" => file.is_hidden(),
"link" => file.is_link(),
"orphan" => file.is_orphan(),
"block" => file.is_block(),
"char" => file.is_char(),
"fifo" => file.is_fifo(),
"sock" => file.is_sock(),
"exec" => file.is_exec(),
"sticky" => file.is_sticky(),
_ => false,
};
self.conds.iter().find(|(c, _)| c.eval(f) == Some(true)).map(|(_, i)| i)
}
#[inline]
fn match_by_glob(&self, file: &File) -> Option<&Icon> {
self.globs.iter().find(|(p, _)| p.match_path(&file.url, file.is_dir())).map(|(_, i)| i)
}
#[inline]
fn match_by_name(&self, file: &File) -> Option<&Icon> {
let name = file.name()?.to_str()?;
if file.is_dir() {
self.dirs.get(name).or_else(|| self.dirs.get(&name.to_ascii_lowercase()))
} else {
self
.files
.get(name)
.or_else(|| self.files.get(&name.to_ascii_lowercase()))
.or_else(|| self.exts.get(file.url.extension()?.to_str()?))
}
}
}
impl<'de> Deserialize<'de> for Icons {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
pub struct Shadow {
globs: Vec<ShadowPat>,
#[serde(default)]
prepend_globs: Vec<ShadowPat>,
#[serde(default)]
append_globs: Vec<ShadowPat>,
dirs: Vec<ShadowStr>,
#[serde(default)]
prepend_dirs: Vec<ShadowStr>,
#[serde(default)]
append_dirs: Vec<ShadowStr>,
files: Vec<ShadowStr>,
#[serde(default)]
prepend_files: Vec<ShadowStr>,
#[serde(default)]
append_files: Vec<ShadowStr>,
exts: Vec<ShadowStr>,
#[serde(default)]
prepend_exts: Vec<ShadowStr>,
#[serde(default)]
append_exts: Vec<ShadowStr>,
conds: Vec<ShadowCond>,
#[serde(default)]
prepend_conds: Vec<ShadowCond>,
#[serde(default)]
append_conds: Vec<ShadowCond>,
}
#[derive(Deserialize)]
pub struct ShadowPat {
name: Pattern,
text: String,
fg_dark: Option<Color>,
#[allow(dead_code)]
fg_light: Option<Color>,
}
#[derive(Deserialize)]
pub struct ShadowStr {
name: String,
text: String,
fg_dark: Option<Color>,
#[allow(dead_code)]
fg_light: Option<Color>,
}
#[derive(Deserialize)]
pub struct ShadowCond {
#[serde(rename = "if")]
if_: Condition,
text: String,
fg_dark: Option<Color>,
#[allow(dead_code)]
fg_light: Option<Color>,
}
let mut shadow = Shadow::deserialize(deserializer)?;
Preset::mix(&mut shadow.globs, shadow.prepend_globs, shadow.append_globs);
Preset::mix(&mut shadow.dirs, shadow.prepend_dirs, shadow.append_dirs);
Preset::mix(&mut shadow.files, shadow.prepend_files, shadow.append_files);
Preset::mix(&mut shadow.exts, shadow.prepend_exts, shadow.append_exts);
Preset::mix(&mut shadow.conds, shadow.prepend_conds, shadow.append_conds);
let globs = shadow
.globs
.into_iter()
.map(|v| {
(v.name, Icon { text: v.text, style: Style { fg: v.fg_dark, ..Default::default() } })
})
.collect();
let conds = shadow
.conds
.into_iter()
.map(|v| {
(v.if_, Icon { text: v.text, style: Style { fg: v.fg_dark, ..Default::default() } })
})
.collect();
fn as_map(v: Vec<ShadowStr>) -> HashMap<String, Icon> {
let mut map = HashMap::with_capacity(v.len());
for item in v {
map.entry(item.name).or_insert(Icon {
text: item.text,
style: Style { fg: item.fg_dark, ..Default::default() },
});
}
map.shrink_to_fit();
map
}
Ok(Self {
globs,
dirs: as_map(shadow.dirs),
files: as_map(shadow.files),
exts: as_map(shadow.exts),
conds,
})
}
}

View file

@ -48,13 +48,13 @@ impl Is {
pub fn check(&self, cha: &Cha) -> bool { pub fn check(&self, cha: &Cha) -> bool {
match self { match self {
Self::None => true, Self::None => true,
Self::Block => cha.is_block_device(), Self::Block => cha.is_block(),
Self::Char => cha.is_char_device(), Self::Char => cha.is_char(),
Self::Exec => cha.is_exec(), Self::Exec => cha.is_exec(),
Self::Fifo => cha.is_fifo(), Self::Fifo => cha.is_fifo(),
Self::Link => cha.is_link(), Self::Link => cha.is_link(),
Self::Orphan => cha.is_orphan(), Self::Orphan => cha.is_orphan(),
Self::Sock => cha.is_socket(), Self::Sock => cha.is_sock(),
Self::Sticky => cha.is_sticky(), Self::Sticky => cha.is_sticky(),
} }
} }

View file

@ -1,11 +1,11 @@
mod filetype; mod filetype;
mod flavor; mod flavor;
mod icon; mod icons;
mod is; mod is;
mod theme; mod theme;
pub use filetype::*; pub use filetype::*;
pub use flavor::*; pub use flavor::*;
pub use icon::*; pub use icons::*;
pub use is::*; pub use is::*;
pub use theme::*; pub use theme::*;

View file

@ -1,11 +1,10 @@
use std::path::PathBuf; use std::{path::PathBuf, str::FromStr};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use validator::Validate; use validator::Validate;
use yazi_shared::{fs::expand_path, theme::Style, Xdg}; use yazi_shared::{fs::expand_path, theme::Style, Xdg};
use super::{Filetype, Flavor, Icon}; use super::{Filetype, Flavor, Icons};
use crate::{validation::check_validation, MERGED_THEME};
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub struct Theme { pub struct Theme {
@ -23,16 +22,17 @@ pub struct Theme {
// File-specific styles // File-specific styles
#[serde(rename = "filetype", deserialize_with = "Filetype::deserialize", skip_serializing)] #[serde(rename = "filetype", deserialize_with = "Filetype::deserialize", skip_serializing)]
pub filetypes: Vec<Filetype>, pub filetypes: Vec<Filetype>,
#[serde(rename = "icon", deserialize_with = "Icon::deserialize", skip_serializing)] #[serde(rename = "icon", skip_serializing)]
pub icons: Vec<Icon>, pub icons: Icons,
} }
impl Default for Theme { impl FromStr for Theme {
fn default() -> Self { type Err = anyhow::Error;
let mut theme: Self = toml::from_str(&MERGED_THEME).unwrap();
check_validation(theme.manager.validate()); fn from_str(s: &str) -> Result<Self, Self::Err> {
check_validation(theme.which.validate()); let mut theme: Self = toml::from_str(s)?;
theme.manager.validate()?;
theme.which.validate()?;
if theme.flavor.use_.is_empty() { if theme.flavor.use_.is_empty() {
theme.manager.syntect_theme = expand_path(&theme.manager.syntect_theme); theme.manager.syntect_theme = expand_path(&theme.manager.syntect_theme);
@ -41,7 +41,7 @@ impl Default for Theme {
Xdg::config_dir().join(format!("flavors/{}.yazi/tmtheme.xml", theme.flavor.use_)); Xdg::config_dir().join(format!("flavors/{}.yazi/tmtheme.xml", theme.flavor.use_));
} }
theme Ok(theme)
} }
} }

View file

@ -1,29 +0,0 @@
use std::{borrow::Cow, process};
use validator::{ValidationErrors, ValidationErrorsKind};
pub fn check_validation(res: Result<(), ValidationErrors>) {
let Err(errors) = res else { return };
for (field, kind) in errors.into_errors() {
match kind {
ValidationErrorsKind::Struct(errors) => check_validation(Err(*errors)),
ValidationErrorsKind::List(errors) => {
for (i, errors) in errors {
eprint!("Config `{field}[{i}]` format error: ");
check_validation(Err(*errors));
eprintln!();
}
}
ValidationErrorsKind::Field(error) => {
for e in error {
eprintln!(
"Config `{field}` format error: {}\n",
e.message.unwrap_or(Cow::Borrowed("unknown error"))
);
}
}
}
}
process::exit(1);
}

View file

@ -1,8 +1,9 @@
use std::str::FromStr;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use validator::Validate; use validator::Validate;
use super::SortBy; use super::SortBy;
use crate::MERGED_YAZI;
#[derive(Debug, Deserialize, Serialize, Validate)] #[derive(Debug, Deserialize, Serialize, Validate)]
pub struct Which { pub struct Which {
@ -10,15 +11,18 @@ pub struct Which {
pub sort_by: SortBy, pub sort_by: SortBy,
pub sort_sensitive: bool, pub sort_sensitive: bool,
pub sort_reverse: bool, pub sort_reverse: bool,
pub sort_translit: bool,
} }
impl Default for Which { impl FromStr for Which {
fn default() -> Self { type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)] #[derive(Deserialize)]
struct Outer { struct Outer {
which: Which, which: Which,
} }
toml::from_str::<Outer>(&MERGED_YAZI).unwrap().which Ok(toml::from_str::<Outer>(s)?.which)
} }
} }

View file

@ -9,7 +9,7 @@ homepage = "https://yazi-rs.github.io"
repository = "https://github.com/sxyazi/yazi" repository = "https://github.com/sxyazi/yazi"
[dependencies] [dependencies]
yazi-adaptor = { path = "../yazi-adaptor", version = "0.2.5" } yazi-adapter = { path = "../yazi-adapter", version = "0.2.5" }
yazi-boot = { path = "../yazi-boot", version = "0.2.5" } yazi-boot = { path = "../yazi-boot", version = "0.2.5" }
yazi-config = { path = "../yazi-config", version = "0.2.5" } yazi-config = { path = "../yazi-config", version = "0.2.5" }
yazi-dds = { path = "../yazi-dds", version = "0.2.5" } yazi-dds = { path = "../yazi-dds", version = "0.2.5" }
@ -19,27 +19,25 @@ yazi-scheduler = { path = "../yazi-scheduler", version = "0.2.5" }
yazi-shared = { path = "../yazi-shared", version = "0.2.5" } yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies # External dependencies
anyhow = "1.0.82" anyhow = "1.0.86"
base64 = "0.22.0"
bitflags = "2.5.0" bitflags = "2.5.0"
crossterm = "0.27.0" crossterm = "0.27.0"
dirs = "5.0.1"
futures = "0.3.30" futures = "0.3.30"
notify = { version = "6.1.1", default-features = false, features = [ "macos_fsevent" ] } notify = { version = "6.1.1", default-features = false, features = [ "macos_fsevent" ] }
parking_lot = "0.12.1" parking_lot = "0.12.3"
ratatui = "=0.26.1" ratatui = "0.27.0"
regex = "1.10.4" regex = "1.10.5"
scopeguard = "1.2.0" scopeguard = "1.2.0"
serde = "1.0.198" serde = "1.0.203"
tokio = { version = "1.37.0", features = [ "full" ] } shell-words = "1.1.0"
tokio = { version = "1.38.0", features = [ "full" ] }
tokio-stream = "0.1.15" tokio-stream = "0.1.15"
tokio-util = "0.7.10" tokio-util = "0.7.11"
unicode-width = "0.1.11" unicode-width = "0.1.13"
# Logging # Logging
tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] } tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] }
[target."cfg(unix)".dependencies] [target."cfg(unix)".dependencies]
libc = "0.2.153" libc = "0.2.155"
[target."cfg(windows)".dependencies]
clipboard-win = "5.3.1"

View file

@ -10,6 +10,9 @@ pub struct Opt {
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(c: Cmd) -> Self { Self { submit: c.bool("submit") } } fn from(c: Cmd) -> Self { Self { submit: c.bool("submit") } }
} }
impl From<bool> for Opt {
fn from(submit: bool) -> Self { Self { submit } }
}
impl Completion { impl Completion {
pub fn close(&mut self, opt: impl Into<Opt>) { pub fn close(&mut self, opt: impl Into<Opt>) {

View file

@ -1,4 +1,4 @@
use std::{mem, path::{MAIN_SEPARATOR, MAIN_SEPARATOR_STR}}; use std::{borrow::Cow, mem, path::{MAIN_SEPARATOR, MAIN_SEPARATOR_STR}};
use tokio::fs; use tokio::fs;
use yazi_shared::{emit, event::{Cmd, Data}, render, Layer}; use yazi_shared::{emit, event::{Cmd, Data}, render, Layer};
@ -33,7 +33,9 @@ impl Completion {
} }
self.ticket = opt.ticket; self.ticket = opt.ticket;
let (parent, child) = Self::split_path(&opt.word); let Some((parent, child)) = Self::split_path(&opt.word) else {
return self.close(false);
};
if self.caches.contains_key(&parent) { if self.caches.contains_key(&parent) {
return self.show( return self.show(
@ -72,12 +74,24 @@ impl Completion {
render!(mem::replace(&mut self.visible, false)); render!(mem::replace(&mut self.visible, false));
} }
#[inline] fn split_path(s: &str) -> Option<(String, String)> {
fn split_path(s: &str) -> (String, String) { if s == "~" {
match s.rsplit_once(SEPARATOR) { return None; // We don't autocomplete a `~`, but `~/`
Some((p, c)) => (format!("{p}{}", MAIN_SEPARATOR), c.to_owned()),
None => (".".to_owned(), s.to_owned()),
} }
let s = if let Some(rest) = s.strip_prefix("~") {
Cow::Owned(format!(
"{}{rest}",
dirs::home_dir().unwrap_or_default().to_string_lossy().trim_end_matches(SEPARATOR),
))
} else {
Cow::Borrowed(s)
};
Some(match s.rsplit_once(SEPARATOR) {
Some((p, c)) => (format!("{p}{}", MAIN_SEPARATOR), c.to_owned()),
None => (".".to_owned(), s.into_owned()),
})
} }
} }
@ -85,28 +99,32 @@ impl Completion {
mod tests { mod tests {
use super::*; use super::*;
fn compare(s: &str, parent: &str, child: &str) -> bool {
matches!(Completion::split_path(s), Some((p, c)) if p == parent && c == child)
}
#[cfg(unix)] #[cfg(unix)]
#[test] #[test]
fn test_split() { fn test_split() {
assert_eq!(Completion::split_path(""), (".".to_owned(), "".to_owned())); assert!(compare("", ".", ""));
assert_eq!(Completion::split_path(" "), (".".to_owned(), " ".to_owned())); assert!(compare(" ", ".", " "));
assert_eq!(Completion::split_path("/"), ("/".to_owned(), "".to_owned())); assert!(compare("/", "/", ""));
assert_eq!(Completion::split_path("//"), ("//".to_owned(), "".to_owned())); assert!(compare("//", "//", ""));
assert_eq!(Completion::split_path("/foo"), ("/".to_owned(), "foo".to_owned())); assert!(compare("/foo", "/", "foo"));
assert_eq!(Completion::split_path("/foo/"), ("/foo/".to_owned(), "".to_owned())); assert!(compare("/foo/", "/foo/", ""));
assert_eq!(Completion::split_path("/foo/bar"), ("/foo/".to_owned(), "bar".to_owned())); assert!(compare("/foo/bar", "/foo/", "bar"));
} }
#[cfg(windows)] #[cfg(windows)]
#[test] #[test]
fn test_split() { fn test_split() {
assert_eq!(Completion::split_path("foo"), (".".to_owned(), "foo".to_owned())); assert!(compare("foo", ".", "foo"));
assert_eq!(Completion::split_path("foo\\"), ("foo\\".to_owned(), "".to_owned())); assert!(compare("foo\\", "foo\\", ""));
assert_eq!(Completion::split_path("foo\\bar"), ("foo\\".to_owned(), "bar".to_owned())); assert!(compare("foo\\bar", "foo\\", "bar"));
assert_eq!(Completion::split_path("foo\\bar\\"), ("foo\\bar\\".to_owned(), "".to_owned())); assert!(compare("foo\\bar\\", "foo\\bar\\", ""));
assert_eq!(Completion::split_path("C:\\"), ("C:\\".to_owned(), "".to_owned())); assert!(compare("C:\\", "C:\\", ""));
assert_eq!(Completion::split_path("C:\\foo"), ("C:\\".to_owned(), "foo".to_owned())); assert!(compare("C:\\foo", "C:\\", "foo"));
assert_eq!(Completion::split_path("C:\\foo\\"), ("C:\\foo\\".to_owned(), "".to_owned())); assert!(compare("C:\\foo\\", "C:\\foo\\", ""));
assert_eq!(Completion::split_path("C:\\foo\\bar"), ("C:\\foo\\".to_owned(), "bar".to_owned())); assert!(compare("C:\\foo\\bar", "C:\\foo\\", "bar"));
} }
} }

View file

@ -2,7 +2,7 @@ use std::{collections::{HashMap, HashSet}, fs::Metadata, mem, ops::Deref, sync::
use tokio::{fs::{self, DirEntry}, select, sync::mpsc::{self, UnboundedReceiver}}; use tokio::{fs::{self, DirEntry}, select, sync::mpsc::{self, UnboundedReceiver}};
use yazi_config::{manager::SortBy, MANAGER}; use yazi_config::{manager::SortBy, MANAGER};
use yazi_shared::fs::{accessible, File, FilesOp, Url, FILES_TICKET}; use yazi_shared::fs::{maybe_exists, File, FilesOp, Url, FILES_TICKET};
use super::{FilesSorter, Filter}; use super::{FilesSorter, Filter};
@ -101,7 +101,7 @@ impl Files {
Ok(m) if mtime == m.modified().ok() => {} Ok(m) if mtime == m.modified().ok() => {}
Ok(m) => return Some(m), Ok(m) => return Some(m),
Err(e) => { Err(e) => {
if accessible(url).await { if maybe_exists(url).await {
FilesOp::IOErr(url.clone(), e.kind()).emit(); FilesOp::IOErr(url.clone(), e.kind()).emit();
} else if let Some(p) = url.parent_url() { } else if let Some(p) = url.parent_url() {
FilesOp::Deleting(p, vec![url.clone()]).emit(); FilesOp::Deleting(p, vec![url.clone()]).emit();

View file

@ -1,4 +1,4 @@
use std::{ffi::OsStr, ops::Range}; use std::{ffi::OsStr, fmt::Display, ops::Range};
use anyhow::Result; use anyhow::Result;
use regex::bytes::{Regex, RegexBuilder}; use regex::bytes::{Regex, RegexBuilder};
@ -9,10 +9,6 @@ pub struct Filter {
regex: Regex, regex: Regex,
} }
impl PartialEq for Filter {
fn eq(&self, other: &Self) -> bool { self.raw == other.raw }
}
impl Filter { impl Filter {
pub fn new(s: &str, case: FilterCase) -> Result<Self> { pub fn new(s: &str, case: FilterCase) -> Result<Self> {
let regex = match case { let regex = match case {
@ -35,6 +31,14 @@ impl Filter {
} }
} }
impl PartialEq for Filter {
fn eq(&self, other: &Self) -> bool { self.raw == other.raw }
}
impl Display for Filter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&self.raw) }
}
#[derive(Default, PartialEq, Eq)] #[derive(Default, PartialEq, Eq)]
pub enum FilterCase { pub enum FilterCase {
Smart, Smart,

View file

@ -1,7 +1,7 @@
use std::{cmp::Ordering, collections::HashMap, mem}; use std::{cmp::Ordering, collections::HashMap, mem};
use yazi_config::manager::SortBy; use yazi_config::manager::SortBy;
use yazi_shared::{fs::{File, Url}, natsort}; use yazi_shared::{fs::{File, Url}, natsort, Transliterator};
#[derive(Clone, Copy, Default, PartialEq)] #[derive(Clone, Copy, Default, PartialEq)]
pub struct FilesSorter { pub struct FilesSorter {
@ -9,6 +9,7 @@ pub struct FilesSorter {
pub sensitive: bool, pub sensitive: bool,
pub reverse: bool, pub reverse: bool,
pub dir_first: bool, pub dir_first: bool,
pub translit: bool,
} }
impl FilesSorter { impl FilesSorter {
@ -76,7 +77,16 @@ impl FilesSorter {
return promote; return promote;
} }
let ordering = natsort(entities[a], entities[b], !self.sensitive); let ordering = if !self.translit {
natsort(entities[a], entities[b], !self.sensitive)
} else {
natsort(
entities[a].transliterate().as_bytes(),
entities[b].transliterate().as_bytes(),
!self.sensitive,
)
};
if self.reverse { ordering.reverse() } else { ordering } if self.reverse { ordering.reverse() } else { ordering }
}); });

View file

@ -1,7 +1,8 @@
use crossterm::event::KeyCode; use crossterm::event::KeyCode;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use yazi_adapter::Dimension;
use yazi_config::{keymap::{Control, Key}, KEYMAP}; use yazi_config::{keymap::{Control, Key}, KEYMAP};
use yazi_shared::{render, render_and, term::Term, Layer}; use yazi_shared::{render, render_and, Layer};
use super::HELP_MARGIN; use super::HELP_MARGIN;
use crate::input::Input; use crate::input::Input;
@ -22,7 +23,7 @@ pub struct Help {
impl Help { impl Help {
#[inline] #[inline]
pub fn limit() -> usize { Term::size().rows.saturating_sub(HELP_MARGIN) as usize } pub fn limit() -> usize { Dimension::available().rows.saturating_sub(HELP_MARGIN) as usize }
pub fn toggle(&mut self, layer: Layer) { pub fn toggle(&mut self, layer: Layer) {
self.visible = !self.visible; self.visible = !self.visible;
@ -43,15 +44,15 @@ impl Help {
}; };
match key { match key {
Key { code: KeyCode::Esc, shift: false, ctrl: false, alt: false } => { Key { code: KeyCode::Esc, shift: false, ctrl: false, alt: false, super_: false } => {
self.in_filter = None; self.in_filter = None;
render!(); render!();
} }
Key { code: KeyCode::Enter, shift: false, ctrl: false, alt: false } => { Key { code: KeyCode::Enter, shift: false, ctrl: false, alt: false, super_: false } => {
self.in_filter = None; self.in_filter = None;
return render_and!(true); // Don't do the `filter_apply` below, since we already have the filtered results. return render_and!(true); // Don't do the `filter_apply` below, since we already have the filtered results.
} }
Key { code: KeyCode::Backspace, shift: false, ctrl: false, alt: false } => { Key { code: KeyCode::Backspace, shift: false, ctrl: false, alt: false, super_: false } => {
input.backspace(false); input.backspace(false);
} }
_ => { _ => {
@ -106,7 +107,7 @@ impl Help {
return None; return None;
} }
if let Some(kw) = self.keyword() { if let Some(kw) = self.keyword() {
return Some((kw.width() as u16, Term::size().rows)); return Some((kw.width() as u16, Dimension::available().rows));
} }
None None
} }

View file

@ -70,6 +70,7 @@ impl Input {
let snap = self.snap_mut(); let snap = self.snap_mut();
match opt.kind.as_str() { match opt.kind.as_str() {
"all" => self.kill_range(..),
"bol" => { "bol" => {
let end = snap.idx(snap.cursor).unwrap_or(snap.len()); let end = snap.idx(snap.cursor).unwrap_or(snap.len());
self.kill_range(..end) self.kill_range(..end)

View file

@ -1,6 +1,7 @@
use yazi_plugin::CLIPBOARD;
use yazi_shared::{event::Cmd, render}; use yazi_shared::{event::Cmd, render};
use crate::{input::{op::InputOp, Input}, CLIPBOARD}; use crate::input::{op::InputOp, Input};
pub struct Opt { pub struct Opt {
before: bool, before: bool,

View file

@ -3,10 +3,10 @@ use std::ops::Range;
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use yazi_config::{popup::Position, INPUT}; use yazi_config::{popup::Position, INPUT};
use yazi_plugin::CLIPBOARD;
use yazi_shared::{render, InputError}; use yazi_shared::{render, InputError};
use super::{mode::InputMode, op::InputOp, InputSnap, InputSnaps}; use super::{mode::InputMode, op::InputOp, InputSnap, InputSnaps};
use crate::CLIPBOARD;
#[derive(Default)] #[derive(Default)]
pub struct Input { pub struct Input {
@ -87,16 +87,17 @@ impl Input {
} }
pub(super) fn flush_value(&mut self) { pub(super) fn flush_value(&mut self) {
let Some(tx) = &self.callback else { return };
self.ticket = self.ticket.wrapping_add(1); self.ticket = self.ticket.wrapping_add(1);
if self.realtime { if self.realtime {
let value = self.snap().value.clone(); let value = self.snap().value.clone();
self.callback.as_ref().unwrap().send(Err(InputError::Typed(value))).ok(); tx.send(Err(InputError::Typed(value))).ok();
} }
if self.completion { if self.completion {
let before = self.partition()[0].to_owned(); let before = self.partition()[0].to_owned();
self.callback.as_ref().unwrap().send(Err(InputError::Completed(before, self.ticket))).ok(); tx.send(Err(InputError::Completed(before, self.ticket))).ok();
} }
} }
} }

View file

@ -19,6 +19,10 @@ impl InputSnaps {
} }
pub(super) fn tag(&mut self, limit: usize) -> bool { pub(super) fn tag(&mut self, limit: usize) -> bool {
if self.versions.len() <= self.idx {
return false;
}
// Sync *current* cursor position to the *last* version: // Sync *current* cursor position to the *last* version:
// Save offset/cursor/ect. of the *current* as the last version, // Save offset/cursor/ect. of the *current* as the last version,
// while keeping the *last* value unchanged. // while keeping the *last* value unchanged.
@ -49,7 +53,7 @@ impl InputSnaps {
} }
pub(super) fn redo(&mut self) -> bool { pub(super) fn redo(&mut self) -> bool {
if self.idx + 1 == self.versions.len() { if self.idx + 1 >= self.versions.len() {
return false; return false;
} }

View file

@ -6,7 +6,6 @@
clippy::unit_arg clippy::unit_arg
)] )]
mod clipboard;
pub mod completion; pub mod completion;
pub mod folder; pub mod folder;
pub mod help; pub mod help;
@ -19,12 +18,9 @@ pub mod tab;
pub mod tasks; pub mod tasks;
pub mod which; pub mod which;
pub use clipboard::*;
pub use step::*; pub use step::*;
pub fn init() { pub fn init() {
CLIPBOARD.with(Default::default);
manager::WATCHED.with(Default::default); manager::WATCHED.with(Default::default);
manager::LINKED.with(Default::default); manager::LINKED.with(Default::default);
} }

View file

@ -4,8 +4,9 @@ use anyhow::{anyhow, Result};
use scopeguard::defer; use scopeguard::defer;
use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}}; use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}};
use yazi_config::{OPEN, PREVIEW}; use yazi_config::{OPEN, PREVIEW};
use yazi_dds::Pubsub;
use yazi_proxy::{AppProxy, TasksProxy, HIDER, WATCHER}; use yazi_proxy::{AppProxy, TasksProxy, HIDER, WATCHER};
use yazi_shared::{fs::{accessible, max_common_root, File, FilesOp, Url}, term::Term}; use yazi_shared::{fs::{max_common_root, maybe_exists, paths_to_same_file, File, FilesOp, Url}, terminal_clear};
use crate::manager::Manager; use crate::manager::Manager;
@ -51,7 +52,7 @@ impl Manager {
old: Vec<PathBuf>, old: Vec<PathBuf>,
new: Vec<PathBuf>, new: Vec<PathBuf>,
) -> Result<()> { ) -> Result<()> {
Term::clear(&mut stderr())?; terminal_clear(&mut stderr())?;
if old.len() != new.len() { if old.len() != new.len() {
eprintln!("Number of old and new differ, press ENTER to exit"); eprintln!("Number of old and new differ, press ENTER to exit");
stdin().read_exact(&mut [0]).await?; stdin().read_exact(&mut [0]).await?;
@ -83,7 +84,7 @@ impl Manager {
for (o, n) in todo { for (o, n) in todo {
let (old, new) = (root.join(&o), root.join(&n)); let (old, new) = (root.join(&o), root.join(&n));
if accessible(&new).await { if maybe_exists(&new).await && !paths_to_same_file(&old, &new).await {
failed.push((o, n, anyhow!("Destination already exists"))); failed.push((o, n, anyhow!("Destination already exists")));
} else if let Err(e) = fs::rename(&old, &new).await { } else if let Err(e) = fs::rename(&old, &new).await {
failed.push((o, n, e.into())); failed.push((o, n, e.into()));
@ -95,6 +96,7 @@ impl Manager {
} }
if !succeeded.is_empty() { if !succeeded.is_empty() {
Pubsub::pub_from_bulk(succeeded.iter().map(|(u, f)| (u, &f.url)).collect());
FilesOp::Upserting(cwd, succeeded).emit(); FilesOp::Upserting(cwd, succeeded).emit();
} }
drop(permit); drop(permit);
@ -106,7 +108,7 @@ impl Manager {
} }
async fn output_failed(failed: Vec<(PathBuf, PathBuf, anyhow::Error)>) -> Result<()> { async fn output_failed(failed: Vec<(PathBuf, PathBuf, anyhow::Error)>) -> Result<()> {
Term::clear(&mut stderr())?; terminal_clear(&mut stderr())?;
{ {
let mut stderr = BufWriter::new(stderr().lock()); let mut stderr = BufWriter::new(stderr().lock());

View file

@ -1,9 +1,10 @@
use std::path::PathBuf; use std::collections::HashMap;
use anyhow::Result;
use tokio::fs; use tokio::fs;
use yazi_config::popup::InputCfg; use yazi_config::popup::InputCfg;
use yazi_proxy::{InputProxy, ManagerProxy}; use yazi_proxy::{InputProxy, TabProxy, WATCHER};
use yazi_shared::{event::Cmd, fs::{accessible, File, FilesOp, Url}}; use yazi_shared::{event::Cmd, fs::{maybe_exists, ok_or_not_found, symlink_realpath, File, FilesOp, Url}};
use crate::manager::Manager; use crate::manager::Manager;
@ -24,29 +25,42 @@ impl Manager {
let Some(Ok(name)) = result.recv().await else { let Some(Ok(name)) = result.recv().await else {
return Ok(()); return Ok(());
}; };
if name.is_empty() {
return Ok(());
}
let path = cwd.join(&name); let new = cwd.join(&name);
if !opt.force && accessible(&path).await { if !opt.force && maybe_exists(&new).await {
match InputProxy::show(InputCfg::overwrite()).recv().await { match InputProxy::show(InputCfg::overwrite()).recv().await {
Some(Ok(c)) if c == "y" || c == "Y" => (), Some(Ok(c)) if c == "y" || c == "Y" => (),
_ => return Ok(()), _ => return Ok(()),
} }
} }
if name.ends_with('/') || name.ends_with('\\') { Self::create_do(new, name.ends_with('/') || name.ends_with('\\')).await
fs::create_dir_all(&path).await?;
} else {
fs::create_dir_all(&path.parent().unwrap()).await.ok();
fs::File::create(&path).await?;
}
let child =
Url::from(path.components().take(cwd.components().count() + 1).collect::<PathBuf>());
if let Ok(f) = File::from(child.clone()).await {
FilesOp::Creating(cwd, vec![f]).emit();
ManagerProxy::hover(Some(child));
}
Ok::<(), anyhow::Error>(())
}); });
} }
async fn create_do(new: Url, dir: bool) -> Result<()> {
let Some(parent) = new.parent_url() else { return Ok(()) };
let _permit = WATCHER.acquire().await.unwrap();
if dir {
fs::create_dir_all(&new).await?;
} else if let Ok(real) = symlink_realpath(&new).await {
ok_or_not_found(fs::remove_file(&new).await)?;
FilesOp::Deleting(parent.clone(), vec![Url::from(real)]).emit();
fs::File::create(&new).await?;
} else {
fs::create_dir_all(&parent).await.ok();
ok_or_not_found(fs::remove_file(&new).await)?;
fs::File::create(&new).await?;
}
if let Ok(f) = File::from(new.clone()).await {
FilesOp::Upserting(parent, HashMap::from_iter([(f.url(), f)])).emit();
TabProxy::reveal(&new)
}
Ok(())
}
} }

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