diff --git a/Cargo.lock b/Cargo.lock index 2b53889a..ecd39549 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1022,17 +1022,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "document-features" version = "0.2.12" @@ -1297,15 +1286,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - [[package]] name = "fsevent-sys" version = "4.1.0" @@ -1472,19 +1452,6 @@ version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" -[[package]] -name = "git2" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" -dependencies = [ - "bitflags 2.10.0", - "libc", - "libgit2-sys", - "log", - "url", -] - [[package]] name = "globset" version = "0.4.18" @@ -1655,135 +1622,12 @@ dependencies = [ "cc", ] -[[package]] -name = "icu_collections" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" -dependencies = [ - "displaydoc", - "potential_utf", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" - -[[package]] -name = "icu_properties" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "potential_utf", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" - -[[package]] -name = "icu_provider" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" -dependencies = [ - "displaydoc", - "icu_locale_core", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "ignore" -version = "0.4.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" -dependencies = [ - "crossbeam-deque", - "globset", - "log", - "memchr", - "regex-automata", - "same-file", - "walkdir", - "winapi-util", -] - [[package]] name = "image" version = "0.25.8" @@ -2088,18 +1932,6 @@ dependencies = [ "cc", ] -[[package]] -name = "libgit2-sys" -version = "0.17.0+1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224" -dependencies = [ - "cc", - "libc", - "libz-sys", - "pkg-config", -] - [[package]] name = "libm" version = "0.2.15" @@ -2116,18 +1948,6 @@ dependencies = [ "libc", ] -[[package]] -name = "libz-sys" -version = "1.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -2140,12 +1960,6 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" -[[package]] -name = "litemap" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" - [[package]] name = "litrs" version = "1.0.0" @@ -2879,15 +2693,6 @@ dependencies = [ "universal-hash", ] -[[package]] -name = "potential_utf" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" -dependencies = [ - "zerovec", -] - [[package]] name = "powerfmt" version = "0.2.0" @@ -3729,12 +3534,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - [[package]] name = "static_assertions" version = "1.1.0" @@ -3786,17 +3585,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "syntect" version = "5.3.0" @@ -3944,16 +3732,6 @@ dependencies = [ "time-core", ] -[[package]] -name = "tinystr" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" -dependencies = [ - "displaydoc", - "zerovec", -] - [[package]] name = "tokio" version = "1.48.0" @@ -4242,30 +4020,12 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "url" -version = "2.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - [[package]] name = "urlencoding" version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - [[package]] name = "utf8parse" version = "0.2.2" @@ -4310,12 +4070,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "vergen" version = "9.0.6" @@ -4874,12 +4628,6 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" -[[package]] -name = "writeable" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" - [[package]] name = "wyz" version = "0.5.1" @@ -5164,9 +4912,7 @@ dependencies = [ "core-foundation-sys", "dirs", "foldhash 0.2.0", - "git2", "hashbrown 0.16.0", - "ignore", "libc", "objc2", "parking_lot", @@ -5414,30 +5160,6 @@ dependencies = [ "yazi-term", ] -[[package]] -name = "yoke" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - [[package]] name = "zerocopy" version = "0.8.27" @@ -5458,66 +5180,12 @@ dependencies = [ "syn", ] -[[package]] -name = "zerofrom" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - [[package]] name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" -[[package]] -name = "zerotrie" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "zune-core" version = "0.4.12" diff --git a/Cargo.toml b/Cargo.toml index 2a7d9ae6..55fb0ce3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,23 +1,23 @@ [workspace] -resolver = "2" -members = ["yazi-*"] -default-members = ["yazi-fm", "yazi-cli"] +resolver = "2" +members = [ "yazi-*" ] +default-members = [ "yazi-fm", "yazi-cli" ] [profile.release] codegen-units = 1 -lto = true -panic = "abort" -strip = true +lto = true +panic = "abort" +strip = true [profile.release-windows] inherits = "release" -panic = "unwind" +panic = "unwind" [profile.dev-opt] -inherits = "release" +inherits = "release" codegen-units = 256 -incremental = true -lto = false +incremental = true +lto = false [workspace.dependencies] ansi-to-tui = "7.0.0" diff --git a/yazi-actor/src/mgr/ignore.rs b/yazi-actor/src/mgr/ignore.rs index 3ba1344e..e6c7ff42 100644 --- a/yazi-actor/src/mgr/ignore.rs +++ b/yazi-actor/src/mgr/ignore.rs @@ -18,8 +18,6 @@ impl Actor for Ignore { const NAME: &str = "ignore"; fn act(cx: &mut Ctx, _: Self::Options) -> Result { - let gitignores = YAZI.files.gitignores; - // Get the appropriate context string for matching exclude rules // For search directories, use "search://**" as the context let cwd = cx.cwd(); @@ -28,119 +26,24 @@ impl Actor for Ignore { } else { cwd.as_path().map(|p| p.display().to_string()).unwrap_or_default() }; - + let exclude_patterns = YAZI.files.excludes_for_context(&cwd_str); // Create glob matcher function for compiled patterns - let glob_matcher = { - let context = cwd_str.clone(); - Arc::new(move |path: &std::path::Path| YAZI.files.matches_path(path, &context)) - }; - - // If gitignores is disabled but we have exclude patterns, apply them - if !gitignores && !exclude_patterns.is_empty() { - // Load ignore filter from exclude patterns only - let ignore_filter = if let Some(path) = cx.cwd().as_path() { - IgnoreFilter::from_patterns(path, &exclude_patterns, Some(glob_matcher.clone())) + let glob_matcher: Option Option + Send + Sync>> = + if !exclude_patterns.is_empty() { + let context = cwd_str.clone(); + Some(Arc::new(move |path: &std::path::Path| YAZI.files.matches_path(path, &context))) } else { None }; - let hovered = cx.hovered().map(|f| f.urn().to_owned()); - let apply = |f: &mut Folder, filter: Option| { - // Always set the filter, even when loading - let changed = f.files.set_ignore_filter(filter); - if f.stage == FolderStage::Loading { - render!(); - false - } else { - render_and!(changed && f.files.catchup_revision()) - } - }; - - // Apply to CWD - if apply(cx.current_mut(), ignore_filter.clone()) { - act!(mgr:hover, cx)?; - act!(mgr:update_paged, cx)?; - } - - // Apply to hovered - if let Some(h) = cx.hovered_folder_mut() { - let hovered_str = if h.url.is_search() { - "search://**".to_string() - } else { - h.url.as_path().map(|p| p.display().to_string()).unwrap_or_default() - }; - let hovered_excludes = YAZI.files.excludes_for_context(&hovered_str); - let hovered_matcher = { - let context = hovered_str.clone(); - Arc::new(move |path: &std::path::Path| YAZI.files.matches_path(path, &context)) - }; - let hovered_filter = if let Some(path) = h.url.as_path() { - IgnoreFilter::from_patterns(path, &hovered_excludes, Some(hovered_matcher)) - } else { - None - }; - - if apply(h, hovered_filter) { - render!(h.repos(None)); - act!(mgr:peek, cx, true)?; - } else if hovered.as_deref() != cx.hovered().map(|f| f.urn()) { - act!(mgr:peek, cx)?; - act!(mgr:watch, cx)?; - } - } - - succ!(); - } - - // If gitignores is disabled and no exclude patterns, remove any ignore filter - if !gitignores { - let hovered = cx.hovered().map(|f| f.urn().to_owned()); - let apply = |f: &mut Folder| { - // Always clear the filter, even when loading - let changed = f.files.set_ignore_filter(None); - if f.stage == FolderStage::Loading { - render!(); - false - } else { - render_and!(changed && f.files.catchup_revision()) - } - }; - - // Apply to CWD and parent - if let (a, Some(b)) = (apply(cx.current_mut()), cx.parent_mut().map(apply)) - && (a | b) - { - act!(mgr:hover, cx)?; - act!(mgr:update_paged, cx)?; - } - - // Apply to hovered - if let Some(h) = cx.hovered_folder_mut() - && apply(h) - { - render!(h.repos(None)); - act!(mgr:peek, cx, true)?; - } else if hovered.as_deref() != cx.hovered().map(|f| f.urn()) { - act!(mgr:peek, cx)?; - act!(mgr:watch, cx)?; - } - - succ!(); - } - - // Load ignore filter from the current directory - let ignore_filter = if let Some(path) = cx.cwd().as_path() { - IgnoreFilter::from_dir(path, &exclude_patterns, gitignores, Some(glob_matcher.clone())) - } else { - None - }; + // Load ignore filter from exclude patterns + let ignore_filter = IgnoreFilter::from_patterns(glob_matcher.clone()); let hovered = cx.hovered().map(|f| f.urn().to_owned()); let apply = |f: &mut Folder, filter: Option| { - // Always set the filter, even when loading, so files are filtered as they - // arrive + // Always set the filter, even when loading let changed = f.files.set_ignore_filter(filter); if f.stage == FolderStage::Loading { render!(); @@ -156,28 +59,22 @@ impl Actor for Ignore { act!(mgr:update_paged, cx)?; } - // Apply to parent (they should use their own ignore file, so we don't apply the - // same filter) Parent folders will be updated when they become the current - // directory - // Apply to hovered if let Some(h) = cx.hovered_folder_mut() { - // Load ignore filter for hovered directory if it's a directory let hovered_str = if h.url.is_search() { "search://**".to_string() } else { h.url.as_path().map(|p| p.display().to_string()).unwrap_or_default() }; let hovered_excludes = YAZI.files.excludes_for_context(&hovered_str); - let hovered_matcher = { - let context = hovered_str.clone(); - Arc::new(move |path: &std::path::Path| YAZI.files.matches_path(path, &context)) - }; - let hovered_filter = if let Some(path) = h.url.as_path() { - IgnoreFilter::from_dir(path, &hovered_excludes, gitignores, Some(hovered_matcher)) - } else { - None - }; + let hovered_matcher: Option Option + Send + Sync>> = + if !hovered_excludes.is_empty() { + let context = hovered_str.clone(); + Some(Arc::new(move |path: &std::path::Path| YAZI.files.matches_path(path, &context))) + } else { + None + }; + let hovered_filter = IgnoreFilter::from_patterns(hovered_matcher); if apply(h, hovered_filter) { render!(h.repos(None)); diff --git a/yazi-config/preset/yazi-default.toml b/yazi-config/preset/yazi-default.toml index b9b8b479..120c654e 100644 --- a/yazi-config/preset/yazi-default.toml +++ b/yazi-config/preset/yazi-default.toml @@ -16,128 +16,103 @@ scrolloff = 5 mouse_events = [ "click", "scroll" ] title_format = "Yazi: {cwd}" -[files] -# Enable git ignore integration -# gitignores = false +[preview] +wrap = "no" +tab_size = 2 +max_width = 600 +max_height = 900 +cache_dir = "" +image_delay = 30 +image_filter = "triangle" +image_quality = 75 +ueberzug_scale = 1 +ueberzug_offset = [ 0, 0, 0, 0 ] + +[files] # Context-specific exclude patterns # Patterns are compiled into glob matchers for efficient matching # Patterns starting with '!' negate (whitelist) previous matches +# Note: To match directories, use patterns like ".git/" or "**/.git/**" # excludes = [ # # SFTP temporary files # { urn = "*.tmp", in = "sftp://**" }, -# # Python cache in search results +# # Python cache in search results # { urn = "/root/**/*.pyc", in = "search://**" }, # # Multiple patterns for /code directory (supports arrays) -# { urn = [".git", ".DS_Store", "__pycache__"], in = "/code/**" }, -# # Negation: show target/ even if previously ignored -# { urn = "!target/", in = "/code/**" }, -# # Fallback rule for all contexts -# { urn = ".DS_Store", in = "*" }, +# { urn = [".git", ".DS_Store", "__pycache__"], in = "*" }, +# # Exclude release and debug directories inside target/ +# { urn = "release", in = "**/target" }, +# # Exclude all json files from all contexts +# { urn = "*.json", in = "*" }, +# # Negation: Show cspell.* even if it's excluded by a previous rule +# { urn = "!cspell*", in = "search://**" }, # ] -[preview] -wrap = "no" -tab_size = 2 -max_width = 600 -max_height = 900 -cache_dir = "" -image_delay = 30 -image_filter = "triangle" -image_quality = 75 -ueberzug_scale = 1 -ueberzug_offset = [0, 0, 0, 0] - [opener] edit = [ - { run = "${EDITOR:-vi} %s", desc = "$EDITOR", for = "unix", block = true }, - { run = "code %s", desc = "code", for = "windows", orphan = true }, - { run = "code -w %s", desc = "code (block)", for = "windows", block = true }, + { run = "${EDITOR:-vi} %s", desc = "$EDITOR", for = "unix", block = true }, + { run = "code %s", desc = "code", for = "windows", orphan = true }, + { run = "code -w %s", desc = "code (block)", for = "windows", block = true }, ] play = [ - { run = "xdg-open %s1", desc = "Play", for = "linux" }, - { run = "open %s", desc = "Play", for = "macos" }, + { run = "xdg-open %s1", desc = "Play", for = "linux" }, + { run = "open %s", desc = "Play", for = "macos" }, { run = 'start "" %s1', orphan = true, desc = "Play", for = "windows" }, - { run = "termux-open %s1", desc = "Play", for = "android" }, + { run = "termux-open %s1", desc = "Play", for = "android" }, { run = "mediainfo %s1; echo 'Press enter to exit'; read _", block = true, desc = "Show media info", for = "unix" }, { run = "mediainfo %s1 & pause", block = true, desc = "Show media info", for = "windows" }, ] open = [ - { run = "xdg-open %s1", desc = "Open", for = "linux" }, - { run = "open %s", desc = "Open", for = "macos" }, - { run = 'start "" %s1', desc = "Open", for = "windows", orphan = true }, + { run = "xdg-open %s1", desc = "Open", for = "linux" }, + { run = "open %s", desc = "Open", for = "macos" }, + { run = 'start "" %s1', desc = "Open", for = "windows", orphan = true }, { run = "termux-open %s1", desc = "Open", for = "android" }, ] reveal = [ - { run = "xdg-open %d1", desc = "Reveal", for = "linux" }, - { run = "open -R %s1", desc = "Reveal", for = "macos" }, + { run = "xdg-open %d1", desc = "Reveal", for = "linux" }, + { run = "open -R %s1", desc = "Reveal", for = "macos" }, { run = "explorer /select,%s1", desc = "Reveal", for = "windows", orphan = true }, - { run = "termux-open %d1", desc = "Reveal", for = "android" }, + { run = "termux-open %d1", desc = "Reveal", for = "android" }, { run = "clear; exiftool %s1; echo 'Press enter to exit'; read _", desc = "Show EXIF", for = "unix", block = true }, ] -extract = [{ run = "ya pub extract --list %s", desc = "Extract here" }] +extract = [ + { run = "ya pub extract --list %s", desc = "Extract here" }, +] download = [ { run = "ya emit download --open %S", desc = "Download and open" }, - { run = "ya emit download %S", desc = "Download" }, + { run = "ya emit download %S", desc = "Download" }, ] [open] rules = [ # Folder - { url = "*/", use = [ - "edit", - "open", - "reveal", - ] }, + { url = "*/", use = [ "edit", "open", "reveal" ] }, # Text - { mime = "text/*", use = [ - "edit", - "reveal", - ] }, + { mime = "text/*", use = [ "edit", "reveal" ] }, # Image - { mime = "image/*", use = [ - "open", - "reveal", - ] }, + { mime = "image/*", use = [ "open", "reveal" ] }, # Media - { mime = "{audio,video}/*", use = [ - "play", - "reveal", - ] }, + { mime = "{audio,video}/*", use = [ "play", "reveal" ] }, # Archive - { mime = "application/{zip,rar,7z*,tar,gzip,xz,zstd,bzip*,lzma,compress,archive,cpio,arj,xar,ms-cab*}", use = [ - "extract", - "reveal", - ] }, + { mime = "application/{zip,rar,7z*,tar,gzip,xz,zstd,bzip*,lzma,compress,archive,cpio,arj,xar,ms-cab*}", use = [ "extract", "reveal" ] }, # JSON - { mime = "application/{json,ndjson}", use = [ - "edit", - "reveal", - ] }, - { mime = "*/javascript", use = [ - "edit", - "reveal", - ] }, + { mime = "application/{json,ndjson}", use = [ "edit", "reveal" ] }, + { mime = "*/javascript", use = [ "edit", "reveal" ] }, # Empty file - { mime = "inode/empty", use = [ - "edit", - "reveal", - ] }, + { mime = "inode/empty", use = [ "edit", "reveal" ] }, # Virtual file system { mime = "vfs/{absent,stale}", use = "download" }, # Fallback - { url = "*", use = [ - "open", - "reveal", - ] }, + { url = "*", use = [ "open", "reveal" ] }, ] [tasks] -micro_workers = 10 -macro_workers = 10 -bizarre_retry = 3 -image_alloc = 536870912 # 512MB -image_bound = [10000, 10000] +micro_workers = 10 +macro_workers = 10 +bizarre_retry = 3 +image_alloc = 536870912 # 512MB +image_bound = [ 10000, 10000 ] suppress_preload = false [plugin] @@ -214,70 +189,70 @@ previewers = [ cursor_blink = false # cd -cd_title = "Change directory:" +cd_title = "Change directory:" cd_origin = "top-center" -cd_offset = [0, 2, 50, 3] +cd_offset = [ 0, 2, 50, 3 ] # create -create_title = ["Create:", "Create (dir):"] +create_title = [ "Create:", "Create (dir):" ] create_origin = "top-center" -create_offset = [0, 2, 50, 3] +create_offset = [ 0, 2, 50, 3 ] # rename -rename_title = "Rename:" +rename_title = "Rename:" rename_origin = "hovered" -rename_offset = [0, 1, 50, 3] +rename_offset = [ 0, 1, 50, 3 ] # filter -filter_title = "Filter:" +filter_title = "Filter:" filter_origin = "top-center" -filter_offset = [0, 2, 50, 3] +filter_offset = [ 0, 2, 50, 3 ] # find -find_title = ["Find next:", "Find previous:"] +find_title = [ "Find next:", "Find previous:" ] find_origin = "top-center" -find_offset = [0, 2, 50, 3] +find_offset = [ 0, 2, 50, 3 ] # search -search_title = "Search via {n}:" +search_title = "Search via {n}:" search_origin = "top-center" -search_offset = [0, 2, 50, 3] +search_offset = [ 0, 2, 50, 3 ] # shell -shell_title = ["Shell:", "Shell (block):"] +shell_title = [ "Shell:", "Shell (block):" ] shell_origin = "top-center" -shell_offset = [0, 2, 50, 3] +shell_offset = [ 0, 2, 50, 3 ] [confirm] # trash -trash_title = "Trash {n} selected file{s}?" -trash_origin = "center" -trash_offset = [0, 0, 70, 20] +trash_title = "Trash {n} selected file{s}?" +trash_origin = "center" +trash_offset = [ 0, 0, 70, 20 ] # delete -delete_title = "Permanently delete {n} selected file{s}?" -delete_origin = "center" -delete_offset = [0, 0, 70, 20] +delete_title = "Permanently delete {n} selected file{s}?" +delete_origin = "center" +delete_offset = [ 0, 0, 70, 20 ] # overwrite -overwrite_title = "Overwrite file?" -overwrite_body = "Will overwrite the following file:" +overwrite_title = "Overwrite file?" +overwrite_body = "Will overwrite the following file:" overwrite_origin = "center" -overwrite_offset = [0, 0, 50, 15] +overwrite_offset = [ 0, 0, 50, 15 ] # quit -quit_title = "Quit?" -quit_body = "The following tasks are still running, are you sure you want to quit?" +quit_title = "Quit?" +quit_body = "The following tasks are still running, are you sure you want to quit?" quit_origin = "center" -quit_offset = [0, 0, 50, 15] +quit_offset = [ 0, 0, 50, 15 ] [pick] -open_title = "Open with:" +open_title = "Open with:" open_origin = "hovered" -open_offset = [0, 1, 50, 7] +open_offset = [ 0, 1, 50, 7 ] [which] -sort_by = "none" +sort_by = "none" sort_sensitive = false -sort_reverse = false -sort_translit = false +sort_reverse = false +sort_translit = false diff --git a/yazi-config/src/files/exclude.rs b/yazi-config/src/files/exclude.rs index ca198d79..ba0f5304 100644 --- a/yazi-config/src/files/exclude.rs +++ b/yazi-config/src/files/exclude.rs @@ -14,8 +14,7 @@ pub struct Exclude { /// Context where this exclude rule applies /// Supports glob patterns like "/code/**", "sftp://**", "search://**", or "*" for all - #[serde(rename = "in")] - pub context: String, + pub r#in: String, #[serde(skip)] compiled: Option, @@ -78,14 +77,39 @@ impl Exclude { pub fn matches_path(&self, path: &Path) -> Option { let compiled = self.compiled.as_ref()?; + // For absolute paths, try both the full path and relative components + // This helps patterns like **/.git/** match /home/user/project/.git + let paths_to_check: Vec<&Path> = if path.is_absolute() { + // Also check each component as if it's relative + // This allows **/.git/** to match /home/user/.git/config + let mut paths = vec![path]; + + // Check if any path component matches by checking relative sub-paths + // For /home/user/.git/config, we want to match against .git/config too + if let Some(components) = path.to_str() { + for (i, _) in components.match_indices('/').skip(1) { + if let Some(subpath) = components.get(i + 1..) { + paths.push(Path::new(subpath)); + } + } + } + paths + } else { + vec![path] + }; + // Check whitelist first (negation takes precedence) - if compiled.whitelists.is_match(path) { - return Some(false); // Explicitly NOT ignored + for p in &paths_to_check { + if compiled.whitelists.is_match(p) { + return Some(false); // Explicitly NOT ignored + } } // Check ignore patterns - if compiled.ignores.is_match(path) { - return Some(true); // Should be ignored + for p in &paths_to_check { + if compiled.ignores.is_match(p) { + return Some(true); // Should be ignored + } } None // No match @@ -94,23 +118,49 @@ impl Exclude { /// Check if this exclude rule applies to the given path context pub fn matches_context(&self, path: &str) -> bool { // Wildcard matches everything - if self.context == "*" { + if self.r#in == "*" { return true; } + // Handle patterns starting with **/ (e.g., **/target or **/target/**) + if self.r#in.starts_with("**/") { + let pattern = &self.r#in[3..]; // Remove leading "**/", e.g., "target" or "target/**" + + // Strip trailing /** if present + let pattern = if let Some(p) = pattern.strip_suffix("/**") { + p + } else { + pattern + }; + + // Check if path ends with the pattern (e.g., /home/user/project/target matches **/target) + if path.ends_with(&format!("/{}", pattern)) || path.ends_with(pattern) { + return true; + } + + // Check if path contains the pattern as a directory segment + if path.contains(&format!("/{}/", pattern)) { + return true; + } + + return false; + } + // Handle glob patterns with wildcard - if self.context.ends_with("/**") { - let prefix = &self.context[..self.context.len() - 3]; + if self.r#in.ends_with("/**") { + let prefix = &self.r#in[..self.r#in.len() - 3]; // Check if path starts with prefix (absolute path match) if path.starts_with(prefix) { return true; } - // Check if path contains the pattern anywhere (for relative patterns like "/target/**") - // This allows "/target/**" to match "/home/user/project/target/debug" + // Check if path contains the pattern anywhere (for relative patterns like + // "/target/**") This allows "/target/**" to match + // "/home/user/project/target/debug" if prefix.starts_with('/') && !prefix.starts_with("//") { - // Single leading slash means relative pattern - check if path contains this segment + // Single leading slash means relative pattern - check if path contains this + // segment let pattern = &prefix[1..]; // Remove leading slash if path.contains(&format!("/{}/", pattern)) || path.ends_with(&format!("/{}", pattern)) { return true; @@ -121,6 +171,6 @@ impl Exclude { } // Exact match or prefix match for non-wildcard patterns - path == self.context || path.starts_with(&format!("{}/", self.context)) + path == self.r#in || path.starts_with(&format!("{}/", self.r#in)) } } diff --git a/yazi-config/src/files/files.rs b/yazi-config/src/files/files.rs index a3e22ab7..33500e3a 100644 --- a/yazi-config/src/files/files.rs +++ b/yazi-config/src/files/files.rs @@ -8,10 +8,6 @@ use super::Exclude; /// Configuration for file filtering #[derive(Debug, Deserialize, DeserializeOver2, Default)] pub struct Files { - /// Enable git ignore integration - #[serde(default)] - pub gitignores: bool, - /// List of exclude rules with context-specific patterns #[serde(default)] pub excludes: Vec, diff --git a/yazi-fs/Cargo.toml b/yazi-fs/Cargo.toml index e4b827b9..edd1e4d7 100644 --- a/yazi-fs/Cargo.toml +++ b/yazi-fs/Cargo.toml @@ -1,46 +1,44 @@ [package] -name = "yazi-fs" -version = "25.9.15" -edition = "2024" -license = "MIT" -authors = ["sxyazi "] +name = "yazi-fs" +version = "25.9.15" +edition = "2024" +license = "MIT" +authors = [ "sxyazi " ] description = "Yazi file system" -homepage = "https://yazi-rs.github.io" -repository = "https://github.com/sxyazi/yazi" +homepage = "https://yazi-rs.github.io" +repository = "https://github.com/sxyazi/yazi" [dependencies] -yazi-ffi = { path = "../yazi-ffi", version = "25.9.15" } -yazi-macro = { path = "../yazi-macro", version = "25.9.15" } +yazi-ffi = { path = "../yazi-ffi", version = "25.9.15" } +yazi-macro = { path = "../yazi-macro", version = "25.9.15" } yazi-shared = { path = "../yazi-shared", version = "25.9.15" } -yazi-shim = { path = "../yazi-shim", version = "25.9.15" } +yazi-shim = { path = "../yazi-shim", version = "25.9.15" } # External dependencies -anyhow = { workspace = true } -arc-swap = "1.7.1" -bitflags = { workspace = true } -dirs = { workspace = true } -foldhash = { workspace = true } -git2 = { workspace = true } -hashbrown = { workspace = true } -ignore = { workspace = true } -parking_lot = { workspace = true } +anyhow = { workspace = true } +arc-swap = "1.7.1" +bitflags = { workspace = true } +dirs = { workspace = true } +foldhash = { workspace = true } +hashbrown = { workspace = true } +parking_lot = { workspace = true } percent-encoding = { workspace = true } -regex = { workspace = true } -scopeguard = { workspace = true } -serde = { workspace = true } -tokio = { workspace = true } -tracing = { workspace = true } +regex = { workspace = true } +scopeguard = { workspace = true } +serde = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } [target."cfg(unix)".dependencies] -libc = { workspace = true } +libc = { workspace = true } uzers = { workspace = true } [target.'cfg(windows)'.dependencies] -windows-sys = { version = "0.61.2", features = ["Win32_Storage_FileSystem"] } +windows-sys = { version = "0.61.2", features = [ "Win32_Storage_FileSystem" ] } [target.'cfg(target_os = "macos")'.dependencies] core-foundation-sys = { workspace = true } -objc = { workspace = true } +objc2 = { workspace = true } [target.'cfg(not(target_os = "android"))'.dependencies] trash = "5.2.5" diff --git a/yazi-fs/src/ignore.rs b/yazi-fs/src/ignore.rs index 0b18ee55..a5803de3 100644 --- a/yazi-fs/src/ignore.rs +++ b/yazi-fs/src/ignore.rs @@ -1,171 +1,46 @@ -use std::{collections::HashSet, path::{Path, PathBuf}, sync::Arc}; +use std::{path::Path, sync::Arc}; use yazi_shared::url::AsUrl; -/// Filter for ignoring files based on git ignore rules and custom exclude -/// patterns. +/// Filter for ignoring files based on custom exclude patterns. /// -/// This filter combines git's native ignore functionality with custom exclude -/// patterns that follow gitignore syntax. Exclude patterns can be -/// context-specific and take precedence over git's ignore rules using the `!` -/// prefix for negation. +/// Exclude patterns can be context-specific and support negation using the `!` +/// prefix for whitelisting. #[derive(Clone)] pub struct IgnoreFilter { - /// Set of paths ignored by git (from git status) - ignored_paths: HashSet, - /// Custom gitignore matcher for exclude patterns - gitignore: Option, - /// Custom glob-based matcher function for advanced pattern matching - /// Returns Some(true) if should be ignored, Some(false) if whitelisted, None if no match - glob_matcher: Option Option + Send + Sync>>, + /// Custom glob-based matcher function for pattern matching + /// Returns Some(true) if should be ignored, Some(false) if whitelisted, None + /// if no match + glob_matcher: Option Option + Send + Sync>>, } impl std::fmt::Debug for IgnoreFilter { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("IgnoreFilter") - .field("ignored_paths", &self.ignored_paths) - .field("gitignore", &self.gitignore) .field("glob_matcher", &self.glob_matcher.as_ref().map(|_| "Some(...)")) .finish() } } impl IgnoreFilter { - /// Creates a new `IgnoreFilter` by checking git ignore status for the given - /// directory. + /// Creates a new `IgnoreFilter` from exclude patterns. /// /// # Arguments /// - /// * `dir` - Directory to check for git ignore status - /// * `exclude_patterns` - Custom gitignore patterns that override git's rules - /// * `use_git` - Whether to integrate with git ignore rules + /// * `glob_matcher` - Custom glob-based matcher function for advanced pattern + /// matching /// /// # Returns /// - /// `Some(IgnoreFilter)` if git repository is found (when use_git=true) or - /// exclude patterns exist, `None` if no git repository and no exclude - /// patterns. - /// - /// # Behavior - /// - /// - Discovers the git repository containing `dir` (if use_git=true) - /// - Collects all ignored paths from git status - /// - Builds a custom gitignore matcher from exclude patterns (relative to - /// repo root) - /// - Exclude patterns are checked first and take precedence over git ignore - /// rules - /// - Negation patterns (starting with `!`) can whitelist git-ignored files - pub fn from_dir( - dir: impl AsRef, - exclude_patterns: &[String], - use_git: bool, - glob_matcher: Option Option + Send + Sync>>, - ) -> Option { - let dir = dir.as_ref(); - - let (workdir, ignored_paths) = if use_git { - // Try to open the git repository for this directory - let repo = git2::Repository::discover(dir).ok()?; - - // Get the workdir (root of the git repository) - let workdir = repo.workdir()?.to_path_buf(); - - // Get git statuses for the repository - let statuses = match repo.statuses(None) { - Ok(s) => s, - Err(_) => return None, - }; - - // Build a set of ALL ignored paths from git status - let mut ignored_paths = HashSet::new(); - - // Manually add .git directory as ignored (like eza does) - ignored_paths.insert(workdir.join(".git")); - - // Add all ignored files from git status - for status in statuses.iter() { - if status.status() == git2::Status::IGNORED - && let Some(path) = status.path() - { - ignored_paths.insert(workdir.join(path)); - } - } - - (workdir, ignored_paths) - } else { - // No git integration, use current directory as base - (dir.to_path_buf(), HashSet::new()) - }; - - // Build custom gitignore from exclude patterns if provided - let gitignore = if !exclude_patterns.is_empty() { - let mut builder = ignore::gitignore::GitignoreBuilder::new(&workdir); - - // Add each exclude pattern - for pattern in exclude_patterns { - let _ = builder.add_line(None, pattern); - } - - builder.build().ok() - } else { - None - }; - - if ignored_paths.is_empty() - || (ignored_paths.len() == 1 && ignored_paths.contains(&workdir.join(".git"))) - { - // If we have no git-ignored paths but we have exclude patterns or glob matcher, still create - // the filter - if gitignore.is_some() || glob_matcher.is_some() { - return Some(Self { ignored_paths, gitignore, glob_matcher }); - } - return None; - } - - // Store ALL ignored paths, not just the ones in the current directory - // This way, when files are loaded later, we can check them against the full set - Some(Self { ignored_paths, gitignore, glob_matcher }) - } - - /// Creates a new `IgnoreFilter` from only exclude patterns without git - /// integration. - /// - /// # Arguments - /// - /// * `dir` - Base directory for pattern matching - /// * `patterns` - Gitignore-style patterns to apply - /// - /// # Returns - /// - /// `Some(IgnoreFilter)` if patterns are provided, `None` if patterns is - /// empty. - /// - /// This is useful when `gitignores = false` but custom exclude patterns - /// are still desired. + /// `Some(IgnoreFilter)` if glob_matcher is provided, `None` otherwise. pub fn from_patterns( - dir: impl AsRef, - patterns: &[String], glob_matcher: Option Option + Send + Sync>>, ) -> Option { - if patterns.is_empty() && glob_matcher.is_none() { + if glob_matcher.is_none() { return None; } - let gitignore = if !patterns.is_empty() { - let dir = dir.as_ref(); - let mut builder = ignore::gitignore::GitignoreBuilder::new(dir); - - // Add each pattern - for pattern in patterns { - let _ = builder.add_line(None, pattern); - } - - builder.build().ok() - } else { - None - }; - - Some(Self { ignored_paths: HashSet::new(), gitignore, glob_matcher }) + Some(Self { glob_matcher }) } /// Checks if a file should be ignored based on its URL. @@ -180,83 +55,19 @@ impl IgnoreFilter { /// /// # Matching Logic /// - /// 1. Check override patterns first: - /// - If matched as whitelist (negation `!`), return `false` (not ignored) - /// - If matched as ignore, return `true` (ignored) - /// 2. Check if path is directly in git's ignored set - /// 3. Check if any parent directory is ignored by git - /// - If parent is ignored, also check if override patterns whitelist it - /// - If parent is whitelisted, children are not ignored - /// - /// This ensures override patterns take precedence and negation patterns work - /// correctly even when navigating inside ignored directories. + /// Uses the glob matcher to check if the path should be ignored. + /// Returns true if matched as ignore, false if whitelisted or no match. pub fn matches_url(&self, url: impl AsUrl) -> bool { let url = url.as_url(); let path = url.loc.as_path(); - // First check glob matcher (highest priority) + // Check glob matcher if let Some(ref matcher) = self.glob_matcher { if let Some(should_ignore) = matcher(path) { return should_ignore; } } - // Then check if override patterns apply (they can negate ignores) - // Override patterns take absolute priority - if let Some(ref gitignore) = self.gitignore { - let matched = gitignore.matched(path, path.is_dir()); - match matched { - ignore::Match::None => { - // No override pattern matched, fall through to git ignore check - } - ignore::Match::Ignore(_) => { - // Override pattern says to ignore - return true; - } - ignore::Match::Whitelist(_) => { - // Override pattern says NOT to ignore (negation pattern like !target/) - // This takes precedence over git ignore - return false; - } - } - } - - // Check exact match first - if self.ignored_paths.contains(path) { - return true; - } - - // Check if any parent directory is ignored - // BUT also check if parent is whitelisted by override patterns - let mut current = path; - while let Some(parent) = current.parent() { - if self.ignored_paths.contains(parent) { - // Parent is ignored by git, but check if override patterns whitelist it - if let Some(ref gitignore) = self.gitignore { - let matched = gitignore.matched(parent, true); // parent is always a directory - if matches!(matched, ignore::Match::Whitelist(_)) { - // Parent is whitelisted, so children should not be ignored - return false; - } - } - return true; - } - current = parent; - } - false } - - /// Check if a path should be ignored - pub fn matches_path(&self, path: &Path) -> bool { self.ignored_paths.contains(path) } -} - -#[cfg(test)] -mod tests { - #[test] - fn test_git_repo_discovery() { - // This test assumes we're running in the yazi git repo - let current_dir = std::env::current_dir().unwrap(); - assert!(git2::Repository::discover(¤t_dir).is_ok()); - } }