From 1ab3df6850640c034a624566abcc8ef968c77a89 Mon Sep 17 00:00:00 2001 From: George Nelson <79223278+clispios@users.noreply.github.com> Date: Sun, 26 May 2024 09:10:33 -0500 Subject: [PATCH] feat!: transliteration option for natural sorting (#1053) Co-authored-by: sxyazi --- cspell.json | 2 +- yazi-config/preset/keymap.toml | 24 +- yazi-config/preset/yazi.toml | 8 +- yazi-config/src/keymap/run.rs | 37 +- yazi-config/src/manager/manager.rs | 1 + yazi-config/src/which/which.rs | 1 + yazi-core/src/folder/sorter.rs | 14 +- yazi-core/src/tab/commands/sort.rs | 11 +- yazi-core/src/tab/config.rs | 3 + yazi-core/src/which/sorter.rs | 15 +- yazi-fm/src/lives/config.rs | 1 + yazi-shared/src/event/cmd.rs | 5 + yazi-shared/src/event/data.rs | 2 + yazi-shared/src/lib.rs | 2 + yazi-shared/src/natsort.rs | 6 +- yazi-shared/src/translit/mod.rs | 5 + yazi-shared/src/translit/table.rs | 762 +++++++++++++++++++++++++++++ yazi-shared/src/translit/traits.rs | 69 +++ 18 files changed, 909 insertions(+), 59 deletions(-) create mode 100644 yazi-shared/src/translit/mod.rs create mode 100644 yazi-shared/src/translit/table.rs create mode 100644 yazi-shared/src/translit/traits.rs diff --git a/cspell.json b/cspell.json index fddaad50..2bbe9f40 100644 --- a/cspell.json +++ b/cspell.json @@ -1 +1 @@ -{"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"],"language":"en","version":"0.2","flagWords":[]} \ No newline at end of file +{"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"],"version":"0.2","flagWords":[],"language":"en"} \ No newline at end of file diff --git a/yazi-config/preset/keymap.toml b/yazi-config/preset/keymap.toml index 22344d7b..110376ca 100644 --- a/yazi-config/preset/keymap.toml +++ b/yazi-config/preset/keymap.toml @@ -106,18 +106,18 @@ keymap = [ { on = [ "N" ], run = "find_arrow --previous", desc = "Go to previous found file" }, # 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" }, diff --git a/yazi-config/preset/yazi.toml b/yazi-config/preset/yazi.toml index 5d4a81e3..a1641428 100644 --- a/yazi-config/preset/yazi.toml +++ b/yazi-config/preset/yazi.toml @@ -6,8 +6,9 @@ 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 @@ -185,9 +186,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 diff --git a/yazi-config/src/keymap/run.rs b/yazi-config/src/keymap/run.rs index 65307a9c..645d2349 100644 --- a/yazi-config/src/keymap/run.rs +++ b/yazi-config/src/keymap/run.rs @@ -1,8 +1,8 @@ -use std::{fmt, mem}; +use std::{fmt, str::FromStr}; -use anyhow::{bail, Result}; +use anyhow::Result; use serde::{de::{self, Visitor}, Deserializer}; -use yazi_shared::event::{Cmd, Data}; +use yazi_shared::event::Cmd; pub(super) fn run_deserialize<'de, D>(deserializer: D) -> Result, D::Error> where @@ -10,33 +10,6 @@ where { struct RunVisitor; - #[allow(clippy::explicit_counter_loop)] - fn parse(s: &str) -> Result { - 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; @@ -50,7 +23,7 @@ where { let mut cmds = vec![]; while let Some(value) = &seq.next_element::()? { - cmds.push(parse(value).map_err(de::Error::custom)?); + 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")); @@ -62,7 +35,7 @@ where where E: de::Error, { - Ok(vec![parse(value).map_err(de::Error::custom)?]) + Ok(vec![Cmd::from_str(value).map_err(de::Error::custom)?]) } } diff --git a/yazi-config/src/manager/manager.rs b/yazi-config/src/manager/manager.rs index 27f4acaa..9386a9dd 100644 --- a/yazi-config/src/manager/manager.rs +++ b/yazi-config/src/manager/manager.rs @@ -13,6 +13,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"))] diff --git a/yazi-config/src/which/which.rs b/yazi-config/src/which/which.rs index b8e3305c..7d738870 100644 --- a/yazi-config/src/which/which.rs +++ b/yazi-config/src/which/which.rs @@ -10,6 +10,7 @@ pub struct Which { pub sort_by: SortBy, pub sort_sensitive: bool, pub sort_reverse: bool, + pub sort_translit: bool, } impl Default for Which { diff --git a/yazi-core/src/folder/sorter.rs b/yazi-core/src/folder/sorter.rs index 5130ac68..eb7079b7 100644 --- a/yazi-core/src/folder/sorter.rs +++ b/yazi-core/src/folder/sorter.rs @@ -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 } }); diff --git a/yazi-core/src/tab/commands/sort.rs b/yazi-core/src/tab/commands/sort.rs index dcf27d8a..1325eaea 100644 --- a/yazi-core/src/tab/commands/sort.rs +++ b/yazi-core/src/tab/commands/sort.rs @@ -8,12 +8,15 @@ use crate::{tab::Tab, tasks::Tasks}; impl Tab { pub fn sort(&mut self, mut c: Cmd, tasks: &Tasks) { + let conf = &mut self.conf; if let Some(by) = c.take_first_str() { - self.conf.sort_by = SortBy::from_str(&by).unwrap_or_default(); + conf.sort_by = SortBy::from_str(&by).unwrap_or_default(); } - self.conf.sort_sensitive = c.bool("sensitive"); - self.conf.sort_reverse = c.bool("reverse"); - self.conf.sort_dir_first = c.bool("dir-first"); + + conf.sort_reverse = c.maybe_bool("reverse").unwrap_or(conf.sort_reverse); + conf.sort_dir_first = c.maybe_bool("dir-first").unwrap_or(conf.sort_dir_first); + conf.sort_sensitive = c.maybe_bool("sensitive").unwrap_or(conf.sort_sensitive); + conf.sort_translit = c.maybe_bool("translit").unwrap_or(conf.sort_translit); self.apply_files_attrs(); ManagerProxy::update_paged(); diff --git a/yazi-core/src/tab/config.rs b/yazi-core/src/tab/config.rs index 9ac497e2..edce7fb7 100644 --- a/yazi-core/src/tab/config.rs +++ b/yazi-core/src/tab/config.rs @@ -9,6 +9,7 @@ pub struct Config { pub sort_sensitive: bool, pub sort_reverse: bool, pub sort_dir_first: bool, + pub sort_translit: bool, // Display pub linemode: String, @@ -23,6 +24,7 @@ impl Default for Config { sort_sensitive: MANAGER.sort_sensitive, sort_reverse: MANAGER.sort_reverse, sort_dir_first: MANAGER.sort_dir_first, + sort_translit: MANAGER.sort_translit, // Display linemode: MANAGER.linemode.to_owned(), @@ -45,6 +47,7 @@ impl Config { sensitive: self.sort_sensitive, reverse: self.sort_reverse, dir_first: self.sort_dir_first, + translit: self.sort_translit, } } } diff --git a/yazi-core/src/which/sorter.rs b/yazi-core/src/which/sorter.rs index 571201be..d2dc8dfb 100644 --- a/yazi-core/src/which/sorter.rs +++ b/yazi-core/src/which/sorter.rs @@ -1,13 +1,14 @@ use std::{borrow::Cow, mem}; use yazi_config::{keymap::ControlCow, which::SortBy, WHICH}; -use yazi_shared::natsort; +use yazi_shared::{natsort, Transliterator}; #[derive(Clone, Copy, PartialEq)] pub struct WhichSorter { pub by: SortBy, pub sensitive: bool, pub reverse: bool, + pub translit: bool, } impl Default for WhichSorter { @@ -16,6 +17,7 @@ impl Default for WhichSorter { by: WHICH.sort_by, sensitive: WHICH.sort_sensitive, reverse: WHICH.sort_reverse, + translit: WHICH.sort_translit, } } } @@ -38,7 +40,16 @@ impl WhichSorter { } indices.sort_unstable_by(|&a, &b| { - let ordering = natsort(entities[a].as_bytes(), entities[b].as_bytes(), !self.sensitive); + let ordering = if !self.translit { + natsort(entities[a].as_bytes(), entities[b].as_bytes(), !self.sensitive) + } else { + natsort( + entities[a].as_bytes().transliterate().as_bytes(), + entities[b].as_bytes().transliterate().as_bytes(), + !self.sensitive, + ) + }; + if self.reverse { ordering.reverse() } else { ordering } }); diff --git a/yazi-fm/src/lives/config.rs b/yazi-fm/src/lives/config.rs index ccdb5490..aa2f598b 100644 --- a/yazi-fm/src/lives/config.rs +++ b/yazi-fm/src/lives/config.rs @@ -26,6 +26,7 @@ impl Config { reg.add_field_method_get("sort_sensitive", |_, me| Ok(me.sort_sensitive)); reg.add_field_method_get("sort_reverse", |_, me| Ok(me.sort_reverse)); reg.add_field_method_get("sort_dir_first", |_, me| Ok(me.sort_dir_first)); + reg.add_field_method_get("sort_translit", |_, me| Ok(me.sort_translit)); reg.add_field_method_get("linemode", |_, me| Ok(me.linemode.to_owned())); reg.add_field_method_get("show_hidden", |_, me| Ok(me.show_hidden)); diff --git a/yazi-shared/src/event/cmd.rs b/yazi-shared/src/event/cmd.rs index f9708e10..37c9f54c 100644 --- a/yazi-shared/src/event/cmd.rs +++ b/yazi-shared/src/event/cmd.rs @@ -60,6 +60,11 @@ impl Cmd { self.args.get(name).and_then(Data::as_bool).unwrap_or(false) } + #[inline] + pub fn maybe_bool(&self, name: &str) -> Option { + self.args.get(name).and_then(Data::as_bool) + } + #[inline] pub fn first(&self) -> Option<&Data> { self.args.get("0") } diff --git a/yazi-shared/src/event/data.rs b/yazi-shared/src/event/data.rs index 927b7567..2b833e7e 100644 --- a/yazi-shared/src/event/data.rs +++ b/yazi-shared/src/event/data.rs @@ -25,6 +25,8 @@ impl Data { pub fn as_bool(&self) -> Option { match self { Self::Boolean(b) => Some(*b), + Self::String(s) if s == "no" => Some(false), + Self::String(s) if s == "yes" => Some(true), _ => None, } } diff --git a/yazi-shared/src/lib.rs b/yazi-shared/src/lib.rs index b9dfdcbd..1cef2c7a 100644 --- a/yazi-shared/src/lib.rs +++ b/yazi-shared/src/lib.rs @@ -16,6 +16,7 @@ pub mod term; pub mod theme; mod throttle; mod time; +mod translit; mod xdg; pub use chars::*; @@ -31,6 +32,7 @@ pub use os::*; pub use ro_cell::*; pub use throttle::*; pub use time::*; +pub use translit::*; pub use xdg::*; pub fn init() { event::Event::init(); } diff --git a/yazi-shared/src/natsort.rs b/yazi-shared/src/natsort.rs index 70e25969..0a3a7c98 100644 --- a/yazi-shared/src/natsort.rs +++ b/yazi-shared/src/natsort.rs @@ -135,12 +135,12 @@ mod tests { #[test] fn test_natsort() { - let dates = vec!["1999-3-3", "1999-12-25", "2000-1-2", "2000-1-10", "2000-3-23"]; - let fractions = vec![ + let dates = ["1999-3-3", "1999-12-25", "2000-1-2", "2000-1-10", "2000-3-23"]; + let fractions = [ "1.002.01", "1.002.03", "1.002.08", "1.009.02", "1.009.10", "1.009.20", "1.010.12", "1.011.02", ]; - let words = vec![ + let words = [ "1-02", "1-2", "1-20", diff --git a/yazi-shared/src/translit/mod.rs b/yazi-shared/src/translit/mod.rs new file mode 100644 index 00000000..a51cf385 --- /dev/null +++ b/yazi-shared/src/translit/mod.rs @@ -0,0 +1,5 @@ +mod table; +mod traits; + +use table::*; +pub use traits::*; diff --git a/yazi-shared/src/translit/table.rs b/yazi-shared/src/translit/table.rs new file mode 100644 index 00000000..1a5fa832 --- /dev/null +++ b/yazi-shared/src/translit/table.rs @@ -0,0 +1,762 @@ +const TABLE_0: [&str; 496] = [ + "A", // À 192 + "A", // Á 193 + "A", //  194 + "A", // à 195 + "A", // Ä 196 + "A", // Å 197 + "AE", // Æ 198 + "C", // Ç 199 + "E", // È 200 + "E", // É 201 + "E", // Ê 202 + "E", // Ë 203 + "I", // Ì 204 + "I", // Í 205 + "I", // Î 206 + "I", // Ï 207 + "D", // Ð 208 + "N", // Ñ 209 + "O", // Ò 210 + "O", // Ó 211 + "O", // Ô 212 + "O", // Õ 213 + "O", // Ö 214 + "×", // × 215 + "O", // Ø 216 + "U", // Ù 217 + "U", // Ú 218 + "U", // Û 219 + "U", // Ü 220 + "Y", // Ý 221 + "T", // Þ 222 + "ss", // ß 223 + "a", // à 224 + "a", // á 225 + "a", // â 226 + "a", // ã 227 + "a", // ä 228 + "a", // å 229 + "ae", // æ 230 + "c", // ç 231 + "e", // è 232 + "e", // é 233 + "e", // ê 234 + "e", // ë 235 + "i", // ì 236 + "i", // í 237 + "i", // î 238 + "i", // ï 239 + "d", // ð 240 + "n", // ñ 241 + "o", // ò 242 + "o", // ó 243 + "o", // ô 244 + "o", // õ 245 + "o", // ö 246 + "÷", // ÷ 247 + "o", // ø 248 + "u", // ù 249 + "u", // ú 250 + "u", // û 251 + "u", // ü 252 + "y", // ý 253 + "t", // þ 254 + "y", // ÿ 255 + "A", // Ā 256 + "a", // ā 257 + "A", // Ă 258 + "a", // ă 259 + "A", // Ą 260 + "a", // ą 261 + "C", // Ć 262 + "c", // ć 263 + "C", // Ĉ 264 + "c", // ĉ 265 + "C", // Ċ 266 + "c", // ċ 267 + "C", // Č 268 + "c", // č 269 + "D", // Ď 270 + "d", // ď 271 + "D", // Đ 272 + "d", // đ 273 + "E", // Ē 274 + "e", // ē 275 + "E", // Ĕ 276 + "e", // ĕ 277 + "E", // Ė 278 + "e", // ė 279 + "E", // Ę 280 + "e", // ę 281 + "E", // Ě 282 + "e", // ě 283 + "G", // Ĝ 284 + "g", // ĝ 285 + "G", // Ğ 286 + "g", // ğ 287 + "G", // Ġ 288 + "g", // ġ 289 + "G", // Ģ 290 + "g", // ģ 291 + "H", // Ĥ 292 + "h", // ĥ 293 + "H", // Ħ 294 + "h", // ħ 295 + "I", // Ĩ 296 + "i", // ĩ 297 + "I", // Ī 298 + "i", // ī 299 + "I", // Ĭ 300 + "i", // ĭ 301 + "I", // Į 302 + "i", // į 303 + "I", // İ 304 + "i", // ı 305 + "IJ", // IJ 306 + "ij", // ij 307 + "J", // Ĵ 308 + "j", // ĵ 309 + "K", // Ķ 310 + "k", // ķ 311 + "k", // ĸ 312 + "L", // Ĺ 313 + "l", // ĺ 314 + "L", // Ļ 315 + "l", // ļ 316 + "L", // Ľ 317 + "l", // ľ 318 + "L", // Ŀ 319 + "l", // ŀ 320 + "L", // Ł 321 + "l", // ł 322 + "N", // Ń 323 + "n", // ń 324 + "N", // Ņ 325 + "n", // ņ 326 + "N", // Ň 327 + "n", // ň 328 + "n", // ʼn 329 + "N", // Ŋ 330 + "n", // ŋ 331 + "O", // Ō 332 + "o", // ō 333 + "O", // Ŏ 334 + "o", // ŏ 335 + "O", // Ő 336 + "o", // ő 337 + "OE", // Œ 338 + "oe", // œ 339 + "R", // Ŕ 340 + "r", // ŕ 341 + "R", // Ŗ 342 + "r", // ŗ 343 + "R", // Ř 344 + "r", // ř 345 + "S", // Ś 346 + "s", // ś 347 + "S", // Ŝ 348 + "s", // ŝ 349 + "S", // Ş 350 + "s", // ş 351 + "S", // Š 352 + "s", // š 353 + "T", // Ţ 354 + "t", // ţ 355 + "T", // Ť 356 + "t", // ť 357 + "T", // Ŧ 358 + "t", // ŧ 359 + "U", // Ũ 360 + "u", // ũ 361 + "U", // Ū 362 + "u", // ū 363 + "U", // Ŭ 364 + "u", // ŭ 365 + "U", // Ů 366 + "u", // ů 367 + "U", // Ű 368 + "u", // ű 369 + "U", // Ų 370 + "u", // ų 371 + "W", // Ŵ 372 + "w", // ŵ 373 + "Y", // Ŷ 374 + "y", // ŷ 375 + "Y", // Ÿ 376 + "Z", // Ź 377 + "z", // ź 378 + "Z", // Ż 379 + "z", // ż 380 + "Z", // Ž 381 + "z", // ž 382 + "s", // ſ 383 + "ƀ", // ƀ 384 + "B", // Ɓ 385 + "Ƃ", // Ƃ 386 + "ƃ", // ƃ 387 + "Ƅ", // Ƅ 388 + "ƅ", // ƅ 389 + "O", // Ɔ 390 + "Ƈ", // Ƈ 391 + "ƈ", // ƈ 392 + "Ɖ", // Ɖ 393 + "D", // Ɗ 394 + "Ƌ", // Ƌ 395 + "ƌ", // ƌ 396 + "ƍ", // ƍ 397 + "Ǝ", // Ǝ 398 + "E", // Ə 399 + "E", // Ɛ 400 + "F", // Ƒ 401 + "f", // ƒ 402 + "Ɠ", // Ɠ 403 + "Ɣ", // Ɣ 404 + "ƕ", // ƕ 405 + "Ɩ", // Ɩ 406 + "Ɨ", // Ɨ 407 + "K", // Ƙ 408 + "k", // ƙ 409 + "l", // ƚ 410 + "l", // ƛ 411 + "Ɯ", // Ɯ 412 + "N", // Ɲ 413 + "ƞ", // ƞ 414 + "O", // Ɵ 415 + "O", // Ơ 416 + "o", // ơ 417 + "Ƣ", // Ƣ 418 + "ƣ", // ƣ 419 + "Ƥ", // Ƥ 420 + "ƥ", // ƥ 421 + "Ʀ", // Ʀ 422 + "Ƨ", // Ƨ 423 + "ƨ", // ƨ 424 + "Ʃ", // Ʃ 425 + "ƪ", // ƪ 426 + "ƫ", // ƫ 427 + "Ƭ", // Ƭ 428 + "ƭ", // ƭ 429 + "Ʈ", // Ʈ 430 + "U", // Ư 431 + "u", // ư 432 + "Ʊ", // Ʊ 433 + "Ʋ", // Ʋ 434 + "Y", // Ƴ 435 + "y", // ƴ 436 + "Z", // Ƶ 437 + "z", // ƶ 438 + "Ʒ", // Ʒ 439 + "Ƹ", // Ƹ 440 + "ƹ", // ƹ 441 + "ƺ", // ƺ 442 + "ƻ", // ƻ 443 + "Ƽ", // Ƽ 444 + "ƽ", // ƽ 445 + "ƾ", // ƾ 446 + "ƿ", // ƿ 447 + "ǀ", // ǀ 448 + "ǁ", // ǁ 449 + "ǂ", // ǂ 450 + "ǃ", // ǃ 451 + "DZ", // DŽ 452 + "Dz", // Dž 453 + "dz", // dž 454 + "LJ", // LJ 455 + "Lj", // Lj 456 + "lj", // lj 457 + "NJ", // NJ 458 + "Nj", // Nj 459 + "nj", // nj 460 + "A", // Ǎ 461 + "a", // ǎ 462 + "I", // Ǐ 463 + "i", // ǐ 464 + "O", // Ǒ 465 + "o", // ǒ 466 + "U", // Ǔ 467 + "u", // ǔ 468 + "U", // Ǖ 469 + "u", // ǖ 470 + "U", // Ǘ 471 + "u", // ǘ 472 + "U", // Ǚ 473 + "u", // ǚ 474 + "U", // Ǜ 475 + "u", // ǜ 476 + "ǝ", // ǝ 477 + "Ǟ", // Ǟ 478 + "ǟ", // ǟ 479 + "Ǡ", // Ǡ 480 + "ǡ", // ǡ 481 + "Ǣ", // Ǣ 482 + "ǣ", // ǣ 483 + "Ǥ", // Ǥ 484 + "ǥ", // ǥ 485 + "G", // Ǧ 486 + "g", // ǧ 487 + "Ǩ", // Ǩ 488 + "ǩ", // ǩ 489 + "O", // Ǫ 490 + "o", // ǫ 491 + "Ǭ", // Ǭ 492 + "ǭ", // ǭ 493 + "Ǯ", // Ǯ 494 + "e", // ǯ 495 + "j", // ǰ 496 + "DZ", // DZ 497 + "Dz", // Dz 498 + "dz", // dz 499 + "G", // Ǵ 500 + "g", // ǵ 501 + "Ƕ", // Ƕ 502 + "Ƿ", // Ƿ 503 + "N", // Ǹ 504 + "n", // ǹ 505 + "A", // Ǻ 506 + "a", // ǻ 507 + "AE", // Ǽ 508 + "ae", // ǽ 509 + "O", // Ǿ 510 + "o", // ǿ 511 + "Ȁ", // Ȁ 512 + "ȁ", // ȁ 513 + "Ȃ", // Ȃ 514 + "ȃ", // ȃ 515 + "Ȅ", // Ȅ 516 + "ȅ", // ȅ 517 + "Ȇ", // Ȇ 518 + "ȇ", // ȇ 519 + "Ȉ", // Ȉ 520 + "ȉ", // ȉ 521 + "Ȋ", // Ȋ 522 + "ȋ", // ȋ 523 + "Ȍ", // Ȍ 524 + "ȍ", // ȍ 525 + "Ȏ", // Ȏ 526 + "ȏ", // ȏ 527 + "Ȑ", // Ȑ 528 + "ȑ", // ȑ 529 + "Ȓ", // Ȓ 530 + "ȓ", // ȓ 531 + "Ȕ", // Ȕ 532 + "ȕ", // ȕ 533 + "Ȗ", // Ȗ 534 + "ȗ", // ȗ 535 + "S", // Ș 536 + "s", // ș 537 + "T", // Ț 538 + "t", // ț 539 + "Ȝ", // Ȝ 540 + "ȝ", // ȝ 541 + "Ȟ", // Ȟ 542 + "ȟ", // ȟ 543 + "Ƞ", // Ƞ 544 + "ȡ", // ȡ 545 + "Ȣ", // Ȣ 546 + "ȣ", // ȣ 547 + "Ȥ", // Ȥ 548 + "ȥ", // ȥ 549 + "Ȧ", // Ȧ 550 + "ȧ", // ȧ 551 + "Ȩ", // Ȩ 552 + "ȩ", // ȩ 553 + "Ȫ", // Ȫ 554 + "ȫ", // ȫ 555 + "Ȭ", // Ȭ 556 + "ȭ", // ȭ 557 + "Ȯ", // Ȯ 558 + "ȯ", // ȯ 559 + "Ȱ", // Ȱ 560 + "ȱ", // ȱ 561 + "Y", // Ȳ 562 + "y", // ȳ 563 + "ȴ", // ȴ 564 + "ȵ", // ȵ 565 + "ȶ", // ȶ 566 + "j", // ȷ 567 + "ȸ", // ȸ 568 + "ȹ", // ȹ 569 + "Ⱥ", // Ⱥ 570 + "Ȼ", // Ȼ 571 + "ȼ", // ȼ 572 + "L", // Ƚ 573 + "Ⱦ", // Ⱦ 574 + "ȿ", // ȿ 575 + "ɀ", // ɀ 576 + "Ɂ", // Ɂ 577 + "ɂ", // ɂ 578 + "Ƀ", // Ƀ 579 + "Ʉ", // Ʉ 580 + "Ʌ", // Ʌ 581 + "Ɇ", // Ɇ 582 + "ɇ", // ɇ 583 + "Ɉ", // Ɉ 584 + "ɉ", // ɉ 585 + "Ɋ", // Ɋ 586 + "ɋ", // ɋ 587 + "Ɍ", // Ɍ 588 + "ɍ", // ɍ 589 + "Ɏ", // Ɏ 590 + "ɏ", // ɏ 591 + "a", // ɐ 592 + "a", // ɑ 593 + "a", // ɒ 594 + "b", // ɓ 595 + "o", // ɔ 596 + "c", // ɕ 597 + "d", // ɖ 598 + "d", // ɗ 599 + "e", // ɘ 600 + "e", // ə 601 + "e", // ɚ 602 + "o", // ɛ 603 + "e", // ɜ 604 + "e", // ɝ 605 + "e", // ɞ 606 + "j", // ɟ 607 + "g", // ɠ 608 + "g", // ɡ 609 + "ɢ", // ɢ 610 + "g", // ɣ 611 + "ɤ", // ɤ 612 + "h", // ɥ 613 + "h", // ɦ 614 + "h", // ɧ 615 + "i", // ɨ 616 + "i", // ɩ 617 + "ɪ", // ɪ 618 + "l", // ɫ 619 + "l", // ɬ 620 + "l", // ɭ 621 + "le", // ɮ 622 + "m", // ɯ 623 + "m", // ɰ 624 + "m", // ɱ 625 + "n", // ɲ 626 + "n", // ɳ 627 + "ɴ", // ɴ 628 + "o", // ɵ 629 + "OE", // ɶ 630 + "ɷ", // ɷ 631 + "p", // ɸ 632 + "r", // ɹ 633 + "r", // ɺ 634 + "r", // ɻ 635 + "r", // ɼ 636 + "r", // ɽ 637 + "r", // ɾ 638 + "r", // ɿ 639 + "ʀ", // ʀ 640 + "R", // ʁ 641 + "s", // ʂ 642 + "s", // ʃ 643 + "j", // ʄ 644 + "s", // ʅ 645 + "s", // ʆ 646 + "t", // ʇ 647 + "t", // ʈ 648 + "u", // ʉ 649 + "u", // ʊ 650 + "v", // ʋ 651 + "v", // ʌ 652 + "w", // ʍ 653 + "y", // ʎ 654 + "ʏ", // ʏ 655 + "z", // ʐ 656 + "z", // ʑ 657 + "e", // ʒ 658 + "e", // ʓ 659 + "ʔ", // ʔ 660 + "ʕ", // ʕ 661 + "ʖ", // ʖ 662 + "C", // ʗ 663 + "o", // ʘ 664 + "ʙ", // ʙ 665 + "e", // ʚ 666 + "G", // ʛ 667 + "ʜ", // ʜ 668 + "j", // ʝ 669 + "k", // ʞ 670 + "ʟ", // ʟ 671 + "q", // ʠ 672 + "ʡ", // ʡ 673 + "ʢ", // ʢ 674 + "dz", // ʣ 675 + "de", // ʤ 676 + "dz", // ʥ 677 + "ts", // ʦ 678 + "ts", // ʧ 679 + "tc", // ʨ 680 + "fn", // ʩ 681 + "ls", // ʪ 682 + "lz", // ʫ 683 + "W", // ʬ 684 + "ʭ", // ʭ 685 + "h", // ʮ 686 + "h", // ʯ 687 +]; + +const TABLE_1: [&str; 246] = [ + "B", // Ḅ 7684 + "b", // ḅ 7685 + "Ḇ", // Ḇ 7686 + "ḇ", // ḇ 7687 + "Ḉ", // Ḉ 7688 + "ḉ", // ḉ 7689 + "Ḋ", // Ḋ 7690 + "ḋ", // ḋ 7691 + "D", // Ḍ 7692 + "d", // ḍ 7693 + "D", // Ḏ 7694 + "d", // ḏ 7695 + "Ḑ", // Ḑ 7696 + "ḑ", // ḑ 7697 + "D", // Ḓ 7698 + "d", // ḓ 7699 + "Ḕ", // Ḕ 7700 + "ḕ", // ḕ 7701 + "Ḗ", // Ḗ 7702 + "ḗ", // ḗ 7703 + "Ḙ", // Ḙ 7704 + "ḙ", // ḙ 7705 + "Ḛ", // Ḛ 7706 + "ḛ", // ḛ 7707 + "Ḝ", // Ḝ 7708 + "ḝ", // ḝ 7709 + "Ḟ", // Ḟ 7710 + "ḟ", // ḟ 7711 + "G", // Ḡ 7712 + "g", // ḡ 7713 + "Ḣ", // Ḣ 7714 + "ḣ", // ḣ 7715 + "H", // Ḥ 7716 + "h", // ḥ 7717 + "Ḧ", // Ḧ 7718 + "ḧ", // ḧ 7719 + "Ḩ", // Ḩ 7720 + "ḩ", // ḩ 7721 + "H", // Ḫ 7722 + "h", // ḫ 7723 + "Ḭ", // Ḭ 7724 + "ḭ", // ḭ 7725 + "Ḯ", // Ḯ 7726 + "ḯ", // ḯ 7727 + "Ḱ", // Ḱ 7728 + "ḱ", // ḱ 7729 + "K", // Ḳ 7730 + "k", // ḳ 7731 + "K", // Ḵ 7732 + "k", // ḵ 7733 + "L", // Ḷ 7734 + "l", // ḷ 7735 + "L", // Ḹ 7736 + "l", // ḹ 7737 + "L", // Ḻ 7738 + "l", // ḻ 7739 + "L", // Ḽ 7740 + "l", // ḽ 7741 + "M", // Ḿ 7742 + "m", // ḿ 7743 + "M", // Ṁ 7744 + "m", // ṁ 7745 + "M", // Ṃ 7746 + "m", // ṃ 7747 + "N", // Ṅ 7748 + "n", // ṅ 7749 + "N", // Ṇ 7750 + "n", // ṇ 7751 + "N", // Ṉ 7752 + "n", // ṉ 7753 + "N", // Ṋ 7754 + "n", // ṋ 7755 + "Ṍ", // Ṍ 7756 + "ṍ", // ṍ 7757 + "Ṏ", // Ṏ 7758 + "ṏ", // ṏ 7759 + "Ṑ", // Ṑ 7760 + "ṑ", // ṑ 7761 + "Ṓ", // Ṓ 7762 + "ṓ", // ṓ 7763 + "Ṕ", // Ṕ 7764 + "ṕ", // ṕ 7765 + "Ṗ", // Ṗ 7766 + "ṗ", // ṗ 7767 + "R", // Ṙ 7768 + "r", // ṙ 7769 + "R", // Ṛ 7770 + "r", // ṛ 7771 + "R", // Ṝ 7772 + "r", // ṝ 7773 + "R", // Ṟ 7774 + "r", // ṟ 7775 + "S", // Ṡ 7776 + "s", // ṡ 7777 + "S", // Ṣ 7778 + "s", // ṣ 7779 + "Ṥ", // Ṥ 7780 + "ṥ", // ṥ 7781 + "Ṧ", // Ṧ 7782 + "ṧ", // ṧ 7783 + "Ṩ", // Ṩ 7784 + "ṩ", // ṩ 7785 + "Ṫ", // Ṫ 7786 + "ṫ", // ṫ 7787 + "T", // Ṭ 7788 + "t", // ṭ 7789 + "T", // Ṯ 7790 + "t", // ṯ 7791 + "T", // Ṱ 7792 + "t", // ṱ 7793 + "Ṳ", // Ṳ 7794 + "ṳ", // ṳ 7795 + "Ṵ", // Ṵ 7796 + "ṵ", // ṵ 7797 + "Ṷ", // Ṷ 7798 + "ṷ", // ṷ 7799 + "Ṹ", // Ṹ 7800 + "ṹ", // ṹ 7801 + "Ṻ", // Ṻ 7802 + "ṻ", // ṻ 7803 + "Ṽ", // Ṽ 7804 + "ṽ", // ṽ 7805 + "Ṿ", // Ṿ 7806 + "ṿ", // ṿ 7807 + "W", // Ẁ 7808 + "w", // ẁ 7809 + "W", // Ẃ 7810 + "w", // ẃ 7811 + "W", // Ẅ 7812 + "w", // ẅ 7813 + "Ẇ", // Ẇ 7814 + "ẇ", // ẇ 7815 + "Ẉ", // Ẉ 7816 + "ẉ", // ẉ 7817 + "Ẋ", // Ẋ 7818 + "ẋ", // ẋ 7819 + "Ẍ", // Ẍ 7820 + "ẍ", // ẍ 7821 + "Y", // Ẏ 7822 + "y", // ẏ 7823 + "Ẑ", // Ẑ 7824 + "ẑ", // ẑ 7825 + "Z", // Ẓ 7826 + "z", // ẓ 7827 + "Z", // Ẕ 7828 + "z", // ẕ 7829 + "h", // ẖ 7830 + "t", // ẗ 7831 + "ẘ", // ẘ 7832 + "ẙ", // ẙ 7833 + "ẚ", // ẚ 7834 + "ẛ", // ẛ 7835 + "ẜ", // ẜ 7836 + "ẝ", // ẝ 7837 + "SS", // ẞ 7838 + "ẟ", // ẟ 7839 + "A", // Ạ 7840 + "a", // ạ 7841 + "A", // Ả 7842 + "a", // ả 7843 + "A", // Ấ 7844 + "a", // ấ 7845 + "A", // Ầ 7846 + "a", // ầ 7847 + "A", // Ẩ 7848 + "a", // ẩ 7849 + "A", // Ẫ 7850 + "a", // ẫ 7851 + "A", // Ậ 7852 + "a", // ậ 7853 + "A", // Ắ 7854 + "a", // ắ 7855 + "A", // Ằ 7856 + "a", // ằ 7857 + "A", // Ẳ 7858 + "a", // ẳ 7859 + "A", // Ẵ 7860 + "a", // ẵ 7861 + "A", // Ặ 7862 + "a", // ặ 7863 + "E", // Ẹ 7864 + "e", // ẹ 7865 + "E", // Ẻ 7866 + "e", // ẻ 7867 + "E", // Ẽ 7868 + "e", // ẽ 7869 + "E", // Ế 7870 + "e", // ế 7871 + "E", // Ề 7872 + "e", // ề 7873 + "E", // Ể 7874 + "e", // ể 7875 + "E", // Ễ 7876 + "e", // ễ 7877 + "E", // Ệ 7878 + "e", // ệ 7879 + "I", // Ỉ 7880 + "i", // ỉ 7881 + "I", // Ị 7882 + "i", // ị 7883 + "O", // Ọ 7884 + "o", // ọ 7885 + "O", // Ỏ 7886 + "o", // ỏ 7887 + "O", // Ố 7888 + "o", // ố 7889 + "O", // Ồ 7890 + "o", // ồ 7891 + "O", // Ổ 7892 + "o", // ổ 7893 + "O", // Ỗ 7894 + "o", // ỗ 7895 + "O", // Ộ 7896 + "o", // ộ 7897 + "O", // Ớ 7898 + "o", // ớ 7899 + "O", // Ờ 7900 + "o", // ờ 7901 + "O", // Ở 7902 + "o", // ở 7903 + "O", // Ỡ 7904 + "o", // ỡ 7905 + "O", // Ợ 7906 + "o", // ợ 7907 + "U", // Ụ 7908 + "u", // ụ 7909 + "U", // Ủ 7910 + "u", // ủ 7911 + "U", // Ứ 7912 + "u", // ứ 7913 + "U", // Ừ 7914 + "u", // ừ 7915 + "U", // Ử 7916 + "u", // ử 7917 + "U", // Ữ 7918 + "u", // ữ 7919 + "U", // Ự 7920 + "u", // ự 7921 + "Y", // Ỳ 7922 + "y", // ỳ 7923 + "Y", // Ỵ 7924 + "y", // ỵ 7925 + "Y", // Ỷ 7926 + "y", // ỷ 7927 + "Y", // Ỹ 7928 + "y", // ỹ 7929 +]; + +const TABLE_2: [&str; 2] = [ + "fi", // fi 64257 + "fl", // fl 64258 +]; + +#[inline(always)] +pub(super) fn lookup(c: char) -> Option<&'static str> { + match c as u16 { + 192..=687 => unsafe { Some(TABLE_0.get_unchecked((c as u16 - 192) as usize)) }, + 7684..=7929 => unsafe { Some(TABLE_1.get_unchecked((c as u16 - 7684) as usize)) }, + 64257..=64258 => unsafe { Some(TABLE_2.get_unchecked((c as u16 - 64257) as usize)) }, + _ => None, + } +} diff --git a/yazi-shared/src/translit/traits.rs b/yazi-shared/src/translit/traits.rs new file mode 100644 index 00000000..3f862e48 --- /dev/null +++ b/yazi-shared/src/translit/traits.rs @@ -0,0 +1,69 @@ +use core::str; +use std::borrow::Cow; + +pub trait Transliterator { + fn transliterate(&self) -> Cow; +} + +impl Transliterator for &[u8] { + fn transliterate(&self) -> Cow { + // Fast path to skip over ASCII chars at the beginning of the string + let ascii_len = self.iter().take_while(|&&c| c < 0x7f).count(); + if ascii_len >= self.len() { + return Cow::Borrowed(unsafe { str::from_utf8_unchecked(self) }); + } + + let (ascii, rest) = self.split_at(ascii_len); + let ascii = unsafe { str::from_utf8_unchecked(ascii) }; + + // Reserve a bit more space to avoid reallocations on longer transliterations + // but instead of `+ 16` uses `| 15` to stay in the smallest allocation bucket + // for short strings + let mut out = String::new(); + out.try_reserve_exact(self.len() | 15).unwrap_or_else(|_| panic!()); + out.push_str(ascii); + + for c in String::from_utf8_lossy(rest).chars() { + if let Some(s) = super::lookup(c) { + out.push_str(s); + } else { + out.push(c); + } + } + Cow::Owned(out) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_transliterate() { + assert_eq!("Æcœ".as_bytes().transliterate(), "AEcoe"); + assert_eq!( + "ěřůøĉĝĥĵŝŭèùÿėįųāēīūļķņģőűëïąćęłńśźżõșțčďĺľňŕšťýžéíñóúüåäöçîşûğăâđêôơưáàãảạ" + .as_bytes() + .transliterate(), + "eruocghjsueuyeiuaeiulkngoueiacelnszzostcdllnrstyzeinouuaaocisugaadeoouaaaaa", + ); + assert_eq!( + "áạàảãăắặằẳẵâấậầẩẫéẹèẻẽêếệềểễiíịìỉĩoóọòỏõôốộồổỗơớợờởỡúụùủũưứựừửữyýỵỳỷỹđ" + .as_bytes() + .transliterate(), + "aaaaaaaaaaaaaaaaaeeeeeeeeeeeiiiiiioooooooooooooooooouuuuuuuuuuuyyyyyyd", + ); + assert_ne!( + "ěřůøĉĝĥĵŝŭèùÿėįųāēīūļķņģőűëïąćęłńśźżõșțčďĺľňŕšťýžéíñóúüåäöçîşûğăâđêôơưáàãảạfifl" + .as_bytes() + .transliterate(), + "ěřůøĉĝĥĵŝŭèùÿėįųāēīūļķņģőűëïąćęłńśźżõșțčďĺľňŕšťýžéíñóúüåäöçîşûğăâđêôơưáàãảạfifl" + ); + assert_eq!( + "THEQUICKBROWNFOXJUMPEDOVERTHELAZYDOGthequickbrownfoxjumpedoverthelazydog" + .as_bytes() + .transliterate(), + "THEQUICKBROWNFOXJUMPEDOVERTHELAZYDOGthequickbrownfoxjumpedoverthelazydog" + ); + } +}