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
- Windows
- Windows WSL
- FreeBSD X11
- FreeBSD Wayland
validations:
required: true
- type: input
@ -22,31 +24,32 @@ body:
placeholder: "ex: kitty v0.32.2"
validations:
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
id: debug
attributes:
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: |
<details>
<!-- Paste the output between the backticks below: -->
```sh
##### ↓↓↓ Paste the output here: ↓↓↓ #####
```
</details>
validations:
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
id: description
attributes:

View file

@ -39,3 +39,13 @@ jobs:
- name: Rustfmt
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
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
run: ./scripts/build.sh ${{ matrix.target }}

View file

@ -28,4 +28,4 @@ jobs:
run: cargo build --verbose
- 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.
- 🖼️ **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.
- 🔌 **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.
- 📦 **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
- 💫 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.)
- 🔄 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!
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
| Platform | Protocol | Support |
| ----------------- | ----------------------------------------------------------------------------------------------------- | --------------------- |
| 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 |
| iTerm2 | [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 |
| 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 |
| 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 |
| Tabby | [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 |
| Fallback | [Chafa](https://hpjansson.org/chafa/) | ☑️ Überzug++ required |
| Platform | Protocol | Support |
| ----------------- | ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| 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-adapter/src/kitty_old.rs) | ✅ 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 |
| 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 |
| 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 |
| VSCode | [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 |
| X11 / Wayland | Window system protocol | ☑️ [Überzug++](https://github.com/jstkdng/ueberzugpp) 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.

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

View file

@ -34,7 +34,7 @@
# Resize logo
for RES in 16 24 32 48 64 128 256; do
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
mkdir -p $out/share/applications

View file

@ -3,7 +3,6 @@ set -euo pipefail
export ARTIFACT_NAME="yazi-$1"
export YAZI_GEN_COMPLETIONS=1
export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=/usr/bin/aarch64-linux-gnu-gcc
# Setup Rust toolchain
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-config
cargo publish -p yazi-proxy
cargo publish -p yazi-adaptor
cargo publish -p yazi-adapter
cargo publish -p yazi-boot
cargo publish -p yazi-dds
cargo publish -p yazi-scheduler
cargo publish -p yazi-plugin
cargo publish -p yazi-core
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]
name = "yazi-adaptor"
name = "yazi-adapter"
version = "0.2.5"
edition = "2021"
license = "MIT"
authors = [ "sxyazi <sxyazi@gmail.com>" ]
description = "Yazi image adaptor"
description = "Yazi image adapter"
homepage = "https://yazi-rs.github.io"
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" }
# External dependencies
anyhow = "1.0.82"
ansi-to-tui = "=3.1.0"
anyhow = "1.0.86"
arc-swap = "1.7.1"
base64 = "0.22.0"
base64 = "0.22.1"
color_quant = "1.1.0"
crossterm = "0.27.0"
futures = "0.3.30"
image = "0.24.9"
imagesize = "0.12.0"
image = "=0.24.9"
imagesize = "0.13.0"
kamadak-exif = "0.5.5"
ratatui = "=0.26.1"
tokio = { version = "1.37.0", features = [ "full" ] }
ratatui = "0.27.0"
scopeguard = "1.2.0"
tokio = { version = "1.38.0", features = [ "full" ] }
# Logging
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 ratatui::layout::Rect;
use tracing::warn;
use yazi_shared::{env_exists, term::Term};
use yazi_shared::env_exists;
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)]
pub enum Adaptor {
pub enum Adapter {
Kitty,
KittyOld,
Iterm2,
@ -21,7 +21,7 @@ pub enum Adaptor {
Chafa,
}
impl Display for Adaptor {
impl Display for Adapter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Kitty => write!(f, "kitty"),
@ -35,49 +35,44 @@ impl Display for Adaptor {
}
}
impl Adaptor {
pub async fn image_show(self, path: &Path, rect: Rect) -> Result<(u32, u32)> {
impl Adapter {
pub async fn image_show(self, path: &Path, max: Rect) -> Result<Rect> {
if max.is_empty() {
return Ok(Rect::default());
}
match self {
Self::Kitty => Kitty::image_show(path, rect).await,
Self::KittyOld => KittyOld::image_show(path, rect).await,
Self::Iterm2 => Iterm2::image_show(path, rect).await,
Self::Sixel => Sixel::image_show(path, rect).await,
_ => Ueberzug::image_show(path, rect).await,
Self::Kitty => Kitty::image_show(path, max).await,
Self::KittyOld => KittyOld::image_show(path, max).await,
Self::Iterm2 => Iterm2::image_show(path, max).await,
Self::Sixel => Sixel::image_show(path, max).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<()> {
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 {
Self::Kitty => Kitty::image_erase(rect),
Self::Iterm2 => Iterm2::image_erase(rect),
Self::KittyOld => KittyOld::image_erase(),
Self::Sixel => Sixel::image_erase(rect),
_ => Ueberzug::image_erase(rect),
Self::Kitty => Kitty::image_erase(area),
Self::Iterm2 => Iterm2::image_erase(area),
Self::KittyOld => KittyOld::image_erase(area),
Self::Sixel => Sixel::image_erase(area),
Self::X11 | Self::Wayland => Ueberzug::image_erase(area),
Self::Chafa => Chafa::image_erase(area),
}
}
#[inline]
pub fn shown_load(self) -> Option<Rect> { SHOWN.load_full().map(|r| *r) }
pub(super) fn start(self) { Ueberzug::start(self); }
#[inline]
pub(super) fn shown_store(rect: Rect, size: (u32, u32)) {
SHOWN.store(Some(Arc::new(
Term::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),
)));
}
pub(super) fn shown_store(area: Rect) { SHOWN.store(Some(Arc::new(area))); }
pub(super) fn start(self) { Ueberzug::start(self); }
#[inline]
pub(super) fn needs_ueberzug(self) -> bool {
@ -85,7 +80,7 @@ impl Adaptor {
}
}
impl Adaptor {
impl Adapter {
pub fn matches() -> Self {
let mut protocols = Emulator::detect().adapters();
@ -93,8 +88,7 @@ impl Adaptor {
protocols.retain(|p| *p == Self::Iterm2);
if env_exists("ZELLIJ_SESSION_NAME") {
protocols.retain(|p| *p == Self::Sixel);
}
if *TMUX && protocols.len() > 1 {
} else if *TMUX {
protocols.retain(|p| *p != Self::KittyOld);
}
if let Some(p) = protocols.first() {
@ -104,7 +98,7 @@ impl Adaptor {
match env::var("XDG_SESSION_TYPE").unwrap_or_default().as_str() {
"x11" => return Self::X11,
"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") {
return Self::Wayland;
@ -116,7 +110,7 @@ impl Adaptor {
return Self::KittyOld;
}
warn!("[Adaptor] Falling back to chafa");
warn!("[Adapter] Falling back to 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 ratatui::layout::Rect;
use yazi_config::{PREVIEW, TASKS};
use yazi_shared::term::Term;
use crate::Dimension;
pub struct Image;
@ -57,7 +58,7 @@ impl Image {
})
.await??;
let (mut w, mut h) = Self::max_size(rect);
let (mut w, mut h) = Self::max_pixel(rect);
if (5..=8).contains(&orientation) {
(w, h) = (h, w);
}
@ -76,8 +77,8 @@ impl Image {
.await?
}
pub(super) fn max_size(rect: Rect) -> (u32, u32) {
Term::ratio()
pub(super) fn max_pixel(rect: Rect) -> (u32, u32) {
Dimension::ratio()
.map(|(r1, r2)| {
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))
@ -85,6 +86,17 @@ impl Image {
.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]
fn filter() -> FilterType {
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 base64::{engine::general_purpose, Engine};
use crossterm::{cursor::MoveTo, queue};
use image::DynamicImage;
use ratatui::layout::Rect;
use yazi_shared::term::Term;
use super::image::Image;
use crate::{adaptor::Adaptor, CLOSE, ESCAPE, START};
use crate::{adapter::Adapter, Emulator, CLOSE, ESCAPE, START};
static DIACRITICS: [char; 297] = [
'\u{0305}',
@ -313,31 +313,31 @@ static DIACRITICS: [char; 297] = [
pub(super) struct Kitty;
impl Kitty {
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());
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 b1 = Self::encode(img).await?;
let b2 = Self::place(&rect)?;
let b2 = Self::place(&area)?;
Adaptor::Kitty.image_hide()?;
Adaptor::shown_store(rect, size);
Term::move_lock((rect.x, rect.y), |stderr| {
Adapter::Kitty.image_hide()?;
Adapter::shown_store(area);
Emulator::move_lock((area.x, area.y), |stderr| {
stderr.write_all(&b1)?;
stderr.write_all(&b2)?;
Ok(size)
Ok(area)
})
}
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)?;
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}")?;
}
write!(stderr, "{}_Gq=1,a=d,d=A{}\\{}", START, ESCAPE, CLOSE)?;
write!(stderr, "{}_Gq=2,a=d,d=A{}\\{}", START, ESCAPE, CLOSE)?;
Ok(())
})
}
@ -351,7 +351,7 @@ impl Kitty {
if let Some(first) = it.next() {
write!(
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,
format,
size.0,
@ -388,11 +388,11 @@ impl Kitty {
.await?
}
fn place(rect: &Rect) -> Result<Vec<u8>> {
let mut buf = Vec::with_capacity(rect.width as usize * rect.height as usize * 3 + 50);
for y in 0..rect.height {
write!(buf, "\x1b[{};{}H\x1b[38;5;1m", rect.y + y + 1, rect.x + 1)?;
for x in 0..rect.width {
fn place(area: &Rect) -> Result<Vec<u8>> {
let mut buf = Vec::with_capacity(area.width as usize * area.height as usize * 3 + 50);
for y in 0..area.height {
write!(buf, "\x1b[{};{}H\x1b[38;5;1m", area.y + y + 1, area.x + 1)?;
for x in 0..area.width {
write!(buf, "\u{10EEEE}")?;
write!(buf, "{}", *DIACRITICS.get(y 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 image::DynamicImage;
use ratatui::layout::Rect;
use yazi_shared::term::Term;
use super::image::Image;
use crate::{adaptor::Adaptor, CLOSE, ESCAPE, START};
use crate::{adapter::Adapter, Emulator, CLOSE, ESCAPE, START};
pub(super) struct KittyOld;
impl KittyOld {
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());
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?;
Adaptor::KittyOld.image_hide()?;
Adaptor::shown_store(rect, size);
Term::move_lock((rect.x, rect.y), |stderr| {
Adapter::KittyOld.image_hide()?;
Adapter::shown_store(area);
Emulator::move_lock((area.x, area.y), |stderr| {
stderr.write_all(&b)?;
Ok(size)
Ok(area)
})
}
#[inline]
pub(super) fn image_erase() -> Result<()> {
pub(super) fn image_erase(_: Rect) -> Result<()> {
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()?;
Ok(())
}
@ -43,7 +42,7 @@ impl KittyOld {
if let Some(first) = it.next() {
write!(
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,
format,
size.0,

View file

@ -1,6 +1,8 @@
#![allow(clippy::unit_arg)]
mod adaptor;
mod adapter;
mod chafa;
mod dimension;
mod emulator;
mod image;
mod iterm2;
@ -9,17 +11,20 @@ mod kitty_old;
mod sixel;
mod ueberzug;
pub use adaptor::*;
pub use adapter::*;
use chafa::*;
pub use dimension::*;
pub use emulator::*;
use iterm2::*;
use kitty::*;
use kitty_old::*;
use sixel::*;
use ueberzug::*;
use yazi_shared::{env_exists, RoCell};
pub use crate::image::*;
pub static ADAPTOR: RoCell<Adaptor> = RoCell::new();
pub static ADAPTOR: RoCell<Adapter> = RoCell::new();
// Tmux support
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();
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" });
CLOSE.init(if *TMUX { "\x1b\\" } else { "" });
ESCAPE.init(if *TMUX { "\x1b\x1b" } else { "\x1b" });
SHOWN.with(Default::default);
ADAPTOR.init(Adaptor::matches());
ADAPTOR.start();
if *TMUX {
_ = std::process::Command::new("tmux")
.args(["set", "-p", "allow-passthrough", "on"])
.stdin(std::process::Stdio::null())
.stdout(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 color_quant::NeuQuant;
use crossterm::{cursor::MoveTo, queue};
use image::DynamicImage;
use ratatui::layout::Rect;
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;
impl Sixel {
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());
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?;
Adaptor::Sixel.image_hide()?;
Adaptor::shown_store(rect, size);
Term::move_lock((rect.x, rect.y), |stderr| {
Adapter::Sixel.image_hide()?;
Adapter::shown_store(area);
Emulator::move_lock((area.x, area.y), |stderr| {
stderr.write_all(&b)?;
Ok(size)
Ok(area)
})
}
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)?;
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

@ -8,7 +8,7 @@ use tracing::{debug, warn};
use yazi_config::PREVIEW;
use yazi_shared::RoCell;
use crate::{Adaptor, Image};
use crate::{Adapter, Dimension};
#[allow(clippy::type_complexity)]
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;
impl Ueberzug {
pub(super) fn start(adaptor: Adaptor) {
if !adaptor.needs_ueberzug() {
pub(super) fn start(adapter: Adapter) {
if !adapter.needs_ueberzug() {
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();
tokio::spawn(async move {
@ -31,7 +31,7 @@ impl Ueberzug {
child = None;
}
if child.is_none() {
child = Self::create_demon(adaptor).ok();
child = Self::create_demon(adapter).ok();
}
if let Some(c) = &mut child {
Self::send_command(c, cmd).await.ok();
@ -41,25 +41,27 @@ impl Ueberzug {
DEMON.init(Some(tx))
}
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<(u32, u32)> {
if let Some(tx) = &*DEMON {
tx.send(Some((path.to_path_buf(), rect)))?;
Adaptor::shown_store(rect, (0, 0));
} else {
pub(super) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
let Some(tx) = &*DEMON else {
bail!("uninitialized ueberzugpp");
}
};
let path = path.to_owned();
let p = path.to_owned();
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);
if w <= max_w as usize && h <= max_h as usize {
return Ok((w as u32, h as u32));
}
let area = Dimension::ratio()
.map(|(r1, r2)| Rect {
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);
Ok(((w as f64 * ratio).round() as u32, (h as f64 * ratio).round() as u32))
tx.send(Some((path.to_owned(), area)))?;
Adapter::shown_store(area);
Ok(area)
}
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
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 { "" })
.kill_on_drop(true)
.stdin(Stdio::piped())
.stdout(Stdio::null())
.stderr(Stdio::null())
.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"
[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-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies
clap = { version = "4.5.4", features = [ "derive" ] }
serde = { version = "1.0.198", features = [ "derive" ] }
clap = { version = "4.5.7", features = [ "derive" ] }
serde = { version = "1.0.203", features = [ "derive" ] }
[build-dependencies]
clap = { version = "4.5.4", features = [ "derive" ] }
clap_complete = "4.5.2"
clap_complete_nushell = "4.5.1"
clap_complete_fig = "4.5.0"
clap = { version = "4.5.7", features = [ "derive" ] }
clap_complete = "4.5.6"
clap_complete_nushell = "4.5.2"
clap_complete_fig = "4.5.1"
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 regex::Regex;
use serde::Serialize;
use yazi_config::PREVIEW;
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()))
}
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 {
format!(
"{} ({} {})",
@ -47,26 +64,29 @@ impl Boot {
}
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();
writeln!(s, "\nYazi")?;
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, " Emulator.via_env: {:?}", yazi_adaptor::Emulator::via_env())?;
writeln!(s, " Emulator.via_csi: {:?}", yazi_adaptor::Emulator::via_csi())?;
writeln!(s, " Emulator.detect: {:?}", yazi_adaptor::Emulator::detect())?;
writeln!(s, " Emulator.via_env: {:?}", yazi_adapter::Emulator::via_env())?;
writeln!(s, " Emulator.via_csi: {:?}", yazi_adapter::Emulator::via_csi())?;
writeln!(s, " Emulator.detect : {:?}", yazi_adapter::Emulator::detect())?;
writeln!(s, "\nAdaptor")?;
writeln!(s, " Adaptor.matches: {:?}", yazi_adaptor::Adaptor::matches())?;
writeln!(s, "\nAdapter")?;
writeln!(s, " Adapter.matches: {:?}", yazi_adapter::Adapter::matches())?;
writeln!(s, "\nDesktop")?;
writeln!(s, " XDG_SESSION_TYPE: {:?}", env::var_os("XDG_SESSION_TYPE"))?;
writeln!(s, " WAYLAND_DISPLAY: {:?}", env::var_os("WAYLAND_DISPLAY"))?;
writeln!(s, " DISPLAY: {:?}", env::var_os("DISPLAY"))?;
writeln!(s, " WAYLAND_DISPLAY : {:?}", env::var_os("WAYLAND_DISPLAY"))?;
writeln!(s, " DISPLAY : {:?}", env::var_os("DISPLAY"))?;
writeln!(s, "\nSSH")?;
writeln!(s, " shared.in_ssh_connection: {:?}", yazi_shared::in_ssh_connection())?;
@ -79,18 +99,11 @@ impl Boot {
)?;
writeln!(s, "\nVariables")?;
writeln!(s, " SHELL: {:?}", env::var_os("SHELL"))?;
writeln!(s, " EDITOR: {:?}", env::var_os("EDITOR"))?;
writeln!(s, " SHELL : {:?}", env::var_os("SHELL"))?;
writeln!(s, " EDITOR : {:?}", env::var_os("EDITOR"))?;
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_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, " YAZI_FILE_ONE : {:?}", env::var_os("YAZI_FILE_ONE"))?;
writeln!(s, " YAZI_CONFIG_HOME : {:?}", env::var_os("YAZI_CONFIG_HOME"))?;
writeln!(s, "\nText Opener")?;
writeln!(
@ -98,13 +111,28 @@ impl Boot {
" default: {:?}",
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, " TMUX: {:?}", *yazi_adaptor::TMUX)?;
writeln!(s, " TMUX : {:?}", *yazi_adapter::TMUX)?;
writeln!(s, " Version: {}", Self::process_output("tmux", "-V"))?;
writeln!(s, "\nUeberzug++")?;
writeln!(s, " Version: {:?}", Command::new("ueberzugpp").arg("--version").output())?;
writeln!(s, "\nDependencies")?;
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!(
@ -145,7 +173,7 @@ impl Default for Boot {
.map(|s| s.split(',').map(|s| s.to_owned()).collect())
.unwrap_or_default();
let boot = Self {
Self {
cwd,
file,
@ -156,11 +184,7 @@ impl Default for Boot {
plugin_dir: config_dir.join("plugins"),
config_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"
[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
anyhow = "1.0.82"
clap = { version = "4.5.4", features = [ "derive" ] }
serde_json = "1.0.116"
tokio = { version = "1.37.0", features = [ "full" ] }
anyhow = "1.0.86"
clap = { version = "4.5.7", features = [ "derive" ] }
crossterm = "0.27.0"
md-5 = "0.10.6"
serde_json = "1.0.117"
tokio = { version = "1.38.0", features = [ "full" ] }
toml_edit = "0.22.14"
[build-dependencies]
anyhow = "1.0.82"
clap = { version = "4.5.4", features = [ "derive" ] }
clap_complete = "4.5.2"
clap_complete_fig = "4.5.0"
clap_complete_nushell = "4.5.1"
serde_json = "1.0.116"
anyhow = "1.0.86"
clap = { version = "4.5.7", features = [ "derive" ] }
clap_complete = "4.5.6"
clap_complete_fig = "4.5.1"
clap_complete_nushell = "4.5.2"
serde_json = "1.0.117"
vergen = { version = "8.3.1", features = [ "build", "git", "gitcl" ] }
[[bin]]
name = "ya"
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_complete::{generate_to, Shell};
use vergen::EmitBuilder;
fn main() -> Result<(), Box<dyn Error>> {
EmitBuilder::builder().build_date().git_sha(true).emit()?;
if env::var_os("YAZI_GEN_COMPLETIONS").is_none() {
return Ok(());
}

View file

@ -4,29 +4,34 @@ use anyhow::{bail, Result};
use clap::{command, Parser, Subcommand};
#[derive(Parser)]
#[command(name = "ya", version, about, long_about = None)]
#[command(propagate_version = true)]
#[command(name = "Ya", about, long_about = None)]
pub(super) struct Args {
#[command(subcommand)]
pub(super) command: Command,
/// Print version
#[arg(short = 'V', long)]
pub(super) version: bool,
}
#[derive(Subcommand)]
pub(super) enum Command {
/// Publish a message to remote instance(s).
Pub(CommandPub),
/// Publish a static message to all remote instances.
PubStatic(CommandPubStatic),
/// Manage packages.
Pack(CommandPack),
/// Subscribe to messages from all remote instances.
Sub(CommandSub),
}
#[derive(clap::Args)]
pub(super) struct CommandPub {
/// The receiver ID.
#[arg(index = 1)]
pub(super) receiver: u64,
/// The kind of message.
#[arg(index = 2)]
#[arg(index = 1)]
pub(super) kind: String,
/// The receiver ID.
#[arg(index = 2)]
pub(super) receiver: Option<u64>,
/// Send the message with a string body.
#[arg(long)]
pub(super) str: Option<String>,
@ -37,34 +42,16 @@ pub(super) struct CommandPub {
impl CommandPub {
#[allow(dead_code)]
pub(super) fn body(&self) -> Result<Cow<str>> {
if let Some(json) = &self.json {
Ok(json.into())
} else if let Some(str) = &self.str {
Ok(serde_json::to_string(str)?.into())
pub(super) fn receiver(&self) -> Result<u64> {
if let Some(receiver) = self.receiver {
Ok(receiver)
} else if let Some(s) = std::env::var("YAZI_PID").ok().filter(|s| !s.is_empty()) {
Ok(s.parse()?)
} 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)]
pub(super) fn body(&self) -> Result<Cow<str>> {
if let Some(json) = &self.json {
@ -72,7 +59,31 @@ impl CommandPubStatic {
} else if let Some(str) = &self.str {
Ok(serde_json::to_string(str)?.into())
} 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 package;
use args::*;
use clap::Parser;
#[tokio::main]
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) => {
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}");
std::process::exit(1);
}
}
Command::PubStatic(cmd) => {
yazi_dds::init();
if let Err(e) = yazi_dds::Client::shot(&cmd.kind, 0, Some(cmd.severity), &cmd.body()?).await {
eprintln!("Cannot send message: {e}");
std::process::exit(1);
Command::Pack(cmd) => {
package::init();
if cmd.install {
package::Package::install_from_config("plugin", false).await?;
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(())

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" }
# External dependencies
anyhow = "1.0.82"
anyhow = "1.0.86"
arc-swap = "1.7.1"
bitflags = "2.5.0"
crossterm = "0.27.0"
globset = "0.4.14"
indexmap = "2.2.6"
ratatui = "=0.26.1"
serde = { version = "1.0.198", features = [ "derive" ] }
shell-words = "1.1.0"
toml = { version = "0.8.12", features = [ "preserve_order" ] }
ratatui = "0.27.0"
serde = { version = "1.0.203", features = [ "derive" ] }
toml = { version = "0.8.14", features = [ "preserve_order" ] }
validator = { version = "0.18.1", features = [ "derive" ] }

View file

@ -5,305 +5,305 @@
[manager]
keymap = [
{ 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 = [ "q" ], run = "quit", desc = "Exit the process" },
{ 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-z>" ], run = "suspend", desc = "Suspend the process" },
{ 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 = "q", run = "quit", desc = "Exit the process" },
{ on = "Q", run = "quit --no-cwd-file", desc = "Exit the process without writing cwd-file" },
{ 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" },
# Navigation
{ on = [ "k" ], run = "arrow -1", desc = "Move cursor up" },
{ on = [ "j" ], run = "arrow 1", desc = "Move cursor down" },
{ on = "k", run = "arrow -1", desc = "Move cursor up" },
{ on = "j", run = "arrow 1", desc = "Move cursor down" },
{ on = [ "K" ], run = "arrow -5", desc = "Move cursor up 5 lines" },
{ on = [ "J" ], run = "arrow 5", desc = "Move cursor down 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 = [ "<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-Up>", run = "arrow -5", desc = "Move cursor up 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-d>" ], run = "arrow 50%", desc = "Move cursor down half 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-u>", run = "arrow -50%", desc = "Move cursor up 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-f>", run = "arrow 100%", desc = "Move cursor down one 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 = [ "<PageUp>" ], run = "arrow -100%", desc = "Move cursor up one page" },
{ on = [ "<PageDown>" ], run = "arrow 100%", desc = "Move cursor down one 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 = "<PageUp>", run = "arrow -100%", desc = "Move cursor up 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 = [ "l" ], run = "enter", desc = "Enter the child directory" },
{ on = "h", run = "leave", desc = "Go back to the parent directory" },
{ on = "l", run = "enter", desc = "Enter the child directory" },
{ on = [ "H" ], run = "back", desc = "Go back to the previous directory" },
{ on = [ "L" ], run = "forward", desc = "Go forward to the next directory" },
{ on = "H", run = "back", desc = "Go back to the previous 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-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-PageDown>" ], run = "seek 5", desc = "Seek down 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-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 = [ "<Up>" ], run = "arrow -1", desc = "Move cursor up" },
{ on = [ "<Down>" ], run = "arrow 1", desc = "Move cursor down" },
{ on = [ "<Left>" ], run = "leave", desc = "Go back to the parent directory" },
{ on = [ "<Right>" ], run = "enter", desc = "Enter the child directory" },
{ on = "<Up>", run = "arrow -1", desc = "Move cursor up" },
{ on = "<Down>", run = "arrow 1", desc = "Move cursor down" },
{ on = "<Left>", run = "leave", desc = "Go back to the parent directory" },
{ on = "<Right>", run = "enter", desc = "Enter the child directory" },
{ 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
{ 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 --unset", desc = "Enter visual mode (unset mode)" },
{ 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 = "<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 --unset", desc = "Enter visual mode (unset mode)" },
{ 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" },
# Operation
{ on = [ "o" ], run = "open", desc = "Open the selected files" },
{ on = [ "O" ], run = "open --interactive", desc = "Open the selected files interactively" },
{ on = [ "<Enter>" ], run = "open", desc = "Open the selected files" },
{ on = [ "<C-Enter>" ], run = "open --interactive", desc = "Open the selected files interactively" },
{ on = [ "y" ], run = "yank", desc = "Copy the 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 = "unyank", desc = "Cancel the yank status of files" },
{ on = [ "p" ], run = "paste", desc = "Paste the files" },
{ on = [ "P" ], run = "paste --force", desc = "Paste the files (overwrite if the destination exists)" },
{ on = [ "-" ], run = "link", desc = "Symlink the absolute path of files" },
{ on = [ "_" ], run = "link --relative", desc = "Symlink the relative path of files" },
{ on = [ "d" ], run = "remove", desc = "Move the files to the trash" },
{ on = [ "D" ], run = "remove --permanently", desc = "Permanently delete the files" },
{ on = [ "a" ], run = "create", desc = "Create a file or directory (ends with / for directories)" },
{ on = [ "r" ], run = "rename --cursor=before_ext", desc = "Rename a file or directory" },
{ on = [ ";" ], run = "shell", desc = "Run a shell command" },
{ on = [ ":" ], run = "shell --block", desc = "Run a shell command (block the UI until the command finishes)" },
{ 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 rg", desc = "Search files by content using ripgrep" },
{ 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 fzf", desc = "Jump to a directory, or reveal a file using fzf" },
{ on = "o", run = "open", desc = "Open selected files" },
{ on = "O", run = "open --interactive", desc = "Open selected files interactively" },
{ on = "<Enter>", run = "open", desc = "Open selected files" },
{ on = "<S-Enter>", run = "open --interactive", desc = "Open selected files interactively" },
{ on = "y", run = "yank", desc = "Copy 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" },
{ on = "p", run = "paste", desc = "Paste yanked files" },
{ on = "P", run = "paste --force", desc = "Paste yanked files (overwrite if the destination exists)" },
{ on = "-", run = "link", desc = "Symlink the absolute path of yanked files" },
{ on = "_", run = "link --relative", desc = "Symlink the relative path of yanked files" },
{ on = "d", run = "remove", desc = "Trash selected files" },
{ on = "D", run = "remove --permanently", desc = "Permanently delete selected files" },
{ on = "a", run = "create", desc = "Create a file (ends with / for directories)" },
{ on = "r", run = "rename --cursor=before_ext", desc = "Rename selected file(s)" },
{ on = ";", run = "shell --interactive", desc = "Run a shell command" },
{ 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 = "s", run = "search fd", desc = "Search files by name using fd" },
{ on = "S", run = "search rg", desc = "Search files by content using ripgrep" },
{ 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 fzf", desc = "Jump to a directory or reveal a file using fzf" },
# Linemode
{ on = [ "m", "s" ], run = "linemode size", desc = "Set linemode to size" },
{ on = [ "m", "p" ], run = "linemode permissions", desc = "Set linemode to permissions" },
{ 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" },
# Copy
{ on = [ "c", "c" ], run = "copy path", desc = "Copy the absolute path" },
{ on = [ "c", "d" ], run = "copy dirname", desc = "Copy the path of the parent directory" },
{ on = [ "c", "f" ], run = "copy filename", desc = "Copy the name of the file" },
{ on = [ "c", "n" ], run = "copy name_without_ext", desc = "Copy the name of the file without the extension" },
{ on = [ "c", "c" ], run = "copy path", desc = "Copy the file path" },
{ on = [ "c", "d" ], run = "copy dirname", desc = "Copy the directory path" },
{ on = [ "c", "f" ], run = "copy filename", desc = "Copy the filename" },
{ on = [ "c", "n" ], run = "copy name_without_ext", desc = "Copy the filename without extension" },
# Filter
{ on = [ "f" ], run = "filter --smart", desc = "Filter the files" },
{ on = "f", run = "filter --smart", desc = "Filter files" },
# Find
{ on = [ "/" ], run = "find --smart", desc = "Find next 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 --previous", desc = "Go to previous found file" },
{ on = "/", run = "find --smart", desc = "Find next file" },
{ on = "?", run = "find --previous --smart", desc = "Find previous file" },
{ on = "n", run = "find_arrow", desc = "Go to the next found" },
{ on = "N", run = "find_arrow --previous", desc = "Go to the previous found" },
# Sorting
{ on = [ ",", "m" ], run = "sort modified --dir-first", desc = "Sort by modified time" },
{ on = [ ",", "M" ], run = "sort modified --reverse --dir-first", desc = "Sort by modified time (reverse)" },
{ on = [ ",", "c" ], run = "sort created --dir-first", desc = "Sort by created time" },
{ on = [ ",", "C" ], run = "sort created --reverse --dir-first", desc = "Sort by created time (reverse)" },
{ on = [ ",", "e" ], run = "sort extension --dir-first", desc = "Sort by extension" },
{ on = [ ",", "E" ], run = "sort extension --reverse --dir-first", desc = "Sort by extension (reverse)" },
{ on = [ ",", "a" ], run = "sort alphabetical --dir-first", desc = "Sort alphabetically" },
{ on = [ ",", "A" ], run = "sort alphabetical --reverse --dir-first", desc = "Sort alphabetically (reverse)" },
{ on = [ ",", "n" ], run = "sort natural --dir-first", desc = "Sort naturally" },
{ on = [ ",", "N" ], run = "sort natural --reverse --dir-first", desc = "Sort naturally (reverse)" },
{ on = [ ",", "s" ], run = "sort size --dir-first", desc = "Sort by size" },
{ on = [ ",", "S" ], run = "sort size --reverse --dir-first", desc = "Sort by size (reverse)" },
{ on = [ ",", "m" ], run = "sort modified --reverse=no", desc = "Sort by modified time" },
{ on = [ ",", "M" ], run = "sort modified --reverse", desc = "Sort by modified time (reverse)" },
{ on = [ ",", "c" ], run = "sort created --reverse=no", desc = "Sort by created time" },
{ on = [ ",", "C" ], run = "sort created --reverse", desc = "Sort by created time (reverse)" },
{ on = [ ",", "e" ], run = "sort extension --reverse=no", desc = "Sort by extension" },
{ on = [ ",", "E" ], run = "sort extension --reverse", desc = "Sort by extension (reverse)" },
{ on = [ ",", "a" ], run = "sort alphabetical --reverse=no", desc = "Sort alphabetically" },
{ on = [ ",", "A" ], run = "sort alphabetical --reverse", desc = "Sort alphabetically (reverse)" },
{ on = [ ",", "n" ], run = "sort natural --reverse=no", desc = "Sort naturally" },
{ on = [ ",", "N" ], run = "sort natural --reverse", desc = "Sort naturally (reverse)" },
{ on = [ ",", "s" ], run = "sort size --reverse=no", desc = "Sort by size" },
{ on = [ ",", "S" ], run = "sort size --reverse", desc = "Sort by size (reverse)" },
# 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 = [ "2" ], run = "tab_switch 1", desc = "Switch to the second 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 = [ "5" ], run = "tab_switch 4", desc = "Switch to the fifth 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 = [ "8" ], run = "tab_switch 7", desc = "Switch to the eighth tab" },
{ on = [ "9" ], run = "tab_switch 8", desc = "Switch to the ninth 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 = "3", run = "tab_switch 2", desc = "Switch to the third 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 = "6", run = "tab_switch 5", desc = "Switch to the sixth 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 = "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 next 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_swap -1", desc = "Swap the current tab with the 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 previous tab" },
{ on = "}", run = "tab_swap 1", desc = "Swap current tab with next tab" },
# Tasks
{ on = [ "w" ], run = "tasks_show", desc = "Show the tasks manager" },
{ on = "w", run = "tasks_show", desc = "Show task manager" },
# Goto
{ 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", "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" },
# Help
{ on = [ "~" ], run = "help", desc = "Open help" },
{ on = "~", run = "help", desc = "Open help" },
]
[tasks]
keymap = [
{ on = [ "<Esc>" ], run = "close", desc = "Hide the task manager" },
{ on = [ "<C-[>" ], run = "close", desc = "Hide the task manager" },
{ on = [ "<C-q>" ], run = "close", desc = "Hide the task manager" },
{ on = [ "w" ], run = "close", desc = "Hide the task manager" },
{ on = "<Esc>", run = "close", desc = "Close task manager" },
{ on = "<C-[>", run = "close", desc = "Close task manager" },
{ on = "<C-c>", run = "close", desc = "Close task manager" },
{ on = "w", run = "close", desc = "Close task manager" },
{ on = [ "k" ], run = "arrow -1", desc = "Move cursor up" },
{ on = [ "j" ], run = "arrow 1", desc = "Move cursor down" },
{ on = "k", run = "arrow -1", desc = "Move cursor up" },
{ on = "j", run = "arrow 1", desc = "Move cursor down" },
{ on = [ "<Up>" ], run = "arrow -1", desc = "Move cursor up" },
{ on = [ "<Down>" ], run = "arrow 1", desc = "Move cursor down" },
{ on = "<Up>", run = "arrow -1", desc = "Move cursor up" },
{ on = "<Down>", run = "arrow 1", desc = "Move cursor down" },
{ on = [ "<Enter>" ], run = "inspect", desc = "Inspect the task" },
{ on = [ "x" ], run = "cancel", desc = "Cancel the task" },
{ on = "<Enter>", run = "inspect", desc = "Inspect the task" },
{ on = "x", run = "cancel", desc = "Cancel the task" },
{ on = [ "~" ], run = "help", desc = "Open help" }
{ on = "~", run = "help", desc = "Open help" }
]
[select]
keymap = [
{ on = [ "<Esc>" ], run = "close", desc = "Cancel selection" },
{ on = [ "<C-[>" ], run = "close", desc = "Cancel selection" },
{ on = [ "<C-q>" ], run = "close", desc = "Cancel selection" },
{ on = [ "<Enter>" ], run = "close --submit", desc = "Submit the selection" },
{ on = "<Esc>", run = "close", desc = "Cancel selection" },
{ on = "<C-[>", run = "close", desc = "Cancel selection" },
{ on = "<C-c>", run = "close", desc = "Cancel selection" },
{ on = "<Enter>", run = "close --submit", desc = "Submit the selection" },
{ on = [ "k" ], run = "arrow -1", desc = "Move cursor up" },
{ on = [ "j" ], run = "arrow 1", desc = "Move cursor down" },
{ on = "k", run = "arrow -1", desc = "Move cursor up" },
{ on = "j", run = "arrow 1", desc = "Move cursor down" },
{ on = [ "K" ], run = "arrow -5", desc = "Move cursor up 5 lines" },
{ on = [ "J" ], run = "arrow 5", desc = "Move cursor down 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 = [ "<Up>" ], run = "arrow -1", desc = "Move cursor up" },
{ on = [ "<Down>" ], run = "arrow 1", desc = "Move cursor down" },
{ on = "<Up>", run = "arrow -1", desc = "Move cursor up" },
{ on = "<Down>", run = "arrow 1", desc = "Move cursor down" },
{ 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-Up>", run = "arrow -5", desc = "Move cursor up 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]
keymap = [
{ on = [ "<C-q>" ], run = "close", desc = "Cancel input" },
{ on = [ "<Enter>" ], run = "close --submit", desc = "Submit the 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-c>", run = "close", desc = "Cancel input" },
{ on = "<Enter>", run = "close --submit", desc = "Submit the 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" },
# Mode
{ on = [ "i" ], run = "insert", desc = "Enter insert 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 = [ "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 = [ "move -999", "visual", "move 999" ], desc = "Enter visual mode and select all" },
{ on = "i", run = "insert", desc = "Enter insert 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 = "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 = [ "move -999", "visual", "move 999" ], desc = "Enter visual mode and select all" },
# Character-wise movement
{ on = [ "h" ], run = "move -1", desc = "Move back a character" },
{ on = [ "l" ], run = "move 1", desc = "Move forward a character" },
{ on = [ "<Left>" ], run = "move -1", desc = "Move back 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-f>" ], run = "move 1", desc = "Move forward a character" },
{ on = "h", run = "move -1", desc = "Move back a character" },
{ on = "l", run = "move 1", desc = "Move forward a character" },
{ on = "<Left>", run = "move -1", desc = "Move back 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-f>", run = "move 1", desc = "Move forward a character" },
# Word-wise movement
{ 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 = [ "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-f>" ], run = "forward --end-of-word", desc = "Move forward to the end of the current or next 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 = "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-f>", run = "forward --end-of-word", desc = "Move forward to the end of the current or next word" },
# Line-wise movement
{ on = [ "0" ], run = "move -999", desc = "Move to the BOL" },
{ on = [ "$" ], run = "move 999", desc = "Move to the EOL" },
{ on = [ "<C-a>" ], run = "move -999", desc = "Move to the BOL" },
{ on = [ "<C-e>" ], run = "move 999", desc = "Move to the EOL" },
{ on = [ "<Home>" ], run = "move -999", desc = "Move to the BOL" },
{ on = [ "<End>" ], run = "move 999", desc = "Move to the EOL" },
{ on = "0", run = "move -999", desc = "Move to the BOL" },
{ on = "$", run = "move 999", desc = "Move to the EOL" },
{ on = "<C-a>", run = "move -999", desc = "Move to the BOL" },
{ on = "<C-e>", run = "move 999", desc = "Move to the EOL" },
{ on = "<Home>", run = "move -999", desc = "Move to the BOL" },
{ on = "<End>", run = "move 999", desc = "Move to the EOL" },
# Delete
{ on = [ "<Backspace>" ], run = "backspace", desc = "Delete the character before 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-d>" ], run = "backspace --under", desc = "Delete the character under 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 = "<C-h>", run = "backspace", desc = "Delete the character before the cursor" },
{ on = "<C-d>", run = "backspace --under", desc = "Delete the character under the cursor" },
# Kill
{ 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-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 = "<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-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" },
# Cut/Yank/Paste
{ on = [ "d" ], run = "delete --cut", desc = "Cut the selected characters" },
{ 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", "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 = [ "y" ], run = "yank", desc = "Copy the selected characters" },
{ 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 = "d", run = "delete --cut", desc = "Cut the selected characters" },
{ 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", "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 = "y", run = "yank", desc = "Copy the selected characters" },
{ 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" },
# Undo/Redo
{ on = [ "u" ], run = "undo", desc = "Undo the last operation" },
{ on = [ "<C-r>" ], run = "redo", desc = "Redo the last operation" },
{ on = "u", run = "undo", desc = "Undo the last operation" },
{ on = "<C-r>", run = "redo", desc = "Redo the last operation" },
# Help
{ on = [ "~" ], run = "help", desc = "Open help" }
{ on = "~", run = "help", desc = "Open help" }
]
[completion]
keymap = [
{ on = [ "<C-q>" ], run = "close", desc = "Cancel 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 = "<C-c>", run = "close", desc = "Cancel 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 = [ "<A-k>" ], run = "arrow -1", desc = "Move cursor up" },
{ on = [ "<A-j>" ], run = "arrow 1", desc = "Move cursor down" },
{ on = "<A-k>", run = "arrow -1", desc = "Move cursor up" },
{ on = "<A-j>", run = "arrow 1", desc = "Move cursor down" },
{ on = [ "<Up>" ], run = "arrow -1", desc = "Move cursor up" },
{ on = [ "<Down>" ], run = "arrow 1", desc = "Move cursor down" },
{ on = "<Up>", run = "arrow -1", desc = "Move cursor up" },
{ on = "<Down>", run = "arrow 1", desc = "Move cursor down" },
{ on = [ "<C-p>" ], run = "arrow -1", desc = "Move cursor up" },
{ on = [ "<C-n>" ], run = "arrow 1", desc = "Move cursor down" },
{ on = "<C-p>", run = "arrow -1", desc = "Move cursor up" },
{ on = "<C-n>", run = "arrow 1", desc = "Move cursor down" },
{ on = [ "~" ], run = "help", desc = "Open help" }
{ on = "~", run = "help", desc = "Open help" }
]
[help]
keymap = [
{ 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 = [ "q" ], run = "close", desc = "Exit the process" },
{ on = [ "<C-q>" ], run = "close", desc = "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 = "q", run = "close", desc = "Exit the process" },
{ on = "<C-c>", run = "close", desc = "Hide the help" },
# Navigation
{ on = [ "k" ], run = "arrow -1", desc = "Move cursor up" },
{ on = [ "j" ], run = "arrow 1", desc = "Move cursor down" },
{ on = "k", run = "arrow -1", desc = "Move cursor up" },
{ on = "j", run = "arrow 1", desc = "Move cursor down" },
{ on = [ "K" ], run = "arrow -5", desc = "Move cursor up 5 lines" },
{ on = [ "J" ], run = "arrow 5", desc = "Move cursor down 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 = [ "<Up>" ], run = "arrow -1", desc = "Move cursor up" },
{ on = [ "<Down>" ], run = "arrow 1", desc = "Move cursor down" },
{ on = "<Up>", run = "arrow -1", desc = "Move cursor up" },
{ on = "<Down>", run = "arrow 1", desc = "Move cursor down" },
{ 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-Up>", run = "arrow -5", desc = "Move cursor up 5 lines" },
{ on = "<S-Down>", run = "arrow 5", desc = "Move cursor down 5 lines" },
# 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 = [
# Images
{ mime = "image/*", fg = "cyan" },
{ mime = "image/*", fg = "yellow" },
# Media
{ mime = "{audio,video}/*", fg = "yellow" },
{ mime = "{audio,video}/*", fg = "magenta" },
# Archives
{ mime = "application/*zip", fg = "magenta" },
{ mime = "application/x-{tar,bzip*,7z-compressed,xz,rar}", fg = "magenta" },
{ mime = "application/{,g}zip", fg = "red" },
{ mime = "application/x-{tar,bzip*,7z-compressed,xz,rar}", fg = "red" },
# 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
# { name = "*", fg = "white" },
@ -189,155 +196,586 @@ rules = [
[icon]
rules = [
# Programming
{ name = "*.c" , text = "", fg = "#599eff" },
{ name = "*.cpp" , text = "", fg = "#519aba" },
{ name = "*.class", text = "", fg = "#cc3e44" },
{ name = "*.cs" , text = "󰌛", fg = "#596706" },
{ name = "*.css" , text = "", fg = "#42a5f5" },
{ name = "*.elm" , text = "", fg = "#4391d2" },
{ name = "*.fish" , text = "", fg = "#4d5a5e" },
{ name = "*.go" , text = "", fg = "#519aba" },
{ name = "*.h" , text = "", fg = "#a074c4" },
{ name = "*.hpp" , text = "", fg = "#a074c4" },
{ name = "*.html" , text = "", fg = "#e44d26" },
{ name = "*.jar" , text = "", fg = "#cc3e44" },
{ name = "*.java" , text = "", fg = "#cc3e44" },
{ name = "*.js" , text = "", fg = "#F1F134" },
{ name = "*.jsx" , text = "", fg = "#20c2e3" },
{ name = "*.lua" , text = "", fg = "#51a0cf" },
{ name = "*.nix" , text = "", fg = "#7ebae4" },
{ name = "*.nu" , text = ">", fg = "#3aa675" },
{ name = "*.php" , text = "", fg = "#a074c4" },
{ name = "*.py" , text = "", fg = "#ffbc03" },
{ name = "*.rb" , text = "", fg = "#701516" },
{ name = "*.rs" , text = "", fg = "#dea584" },
{ name = "*.sbt" , text = "", fg = "#4d5a5e" },
{ name = "*.scala", text = "", fg = "#cc463e" },
{ name = "*.scss" , text = "", fg = "#f55385" },
{ name = "*.sh" , text = "", fg = "#4d5a5e" },
{ name = "*.swift", text = "", fg = "#e37933" },
{ name = "*.ts" , text = "", fg = "#519aba" },
{ name = "*.tsx" , text = "", fg = "#1354bf" },
{ name = "*.vim" , text = "", fg = "#019833" },
{ name = "*.vue" , text = "󰡄", fg = "#8dc149" },
globs = []
dirs = [
{ 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 = "" },
]
files = [
{ name = ".babelrc", text = "", fg_dark = "#cbcb41", fg_light = "#666620" },
{ name = ".bash_profile", text = "", fg_dark = "#89e051", fg_light = "#447028" },
{ name = ".bashrc", text = "", fg_dark = "#89e051", fg_light = "#447028" },
{ name = ".dockerignore", text = "󰡨", fg_dark = "#458ee6", fg_light = "#2e5f99" },
{ name = ".ds_store", text = "", fg_dark = "#41535b", fg_light = "#41535b" },
{ name = ".editorconfig", text = "", fg_dark = "#fff2f2", fg_light = "#333030" },
{ name = ".env", text = "", fg_dark = "#faf743", fg_light = "#32310d" },
{ name = ".eslintignore", text = "", fg_dark = "#4b32c3", fg_light = "#4b32c3" },
{ name = ".eslintrc", text = "", fg_dark = "#4b32c3", fg_light = "#4b32c3" },
{ name = ".gitattributes", text = "", fg_dark = "#f54d27", fg_light = "#b83a1d" },
{ name = ".gitconfig", text = "", fg_dark = "#f54d27", fg_light = "#b83a1d" },
{ name = ".gitignore", text = "", fg_dark = "#f54d27", fg_light = "#b83a1d" },
{ name = ".gitlab-ci.yml", text = "", fg_dark = "#e24329", fg_light = "#aa321f" },
{ name = ".gitmodules", text = "", fg_dark = "#f54d27", fg_light = "#b83a1d" },
{ name = ".gtkrc-2.0", text = "", fg_dark = "#ffffff", fg_light = "#333333" },
{ name = ".gvimrc", text = "", fg_dark = "#019833", fg_light = "#017226" },
{ 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
{ name = "*.conf", text = "", fg = "#6d8086" },
{ name = "*.ini" , text = "", fg = "#6d8086" },
{ name = "*.json", text = "", fg = "#cbcb41" },
{ 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 = "" },
# Fallback
{ if = "dir", text = "" },
{ if = "exec", text = "" },
{ if = "!dir", text = "" },
]
# : }}}

View file

@ -6,12 +6,14 @@
ratio = [ 1, 4, 3 ]
sort_by = "alphabetical"
sort_sensitive = false
sort_reverse = false
sort_reverse = false
sort_dir_first = true
sort_translit = false
linemode = "none"
show_hidden = false
show_symlink = true
scrolloff = 5
mouse_events = [ "click", "scroll" ]
[preview]
tab_size = 2
@ -27,16 +29,16 @@ ueberzug_offset = [ 0, 0, 0, 0 ]
[opener]
edit = [
{ run = '${EDITOR:=vi} "$@"', desc = "$EDITOR", block = true, for = "unix" },
{ run = 'code "%*"', orphan = true, desc = "code", for = "windows" },
{ run = 'code -w "%*"', block = true, desc = "code (block)", for = "windows" },
{ run = 'code %*', orphan = true, desc = "code", for = "windows" },
{ run = 'code -w %*', block = true, desc = "code (block)", for = "windows" },
]
open = [
{ run = 'xdg-open "$@"', desc = "Open", for = "linux" },
{ run = 'xdg-open "$1"', desc = "Open", for = "linux" },
{ run = 'open "$@"', desc = "Open", for = "macos" },
{ run = 'start "" "%1"', orphan = true, desc = "Open", for = "windows" },
]
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 = '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" },
@ -46,8 +48,8 @@ extract = [
{ run = 'unar "%1"', desc = "Extract here", for = "windows" },
]
play = [
{ run = 'mpv "$@"', orphan = true, for = "unix" },
{ run = 'mpv "%1"', orphan = true, for = "windows" },
{ run = 'mpv --force-window "$@"', orphan = true, for = "unix" },
{ 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" },
]
@ -60,13 +62,13 @@ rules = [
{ mime = "{audio,video}/*", use = [ "play", "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/json", use = [ "edit", "reveal" ] },
{ mime = "*/javascript", use = [ "edit", "reveal" ] },
{ mime = "application/{json,x-ndjson}", use = [ "edit", "reveal" ] },
{ mime = "*/javascript", use = [ "edit", "reveal" ] },
{ mime = "*", use = [ "open", "reveal" ] },
{ name = "*", use = [ "open", "reveal" ] },
]
[tasks]
@ -79,14 +81,21 @@ suppress_preload = false
[plugin]
fetchers = [
# Mimetype
{ id = "mime", name = "*", run = "mime", if = "!mime", prio = "high" },
]
preloaders = [
{ name = "*", cond = "!mime", run = "mime", multi = true, prio = "high" },
# Image
{ mime = "image/{avif,heic,jxl,svg+xml}", run = "magick" },
{ mime = "image/*", run = "image" },
# Video
{ mime = "video/*", run = "video" },
# PDF
{ mime = "application/pdf", run = "pdf" },
# Font
{ mime = "font/*", run = "font" },
{ mime = "application/vnd.ms-opentype", run = "font" },
]
previewers = [
{ name = "*/", run = "folder", sync = true },
@ -94,22 +103,27 @@ previewers = [
{ mime = "text/*", run = "code" },
{ mime = "*/{xml,javascript,x-wine-extension-ini}", run = "code" },
# JSON
{ mime = "application/json", run = "json" },
{ mime = "application/{json,x-ndjson}", run = "json" },
# Image
{ mime = "image/vnd.djvu", run = "noop" },
{ mime = "image/*", run = "image" },
{ mime = "image/{avif,heic,jxl,svg+xml}", run = "magick" },
{ mime = "image/*", run = "image" },
# Video
{ mime = "video/*", run = "video" },
# PDF
{ mime = "application/pdf", run = "pdf" },
# Archive
{ mime = "application/*zip", run = "archive" },
{ mime = "application/x-{tar,bzip*,7z-compressed,xz,rar}", run = "archive" },
{ mime = "application/{,g}zip", 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
{ name = "*", run = "file" },
]
[input]
cursor_blink = true
# cd
cd_title = "Change directory:"
cd_origin = "top-center"
@ -171,11 +185,10 @@ open_origin = "hovered"
open_offset = [ 0, 1, 50, 7 ]
[which]
sort_by = "none"
sort_by = "none"
sort_sensitive = false
sort_reverse = false
sort_reverse = false
sort_translit = false
[log]
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 super::Key;
use crate::DEPRECATED_EXEC;
#[derive(Debug, Default)]
#[derive(Debug, Default, Deserialize)]
pub struct Control {
#[serde(deserialize_with = "super::deserialize_on")]
pub on: Vec<Key>,
#[serde(deserialize_with = "super::deserialize_run")]
pub run: Vec<Cmd>,
pub desc: Option<String>,
}
@ -24,7 +25,7 @@ impl Control {
#[inline]
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]
@ -40,33 +41,3 @@ impl Control {
|| 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 crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use serde::Deserialize;
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash)]
#[serde(try_from = "String")]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Key {
pub code: KeyCode,
pub shift: bool,
pub ctrl: bool,
pub alt: bool,
pub code: KeyCode,
pub shift: bool,
pub ctrl: bool,
pub alt: bool,
pub super_: bool,
}
impl Key {
#[inline]
pub fn plain(&self) -> Option<char> {
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,
}
}
#[inline]
pub fn is_enter(&self) -> bool {
matches!(self, Key { code: KeyCode::Enter, shift: false, ctrl: false, alt: false })
}
}
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 {
@ -56,6 +52,7 @@ impl From<KeyEvent> for Key {
shift,
ctrl: value.modifiers.contains(KeyModifiers::CONTROL),
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();
while let Some(x) = it.next() {
match x {
"S-" => key.shift = true,
"C-" => key.ctrl = true,
"A-" => key.alt = true,
while let Some(next) = it.next() {
match next.to_ascii_lowercase().as_str() {
"s-" => key.shift = true,
"c-" => key.ctrl = true,
"a-" => key.alt = true,
"d-" => key.super_ = true,
"Space" => key.code = KeyCode::Char(' '),
"Backspace" => key.code = KeyCode::Backspace,
"Enter" => key.code = KeyCode::Enter,
"Left" => key.code = KeyCode::Left,
"Right" => key.code = KeyCode::Right,
"Up" => key.code = KeyCode::Up,
"Down" => key.code = KeyCode::Down,
"Home" => key.code = KeyCode::Home,
"End" => key.code = KeyCode::End,
"PageUp" => key.code = KeyCode::PageUp,
"PageDown" => key.code = KeyCode::PageDown,
"Tab" => key.code = KeyCode::Tab,
"BackTab" => key.code = KeyCode::BackTab,
"Delete" => key.code = KeyCode::Delete,
"Insert" => key.code = KeyCode::Insert,
"F1" => key.code = KeyCode::F(1),
"F2" => key.code = KeyCode::F(2),
"F3" => key.code = KeyCode::F(3),
"F4" => key.code = KeyCode::F(4),
"F5" => key.code = KeyCode::F(5),
"F6" => key.code = KeyCode::F(6),
"F7" => key.code = KeyCode::F(7),
"F8" => key.code = KeyCode::F(8),
"F9" => key.code = KeyCode::F(9),
"F10" => key.code = KeyCode::F(10),
"F11" => key.code = KeyCode::F(11),
"F12" => key.code = KeyCode::F(12),
"Esc" => key.code = KeyCode::Esc,
"space" => key.code = KeyCode::Char(' '),
"backspace" => key.code = KeyCode::Backspace,
"enter" => key.code = KeyCode::Enter,
"left" => key.code = KeyCode::Left,
"right" => key.code = KeyCode::Right,
"up" => key.code = KeyCode::Up,
"down" => key.code = KeyCode::Down,
"home" => key.code = KeyCode::Home,
"end" => key.code = KeyCode::End,
"pageup" => key.code = KeyCode::PageUp,
"pagedown" => key.code = KeyCode::PageDown,
"tab" => key.code = KeyCode::Tab,
"backtab" => key.code = KeyCode::BackTab,
"delete" => key.code = KeyCode::Delete,
"insert" => key.code = KeyCode::Insert,
"f1" => key.code = KeyCode::F(1),
"f2" => key.code = KeyCode::F(2),
"f3" => key.code = KeyCode::F(3),
"f4" => key.code = KeyCode::F(4),
"f5" => key.code = KeyCode::F(5),
"f6" => key.code = KeyCode::F(6),
"f7" => key.code = KeyCode::F(7),
"f8" => key.code = KeyCode::F(8),
"f9" => key.code = KeyCode::F(9),
"f10" => key.code = KeyCode::F(10),
"f11" => key.code = KeyCode::F(11),
"f12" => key.code = KeyCode::F(12),
"esc" => key.code = KeyCode::Esc,
c if it.peek().is_none() => {
key.code = KeyCode::Char(c.chars().next().unwrap());
}
k => bail!("unknown key: {k}"),
_ => match next {
s if it.peek().is_none() => key.code = KeyCode::Char(s.chars().next().unwrap()),
s => bail!("unknown key: {s}"),
},
}
}
@ -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 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(c) = self.plain() {
@ -140,6 +132,9 @@ impl Display for Key {
}
write!(f, "<")?;
if self.super_ {
write!(f, "D-")?;
}
if self.ctrl {
write!(f, "C-")?;
}

View file

@ -1,8 +1,10 @@
use std::str::FromStr;
use serde::{Deserialize, Deserializer};
use yazi_shared::Layer;
use super::Control;
use crate::{Preset, MERGED_KEYMAP};
use crate::Preset;
#[derive(Debug)]
pub struct Keymap {
@ -14,6 +16,28 @@ pub struct Keymap {
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 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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 cow;
mod deserializers;
mod key;
mod keymap;
mod run;
pub use control::*;
pub use cow::*;
use deserializers::*;
pub use key::*;
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)]
use std::str::FromStr;
use yazi_shared::{RoCell, Xdg};
pub mod headsup;
pub mod keymap;
mod layout;
mod log;
@ -16,7 +17,6 @@ pub mod preview;
mod priority;
mod tasks;
pub mod theme;
mod validation;
pub mod which;
pub use layout::*;
@ -24,17 +24,8 @@ pub(crate) use pattern::*;
pub(crate) use preset::*;
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 HEADSUP: RoCell<headsup::Headsup> = RoCell::new();
pub static KEYMAP: RoCell<keymap::Keymap> = RoCell::new();
pub static LOG: RoCell<log::Log> = 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<()> {
let config_dir = Xdg::config_dir();
MERGED_YAZI.init(Preset::yazi(&config_dir)?);
MERGED_KEYMAP.init(Preset::keymap(&config_dir)?);
MERGED_THEME.init(Preset::theme(&config_dir)?);
let yazi_toml = &Preset::yazi(&config_dir)?;
let keymap_toml = &Preset::keymap(&config_dir)?;
let theme_toml = &Preset::theme(&config_dir)?;
LAYOUT.with(Default::default);
HEADSUP.with(Default::default);
KEYMAP.with(Default::default);
LOG.with(Default::default);
MANAGER.with(Default::default);
OPEN.with(Default::default);
PLUGIN.with(Default::default);
PREVIEW.with(Default::default);
TASKS.with(Default::default);
THEME.with(Default::default);
INPUT.with(Default::default);
SELECT.with(Default::default);
WHICH.with(Default::default);
KEYMAP.init(<_>::from_str(keymap_toml)?);
LOG.init(<_>::from_str(yazi_toml)?);
MANAGER.init(<_>::from_str(yazi_toml)?);
OPEN.init(<_>::from_str(yazi_toml)?);
PLUGIN.init(<_>::from_str(yazi_toml)?);
PREVIEW.init(<_>::from_str(yazi_toml)?);
TASKS.init(<_>::from_str(yazi_toml)?);
THEME.init(<_>::from_str(theme_toml)?);
INPUT.init(<_>::from_str(yazi_toml)?);
SELECT.init(<_>::from_str(yazi_toml)?);
WHICH.init(<_>::from_str(yazi_toml)?);
// TODO: remove this once Yazi 0.3 is released --
if !HEADSUP.disable_exec_warn && DEPRECATED_EXEC.load(std::sync::atomic::Ordering::Relaxed) {
eprintln!(
r#"
WARNING: `exec` will be deprecated in the next major version v0.3 and replaced by `run`.
// TODO: Remove in v0.3.2
for c in &KEYMAP.manager {
for r in &c.run {
if r.name == "shell" && !r.bool("confirm") && !r.bool("interactive") {
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`.
---
Add `disable_exec_warn = true` to your `yazi.toml` under `[headsup]` to suppress this warning.
"#
);
Please replace e.g. `shell` with `shell --interactive`, `shell "my-template"` with `shell "my-template" --interactive`, in your keymap.toml"#
);
return 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)]
pub struct Log {
pub enabled: bool,
}
impl Default for Log {
fn default() -> Self { toml::from_str(&MERGED_YAZI).unwrap() }
impl FromStr for Log {
type Err = toml::de::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { toml::from_str(s) }
}
impl<'de> Deserialize<'de> for Log {

View file

@ -1,8 +1,9 @@
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use validator::Validate;
use super::{ManagerRatio, SortBy};
use crate::{validation::check_validation, MERGED_YAZI};
use super::{ManagerRatio, MouseEvents, SortBy};
#[derive(Debug, Deserialize, Serialize, Validate)]
pub struct Manager {
@ -13,6 +14,7 @@ pub struct Manager {
pub sort_sensitive: bool,
pub sort_reverse: bool,
pub sort_dir_first: bool,
pub sort_translit: bool,
// Display
#[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_symlink: bool,
pub scrolloff: u8,
pub mouse_events: MouseEvents,
}
impl Default for Manager {
fn default() -> Self {
impl FromStr for Manager {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)]
struct Outer {
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());
manager
Ok(manager)
}
}

View file

@ -1,7 +1,9 @@
mod manager;
mod mouse;
mod ratio;
mod sorting;
pub use manager::*;
pub use mouse::*;
pub use ratio::*;
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 serde::{Deserialize, Deserializer};
use yazi_shared::MIME_DIR;
use super::Opener;
use crate::{open::OpenRule, Preset, MERGED_YAZI};
use crate::{open::OpenRule, Preset};
#[derive(Debug)]
pub struct Open {
@ -13,20 +13,16 @@ pub struct Open {
openers: HashMap<String, IndexSet<Opener>>,
}
impl Default for Open {
fn default() -> Self { toml::from_str(&MERGED_YAZI).unwrap() }
}
impl Open {
pub fn openers<P, M>(&self, path: P, mime: M) -> Option<IndexSet<&Opener>>
where
P: AsRef<Path>,
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| {
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
.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 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
@ -78,6 +80,13 @@ impl<'de> Deserialize<'de> for Open {
}
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);
let openers = outer

View file

@ -1,9 +1,5 @@
use std::sync::atomic::Ordering;
use serde::{Deserialize, Deserializer};
use crate::DEPRECATED_EXEC;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Opener {
pub run: String,
@ -36,9 +32,7 @@ impl<'de> Deserialize<'de> for Opener {
{
#[derive(Deserialize)]
pub struct Shadow {
run: Option<String>,
// TODO: remove this once Yazi 0.3 is released --
exec: Option<String>,
run: String,
#[serde(default)]
block: bool,
#[serde(default)]
@ -50,13 +44,7 @@ impl<'de> Deserialize<'de> for Opener {
let shadow = Shadow::deserialize(deserializer)?;
// TODO: remove this once Yazi 0.3 is released --
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
let run = shadow.run;
if run.is_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>,
}
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 {
fn deserialize<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
where

View file

@ -6,25 +6,27 @@ use serde::Deserialize;
#[derive(Debug, Deserialize)]
#[serde(try_from = "String")]
pub struct Pattern {
inner: globset::GlobMatcher,
is_star: bool,
is_folder: bool,
inner: globset::GlobMatcher,
is_dir: bool,
is_star: bool,
}
impl Pattern {
#[inline]
pub fn match_mime(&self, str: impl AsRef<str>) -> bool { 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))
pub fn match_mime(&self, str: impl AsRef<str>) -> bool {
self.is_star || self.inner.is_match(str.as_ref())
}
#[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]
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 {
@ -36,13 +38,13 @@ impl TryFrom<&str> for Pattern {
let inner = GlobBuilder::new(b)
.case_insensitive(a.len() == s.len())
.literal_separator(b.contains('/'))
.literal_separator(false)
.backslash_escape(false)
.empty_alternates(false)
.empty_alternates(true)
.build()?
.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 props;
mod rule;
mod run;
mod preloader;
mod previewer;
pub use fetcher::*;
pub use plugin::*;
pub use props::*;
pub use rule::*;
#[allow(unused_imports)]
pub use run::*;
pub use preloader::*;
pub use previewer::*;
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 yazi_shared::MIME_DIR;
use super::PluginRule;
use crate::{plugin::MAX_PRELOADERS, Preset, MERGED_YAZI};
use super::{Fetcher, Preloader, Previewer};
use crate::{plugin::MAX_PREWORKERS, Preset};
#[derive(Deserialize)]
pub struct Plugin {
pub preloaders: Vec<PluginRule>,
pub previewers: Vec<PluginRule>,
pub fetchers: Vec<Fetcher>,
pub preloaders: Vec<Preloader>,
pub previewers: Vec<Previewer>,
}
impl Default for Plugin {
fn default() -> Self {
impl Plugin {
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)]
struct Outer {
plugin: Shadow,
@ -21,20 +70,26 @@ impl Default for Plugin {
#[derive(Deserialize)]
struct Shadow {
preloaders: Vec<PluginRule>,
fetchers: Vec<Fetcher>,
#[serde(default)]
prepend_preloaders: Vec<PluginRule>,
prepend_fetchers: Vec<Fetcher>,
#[serde(default)]
append_preloaders: Vec<PluginRule>,
append_fetchers: Vec<Fetcher>,
previewers: Vec<PluginRule>,
preloaders: Vec<Preloader>,
#[serde(default)]
prepend_previewers: Vec<PluginRule>,
prepend_preloaders: Vec<Preloader>,
#[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()) {
shadow.previewers.retain(|r| !r.any_file());
}
@ -42,48 +97,25 @@ impl Default for Plugin {
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.previewers, shadow.prepend_previewers, shadow.append_previewers);
if shadow.preloaders.len() > MAX_PRELOADERS as usize {
panic!("Too many preloaders");
if shadow.fetchers.len() + shadow.preloaders.len() > MAX_PREWORKERS as usize {
panic!("Fetchers and preloaders exceed the limit of {MAX_PREWORKERS}");
}
for (i, preloader) in shadow.preloaders.iter_mut().enumerate() {
if preloader.sync {
panic!("Preloaders cannot be synchronous");
}
preloader.id = i as u8;
for (i, p) in shadow.fetchers.iter_mut().enumerate() {
p.idx = i as u8;
}
for (i, p) in shadow.preloaders.iter_mut().enumerate() {
p.idx = shadow.fetchers.len() as u8 + i as u8;
}
Self { 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))
Ok(Self {
fetchers: shadow.fetchers,
preloaders: shadow.preloaders,
previewers: shadow.previewers,
})
}
}

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 super::{Offset, Origin};
use crate::MERGED_YAZI;
#[derive(Deserialize)]
pub struct Input {
pub cursor_blink: bool,
// cd
pub cd_title: String,
pub cd_origin: Origin,
@ -61,18 +64,19 @@ pub struct Input {
pub quit_offset: Offset,
}
impl Default for Input {
fn default() -> Self {
impl Input {
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)]
struct Outer {
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 ratatui::layout::Rect;
use yazi_shared::term::Term;
use super::{Offset, Origin};
@ -14,10 +13,9 @@ impl Position {
#[inline]
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::*;
let Offset { x, y, width, height } = self.offset;
let WindowSize { columns, rows, .. } = Term::size();
let max_x = columns.saturating_sub(width);
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 WindowSize { columns, rows, .. } = Term::size();
let above =
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 super::{Offset, Origin};
use crate::MERGED_YAZI;
#[derive(Deserialize)]
pub struct Select {
@ -11,18 +12,19 @@ pub struct Select {
pub open_offset: Offset,
}
impl Default for Select {
fn default() -> Self {
impl Select {
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)]
struct Outer {
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 toml::{Table, Value};
@ -8,17 +8,17 @@ use crate::theme::Flavor;
pub(crate) struct 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"))
}
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"))
}
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 {
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 {
return Self::merge_str(&user, include_str!("../preset/theme.toml"));
@ -37,18 +37,18 @@ impl Preset {
}
#[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()?;
Self::merge(&mut t, base.parse()?, 2);
Ok(t.to_string())
Ok(t.to_string().into())
}
#[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();
if s.is_empty() {
return Ok(base.to_string());
return Ok(base.into());
}
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 validator::Validate;
use yazi_shared::fs::expand_path;
use crate::{validation::check_validation, Xdg, MERGED_YAZI};
use crate::Xdg;
#[derive(Debug, Serialize)]
pub struct Preview {
@ -22,8 +23,18 @@ pub struct Preview {
pub ueberzug_offset: (f32, f32, f32, f32),
}
impl Default for Preview {
fn default() -> Self {
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))
}
}
impl FromStr for Preview {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)]
struct Outer {
preview: Shadow,
@ -46,17 +57,14 @@ impl Default for Preview {
ueberzug_offset: (f32, f32, f32, f32),
}
let preview = toml::from_str::<Outer>(&MERGED_YAZI).unwrap().preview;
check_validation(preview.validate());
let preview = toml::from_str::<Outer>(s)?.preview;
preview.validate()?;
let cache_dir =
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() {
std::fs::create_dir(&cache_dir).expect("Failed to create cache directory");
}
Preview {
Ok(Preview {
tab_size: preview.tab_size,
max_width: preview.max_width,
max_height: preview.max_height,
@ -69,14 +77,6 @@ impl Default for Preview {
ueberzug_scale: preview.ueberzug_scale,
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 validator::Validate;
use crate::{validation::check_validation, MERGED_YAZI};
#[derive(Debug, Deserialize, Validate)]
pub struct Tasks {
#[validate(range(min = 1, message = "Cannot be less than 1"))]
@ -18,16 +18,18 @@ pub struct Tasks {
pub suppress_preload: bool,
}
impl Default for Tasks {
fn default() -> Self {
impl FromStr for Tasks {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)]
struct Outer {
tasks: Tasks,
}
let tasks = toml::from_str::<Outer>(&MERGED_YAZI).unwrap().tasks;
check_validation(tasks.validate());
let tasks = toml::from_str::<Outer>(s)?.tasks;
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 {
match self {
Self::None => true,
Self::Block => cha.is_block_device(),
Self::Char => cha.is_char_device(),
Self::Block => cha.is_block(),
Self::Char => cha.is_char(),
Self::Exec => cha.is_exec(),
Self::Fifo => cha.is_fifo(),
Self::Link => cha.is_link(),
Self::Orphan => cha.is_orphan(),
Self::Sock => cha.is_socket(),
Self::Sock => cha.is_sock(),
Self::Sticky => cha.is_sticky(),
}
}

View file

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

View file

@ -1,11 +1,10 @@
use std::path::PathBuf;
use std::{path::PathBuf, str::FromStr};
use serde::{Deserialize, Serialize};
use validator::Validate;
use yazi_shared::{fs::expand_path, theme::Style, Xdg};
use super::{Filetype, Flavor, Icon};
use crate::{validation::check_validation, MERGED_THEME};
use super::{Filetype, Flavor, Icons};
#[derive(Deserialize, Serialize)]
pub struct Theme {
@ -23,16 +22,17 @@ pub struct Theme {
// File-specific styles
#[serde(rename = "filetype", deserialize_with = "Filetype::deserialize", skip_serializing)]
pub filetypes: Vec<Filetype>,
#[serde(rename = "icon", deserialize_with = "Icon::deserialize", skip_serializing)]
pub icons: Vec<Icon>,
#[serde(rename = "icon", skip_serializing)]
pub icons: Icons,
}
impl Default for Theme {
fn default() -> Self {
let mut theme: Self = toml::from_str(&MERGED_THEME).unwrap();
impl FromStr for Theme {
type Err = anyhow::Error;
check_validation(theme.manager.validate());
check_validation(theme.which.validate());
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut theme: Self = toml::from_str(s)?;
theme.manager.validate()?;
theme.which.validate()?;
if theme.flavor.use_.is_empty() {
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_));
}
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 validator::Validate;
use super::SortBy;
use crate::MERGED_YAZI;
#[derive(Debug, Deserialize, Serialize, Validate)]
pub struct Which {
@ -10,15 +11,18 @@ pub struct Which {
pub sort_by: SortBy,
pub sort_sensitive: bool,
pub sort_reverse: bool,
pub sort_translit: bool,
}
impl Default for Which {
fn default() -> Self {
impl FromStr for Which {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)]
struct Outer {
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"
[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-config = { path = "../yazi-config", 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" }
# External dependencies
anyhow = "1.0.82"
base64 = "0.22.0"
anyhow = "1.0.86"
bitflags = "2.5.0"
crossterm = "0.27.0"
dirs = "5.0.1"
futures = "0.3.30"
notify = { version = "6.1.1", default-features = false, features = [ "macos_fsevent" ] }
parking_lot = "0.12.1"
ratatui = "=0.26.1"
regex = "1.10.4"
parking_lot = "0.12.3"
ratatui = "0.27.0"
regex = "1.10.5"
scopeguard = "1.2.0"
serde = "1.0.198"
tokio = { version = "1.37.0", features = [ "full" ] }
serde = "1.0.203"
shell-words = "1.1.0"
tokio = { version = "1.38.0", features = [ "full" ] }
tokio-stream = "0.1.15"
tokio-util = "0.7.10"
unicode-width = "0.1.11"
tokio-util = "0.7.11"
unicode-width = "0.1.13"
# Logging
tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] }
[target."cfg(unix)".dependencies]
libc = "0.2.153"
[target."cfg(windows)".dependencies]
clipboard-win = "5.3.1"
libc = "0.2.155"

View file

@ -10,6 +10,9 @@ pub struct Opt {
impl From<Cmd> for Opt {
fn from(c: Cmd) -> Self { Self { submit: c.bool("submit") } }
}
impl From<bool> for Opt {
fn from(submit: bool) -> Self { Self { submit } }
}
impl Completion {
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 yazi_shared::{emit, event::{Cmd, Data}, render, Layer};
@ -33,7 +33,9 @@ impl Completion {
}
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) {
return self.show(
@ -72,12 +74,24 @@ impl Completion {
render!(mem::replace(&mut self.visible, false));
}
#[inline]
fn split_path(s: &str) -> (String, String) {
match s.rsplit_once(SEPARATOR) {
Some((p, c)) => (format!("{p}{}", MAIN_SEPARATOR), c.to_owned()),
None => (".".to_owned(), s.to_owned()),
fn split_path(s: &str) -> Option<(String, String)> {
if s == "~" {
return None; // We don't autocomplete a `~`, but `~/`
}
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 {
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)]
#[test]
fn test_split() {
assert_eq!(Completion::split_path(""), (".".to_owned(), "".to_owned()));
assert_eq!(Completion::split_path(" "), (".".to_owned(), " ".to_owned()));
assert_eq!(Completion::split_path("/"), ("/".to_owned(), "".to_owned()));
assert_eq!(Completion::split_path("//"), ("//".to_owned(), "".to_owned()));
assert_eq!(Completion::split_path("/foo"), ("/".to_owned(), "foo".to_owned()));
assert_eq!(Completion::split_path("/foo/"), ("/foo/".to_owned(), "".to_owned()));
assert_eq!(Completion::split_path("/foo/bar"), ("/foo/".to_owned(), "bar".to_owned()));
assert!(compare("", ".", ""));
assert!(compare(" ", ".", " "));
assert!(compare("/", "/", ""));
assert!(compare("//", "//", ""));
assert!(compare("/foo", "/", "foo"));
assert!(compare("/foo/", "/foo/", ""));
assert!(compare("/foo/bar", "/foo/", "bar"));
}
#[cfg(windows)]
#[test]
fn test_split() {
assert_eq!(Completion::split_path("foo"), (".".to_owned(), "foo".to_owned()));
assert_eq!(Completion::split_path("foo\\"), ("foo\\".to_owned(), "".to_owned()));
assert_eq!(Completion::split_path("foo\\bar"), ("foo\\".to_owned(), "bar".to_owned()));
assert_eq!(Completion::split_path("foo\\bar\\"), ("foo\\bar\\".to_owned(), "".to_owned()));
assert_eq!(Completion::split_path("C:\\"), ("C:\\".to_owned(), "".to_owned()));
assert_eq!(Completion::split_path("C:\\foo"), ("C:\\".to_owned(), "foo".to_owned()));
assert_eq!(Completion::split_path("C:\\foo\\"), ("C:\\foo\\".to_owned(), "".to_owned()));
assert_eq!(Completion::split_path("C:\\foo\\bar"), ("C:\\foo\\".to_owned(), "bar".to_owned()));
assert!(compare("foo", ".", "foo"));
assert!(compare("foo\\", "foo\\", ""));
assert!(compare("foo\\bar", "foo\\", "bar"));
assert!(compare("foo\\bar\\", "foo\\bar\\", ""));
assert!(compare("C:\\", "C:\\", ""));
assert!(compare("C:\\foo", "C:\\", "foo"));
assert!(compare("C:\\foo\\", "C:\\foo\\", ""));
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 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};
@ -101,7 +101,7 @@ impl Files {
Ok(m) if mtime == m.modified().ok() => {}
Ok(m) => return Some(m),
Err(e) => {
if accessible(url).await {
if maybe_exists(url).await {
FilesOp::IOErr(url.clone(), e.kind()).emit();
} else if let Some(p) = url.parent_url() {
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 regex::bytes::{Regex, RegexBuilder};
@ -9,10 +9,6 @@ pub struct Filter {
regex: Regex,
}
impl PartialEq for Filter {
fn eq(&self, other: &Self) -> bool { self.raw == other.raw }
}
impl Filter {
pub fn new(s: &str, case: FilterCase) -> Result<Self> {
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)]
pub enum FilterCase {
Smart,

View file

@ -1,7 +1,7 @@
use std::{cmp::Ordering, collections::HashMap, mem};
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)]
pub struct FilesSorter {
@ -9,6 +9,7 @@ pub struct FilesSorter {
pub sensitive: bool,
pub reverse: bool,
pub dir_first: bool,
pub translit: bool,
}
impl FilesSorter {
@ -76,7 +77,16 @@ impl FilesSorter {
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 }
});

View file

@ -1,7 +1,8 @@
use crossterm::event::KeyCode;
use unicode_width::UnicodeWidthStr;
use yazi_adapter::Dimension;
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 crate::input::Input;
@ -22,7 +23,7 @@ pub struct Help {
impl Help {
#[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) {
self.visible = !self.visible;
@ -43,15 +44,15 @@ impl Help {
};
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;
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;
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);
}
_ => {
@ -106,7 +107,7 @@ impl Help {
return None;
}
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
}

View file

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

View file

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

View file

@ -3,10 +3,10 @@ use std::ops::Range;
use tokio::sync::mpsc::UnboundedSender;
use unicode_width::UnicodeWidthStr;
use yazi_config::{popup::Position, INPUT};
use yazi_plugin::CLIPBOARD;
use yazi_shared::{render, InputError};
use super::{mode::InputMode, op::InputOp, InputSnap, InputSnaps};
use crate::CLIPBOARD;
#[derive(Default)]
pub struct Input {
@ -87,16 +87,17 @@ impl Input {
}
pub(super) fn flush_value(&mut self) {
let Some(tx) = &self.callback else { return };
self.ticket = self.ticket.wrapping_add(1);
if self.realtime {
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 {
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 {
if self.versions.len() <= self.idx {
return false;
}
// Sync *current* cursor position to the *last* version:
// Save offset/cursor/ect. of the *current* as the last version,
// while keeping the *last* value unchanged.
@ -49,7 +53,7 @@ impl InputSnaps {
}
pub(super) fn redo(&mut self) -> bool {
if self.idx + 1 == self.versions.len() {
if self.idx + 1 >= self.versions.len() {
return false;
}

View file

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

View file

@ -4,8 +4,9 @@ use anyhow::{anyhow, Result};
use scopeguard::defer;
use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}};
use yazi_config::{OPEN, PREVIEW};
use yazi_dds::Pubsub;
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;
@ -51,7 +52,7 @@ impl Manager {
old: Vec<PathBuf>,
new: Vec<PathBuf>,
) -> Result<()> {
Term::clear(&mut stderr())?;
terminal_clear(&mut stderr())?;
if old.len() != new.len() {
eprintln!("Number of old and new differ, press ENTER to exit");
stdin().read_exact(&mut [0]).await?;
@ -83,7 +84,7 @@ impl Manager {
for (o, n) in todo {
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")));
} else if let Err(e) = fs::rename(&old, &new).await {
failed.push((o, n, e.into()));
@ -95,6 +96,7 @@ impl Manager {
}
if !succeeded.is_empty() {
Pubsub::pub_from_bulk(succeeded.iter().map(|(u, f)| (u, &f.url)).collect());
FilesOp::Upserting(cwd, succeeded).emit();
}
drop(permit);
@ -106,7 +108,7 @@ impl Manager {
}
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());

View file

@ -1,9 +1,10 @@
use std::path::PathBuf;
use std::collections::HashMap;
use anyhow::Result;
use tokio::fs;
use yazi_config::popup::InputCfg;
use yazi_proxy::{InputProxy, ManagerProxy};
use yazi_shared::{event::Cmd, fs::{accessible, File, FilesOp, Url}};
use yazi_proxy::{InputProxy, TabProxy, WATCHER};
use yazi_shared::{event::Cmd, fs::{maybe_exists, ok_or_not_found, symlink_realpath, File, FilesOp, Url}};
use crate::manager::Manager;
@ -24,29 +25,42 @@ impl Manager {
let Some(Ok(name)) = result.recv().await else {
return Ok(());
};
if name.is_empty() {
return Ok(());
}
let path = cwd.join(&name);
if !opt.force && accessible(&path).await {
let new = cwd.join(&name);
if !opt.force && maybe_exists(&new).await {
match InputProxy::show(InputCfg::overwrite()).recv().await {
Some(Ok(c)) if c == "y" || c == "Y" => (),
_ => return Ok(()),
}
}
if name.ends_with('/') || name.ends_with('\\') {
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>(())
Self::create_do(new, name.ends_with('/') || name.ends_with('\\')).await
});
}
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