diff --git a/.zed/settings.json b/.zed/settings.json deleted file mode 100644 index 2cc7b98..0000000 --- a/.zed/settings.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "format_on_save": "on", - "lsp": { - "rust-analyzer": { - "initialization_options": { - "check": { - "command": "clippy", - }, - "rustfmt": { - "extraArgs": ["+nightly"], - }, - }, - }, - }, -} diff --git a/Cargo.lock b/Cargo.lock index 8b4a940..e574618 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -255,7 +255,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -266,7 +266,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -1361,11 +1361,10 @@ dependencies = [ [[package]] name = "cosmic-files" -version = "1.0.13" +version = "1.0.11" dependencies = [ "anyhow", "atomic_float", - "bstr", "bzip2", "compio", "cosmic-client-toolkit", @@ -1429,7 +1428,7 @@ dependencies = [ [[package]] name = "cosmic-files-applet" -version = "1.0.13" +version = "1.0.11" dependencies = [ "cosmic-files", "log", @@ -1814,7 +1813,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -1985,7 +1984,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -2463,7 +2462,7 @@ dependencies = [ "libc", "log", "rustversion", - "windows-link 0.2.1", + "windows-link 0.1.3", "windows-result 0.4.1", ] @@ -2592,7 +2591,7 @@ dependencies = [ "gobject-sys", "libc", "system-deps", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -3885,7 +3884,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde_core", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -4227,7 +4226,7 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a1886916523694cd6ea3d175f03a1e5010699a2a4cc13696d83d7bea1d80638" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -4883,7 +4882,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -5198,9 +5197,9 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "open" -version = "5.3.5" +version = "5.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fbaa89d2ddc8473c78a3adf69eea8cffa28c483b8e02a971ef31527cd0fc92c" +checksum = "43bb73a7fa3799b198970490a51174027ba0d4ec504b03cd08caf513d40024bc" dependencies = [ "is-wsl", "libc", @@ -5260,7 +5259,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] @@ -6247,7 +6246,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -6621,7 +6620,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -6839,7 +6838,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix 1.1.4", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -7268,7 +7267,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ "memoffset", "tempfile", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -7977,7 +7976,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 5e4d2c0..a55104f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,16 +1,16 @@ [package] name = "cosmic-files" -version = "1.0.13" +version = "1.0.11" authors = ["Jeremy Soller "] edition = "2024" license = "GPL-3.0-only" -rust-version = "1.93" +rust-version = "1.90" [dependencies] anyhow = "1" jiff = "0.2" jiff-icu = "0.2" -icu = { version = "2.2.0", features = ["compiled_data"] } +icu = { version = "2.1.1", features = ["compiled_data"] } cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "160b086", optional = true } cosmic-mime-apps = { git = "https://github.com/pop-os/cosmic-mime-apps.git", optional = true } dirs = "6.0.0" @@ -24,7 +24,7 @@ log = "0.4" mime_guess = "2" notify-debouncer-full = "0.7" notify-rust = { version = "4", optional = true } -open = "5.3.4" +open = "5.3.3" paste = "1.0" regex = "1" rustc-hash = "2.1" @@ -36,15 +36,15 @@ tokio = { version = "1", features = ["process", "sync"] } trash = { git = "https://github.com/jackpot51/trash-rs.git", branch = "cosmic" } url = "2.5" walkdir = "2.5.0" -wayland-client = { version = "0.31.14", optional = true } +wayland-client = { version = "0.31.13", optional = true } xdg = { version = "3.0", optional = true } xdg-mime = { git = "https://github.com/ebassi/xdg-mime-rs" } # Compression bzip2 = { version = "0.6", optional = true } #TODO: replace with pure Rust crate flate2 = "1.1" -tar = "0.4.45" +tar = "0.4.44" lzma-rust2 = { version = "0.16", optional = true } -ordermap = { version = "1.2.0", features = ["serde"] } +ordermap = { version = "1.1.0", features = ["serde"] } # Internationalization i18n-embed = { version = "0.16", features = [ "fluent-system", @@ -61,11 +61,10 @@ jxl-oxide = { version = "0.12.5", features = ["image"] } num_cpus = "1.17.0" filetime = "0.2" tracing = "0.1.44" -tracing-subscriber = { version = "0.3.23", features = ["env-filter"] } +tracing-subscriber = { version = "0.3.22", features = ["env-filter"] } thiserror = "2.0.18" atomic_float = "1.1.0" num_enum = "0.7.6" -bstr = "1.12.1" # Completion-based IO runtime to enable io_uring / IOCP file IO support. [dependencies.compio] diff --git a/build.rs b/build.rs index 206e897..5edc53b 100644 --- a/build.rs +++ b/build.rs @@ -1,5 +1,4 @@ -use std::path::PathBuf; -use std::{env, fs}; +use std::{env, fs, path::PathBuf}; use xdgen::{App, Context, FluentString}; fn main() { diff --git a/cosmic-files-applet/Cargo.toml b/cosmic-files-applet/Cargo.toml index 8fcf2bd..b20226b 100644 --- a/cosmic-files-applet/Cargo.toml +++ b/cosmic-files-applet/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmic-files-applet" -version = "1.0.13" +version = "1.0.11" edition = "2024" [dependencies] diff --git a/debian/changelog b/debian/changelog index e4697d2..bea1aee 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,15 +1,3 @@ -cosmic-files (1.0.13) noble; urgency=medium - - * Epoch 1.0.13 version update - - -- Jeremy Soller Tue, 12 May 2026 09:39:14 -0600 - -cosmic-files (1.0.12) noble; urgency=medium - - * Epoch 1.0.12 version update - - -- Jeremy Soller Tue, 05 May 2026 10:23:57 -0600 - cosmic-files (1.0.11) noble; urgency=medium * Epoch 1.0.11 version update diff --git a/examples/copy.rs b/examples/copy.rs index 7f37abd..6396209 100644 --- a/examples/copy.rs +++ b/examples/copy.rs @@ -1,8 +1,6 @@ -use cosmic_files::operation::recursive::{Context, Method}; -use cosmic_files::operation::{Controller, ReplaceResult}; -use std::error::Error; -use std::io; -use std::path::PathBuf; +use cosmic_files::operation::recursive::Method; +use cosmic_files::operation::{Controller, ReplaceResult, recursive::Context}; +use std::{error::Error, io, path::PathBuf}; #[compio::main] async fn main() -> Result<(), Box> { diff --git a/examples/dialog.rs b/examples/dialog.rs index df77069..be93576 100644 --- a/examples/dialog.rs +++ b/examples/dialog.rs @@ -1,12 +1,15 @@ -use cosmic::app::{self, Core, Settings, Task}; -use cosmic::iced::{Subscription, window}; -use cosmic::{Application, Element, executor, widget}; +use cosmic::{ + Application, Element, + app::{self, Core, Settings, Task}, + executor, + iced::{Subscription, window}, + widget, +}; use cosmic_files::dialog::{ Dialog, DialogChoice, DialogChoiceOption, DialogFilter, DialogFilterPattern, DialogKind, DialogMessage, DialogResult, DialogSettings, }; -use tracing_subscriber::layer::SubscriberExt; -use tracing_subscriber::util::SubscriberInitExt; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; fn main() -> Result<(), Box> { let log_format = tracing_subscriber::fmt::format() diff --git a/i18n/ar/cosmic_files.ftl b/i18n/ar/cosmic_files.ftl index c4e3292..867e83c 100644 --- a/i18n/ar/cosmic_files.ftl +++ b/i18n/ar/cosmic_files.ftl @@ -372,14 +372,3 @@ show-recents = مجلد الحديثة في الشريط الجانبي clear-recents-history = امحُ التأريخ الحديث copy-path = انسخ المسار mixed = مختلط -context-action = إجراء السياق -context-action-confirm-title = شغِّل "{ $name }"؟ -context-action-confirm-warning = - سيُشغِّل هذا على { $items } { $items -> - [one] عنصر - [two] عنصرين - [few] عناصر - [many] عنصرًا - *[other] عنصر - }. -run = شغِّل diff --git a/i18n/ca/cosmic_files.ftl b/i18n/ca/cosmic_files.ftl index 48e7033..010ddb0 100644 --- a/i18n/ca/cosmic_files.ftl +++ b/i18n/ca/cosmic_files.ftl @@ -74,7 +74,7 @@ name-no-slashes = El nom no pot contenir barres. ## Open/Save Dialog -cancel = Cancel·lar +cancel = Cancel·la create = Crea open = Obre open-file = Obre el fixer @@ -352,4 +352,3 @@ sort-newest-first = Primer més recents sort-oldest-first = Primer més antics sort-smallest-to-largest = De petit a gran sort-largest-to-smallest = De gran a petit -run = Executa diff --git a/i18n/cs/cosmic_files.ftl b/i18n/cs/cosmic_files.ftl index 29814ba..54b9e10 100644 --- a/i18n/cs/cosmic_files.ftl +++ b/i18n/cs/cosmic_files.ftl @@ -17,7 +17,7 @@ size = Velikost ## Empty Trash Dialog -empty-trash = Vyprázdnit koš +empty-trash = Vysypat koš empty-trash-warning = Položky v koši budou trvale smazány ## New File/Folder Dialog @@ -195,8 +195,8 @@ deleted = [few] položky *[other] položek } z koše -emptying-trash = Vyprazdňování koše ({ $progress })... -emptied-trash = Koš vyprázdněn +emptying-trash = Vysypávání koše ({ $progress })... +emptied-trash = Koš vysypán restoring = Obnovování { $items } { $items -> [one] položky @@ -403,7 +403,7 @@ sort-largest-to-smallest = Od největšího po nejmenší gallery-preview = Náhled galerie sort = Řazení sort-a-z = A-Z -empty-trash-title = Vyprázdnit koš? +empty-trash-title = Vysypat koš? type-to-search-select = Vybere první shodující se soubor nebo složku pasted-image = Vložený obrázek pasted-text = Vložený text @@ -418,12 +418,3 @@ show-recents = Složka „Nedávné“ v postranním panelu copy-path = Kopírovat cestu clear-recents-history = Vymazat historii „Nedávné“ mixed = Různé -context-action = Kontextová akce -context-action-confirm-title = Spustit „{ $name }“? -context-action-confirm-warning = - Tato akce se spustí pro { $items } { $items -> - [one] položku - [few] položky - *[other] položek - }. -run = Spustit diff --git a/i18n/de/cosmic_files.ftl b/i18n/de/cosmic_files.ftl index 993af2d..98d2f1b 100644 --- a/i18n/de/cosmic_files.ftl +++ b/i18n/de/cosmic_files.ftl @@ -429,11 +429,3 @@ remove-from-recents = Aus den zuletzt verwendeten Elementen entfernen move-to = Verschieben nach... copy-path = Pfad kopieren mixed = Gemischt -context-action = Kontextaktion -context-action-confirm-title = „{ $name }“ ausführen? -context-action-confirm-warning = - Dies wird ausgeführt auf { $items } { $items -> - [one] Element - *[other] Elementen - }. -run = Ausführen diff --git a/i18n/el/cosmic_files.ftl b/i18n/el/cosmic_files.ftl index 7471630..b28272c 100644 --- a/i18n/el/cosmic_files.ftl +++ b/i18n/el/cosmic_files.ftl @@ -1,329 +1,15 @@ empty-folder = Άδειος φάκελος no-results = Δεν βρέθηκαν αποτελέσματα -trash = Απορρίμματα +trash = Κάδος Ανακύκλωσης recents = Πρόσφατα -cosmic-files = Αρχεία COSMIC -empty-folder-hidden = Άδειος φάκελος (περιέχει κρυφά στοιχεία) +cosmic-files = COSMIC Αρχεία +empty-folder-hidden = Άδειος φάκελος (περιέχει κρυφά αντικείμενα) filesystem = Σύστημα αρχείων home = Προσωπικός φάκελος -networks = Δίκτυα -comment = Διαχείριση αρχείων για το περιβάλλον επιφάνειας εργασίας COSMIC -keywords = Αρχείο;Φάκελος;Διαχείριση;Folder;Manager; +networks = Δίκτυο +comment = Αρχεία για το COSMIC περιβάλλον +keywords = Φάκελος;Διαχειριστής; rename = Μετονομασία... close-tab = Κλείσιμο καρτέλας -light = Ανοιχτόχρωμο -dark = Σκουρόχρωμο -connect = Σύνδεση -dismiss = Απόρριψη μηνύματος -copy_noun = Αντιγραφή -open-file = Άνοιγμα αρχείου -save = Αποθήκευση -password = Κωδικός πρόσβασης -remove = Αφαίρεση -create = Δημιουργία -pause = Παύση -quit = Έξοδος -calculating = Υπολογισμός... -keep = Διατήρηση -edit = Επεξεργασία -connecting = Σύνδεση... -copy = Αντιγραφή -theme = Θέμα -appearance = Εμφάνιση -name = Όνομα -resume = Συνέχιση -username = Όνομα χρήστη -delete = Διαγραφή -repository = Αποθετήριο -support = Υποστήριξη -eject = Εξαγωγή -group = Ομάδα -skip = Παράλειψη -paste = Επικόλληση -menu-settings = Ρυθμίσεις... -view = Προβολή -undo = Αναίρεση -details = Λεπτομέρειες -sort-a-z = Α-Ω -extract-here = Αποσυμπίεση -cancel = Ακύρωση -sort-z-a = Ω-Α -open = Άνοιγμα -history = Ιστορικό -domain = Τομέας -sort = Ταξινόμηση -settings = Ρυθμίσεις -pending = Σε εκκρεμότητα -open-folder = Άνοιγμα φακέλου -replace = Αντικατάσταση -cut = Αποκοπή -file = Αρχείο -today = Σήμερα -compress = Συμπίεση... -size = Μέγεθος -related-apps = Σχετικές εφαρμογές -zoom-in = Μεγέθυνση -select-all = Επιλογή όλων -icon-size-and-spacing = Μέγεθος και απόσταση εικονιδίων -new-window = Νέο παράθυρο -zoom-out = Σμίκρυνση -default-size = Προεπιλεγμένο μέγεθος -create-archive = Δημιουργία συμπιεσμένου αρχείου -other-apps = Άλλες εφαρμογές -rename-folder = Μετονομασία φακέλου -folder-name = Όνομα φακέλου -connect-anonymously = Ανώνυμη σύνδεση -replace-with = Αντικατάσταση με -mounted-drives = Προσαρτημένες μονάδες -desktop-view-options = Επιλογές προβολής επιφάνειας εργασίας... -show-on-desktop = Εμφάνιση στην επιφάνεια εργασίας -trash-folder-icon = Εικονίδιο φακέλου απορριμμάτων -open-with = Άνοιγμα με -keep-both = Διατήρηση αμφότερων -icon-size = Μέγεθος εικονιδίων -open-with-title = Πώς θέλετε να ανοίξετε το «{ $name }»; -extract-password-required = Απαιτείται κωδικός πρόσβασης -rename-file = Μετονομασία αρχείου -file-name = Όνομα αρχείου -save-file = Αποθήκευση αρχείου -name-hidden = Τα ονόματα που ξεκινούν με «.» θα αποκρύπτονται -folder-already-exists = Υπάρχει ήδη ένας φάκελος με αυτό το όνομα -empty-trash = Άδειασμα απορριμμάτων -permanently-delete-question = Οριστική διαγραφή; -copy-to-button-label = Αντιγραφή -move-to-button-label = Μετακίνηση -run = Εκτέλεση -copy-to-title = Επιλογή προορισμού αντιγραφής -sort-newest-first = Πρώτα τα νεότερα -default-app = { $name } (προεπιλογή) -renamed = Έγινε μετονομασία από «{ $from }» σε «{ $to }» -read-execute = Ανάγνωση και εκτέλεση -deleted = - Έγινε διαγραφή { $items } { $items -> - [one] στοιχείου - *[other] στοιχείων - } από τα { trash } -item-modified = Ημερομηνία τροποποίησης: { $modified } -list-view = Προβολή λίστας -reload-folder = Επαναφόρτωση φακέλου -favorite-path-error = Σφάλμα ανοίγματος καταλόγου -progress = { $percent }% -remove-from-sidebar = Αφαίρεση από την πλαϊνή στήλη -restoring = - Ανάκτηση { $items } { $items -> - [one] στοιχείου - *[other] στοιχείων - } από τα { trash } ({ $progress })... -network-drive-error = Αδυναμία πρόσβασης σε μονάδα δικτύου -gallery-preview = Προεπισκόπηση συλλογής -sort-smallest-to-largest = Από τα μικρότερα στα μεγαλύτερα -removing-from-recents = - Αφαίρεση { $items } { $items -> - [one] στοιχείου - *[other] στοιχείων - } από τα { recents } -type-to-search-enter-path = Εισέρχεται στη διαδρομή προς τον κατάλογο ή το αρχείο -emptying-trash = Άδειασμα φακέλου «{ trash }» ({ $progress })... -trashed-on = Ημερομηνία διαγραφής -compressing = - Συμπίεση { $items } { $items -> - [one] στοιχείου - *[other] στοιχείων - } από τον φάκελο «{ $from }» στο αρχείο «{ $to }» ({ $progress })... -move-to-trash = Μετακίνηση στα απορρίμματα -menu-about = Σχετικά με τα Αρχεία COSMIC... -setting-executable-and-launching = Ορισμός του «{ $name }» ως εκτελέσιμου και εκκίνηση -open-multiple-files = Άνοιγμα πολλαπλών αρχείων -menu-open-with = Άνοιγμα με... -extracted = - Έγινε αποσυμπίεση { $items } { $items -> - [one] στοιχείου - *[other] στοιχείων - } από το αρχείο «{ $from }» στον φάκελο «{ $to }» -create-new-folder = Δημιουργία νέου φακέλου -original-file = Πρωτότυπο αρχείο -read-write-execute = Ανάγνωση, εγγραφή και εκτέλεση -set-permissions = Έγινε ορισμός των δικαιωμάτων για το «{ $name }» σε: { $mode } -sort-by-size = Ταξινόμηση κατά μέγεθος -item-size = Μέγεθος: { $size } -permanently-deleting = - Οριστική διαγραφή { $items } { $items -> - [one] στοιχείου - *[other] στοιχείων - } -read-write = Ανάγνωση και εγγραφή -none = Κανένα -items = Στοιχεία: { $items } -type = Τύπος: { $mime } -compressed = - Έγινε συμπίεση { $items } { $items -> - [one] στοιχείου - *[other] στοιχείων - } από τον φάκελο «{ $from }» στο αρχείο «{ $to }» -replace-warning = Θέλετε να το αντικαταστήσετε με αυτό που αποθηκεύετε; Αυτό θα οδηγήσει στην αντικατάσταση του περιεχομένου του. -new-file = Νέο αρχείο... -open-in-terminal = Άνοιγμα σε τερματικό -open-multiple-folders = Άνοιγμα πολλαπλών φακέλων -remember-password = Απομνημόνευση κωδικού πρόσβασης -show-details = Εμφάνιση λεπτομερειών -grid-spacing = Απόσταση πλέγματος -extract-to = Αποσυμπίεση σε... -add-network-drive = Προσθήκη μονάδας δικτύου -copying = - Αντιγραφή { $items } { $items -> - [one] στοιχείου - *[other] στοιχείων - } από τον φάκελο «{ $from }» στον φάκελο «{ $to }» ({ $progress })... -sort-oldest-first = Πρώτα τα παλαιότερα -create-new-file = Δημιουργία νέου αρχείου -sort-by-trashed = Ταξινόμηση κατά ημερομηνία διαγραφής -replace-warning-operation = Θέλετε να το αντικαταστήσετε; Αυτό θα οδηγήσει στην αντικατάσταση του περιεχομένου του. -try-again = Δοκιμή ξανά -copied = - Έγινε αντιγραφή { $items } { $items -> - [one] στοιχείου - *[other] στοιχείων - } από τον φάκελο «{ $from }» στον φάκελο «{ $to }» -other = Άλλο -open-in-new-window = Άνοιγμα σε νέο παράθυρο -sort-by-modified = Ταξινόμηση κατά ημερομηνία τροποποίησης -list-directories-first = Παράθεση των καταλόγων πρώτα -read-only = Μόνο ανάγνωση -browse-store = Περιήγηση στο { $store } -enter-server-address = Εισαγάγετε τη διεύθυνση διακομιστή -remove-from-recents = Αφαίρεση από τα πρόσφατα -apply-to-all = Εφαρμογή σε όλα -moving = - Μετακίνηση { $items } { $items -> - [one] στοιχείου - *[other] στοιχείων - } από τον φάκελο «{ $from }» στον φάκελο «{ $to }» ({ $progress })... -change-wallpaper = Αλλαγή ταπετσαρίας... -network-drive-description = - Οι διευθύνσεις διακομιστών αποτελούνται από ένα πρόθεμα πρωτοκόλλου και μια διεύθυνση. - Παραδείγματα: ssh://192.168.0.1, ftp://[2001:db8::1] -deleting = - Διαγραφή { $items } { $items -> - [one] στοιχείου - *[other] στοιχείων - } από τα { trash } ({ $progress })... -single-click = Μονό κλικ για άνοιγμα -setting-permissions = Ορισμός των δικαιωμάτων για το «{ $name }» σε: { $mode } -owner = Κάτοχος -creating = Δημιουργία του «{ $name }» στον φάκελο «{ $parent }» -execute-only = Μόνο εκτέλεση -open-item-location = Άνοιγμα τοποθεσίας στοιχείου -set-executable-and-launched = Έγινε ορισμός του «{ $name }» ως εκτελέσιμου και εκκινήθηκε -mount-error = Αδυναμία πρόσβασης στη μονάδα -grid-view = Προβολή πλέγματος -set-and-launch = Ορισμός και εκκίνηση -removed-from-recents = - Έγινε αφαίρεση { $items } { $items -> - [one] στοιχείου - *[other] στοιχείων - } από τα { recents } -add-to-sidebar = Προσθήκη στην πλαϊνή στήλη -item-created = Ημερομηνία δημιουργίας: { $created } -network-drive-schemes = - Διαθέσιμα πρωτόκολλα,Πρόθεμα - AppleTalk,afp:// - File Transfer Protocol,ftp:// ή ftps:// - Network File System,nfs:// - Server Message Block,smb:// - SSH File Transfer Protocol,sftp:// ή ssh:// - WebDAV,dav:// ή davs:// -set-executable-and-launch = Ορισμός ως εκτελέσιμο και εκκίνηση -restored = - Έγινε ανάκτηση { $items } { $items -> - [one] στοιχείου - *[other] στοιχείων - } από τα { trash } -type-to-search-recursive = Κάνει αναζήτηση στον τρέχοντα φάκελο και όλους τους υποφακέλους -progress-paused = { $percent }%, σε παύση -cancelled = Ακυρωμένες -new-folder = Νέος φάκελος... -match-desktop = Συμφωνία με την επιφάνεια εργασίας -operations-running-finished = - Εκτέλεση { $running } { $running -> - [one] διεργασίας - *[other] διεργασιών - } ({ $percent }%), { $finished } ολοκληρωμένες... -sort-by-name = Ταξινόμηση κατά όνομα -edit-history = Ιστορικό επεξεργασιών -show-hidden-files = Εμφάνιση κρυφών αρχείων -progress-failed = { $percent }%, απέτυχε -item-accessed = Ημερομηνία πρόσβασης: { $accessed } -extract-to-title = Αποσυμπίεση σε φάκελο -extracting = - Αποσυμπίεση { $items } { $items -> - [one] στοιχείου - *[other] στοιχείων - } από το αρχείο «{ $from }» στον φάκελο «{ $to }» ({ $progress })... -permanently-deleted = - Έγινε οριστική διαγραφή { $items } { $items -> - [one] στοιχείου - *[other] στοιχείων - } -complete = Ολοκληρωμένες -write-execute = Εγγραφή και εκτέλεση -desktop-folder-content = Περιεχόμενο φακέλου επιφάνειας εργασίας -renaming = Μετονομασία από «{ $from }» σε «{ $to }» -set-executable-and-launch-description = Θέλετε να ορίσετε το «{ $name }» ως εκτελέσιμο και να το εκκινήσετε; -no-history = Δεν υπάρχουν στοιχεία στο ιστορικό. -emptied-trash = Έγινε άδειασμα του φακέλου «{ trash }» -sort-largest-to-smallest = Από τα μεγαλύτερα στα μικρότερα -restore-from-trash = Ανάκτηση από τα απορρίμματα -moved = - Έγινε μετακίνηση { $items } { $items -> - [one] στοιχείου - *[other] στοιχείων - } από τον φάκελο «{ $from }» στον φάκελο «{ $to }» -progress-cancelled = { $percent }%, ακυρώθηκε -open-in-new-tab = Άνοιγμα σε νέα καρτέλα -unknown-folder = άγνωστος φάκελος -created = Έγινε δημιουργία του «{ $name }» στον φάκελο «{ $parent }» -delete-permanently = Οριστική διαγραφή -write-only = Μόνο εγγραφή -display-settings = Ρυθμίσεις οθόνης... -new-tab = Νέα καρτέλα -failed = Αποτυχημένες -modified = Ημερομηνία τροποποίησης -desktop-appearance = Εμφάνιση επιφάνειας εργασίας... -file-already-exists = Υπάρχει ήδη ένα αρχείο με αυτό το όνομα -permanently-delete-warning = Θα διαγραφούν οριστικά τα εξής: { $target }. Δεν είναι δυνατή η αναίρεση αυτής της ενέργειας. -favorite-path-error-description = - Αδυναμία ανοίγματος του «{ $path }» - Το «{ $path }» ενδέχεται να μην υπάρχει ή να μην έχετε το δικαίωμα να το ανοίξετε - - Θέλετε να το αφαιρέσετε από την πλαϊνή στήλη; -empty-trash-warning = Τα στοιχεία του φακέλου «Απορρίμματα» θα διαγραφούν οριστικά -empty-trash-title = Άδειασμα απορριμμάτων; -type-to-search = Πληκτρολόγηση για αναζήτηση -notification-in-progress = Βρίσκονται σε εξέλιξη διεργασίες αρχείων -name-no-slashes = Το όνομα δεν μπορεί να περιέχει καθέτους -replace-title = Το «{ $filename }» υπάρχει ήδη σε αυτήν την τοποθεσία -name-invalid = Το όνομα δεν μπορεί να είναι «{ $filename }» -operations-running = - Εκτέλεση { $running } { $running -> - [one] διεργασίας - *[other] διεργασιών - } ({ $percent }%)... -context-action-confirm-title = Εκτέλεση του «{ $name }»; -pasted-image = Επικολλημένη εικόνα -pasted-text = Επικολλημένο κείμενο -pasted-video = Επικολλημένο βίντεο -show-recents = Φάκελος «Πρόσφατα» στην πλαϊνή στήλη -move-to = Μετακίνηση σε... -copy-path = Αντιγραφή διαδρομής -move-to-title = Επιλογή προορισμού μετακίνησης -selected-items = { $items } επιλεγμένα στοιχεία -context-action = Ενέργεια περιβάλλοντος -copy-to = Αντιγραφή σε... -mixed = Μικτό -type-to-search-select = Επιλέγει την πρώτη αντιστοιχία αρχείου ή φακέλου -clear-recents-history = Απαλοιφή ιστορικού πρόσφατων -context-action-confirm-warning = - Θα εκτελεστεί σε { $items } { $items -> - [one] στοιχείο - *[other] στοιχεία - }. +light = Φωτεινό +dark = Σκοτεινό diff --git a/i18n/en/cosmic_files.ftl b/i18n/en/cosmic_files.ftl index 489ff7a..8e4f54e 100644 --- a/i18n/en/cosmic_files.ftl +++ b/i18n/en/cosmic_files.ftl @@ -117,7 +117,6 @@ permanently-delete-warning = {$target} will be permanently deleted. This action ## Rename Dialog rename-file = Rename file rename-folder = Rename folder -rename-confirm = Rename ## Replace Dialog replace = Replace diff --git a/i18n/fi/cosmic_files.ftl b/i18n/fi/cosmic_files.ftl index f1c3c58..f22b7b4 100644 --- a/i18n/fi/cosmic_files.ftl +++ b/i18n/fi/cosmic_files.ftl @@ -405,12 +405,3 @@ removed-from-recents = [one] kohde *[other] kohdetta } viimeaikaisista -mixed = Sekoitettu -context-action = Kontekstitoiminto -context-action-confirm-title = Suoritetaanko "{ $name }"? -context-action-confirm-warning = - Tämä suorittaa { $items } { $items -> - [one] kohteen - *[other] kohdetta - }. -run = Suorita diff --git a/i18n/fr/cosmic_files.ftl b/i18n/fr/cosmic_files.ftl index 9f7941a..214781e 100644 --- a/i18n/fr/cosmic_files.ftl +++ b/i18n/fr/cosmic_files.ftl @@ -443,11 +443,3 @@ show-recents = Dossier Récents dans la barre latérale copy-path = Copier le chemin clear-recents-history = Effacer l'historique des Récents mixed = Mixte -context-action-confirm-title = Exécuter "{ $name }" ? -context-action-confirm-warning = - Cela exécutera sur { $items } { $items -> - [one] élément - *[other] éléments - }. -run = Exécuter -context-action = Action contextuelle diff --git a/i18n/ga/cosmic_files.ftl b/i18n/ga/cosmic_files.ftl index 04d546f..5a9aa9a 100644 --- a/i18n/ga/cosmic_files.ftl +++ b/i18n/ga/cosmic_files.ftl @@ -434,11 +434,3 @@ show-recents = Fillteán le déanaí sa bharra taoibh clear-recents-history = Glan stair na n-earraí le déanaí copy-path = Cóipeáil an chosán mixed = Measctha -context-action = Gníomh comhthéacsúil -context-action-confirm-title = Rith "{ $name }"? -context-action-confirm-warning = - Rithfidh sé seo ar { $items } { $items -> - [one] mhír - *[other] míreanna - }. -run = Rith diff --git a/i18n/hu/cosmic_files.ftl b/i18n/hu/cosmic_files.ftl index 2cf31e6..6a87c44 100644 --- a/i18n/hu/cosmic_files.ftl +++ b/i18n/hu/cosmic_files.ftl @@ -437,11 +437,3 @@ show-recents = Legutóbbiak mappa megjelenítése az oldalsávban copy-path = Útvonal másolása clear-recents-history = Legutóbbiak előzményének törlése mixed = Vegyes -context-action = Helyi művelet -context-action-confirm-title = Futtatod ezt: „{ $name }”? -context-action-confirm-warning = - Ez a művelet { $items } { $items -> - [one] elemen - *[other] elemen - } fog lefutni. -run = Futtatás diff --git a/i18n/id/cosmic_files.ftl b/i18n/id/cosmic_files.ftl index aefff98..2f1f21b 100644 --- a/i18n/id/cosmic_files.ftl +++ b/i18n/id/cosmic_files.ftl @@ -319,11 +319,3 @@ show-recents = Map terbaru di bilah sisi clear-recents-history = Bersihkan riwayat Terbaru copy-path = Salin jalur mixed = Bercampur -context-action = Tindakan konteks -context-action-confirm-title = Jalankan "{ $name }"? -run = Jalankan -context-action-confirm-warning = - Ini akan dijalankan pada { $items } { $items -> - [one] item - *[other] item - }. diff --git a/i18n/kk/cosmic_files.ftl b/i18n/kk/cosmic_files.ftl index 8aabf01..3b97025 100644 --- a/i18n/kk/cosmic_files.ftl +++ b/i18n/kk/cosmic_files.ftl @@ -319,11 +319,3 @@ show-recents = Бүйір панеліндегі «Жуырдағы құжатт clear-recents-history = Жуырдағылар тарихын өшіру copy-path = Орналасқан жолын көшіру mixed = Аралас -context-action = Контекст әрекеті -context-action-confirm-title = "{ $name }" орындау керек пе? -context-action-confirm-warning = - Бұл { $items } орындалады { $items -> - [one] нәрсеге - *[other] нәрсеге - }. -run = Орындау diff --git a/i18n/ko/cosmic_files.ftl b/i18n/ko/cosmic_files.ftl index 5683c39..3a9f2f6 100644 --- a/i18n/ko/cosmic_files.ftl +++ b/i18n/ko/cosmic_files.ftl @@ -277,4 +277,3 @@ copy-to-button-label = 복사 move-to-button-label = 이동 clear-recents-history = 최근 기록 비우기 copy-path = 복사 경로 -move-to-title = 이동 위치 선택 diff --git a/i18n/lo/cosmic_files.ftl b/i18n/lo/cosmic_files.ftl deleted file mode 100644 index e69de29..0000000 diff --git a/i18n/lt/cosmic_files.ftl b/i18n/lt/cosmic_files.ftl index 2a8e36f..acceb7c 100644 --- a/i18n/lt/cosmic_files.ftl +++ b/i18n/lt/cosmic_files.ftl @@ -1,5 +1,5 @@ progress = { $percent }% -cosmic-files = COSMIC Failai +cosmic-files = Cosmic Files empty-folder = Tuščias aplankas empty-folder-hidden = Tuščias aplankas (turi paslėptų failų) no-results = Rezultatų nėra @@ -76,7 +76,7 @@ delete = Ištrinti permanently-delete-warning = { $target } bus ištrintas visam laikui. Šis veiksmas yra negrįžtamas. rename-file = Pervadinti failą rename-folder = Pervadinti aplanką -replace = Keisti +replace = Pakeisti replace-title = „{ $filename }“ jau egzistuoja šioje vietoje replace-warning = Ar norite pakeisti tai su tuo, kas yra įrašoma? Keičiant bus pakeistas turinys. replace-warning-operation = Ar norite pakeisti tai? Pakeičiant bus keičiamas turinys. @@ -244,7 +244,7 @@ item-created = Sukurtas: { $created } item-modified = Modifikuota: { $modified } item-accessed = Paskutinė prieiga: { $accessed } calculating = Skaičiuojama... -settings = Nuostatos +settings = Nustatymai single-click = Vieno paspaudimo atidarymas appearance = Išvaizda match-desktop = Pagal darbalaukio temą @@ -280,18 +280,18 @@ quit = Išeiti edit = Redaguoti cut = Iškirpti copy = Kopijuoti -paste = Įdėti -select-all = Žymėti viską -zoom-in = Artinti +paste = Įklijuoti +select-all = Pažymėti viską +zoom-in = Priartinti default-size = Numatytas dydis -zoom-out = Tolinti +zoom-out = Nutolinti view = Rodymas grid-view = Tinklelio išdėstymas list-view = Sąrašo išdėstymas show-hidden-files = Rodyti paslėptus failus list-directories-first = Pirmiau pateikti aplankus gallery-preview = Galerijos peržiūra -menu-settings = Nuostatos... +menu-settings = Nustatymai... menu-about = Apie COSMIC Files... sort = Rikiuoti sort-a-z = A-Ž @@ -302,7 +302,7 @@ sort-smallest-to-largest = Nuo mažiausio iki didžiausio sort-largest-to-smallest = Nuo didžiausio iki mažiausio dark = Tamsus light = Šviesus -comment = COSMIC aplinkos failų tvarkyklė +comment = COSMIC desktop failų tvarkyklė keywords = Aplankas;Tvarkyklė; copy-to-title = Pasirinkti kopijavimo vietą copy-to-button-label = Kopijuoti @@ -317,4 +317,3 @@ clear-recents-history = Išvalyti Neseniai naudotų istoriją copy-to = Kopijuoti į... move-to = Perkeltiį į... copy-path = Kopijuoti kelią -theme = Stilius diff --git a/i18n/pl/cosmic_files.ftl b/i18n/pl/cosmic_files.ftl index 9af6027..b0bc7a8 100644 --- a/i18n/pl/cosmic_files.ftl +++ b/i18n/pl/cosmic_files.ftl @@ -438,11 +438,3 @@ show-recents = Ostatnie katalogi w panelu bocznym clear-recents-history = Wyczyść bierzącą historię copy-path = Skopiuj ścieżkę mixed = Mieszane -context-action = Akcja sytuacyjna -context-action-confirm-title = Uruchomić "{ $name }"? -context-action-confirm-warning = - Zostanie uruchomionych { $items } { $items -> - [one] element - *[other] elementów - }. -run = Uruchom diff --git a/i18n/pt-BR/cosmic_files.ftl b/i18n/pt-BR/cosmic_files.ftl index 6d33279..86ebd88 100644 --- a/i18n/pt-BR/cosmic_files.ftl +++ b/i18n/pt-BR/cosmic_files.ftl @@ -437,11 +437,3 @@ show-recents = Pasta de recentes na barra lateral clear-recents-history = Limpar histórico de recentes copy-path = Copiar caminho mixed = Misto -context-action = Ação de contexto -context-action-confirm-title = Executar "{ $name }"? -context-action-confirm-warning = - Isso será executado em { $items } { $items -> - [one] item - *[other] itens - }. -run = Executar diff --git a/i18n/pt/cosmic_files.ftl b/i18n/pt/cosmic_files.ftl index 211b094..33921f1 100644 --- a/i18n/pt/cosmic_files.ftl +++ b/i18n/pt/cosmic_files.ftl @@ -352,5 +352,3 @@ sort-newest-first = Mais recentes primeiro sort-oldest-first = Mais antigos primeiro sort-smallest-to-largest = Do menor para o maior sort-largest-to-smallest = Do maior para o menor -context-action-confirm-title = Executar "{ $name }"? -run = Executar diff --git a/i18n/ru/cosmic_files.ftl b/i18n/ru/cosmic_files.ftl index f8e76c1..e9673f1 100644 --- a/i18n/ru/cosmic_files.ftl +++ b/i18n/ru/cosmic_files.ftl @@ -381,11 +381,3 @@ show-recents = «Недавние документы» в бок. панели clear-recents-history = Очистить историю недавних copy-path = Копировать путь mixed = Смешанные -context-action = Контекстная команда -context-action-confirm-title = Выполнить «{ $name }»? -context-action-confirm-warning = - Команда затронет { $items } { $items -> - [one] элемент - *[other] элем. - }. -run = Выполнить diff --git a/i18n/sr/cosmic_files.ftl b/i18n/sr/cosmic_files.ftl index b71afe5..e69de29 100644 --- a/i18n/sr/cosmic_files.ftl +++ b/i18n/sr/cosmic_files.ftl @@ -1,329 +0,0 @@ -open-file = Отвори датотеку -quit = Изађи -cancel = Откажи -open = Отвори -run = Покрени -connect = Повежи -save = Сачувај -password = Лозинка -remove = Уклони -appearance = Изглед -username = Корисничко име -light = Светла -dark = Тамна -settings = Подешавања -replace = Замени -size = Величина -sort-newest-first = Најновије прво -default-app = { $name } (подразумевано) -renamed = Преименована „{ $from }“ у „{ $to }“ -read-execute = Читање и извршавање -deleted = - Обрисано је { $items } { $items -> - [one] ставка - *[other] ставки - } из { trash } -item-modified = Измењено: { $modified } -dismiss = Одбаци поруку -list-view = Преглед у виду списака -reload-folder = Поново учитај фасциклу -copy_noun = Копија -favorite-path-error = Грешка при отварању директоријума -progress = { $percent }% -remove-from-sidebar = Уклони из бочне траке -related-apps = Повезани програми -restoring = - Враћање { $items } { $items -> - [one] ставке - *[other] ставки - } из { trash } ({ $progress })... -network-drive-error = Немогуће приступити мрежном уређају -gallery-preview = Преглед галерије -sort-smallest-to-largest = Од најмање до највеће -zoom-in = Увећајте приказ -select-all = Означи све -icon-size-and-spacing = Величина и размак иконица -removing-from-recents = - Уклањање { $items } { $items -> - [one] ставке - *[other] ставки - } из { recents } -cosmic-files = Космик датотеке -type-to-search-enter-path = Уноси путању до директоријума или датотеке -trash = Смеће -emptying-trash = Пражњење { trash } ({ $progress })... -trashed-on = Премештено у смеће -new-window = Нови прозор -zoom-out = Умањите приказ -compressing = - Сажимање { $items } { $items -> - [one] ставке - *[other] ставки - } из „{ $from }“ у „{ $to }“ ({ $progress })... -move-to-trash = Премести у смеће -menu-about = О програму Космик Датотеке... -setting-executable-and-launching = Подешавање „{ $name }“ као извршне датотеке и покретање -open-multiple-files = Отвори више датотека -default-size = Подразумевана величина -menu-open-with = Отвори програмом... -extracted = - Извлачено { $items } { $items -> - [one] ставка - *[other] ставки - } из „{ $from }“ у „{ $to }“ -create-new-folder = Направи нову фасциклу -original-file = Изворна датотека -create = Направи -create-archive = Направи архиву -read-write-execute = Читање, уписивање и извршавање -other-apps = Остали програми -set-permissions = Овлашћења за „{ $name }“ су постављена на { $mode } -pause = Паузирај -calculating = Израчунавам… -sort-by-size = Поређај по величини -rename = Преименуј... -empty-folder-hidden = Празна фасцикла (има сакривених ставки) -keep = Задржи -item-size = Величина: { $size } -permanently-deleting = - Трајно брисање { $items } { $items -> - [one] ставке - *[other] ставки - } -edit = Уреди -connecting = Повезујем се... -read-write = Читање и уписивање -copy = Умножи -none = Ништа -items = Ставки: { $items } -no-results = Нису пронађени резултати -theme = Тема -type = Врста: { $mime } -compressed = - Сажето { $items } { $items -> - [one] ставка - *[other] ставки - } из „{ $from }“ у „{ $to }“ -replace-warning = Да ли желите да га замените оним који снимате? Замена ће преписати његов садржај. -rename-folder = Преименуј фасциклу -new-file = Нова датотека... -close-tab = Затвори језичак -name = Назив -open-in-terminal = Отвори у терминалу -resume = Настави -open-multiple-folders = Отвори више фасцикла -remember-password = Запамти лозинку -show-details = Прикажи детаље -grid-spacing = Размак мреже -extract-to = Распакуј у... -add-network-drive = Додај мрежни уређај -copying = - Умножавање { $items } { $items -> - [one] ставке - *[other] ставки - } из „{ $from }“ у „{ $to }“ ({ $progress })... -delete = Обриши -sort-oldest-first = Најстарије прво -repository = Ризница -create-new-file = Направи нову датотеку -sort-by-trashed = Поређај по времену брисања -replace-warning-operation = Да ли желите да га замените? Замена ће преписати његов садржај. -support = Подршка -try-again = Покушај поново -eject = Избаци -copied = - Умножено { $items } { $items -> - [one] ставке - *[other] ставки - } из „{ $from }“ у „{ $to }“ -other = Друго -open-in-new-window = Отвори у новом прозору -empty-folder = Празна фасцикла -sort-by-modified = Поређај по датуму измене -list-directories-first = Прикажи директоријуме прво -read-only = Само за читање -folder-name = Назив фасцикле -browse-store = Разгледај { $store } -enter-server-address = Унесите адресу сервера -remove-from-recents = Уклони из недавних -connect-anonymously = Повежи се анонимно -group = Група -apply-to-all = Примени на све -skip = Прескочи -paste = Залепи -menu-settings = Подешавања... -moving = - Премештање { $items } { $items -> - [one] датотеке - *[other] датотека - } из „{ $from }“ у „{ $to }“ ({ $progress })... -replace-with = Замени са -recents = Недавно -change-wallpaper = Промени позадину... -network-drive-description = - Адресе сервера укључују префикс протокола и адресу. - Примери: ssh://192.168.0.1, ftp://[2001:db8::1] -deleting = - Брише се { $items } { $items -> - [one] ставка - *[other] ставки - } из { trash } ({ $progress })... -single-click = Један клик за отварање -view = Преглед -undo = Опозови -setting-permissions = Подешавање овлашћења за „{ $name }“ на { $mode } -owner = Власник -creating = Правим „{ $name }“ у „{ $parent }“ -execute-only = Само извршавање -open-item-location = Отвори локацију ставке -details = Детаљи -set-executable-and-launched = Постављено „{ $name }“ као извршну датотеку и покренуто -mounted-drives = Прикључени дискови -sort-a-z = А-Ш -mount-error = Немогуће приступити уређају -extract-here = Извуци -grid-view = Преглед у виду мреже -filesystem = Систем датотека -set-and-launch = Подеси и покрени -removed-from-recents = - Уклоњено { $items } { $items -> - [one] ставке - *[other] ставки - } из { recents } -add-to-sidebar = Додај у страничник -item-created = Направљено: { $created } -network-drive-schemes = - Доступни протоколи,Префикс - ЕплТок,afp:// - Протокол за пренос датотека,ftp:// или ftps:// - Мрежни систем датотека,nfs:// - Серверски блок порука,smb:// - SSH протокол за пренос датотека,sftp:// или ssh:// - ВебДАВ,dav:// или davs:// -home = Лична -set-executable-and-launch = Постави као извршну и покрени -restored = - Враћено { $items } { $items -> - [one] ставка - *[other] ставки - } из { trash } -sort-z-a = Ш-А -type-to-search-recursive = Претражује тренутну фасциклу и све подфасцикле -history = Историјат -progress-paused = { $percent }%, паузирано -desktop-view-options = Могућности приказа радне површине... -show-on-desktop = Прикажи на радној површини -cancelled = Отказано -new-folder = Нова фасцикла... -match-desktop = Прати радну површину -domain = Домен -operations-running-finished = - { $running } { $running -> - [one] радња покренута - *[other] радње покренуте - } ({ $percent }%), { $finished } завршено... -sort-by-name = Поређај по називу -edit-history = Историјат уређивања -sort = Поређај -show-hidden-files = Прикажи скривене датотеке -progress-failed = { $percent }%, није успело -trash-folder-icon = Иконица фасцикле Смеће -item-accessed = Приступљено: { $accessed } -extract-to-title = Распакуј у фасциклу -open-with = Отвори помоћу -keep-both = Задржи оба -icon-size = Величина иконице -open-with-title = Како желите да отворите „{ $name }“? -extracting = - Извлачење { $items } { $items -> - [one] ставке - *[other] ставки - } из „{ $from }“ у „{ $to }“ ({ $progress })... -permanently-deleted = - Трајно обрисано { $items } { $items -> - [one] ставке - *[other] ставки - } -complete = Завршено -write-execute = Писање и извршавање -extract-password-required = Потребна је лозинка -pending = У току -desktop-folder-content = Садржај фасцикле радне површине -renaming = Преименовање „{ $from }“ у „{ $to }“ -set-executable-and-launch-description = Да ли желите да поставите „{ $name }“ као извршну и покренете је? -no-history = Нема ставки у историјату. -open-folder = Отвори фасциклу -emptied-trash = Опражњено { trash } -rename-file = Преименуј датотеку -sort-largest-to-smallest = Од највеће до најмање -restore-from-trash = Врати из смећа -cut = Исеци -moved = - Премештено { $items } { $items -> - [one] датотеке - *[other] датотека - } из „{ $from }“ у „{ $to }“ -progress-cancelled = { $percent }%, отказано -open-in-new-tab = Отвори у новом језичку -unknown-folder = непозната фасцикла -file = Датотека -file-name = Назив датотеке -save-file = Сачувај датотеку -created = Направљено „{ $name }“ у „{ $parent }“ -delete-permanently = Трајно обриши -networks = Мреже -write-only = Само писање -today = Данас -display-settings = Подешавања екрана... -new-tab = Нови језичак -failed = Неуспешно -modified = Измењено -desktop-appearance = Изглед радне површине... -file-already-exists = Датотека са овим називом већ постоји -name-hidden = Називи који почињу тачком „.“ ће бити сакривени -folder-already-exists = Фасцикла са овим називом већ постоји -permanently-delete-warning = { $target } ће бити трајно обрисано. Ова радња се не може поништити. -favorite-path-error-description = - Не можемо да отворимо „{ $path }“ - „{ $path }“ можда не постоји или немате дозволу за његово отварање - - Желите ли да га уклоните из бочне површи? -empty-trash-warning = Ставке у смећу биће трајно обрисане -empty-trash = Испразни смеће -empty-trash-title = Испразнити смеће? -type-to-search = Куцајте за претрагу -notification-in-progress = Радње са датотекама су у току -name-no-slashes = Назив не може садржати косе црте -permanently-delete-question = Трајно обриши? -replace-title = „{ $filename }“ већ постоји на овој локацији -name-invalid = Назив не може бити „{ $filename }“ -operations-running = - { $running } { $running -> - [one] радња покренута - *[other] радње покренуте - } ({ $percent }%)... -comment = Управник датотека за Космик радну површину -keywords = Folder;Manager;Фасцикла;Управник;fascikla;upravnik; -copy-to-title = Изабери одредиште умножавања -copy-to-button-label = Умножи -move-to-title = Изабери одредиште премештања -move-to-button-label = Помери -context-action = Контекстна радња -context-action-confirm-title = Покрени „{ $name }“? -context-action-confirm-warning = - Ово ће се извршити на { $items } { $items -> - [one] ставку - *[other] ставки - }. -selected-items = Изабраних { $items } ставки -mixed = Помешано -pasted-image = Убачена слика -pasted-text = Убачен текст -pasted-video = Убачен видео -show-recents = Недавна фасцикла у бочној површи -type-to-search-select = Обира прву подударајућу датотеку или фасциклу -clear-recents-history = Очисти историју недавних -compress = Сажми... -copy-to = Умножи у... -move-to = Помери у... -copy-path = Умножи путању diff --git a/i18n/sv/cosmic_files.ftl b/i18n/sv/cosmic_files.ftl index 974c7dc..cbd28dd 100644 --- a/i18n/sv/cosmic_files.ftl +++ b/i18n/sv/cosmic_files.ftl @@ -410,11 +410,3 @@ move-to = Flytta till... show-recents = Mapp för senast använda filer i sidofältet clear-recents-history = Töm historik för Senaste copy-path = Kopiera sökväg -context-action = Kontextåtgärd -context-action-confirm-title = Kör "{ $name }"? -context-action-confirm-warning = - Detta kommer att köras på { $items } { $items -> - [one] objekt - *[other] objekt - }. -run = Kör diff --git a/i18n/uk/cosmic_files.ftl b/i18n/uk/cosmic_files.ftl index 7299a7d..611c2b1 100644 --- a/i18n/uk/cosmic_files.ftl +++ b/i18n/uk/cosmic_files.ftl @@ -5,7 +5,7 @@ filesystem = Файлова система home = Домівка trash = Смітник recents = Нещодавні -undo = Скасувати +undo = Відмінити # List view name = Назва modified = Змінено @@ -86,7 +86,6 @@ copying = copied = Скопійовано { $items } { $items -> [one] елемент - [few] елементи *[other] елеменів } з «{ $from }» в «{ $to }» emptying-trash = Спорожнення { trash } ({ $progress })... @@ -99,8 +98,7 @@ moving = moved = Переміщено { $items } { $items -> [one] елемент - [few] елементи - *[other] елементів + *[other] елементи } з «{ $from }» в «{ $to }» renaming = Перейменування «{ $from }» на «{ $to }» renamed = Перейменовано «{ $from }» на «{ $to }» @@ -112,8 +110,7 @@ restoring = restored = Відновлено { $items } { $items -> [one] елемент - [few] елементи - *[other] елементів + *[other] елементи } з { trash } unknown-folder = невідома тека @@ -226,7 +223,7 @@ permanently-delete-question = Остаточно видалити? delete = Видалити permanently-delete-warning = { $target } буде остаточно видалено. Цю дію не можна скасувати. set-executable-and-launch = Зробити виконуваним і запустити -set-executable-and-launch-description = Бажаєте зробити «{ $name }» виконуваним і запустити його? +set-executable-and-launch-description = Бажаєте зробити "{ $name }" виконуваним і запустити його? set-and-launch = Зробити і запустити open-with = Відкрити за допомогою owner = Власник @@ -248,7 +245,7 @@ favorite-path-error-description = Вилучити з бічної панелі? keep = Залишити add-network-drive = Додати мережевий диск -connect = З’єднати +connect = З'єднати connect-anonymously = З'єднатись анонімно connecting = З'єднання… domain = Домен @@ -277,13 +274,12 @@ compressing = Стиснення { $items } { $items -> [one] елемента *[other] елементів - } з «{ $from }» до «{ $to }» ({ $progress })... + } з "{ $from }" до "{ $to }" ({ $progress })... compressed = Стиснуто { $items } { $items -> [one] елемент - [few] елементи - *[other] елементів - } з «{ $from }» до «{ $to }» + *[other] елементи + } з "{ $from }" до "{ $to }" deleting = Видалення { $items } { $items -> [one] елемента @@ -292,7 +288,6 @@ deleting = deleted = Видалено { $items } { $items -> [one] елемент - [few] елементи *[other] елементи } з { trash } extracting = @@ -303,8 +298,7 @@ extracting = extracted = Видобуто { $items } { $items -> [one] елемент - [few] елементи - *[other] елементів + *[other] елементи } з «{ $from }» в «{ $to }» setting-executable-and-launching = Надання «{ $name }» прав на виконання та запуск set-executable-and-launched = «{ $name }» надано права на виконання і відкрито @@ -349,8 +343,7 @@ permanently-deleting = permanently-deleted = Остаточно вилучено { $items } { $items -> [one] елемент - [few] елементи - *[other] елементів + *[other] елементи } removing-from-recents = Вилучення { $items } { $items -> @@ -360,14 +353,13 @@ removing-from-recents = removed-from-recents = Вилучено { $items } { $items -> [one] елемент - [few] елементи - *[other] елементів + *[other] елементи } з { recents } empty-trash-title = Спорожити смітник? type-to-search-select = Вибирає перший відповідний файл або папку -pasted-image = Вставлене зображення -pasted-text = Вставлений текст -pasted-video = Вставлене відео +pasted-image = Вставлене Зображення +pasted-text = Вставлений Текст +pasted-video = Вставлене Видиво copy-to-button-label = Копіювати move-to-button-label = Перемістити copy-to = Копіювати до… @@ -379,11 +371,3 @@ keywords = Тека;Папка;Провідник;Менеджер;Катало show-recents = Тека «Нещодавні» на бічній панелі copy-path = Копіювати шлях clear-recents-history = Очистити нещодавні -context-action-confirm-title = Запустити «{ $name }»? -run = Виконати -context-action-confirm-warning = - Запуститься для { $items } { $items -> - [one] елемента - *[other] елементів - }. -context-action = Контекстна дія diff --git a/i18n/zh-CN/cosmic_files.ftl b/i18n/zh-CN/cosmic_files.ftl index abc8fda..11441d1 100644 --- a/i18n/zh-CN/cosmic_files.ftl +++ b/i18n/zh-CN/cosmic_files.ftl @@ -167,7 +167,7 @@ read-write-execute = 读取、写入和执行 ## Favorite Path Error Dialog -favorite-path-error = 打开目录时出错 +favorite-path-error = 打开路径时出错 favorite-path-error-description = 无法打开 "{ $path }" 。 "{ $path }" 可能不存在或您没有权限打开它。 @@ -348,7 +348,7 @@ light = 亮色模式 type-to-search = 输入即可搜索 type-to-search-recursive = 搜索当前文件夹及其所有子文件夹 -type-to-search-enter-path = 输入文件夹或文件目录 +type-to-search-enter-path = 输入文件夹或文件路径 # Context menu add-to-sidebar = 加入侧边栏 compress = 压缩… @@ -437,11 +437,3 @@ clear-recents-history = 清除最近访问历史 copy-path = 复制文件路径 show-recents = 侧边栏中的最近访问 mixed = 混合 -context-action-confirm-title = 运行 “{ $name }” ? -run = 运行 -context-action-confirm-warning = - 该行动将会在 { $items } { $items -> - [one] 项目 - *[other] 项目 - } 上运行。 -context-action = 环境行动 diff --git a/i18n/zh-TW/cosmic_files.ftl b/i18n/zh-TW/cosmic_files.ftl index b356db3..2bf8e5b 100644 --- a/i18n/zh-TW/cosmic_files.ftl +++ b/i18n/zh-TW/cosmic_files.ftl @@ -42,7 +42,7 @@ name-no-slashes = 名稱不能包含斜線 ## Open/Save Dialog cancel = 取消 -create = 建立 +create = 創作 open = 開啟 open-file = 開啟檔案 open-folder = 開啟資料夾 @@ -358,7 +358,7 @@ calculating = 計算中... single-click = 點按以開啟 type-to-search = 輸入進行搜尋 type-to-search-recursive = 搜尋目前資料夾及全部子資料夾 -type-to-search-enter-path = 輸入目錄或檔案的目錄 +type-to-search-enter-path = 輸入目錄或檔案的路徑 delete-permanently = 永久刪除 eject = 彈出 remove-from-recents = 從最近項目中移除 @@ -384,11 +384,3 @@ set-executable-and-launched = 設定「{ $name }」為可以執行並已經啟 setting-permissions = 設定「{ $name }」的權限至 { $mode } set-permissions = 設定「{ $name }」的權限至 { $mode } mixed = 混合 -context-action = 環境行動 -context-action-confirm-title = 執行「{ $name }」嗎? -context-action-confirm-warning = - 該行動將會在 { $items } { $items -> - [one] 項目 - *[other] 項目 - } 上執行。 -run = 執行 diff --git a/rust-toolchain.toml b/rust-toolchain.toml deleted file mode 100644 index fb5449a..0000000 --- a/rust-toolchain.toml +++ /dev/null @@ -1,3 +0,0 @@ -[toolchain] -channel = "1.93.0" -components = ["clippy", "rustfmt"] diff --git a/rustfmt.toml b/rustfmt.toml deleted file mode 100644 index c1578aa..0000000 --- a/rustfmt.toml +++ /dev/null @@ -1 +0,0 @@ -imports_granularity = "Module" diff --git a/src/app.rs b/src/app.rs index 0cb2948..7bd3884 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,23 +1,8 @@ // Copyright 2023 System76 // SPDX-License-Identifier: GPL-3.0-only -use cosmic::app::{self, Core, Task, context_drawer}; -use cosmic::cosmic_config::{self, ConfigSet}; -use cosmic::iced::clipboard::dnd::DndAction; -use cosmic::iced::core::SmolStr; -use cosmic::iced::core::widget::operation::focusable::unfocus; -use cosmic::iced::futures::{self, SinkExt}; -use cosmic::iced::keyboard::{Event as KeyEvent, Key, Modifiers}; #[cfg(all(feature = "wayland", feature = "desktop-applet"))] use cosmic::iced::platform_specific::shell::wayland::commands::overlap_notify::overlap_notify; -use cosmic::iced::runtime::{clipboard, task}; -use cosmic::iced::widget::button::focus; -use cosmic::iced::widget::scrollable; -use cosmic::iced::widget::scrollable::AbsoluteOffset; -use cosmic::iced::window::{self, Event as WindowEvent, Id as WindowId}; -use cosmic::iced::{ - self, Alignment, Event, Length, Rectangle, Size, Subscription, event, mouse, stream, -}; #[cfg(all(feature = "wayland", feature = "desktop-applet"))] use cosmic::iced::{ Limits, Point, @@ -29,16 +14,41 @@ use cosmic::iced::{ Anchor, KeyboardInteractivity, Layer, destroy_layer_surface, get_layer_surface, }, }; -use cosmic::widget::about::About; -use cosmic::widget::dnd_destination::DragId; -use cosmic::widget::menu::action::MenuAction; -use cosmic::widget::menu::key_bind::KeyBind; -use cosmic::widget::segmented_button::{self, Entity, ReorderEvent}; -use cosmic::widget::{self, icon, settings, space}; -use cosmic::{Application, ApplicationExt, Element, cosmic_theme, executor, style, surface, theme}; +use cosmic::{ + Application, ApplicationExt, Element, + app::{self, Core, Task, context_drawer}, + cosmic_config::{self, ConfigSet}, + cosmic_theme, executor, + iced::core::widget::operation::focusable::unfocus, + iced::runtime::{clipboard, task}, + iced::widget::{button::focus, scrollable::AbsoluteOffset}, + iced::{ + self, Alignment, Event, Length, Rectangle, Size, Subscription, + clipboard::dnd::DndAction, + core::SmolStr, + event, + futures::{self, SinkExt}, + keyboard::{Event as KeyEvent, Key, Modifiers}, + mouse, stream, + widget::scrollable, + window::{self, Event as WindowEvent, Id as WindowId}, + }, + style, surface, theme, + widget::{ + self, + about::About, + dnd_destination::DragId, + icon, + menu::{action::MenuAction, key_bind::KeyBind}, + segmented_button::{self, Entity, ReorderEvent}, + space, + }, +}; use mime_guess::Mime; -use notify_debouncer_full::notify::{self, RecommendedWatcher}; -use notify_debouncer_full::{DebouncedEvent, Debouncer, RecommendedCache, new_debouncer}; +use notify_debouncer_full::{ + DebouncedEvent, Debouncer, RecommendedCache, new_debouncer, + notify::{self, RecommendedWatcher}, +}; use rustc_hash::{FxHashMap, FxHashSet}; use slotmap::Key as SlotMapKey; #[cfg(feature = "notify")] @@ -61,33 +71,37 @@ use trash::TrashItem; #[cfg(all(feature = "wayland", feature = "desktop-applet"))] use wayland_client::{Proxy, protocol::wl_output::WlOutput}; -use crate::clipboard::{ - ClipboardCache, ClipboardCopy, ClipboardKind, ClipboardPaste, ClipboardPasteImage, - ClipboardPasteText, ClipboardPasteVideo, +use crate::{ + FxOrderMap, + clipboard::{ + ClipboardCache, ClipboardCopy, ClipboardKind, ClipboardPaste, ClipboardPasteImage, + ClipboardPasteText, ClipboardPasteVideo, + }, + config::{ + AppTheme, Config, DesktopConfig, Favorite, IconSizes, State, TIME_CONFIG_ID, TabConfig, + TimeConfig, ToolbarAction, TypeToSearch, default_toolbar, + }, + context_action, + dialog::{Dialog, DialogKind, DialogMessage, DialogResult, DialogSettings}, + fl, home_dir, + key_bind::key_binds, + localize::LANGUAGE_SORTER, + menu, + mime_app::{MimeApp, MimeAppCache}, + mime_icon, + mounter::{MOUNTERS, MounterAuth, MounterItem, MounterItems, MounterKey, MounterMessage}, + operation::{ + Controller, Operation, OperationError, OperationErrorType, OperationSelection, + ReplaceResult, copy_unique_path, + }, + spawn_detached::spawn_detached, + tab::{ + self, HOVER_DURATION, HeadingOptions, ItemMetadata, Location, SORT_OPTION_FALLBACK, + SearchLocation, Tab, + }, + trash::{Trash, TrashExt}, + zoom::{zoom_in_view, zoom_out_view, zoom_to_default}, }; -use crate::config::{ - AppTheme, Config, DesktopConfig, Favorite, IconSizes, State, TIME_CONFIG_ID, TabConfig, - TimeConfig, ToolbarAction, TypeToSearch, default_toolbar, -}; -use crate::dialog::{Dialog, DialogKind, DialogMessage, DialogResult, DialogSettings}; -use crate::key_bind::key_binds; -use crate::localize::LANGUAGE_SORTER; -use crate::mime_app::{self, MimeApp, MimeAppCache}; -use crate::mounter::{ - MOUNTERS, MounterAuth, MounterItem, MounterItems, MounterKey, MounterMessage, -}; -use crate::operation::{ - Controller, Operation, OperationError, OperationErrorType, OperationSelection, ReplaceResult, - copy_unique_path, -}; -use crate::spawn_detached::spawn_detached; -use crate::tab::{ - self, HOVER_DURATION, HeadingOptions, ItemMetadata, Location, SORT_OPTION_FALLBACK, - SearchLocation, Tab, -}; -use crate::trash::{Trash, TrashExt}; -use crate::zoom::{zoom_in_view, zoom_out_view, zoom_to_default}; -use crate::{FxOrderMap, context_action, fl, home_dir, menu, mime_icon}; static PERMANENT_DELETE_BUTTON_ID: LazyLock = LazyLock::new(|| widget::Id::new("permanent-delete-button")); @@ -392,7 +406,7 @@ impl MenuAction for Action { } #[derive(Clone, Debug)] -pub struct PreviewItem(pub Box); +pub struct PreviewItem(pub tab::Item); impl PartialEq for PreviewItem { fn eq(&self, other: &Self) -> bool { @@ -564,7 +578,7 @@ pub enum Message { TabRescan( Entity, Location, - Option>, + Option, Vec, Option>, ), @@ -698,8 +712,8 @@ pub enum DialogPage { dir: bool, }, Replace { - from: Box, - to: Box, + from: tab::Item, + to: tab::Item, multiple: bool, apply_to_all: bool, conflict_count: usize, @@ -1014,39 +1028,31 @@ impl App { #[cfg(feature = "desktop")] fn launch_desktop_entries(paths: &[impl AsRef]) { use cosmic::desktop::fde::DesktopEntry; - let locales = cosmic::desktop::fde::get_languages_from_env(); for path in paths.iter().map(AsRef::as_ref) { match DesktopEntry::from_path::<&str>(path, None) { Ok(entry) => match entry.exec() { - Some(exec) => { - match mime_app::exec_to_command( - exec, - entry.name(&locales).as_deref().unwrap_or_default(), - Some(path), - &[] as &[&str; 0], - ) { - Some(commands) => { - let cwd_opt = entry.desktop_entry("Path"); + Some(exec) => match crate::mime_app::exec_to_command(exec, &[] as &[&str; 0]) { + Some(commands) => { + let cwd_opt = entry.desktop_entry("Path"); - for mut command in commands { - if let Some(cwd) = cwd_opt { - command.current_dir(cwd); - } + for mut command in commands { + if let Some(cwd) = cwd_opt { + command.current_dir(cwd); + } - if let Err(err) = spawn_detached(&mut command) { - log::warn!("failed to execute {}: {}", path.display(), err); - } + if let Err(err) = spawn_detached(&mut command) { + log::warn!("failed to execute {}: {}", path.display(), err); } } - None => { - log::warn!( - "failed to parse {}: invalid Desktop Entry/Exec", - path.display() - ); - } } - } + None => { + log::warn!( + "failed to parse {}: invalid Desktop Entry/Exec", + path.display() + ); + } + }, None => { log::warn!( "failed to parse {}: missing Desktop Entry/Exec", @@ -1509,7 +1515,7 @@ impl App { // Manually rescan any trash tabs after any operation is completed commands.push(self.rescan_trash()); - Task::batch(commands) + return Task::batch(commands); } fn handle_operation_errors(&mut self, errors: Vec<(u64, OperationError)>) -> Task { @@ -1554,7 +1560,7 @@ impl App { } // Manually rescan any trash tabs after any operation is completed tasks.push(self.rescan_trash()); - Task::batch(tasks) + return Task::batch(tasks); } fn remove_window(&mut self, id: &window::Id) { @@ -1932,8 +1938,6 @@ impl App { if let Some(path) = favorite.path_opt() { let name = if matches!(favorite, Favorite::Home) { fl!("home") - } else if let Favorite::Network { name, .. } = favorite { - name.clone() } else if let Some(file_name) = path.file_name().and_then(|x| x.to_str()) { file_name.to_string() } else { @@ -2109,7 +2113,7 @@ impl App { fn network_drive(&self) -> Element<'_, Message> { let cosmic_theme::Spacing { space_xxs, space_m, .. - } = theme::spacing(); + } = theme::active().cosmic().spacing; let mut table = widget::column::with_capacity(8); for (i, line) in fl!("network-drive-schemes").lines().enumerate() { let mut row = widget::row::with_capacity(2); @@ -2140,23 +2144,25 @@ impl App { fn desktop_view_options(&self) -> Element<'_, Message> { let cosmic_theme::Spacing { space_m, space_l, .. - } = theme::spacing(); + } = theme::active().cosmic().spacing; let config = self.config.desktop; - let show_on_desktop = settings::section() - .title(fl!("show-on-desktop")) - .add( - settings::item::builder(fl!("desktop-folder-content")).toggler( - config.show_content, - move |show_content| { - Message::DesktopConfig(DesktopConfig { - show_content, - ..config - }) - }, - ), - ) - .add(settings::item::builder(fl!("mounted-drives")).toggler( + let mut column = widget::column::with_capacity(2); + + let mut section = widget::settings::section().title(fl!("show-on-desktop")); + section = section.add( + widget::settings::item::builder(fl!("desktop-folder-content")).toggler( + config.show_content, + move |show_content| { + Message::DesktopConfig(DesktopConfig { + show_content, + ..config + }) + }, + ), + ); + section = section.add( + widget::settings::item::builder(fl!("mounted-drives")).toggler( config.show_mounted_drives, move |show_mounted_drives| { Message::DesktopConfig(DesktopConfig { @@ -2164,8 +2170,10 @@ impl App { ..config }) }, - )) - .add(settings::item::builder(fl!("trash-folder-icon")).toggler( + ), + ); + section = section.add( + widget::settings::item::builder(fl!("trash-folder-icon")).toggler( config.show_trash, move |show_trash| { Message::DesktopConfig(DesktopConfig { @@ -2173,49 +2181,50 @@ impl App { ..config }) }, - )); + ), + ); + column = column.push(section); + let mut section = widget::settings::section().title(fl!("icon-size-and-spacing")); let icon_size = config.icon_size; - let grid_spacing = config.grid_spacing; - let icon_size_and_spacing = settings::section() - .title(fl!("icon-size-and-spacing")) - .add( - settings::item::builder(fl!("icon-size")) - .description(format!("{icon_size}%")) - .control( - widget::slider(50..=500, icon_size.get(), move |new_value| { - Message::DesktopConfig(DesktopConfig { - icon_size: NonZeroU16::new(new_value).unwrap_or(icon_size), - ..config - }) + section = section.add( + widget::settings::item::builder(fl!("icon-size")) + .description(format!("{icon_size}%")) + .control( + widget::slider(50..=500, icon_size.get(), move |new_value| { + Message::DesktopConfig(DesktopConfig { + icon_size: NonZeroU16::new(new_value).unwrap_or(icon_size), + ..config }) - .step(25u16), - ), - ) - .add( - settings::item::builder(fl!("grid-spacing")) - .description(format!("{grid_spacing}%")) - .control( - widget::slider(50..=500, grid_spacing.get(), move |new_value| { - Message::DesktopConfig(DesktopConfig { - grid_spacing: NonZeroU16::new(new_value).unwrap_or(grid_spacing), - ..config - }) - }) - .step(25u16), - ), - ); + }) + .step(25u16), + ), + ); - widget::column::with_capacity(2) + let grid_spacing = config.grid_spacing; + section = section.add( + widget::settings::item::builder(fl!("grid-spacing")) + .description(format!("{grid_spacing}%")) + .control( + widget::slider(50..=500, grid_spacing.get(), move |new_value| { + Message::DesktopConfig(DesktopConfig { + grid_spacing: NonZeroU16::new(new_value).unwrap_or(grid_spacing), + ..config + }) + }) + .step(25u16), + ), + ); + column = column.push(section); + + column .padding([0, space_l, space_l, space_l]) .spacing(space_m) - .push(show_on_desktop) - .push(icon_size_and_spacing) .into() } fn edit_history(&self) -> Element<'_, Message> { - let cosmic_theme::Spacing { space_m, .. } = theme::spacing(); + let cosmic_theme::Spacing { space_m, .. } = theme::active().cosmic().spacing; let mut children = Vec::new(); @@ -2307,7 +2316,7 @@ impl App { kind: &'a PreviewKind, context_drawer: bool, ) -> Element<'a, tab::Message> { - let cosmic_theme::Spacing { space_l, .. } = theme::spacing(); + let cosmic_theme::Spacing { space_l, .. } = theme::active().cosmic().spacing; let mut children = Vec::with_capacity(1); let entity = entity_opt.unwrap_or_else(|| self.tab_model.active()); @@ -2377,8 +2386,8 @@ impl App { let tab_config = self.config.tab; // TODO: Should dialog be updated here too? - settings::view_column(vec![ - settings::section() + widget::settings::view_column(vec![ + widget::settings::section() .title(fl!("appearance")) .add({ let app_theme_selected = match self.config.app_theme { @@ -2386,7 +2395,7 @@ impl App { AppTheme::Light => 2, AppTheme::System => 0, }; - settings::item::builder(fl!("theme")).control(widget::dropdown( + widget::settings::item::builder(fl!("theme")).control(widget::dropdown( &self.app_themes, Some(app_theme_selected), move |index| { @@ -2399,32 +2408,31 @@ impl App { )) }) .into(), - settings::section() + widget::settings::section() .title(fl!("type-to-search")) - .add( - settings::item::builder(fl!("type-to-search-recursive")).radio( - TypeToSearch::Recursive, - Some(self.config.type_to_search), - Message::SetTypeToSearch, - ), - ) - .add( - settings::item::builder(fl!("type-to-search-enter-path")).radio( - TypeToSearch::EnterPath, - Some(self.config.type_to_search), - Message::SetTypeToSearch, - ), - ) - .add(settings::item::builder(fl!("type-to-search-select")).radio( + .add(widget::radio( + widget::text::body(fl!("type-to-search-recursive")), + TypeToSearch::Recursive, + Some(self.config.type_to_search), + Message::SetTypeToSearch, + )) + .add(widget::radio( + widget::text::body(fl!("type-to-search-enter-path")), + TypeToSearch::EnterPath, + Some(self.config.type_to_search), + Message::SetTypeToSearch, + )) + .add(widget::radio( + widget::text::body(fl!("type-to-search-select")), TypeToSearch::SelectByPrefix, Some(self.config.type_to_search), Message::SetTypeToSearch, )) .into(), - settings::section() + widget::settings::section() .title(fl!("other")) .add({ - settings::item::builder(fl!("single-click")).toggler( + widget::settings::item::builder(fl!("single-click")).toggler( tab_config.single_click, move |single_click| { Message::TabConfig(TabConfig { @@ -2435,7 +2443,7 @@ impl App { ) }) .add({ - settings::item::builder(fl!("show-recents")) + widget::settings::item::builder(fl!("show-recents")) .toggler(self.config.show_recents, Message::SetShowRecents) }) .into(), @@ -3245,12 +3253,10 @@ impl Application for App { Message::Config(config) => { if config != self.config { log::info!("update config"); - // Show details and military time are preserved for existing instances + // Show details is preserved for existing instances let show_details = self.config.show_details; - let military_time = self.config.tab.military_time; self.config = config; self.config.show_details = show_details; - self.config.tab.military_time = military_time; return self.update_config(); } } @@ -4968,14 +4974,6 @@ impl Application for App { tab::Command::DropFiles(to, from) => { commands.push(self.update(Message::PasteContents(to, from))); } - tab::Command::ClearRecents => { - match recently_used_xbel::clear_recently_used() { - Ok(()) => {} - Err(err) => { - log::warn!("failed to clear recents history: {}", err); - } - } - } tab::Command::EmptyTrash => { return self.push_dialog( DialogPage::EmptyTrash, @@ -5608,7 +5606,7 @@ impl Application for App { Ok(item) => { self.context_page = ContextPage::Preview( None, - PreviewKind::Custom(PreviewItem(Box::new(item))), + PreviewKind::Custom(PreviewItem(item)), ); self.set_show_context(true); } @@ -5915,7 +5913,7 @@ impl Application for App { let cosmic_theme::Spacing { space_xxs, space_s, .. - } = theme::spacing(); + } = theme::active().cosmic().spacing; let dialog = match dialog_page { DialogPage::Compress { @@ -6054,7 +6052,7 @@ impl Application for App { } DialogPage::FailedOperations(ids) => { let errors: Vec = ids - .iter() + .into_iter() .filter_map(|id| match self.failed_operations.get(id) { Some((operation, _, err)) => Some(format!("{operation:#?}\n{err}")), _ => None, @@ -6547,7 +6545,7 @@ impl Application for App { dialog .primary_action( - widget::button::suggested(fl!("rename-confirm")) + widget::button::suggested(fl!("rename")) .on_press_maybe(complete_maybe.clone()), ) .secondary_action( @@ -6691,7 +6689,7 @@ impl Application for App { let cosmic_theme::Spacing { space_xs, space_s, .. - } = theme::spacing(); + } = theme::active().cosmic().spacing; let mut title = String::new(); let mut total_progress = 0.0; @@ -6851,7 +6849,7 @@ impl Application for App { space_xs, space_s, .. - } = theme::spacing(); + } = theme::active().cosmic().spacing; let mut tab_column = widget::column::with_capacity(4); @@ -7473,17 +7471,21 @@ impl Application for App { // Ideally, tests would use the cap-std crate which limits path traversal. #[cfg(test)] pub(crate) mod test_utils { - use std::cmp::Ordering; - use std::fs::File; - use std::io::{self, Write}; - use std::iter; - use std::path::Path; + use std::{ + cmp::Ordering, + fs::File, + io::{self, Write}, + iter, + path::Path, + }; use log::{debug, trace}; use tempfile::{TempDir, tempdir}; - use crate::config::{IconSizes, TabConfig, ThumbCfg}; - use crate::tab::Item; + use crate::{ + config::{IconSizes, TabConfig, ThumbCfg}, + tab::Item, + }; use super::*; diff --git a/src/archive.rs b/src/archive.rs index cff0c1e..ba11cbb 100644 --- a/src/archive.rs +++ b/src/archive.rs @@ -1,14 +1,16 @@ -use crate::mime_icon::mime_for_path; -use crate::operation::{Controller, OpReader, OperationError, OperationErrorType, sync_to_disk}; +use crate::{ + mime_icon::mime_for_path, + operation::{Controller, OpReader, OperationError, OperationErrorType, sync_to_disk}, +}; use cosmic::iced::futures; -use jiff::Zoned; -use jiff::civil::DateTime; -use jiff::tz::TimeZone; -use std::collections::HashSet; -use std::fs; -use std::io::{self, Read, Write}; -use std::path::{Path, PathBuf}; -use std::time::SystemTime; +use jiff::{Zoned, civil::DateTime, tz::TimeZone}; +use std::{ + collections::HashSet, + fs, + io::{self, Read, Write}, + path::{Path, PathBuf}, + time::SystemTime, +}; use zip::result::ZipError; pub const SUPPORTED_ARCHIVE_TYPES: &[&str] = &[ @@ -110,8 +112,7 @@ fn zip_extract>( password: Option<&str>, controller: Controller, ) -> zip::result::ZipResult<()> { - use std::ffi::OsString; - use std::fs; + use std::{ffi::OsString, fs}; use zip::result::ZipError; fn make_writable_dir_all>( diff --git a/src/channel.rs b/src/channel.rs index 2a295f9..e91bc35 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -1,9 +1,13 @@ // Copyright 2025 System76 // SPDX-License-Identifier: MPL-2.0 -use std::collections::VecDeque; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, Mutex}; +use std::{ + collections::VecDeque, + sync::{ + Arc, Mutex, + atomic::{AtomicBool, Ordering}, + }, +}; /// Create a channel backed by `tokio::sync::Notify` and a sync mutex with a vec deque. pub fn channel() -> (Sender, Receiver) { diff --git a/src/clipboard.rs b/src/clipboard.rs index 7d06be5..89da7d7 100644 --- a/src/clipboard.rs +++ b/src/clipboard.rs @@ -2,10 +2,12 @@ // SPDX-License-Identifier: GPL-3.0-only use cosmic::iced::clipboard::mime::{AllowedMimeTypes, AsMimeTypes}; -use std::borrow::Cow; -use std::error::Error; -use std::path::{Path, PathBuf}; -use std::str; +use std::{ + borrow::Cow, + error::Error, + path::{Path, PathBuf}, + str, +}; use url::Url; #[derive(Clone, Copy, Debug)] @@ -130,7 +132,6 @@ impl TryFrom<(Vec, String)> for ClipboardPaste { match mime.as_str() { "text/uri-list" => { let text = str::from_utf8(&data)?; - for line in text.lines().filter(|line| { let line = line.trim(); !line.is_empty() && !line.starts_with('#') diff --git a/src/config.rs b/src/config.rs index 8f493c0..6d89367 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,18 +1,20 @@ // SPDX-License-Identifier: GPL-3.0-only -use std::any::TypeId; -use std::num::NonZeroU16; -use std::path::PathBuf; +use std::{any::TypeId, num::NonZeroU16, path::PathBuf}; -use cosmic::cosmic_config::cosmic_config_derive::CosmicConfigEntry; -use cosmic::cosmic_config::{self, CosmicConfigEntry}; -use cosmic::iced::Subscription; -use cosmic::{Application, theme}; +use cosmic::{ + Application, + cosmic_config::{self, CosmicConfigEntry, cosmic_config_derive::CosmicConfigEntry}, + iced::Subscription, + theme, +}; use serde::{Deserialize, Serialize}; -use crate::FxOrderMap; -use crate::app::App; -use crate::tab::{HeadingOptions, Location, View}; +use crate::{ + FxOrderMap, + app::App, + tab::{HeadingOptions, Location, View}, +}; pub use crate::context_action::{ContextActionPreset, ContextActionSelection}; diff --git a/src/context_action.rs b/src/context_action.rs index 34dfb6e..86f3bc6 100644 --- a/src/context_action.rs +++ b/src/context_action.rs @@ -4,8 +4,7 @@ use std::path::PathBuf; use serde::{Deserialize, Serialize}; -use crate::mime_app; -use crate::spawn_detached::spawn_detached; +use crate::{mime_app, spawn_detached::spawn_detached}; #[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub enum ContextActionSelection { @@ -47,7 +46,7 @@ impl ContextActionPreset { } for step in &self.steps { - let Some(commands) = mime_app::exec_to_command(step, &self.name, None, paths) else { + let Some(commands) = mime_app::exec_to_command(step, paths) else { log::warn!( "failed to parse context action {:?}: invalid Exec {:?}", self.name, diff --git a/src/dialog.rs b/src/dialog.rs index eac9c96..6bdeee6 100644 --- a/src/dialog.rs +++ b/src/dialog.rs @@ -1,46 +1,58 @@ // Copyright 2023 System76 // SPDX-License-Identifier: GPL-3.0-only -use cosmic::app::cosmic::Cosmic; -use cosmic::app::{Core, Task, context_drawer}; -use cosmic::iced::core::SmolStr; -use cosmic::iced::core::widget::operation; -use cosmic::iced::futures::{self, SinkExt}; -use cosmic::iced::keyboard::key::Named; -use cosmic::iced::keyboard::{Event as KeyEvent, Key, Modifiers}; -use cosmic::iced::platform_specific::shell::{self as iced_winit, SurfaceIdWrapper}; -use cosmic::iced::widget::scrollable; -use cosmic::iced::widget::scrollable::AbsoluteOffset; -use cosmic::iced::{ - self, Alignment, Event, Length, Size, Subscription, event, mouse, stream, window, +use cosmic::{ + Application, ApplicationExt, Element, + app::{Core, Task, context_drawer, cosmic::Cosmic}, + cosmic_config, cosmic_theme, executor, + iced::core::widget::operation, + iced::platform_specific::shell::{self as iced_winit, SurfaceIdWrapper}, + iced::widget::scrollable::AbsoluteOffset, + iced::{ + self, Alignment, Event, Length, Size, Subscription, + core::SmolStr, + event, + futures::{self, SinkExt}, + keyboard::{Event as KeyEvent, Key, Modifiers, key::Named}, + mouse, stream, + widget::scrollable, + window, + }, + theme, + widget::{ + self, Operation, + menu::{Action as MenuAction, KeyBind, key_bind::Modifier}, + segmented_button, + }, }; -use cosmic::widget::menu::key_bind::Modifier; -use cosmic::widget::menu::{Action as MenuAction, KeyBind}; -use cosmic::widget::{self, Operation, segmented_button}; -use cosmic::{Application, ApplicationExt, Element, cosmic_config, cosmic_theme, executor, theme}; use mime_guess::{Mime, mime}; -use notify_debouncer_full::notify::{self, RecommendedWatcher}; -use notify_debouncer_full::{DebouncedEvent, Debouncer, RecommendedCache, new_debouncer}; +use notify_debouncer_full::{ + DebouncedEvent, Debouncer, RecommendedCache, new_debouncer, + notify::{self, RecommendedWatcher}, +}; use recently_used_xbel::update_recently_used; use rustc_hash::{FxHashMap, FxHashSet}; -use std::any::TypeId; -use std::collections::{HashMap, VecDeque}; -use std::path::PathBuf; -use std::time::{self, Instant}; -use std::{env, fmt, fs}; +use std::{ + any::TypeId, + collections::{HashMap, VecDeque}, + env, fmt, fs, + path::PathBuf, + time::{self, Instant}, +}; -use crate::app::{ - Action, ContextPage, Message as AppMessage, PreviewItem, PreviewKind, REPLACE_BUTTON_ID, +use crate::{ + app::{ + Action, ContextPage, Message as AppMessage, PreviewItem, PreviewKind, REPLACE_BUTTON_ID, + }, + config::{Config, DialogConfig, Favorite, TIME_CONFIG_ID, ThumbCfg, TimeConfig, TypeToSearch}, + fl, home_dir, + key_bind::key_binds, + localize::LANGUAGE_SORTER, + menu, + mounter::{MOUNTERS, MounterItem, MounterItems, MounterKey, MounterMessage}, + tab::{self, ItemMetadata, Location, SearchLocation, Tab}, + zoom::{zoom_in_view, zoom_out_view, zoom_to_default}, }; -use crate::config::{ - Config, DialogConfig, Favorite, TIME_CONFIG_ID, ThumbCfg, TimeConfig, TypeToSearch, -}; -use crate::key_bind::key_binds; -use crate::localize::LANGUAGE_SORTER; -use crate::mounter::{MOUNTERS, MounterItem, MounterItems, MounterKey, MounterMessage}; -use crate::tab::{self, ItemMetadata, Location, SearchLocation, Tab}; -use crate::zoom::{zoom_in_view, zoom_out_view, zoom_to_default}; -use crate::{fl, home_dir, menu}; #[derive(Clone, Debug)] pub struct DialogMessage(cosmic::Action); @@ -475,7 +487,7 @@ enum Message { TabMessage(tab::Message), TabRescan( Location, - Option>, + Option, Vec, Option>, ), @@ -575,7 +587,7 @@ impl App { space_s, space_l, .. - } = theme::spacing(); + } = theme::active().cosmic().spacing; let is_condensed = self.core().is_condensed(); let mut col = widget::column::with_capacity(2).spacing(space_xxs); @@ -831,10 +843,9 @@ impl App { fn update_config(&mut self) -> Task { self.core.window.show_context = self.flags.config.dialog.show_details; - let config = self.flags.config.dialog_tab(); - self.tab.config.view = config.view; + self.tab.config = self.flags.config.dialog_tab(); self.update_nav_model(); - self.update(Message::TabMessage(tab::Message::Config(config))) + self.update(Message::TabMessage(tab::Message::Config(self.tab.config))) } fn with_dialog_config(&mut self, f: F) -> Task { @@ -897,8 +908,6 @@ impl App { if let Some(path) = favorite.path_opt() { let name = if matches!(favorite, Favorite::Home) { fl!("home") - } else if let Favorite::Network { name, .. } = favorite { - name.clone() } else if let Some(file_name) = path.file_name().and_then(|x| x.to_str()) { file_name.to_string() } else { @@ -1122,7 +1131,7 @@ impl Application for App { } fn dialog(&self) -> Option> { - let cosmic_theme::Spacing { space_xxs, .. } = theme::spacing(); + let cosmic_theme::Spacing { space_xxs, .. } = theme::active().cosmic().spacing; //TODO: should gallery view just be a dialog? if self.tab.gallery { @@ -1400,10 +1409,7 @@ impl Application for App { Message::Config(config) => { if config != self.flags.config { log::info!("update config"); - // Don't overwrite military time - let military_time = self.flags.config.tab.military_time; self.flags.config = config; - self.flags.config.tab.military_time = military_time; return self.update_config(); } } @@ -2017,7 +2023,7 @@ impl Application for App { /// Creates a view after each update. fn view(&self) -> Element<'_, Message> { - let cosmic_theme::Spacing { space_xxs, .. } = theme::spacing(); + let cosmic_theme::Spacing { space_xxs, .. } = theme::active().cosmic().spacing; let mut col = widget::column::with_capacity(2); diff --git a/src/key_bind.rs b/src/key_bind.rs index f47d403..57a9dbf 100644 --- a/src/key_bind.rs +++ b/src/key_bind.rs @@ -1,10 +1,11 @@ -use cosmic::iced::core::keyboard::key::Named; -use cosmic::iced::keyboard::Key; -use cosmic::widget::menu::key_bind::{KeyBind, Modifier}; +use cosmic::{ + iced::core::keyboard::key::Named, + iced::keyboard::Key, + widget::menu::key_bind::{KeyBind, Modifier}, +}; use std::collections::HashMap; -use crate::app::Action; -use crate::tab; +use crate::{app::Action, tab}; //TODO: load from config pub fn key_binds(mode: &tab::Mode) -> HashMap { diff --git a/src/large_image.rs b/src/large_image.rs index 1d78bc5..8686a97 100644 --- a/src/large_image.rs +++ b/src/large_image.rs @@ -1,7 +1,9 @@ use cosmic::widget; use image::ImageReader; -use std::collections::{HashMap, HashSet}; -use std::path::{Path, PathBuf}; +use std::{ + collections::{HashMap, HashSet}, + path::{Path, PathBuf}, +}; /// Bytes per pixel in RGBA format (Red, Green, Blue, Alpha = 4 bytes) pub const RGBA_BYTES_PER_PIXEL: u64 = 4; diff --git a/src/lib.rs b/src/lib.rs index 44b87f6..a426695 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,16 +1,15 @@ // Copyright 2023 System76 // SPDX-License-Identifier: GPL-3.0-only -use cosmic::app::Settings; -use cosmic::iced::Limits; -use std::path::PathBuf; -use std::{env, fs, process}; -use tracing_subscriber::layer::SubscriberExt; -use tracing_subscriber::util::SubscriberInitExt; +use cosmic::{app::Settings, iced::Limits}; +use std::{env, fs, path::PathBuf, process}; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; -use crate::app::{App, Flags}; -use crate::config::{Config, State}; -use crate::tab::Location; +use crate::{ + app::{App, Flags}, + config::{Config, State}, + tab::Location, +}; pub mod app; mod archive; diff --git a/src/load_image.rs b/src/load_image.rs index 88ceb1b..d54fe7f 100644 --- a/src/load_image.rs +++ b/src/load_image.rs @@ -1,10 +1,11 @@ use cosmic::iced::{core as iced_core, widget as iced_widget}; use iced_core::event::Event; +use iced_core::layout; +use iced_core::mouse; +use iced_core::overlay; +use iced_core::renderer; use iced_core::widget::{Operation, Tree}; -use iced_core::{ - Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse, overlay, - renderer, -}; +use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget}; pub fn loaded_image<'a, Message: 'static, Theme>( handle: ::Handle, diff --git a/src/localize.rs b/src/localize.rs index b569807..f561a42 100644 --- a/src/localize.rs +++ b/src/localize.rs @@ -1,10 +1,13 @@ // SPDX-License-Identifier: GPL-3.0-only -use i18n_embed::fluent::{FluentLanguageLoader, fluent_language_loader}; -use i18n_embed::{DefaultLocalizer, LanguageLoader, Localizer}; -use icu::collator::options::CollatorOptions; -use icu::collator::preferences::CollationNumericOrdering; -use icu::collator::{Collator, CollatorBorrowed, CollatorPreferences}; +use i18n_embed::{ + DefaultLocalizer, LanguageLoader, Localizer, + fluent::{FluentLanguageLoader, fluent_language_loader}, +}; +use icu::collator::{ + Collator, CollatorBorrowed, CollatorPreferences, options::CollatorOptions, + preferences::CollationNumericOrdering, +}; use icu::locale::Locale; use rust_embed::RustEmbed; use std::sync::LazyLock; diff --git a/src/menu.rs b/src/menu.rs index d167f41..9de9af4 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -1,26 +1,31 @@ // SPDX-License-Identifier: GPL-3.0-only -use cosmic::app::Core; -use cosmic::iced::advanced::widget::text::Style as TextStyle; -use cosmic::iced::keyboard::Modifiers; -use cosmic::iced::{Alignment, Background, Border, Length}; -use cosmic::widget::menu::key_bind::KeyBind; -use cosmic::widget::menu::{self, ItemHeight, ItemWidth, MenuBar}; -use cosmic::widget::{ - self, Row, button, column, container, divider, responsive_menu_bar, space, text, +use cosmic::{ + Element, + app::Core, + iced::{ + Alignment, Background, Border, Length, advanced::widget::text::Style as TextStyle, + keyboard::Modifiers, + }, + theme, + widget::{ + self, Row, button, column, container, divider, + menu::{self, ItemHeight, ItemWidth, MenuBar, key_bind::KeyBind}, + responsive_menu_bar, space, text, + }, }; -use cosmic::{Element, theme}; #[cfg(feature = "desktop")] use i18n_embed::LanguageLoader; use mime_guess::Mime; -use std::collections::HashMap; -use std::sync::LazyLock; +use std::{collections::HashMap, sync::LazyLock}; -use crate::app::{Action, Message}; -use crate::config::{Config, ContextActionPreset}; -use crate::fl; -use crate::tab::{self, HeadingOptions, Location, LocationMenuAction, SearchLocation, Tab}; -use crate::trash::{Trash, TrashExt}; +use crate::{ + app::{Action, Message}, + config::{Config, ContextActionPreset}, + fl, + tab::{self, HeadingOptions, Location, LocationMenuAction, SearchLocation, Tab}, + trash::{Trash, TrashExt}, +}; static MENU_ID: LazyLock = LazyLock::new(|| cosmic::widget::Id::new("responsive-menu")); @@ -34,7 +39,7 @@ macro_rules! menu_button { .height(Length::Fixed(24.0)) .align_y(Alignment::Center) ) - .padding([theme::spacing().space_xxs, 16]) + .padding([theme::active().cosmic().spacing.space_xxs, 16]) .width(Length::Fill) .class(theme::Button::MenuItem) ); @@ -139,11 +144,12 @@ pub fn context_menu<'a>( Some(Location::Trash) | Some(Location::Search(SearchLocation::Trash, ..)) => { selected_trash_only = true } - Some(Location::Path(path)) + Some(Location::Path(path)) => { if selected == 1 - && path.extension().and_then(|s| s.to_str()) == Some("desktop") => - { - selected_desktop_entry = Some(&**path); + && path.extension().and_then(|s| s.to_str()) == Some("desktop") + { + selected_desktop_entry = Some(&**path); + } } _ => (), } @@ -574,7 +580,7 @@ pub fn dialog_menu( ]) .item_height(ItemHeight::Dynamic(40)) .item_width(ItemWidth::Uniform(360)) - .spacing(theme::spacing().space_xxxs.into()) + .spacing(theme::active().cosmic().spacing.space_xxxs.into()) .into() } @@ -629,7 +635,7 @@ pub fn menu_bar<'a>( responsive_menu_bar() .item_height(ItemHeight::Dynamic(40)) .item_width(ItemWidth::Uniform(360)) - .spacing(theme::spacing().space_xxxs.into()) + .spacing(theme::active().cosmic().spacing.space_xxxs.into()) .into_element( core, key_binds, diff --git a/src/mime_app.rs b/src/mime_app.rs index 9b385e3..a9bf63e 100644 --- a/src/mime_app.rs +++ b/src/mime_app.rs @@ -1,7 +1,6 @@ // Copyright 2023 System76 // SPDX-License-Identifier: GPL-3.0-only -use bstr::{BString, ByteSlice, ByteVec}; #[cfg(feature = "desktop")] use cosmic::desktop; use cosmic::widget; @@ -11,7 +10,6 @@ use rustc_hash::FxHashMap; use std::{cmp::Ordering, fs, io, time::Instant}; use std::{ ffi::OsStr, - os::unix::ffi::OsStrExt, path::{Path, PathBuf}, process, }; @@ -23,106 +21,134 @@ const DEPRECATED_HANDLERS: [&str; 6] = ["%d", "%D", "%n", "%N", "%v", "%m"]; pub fn exec_to_command( exec: &str, - entry_name: &str, - entry_path: Option<&Path>, path_opt: &[impl AsRef], ) -> Option> { - let arguments = shlex::split(exec)?; + let args_vec = shlex::split(exec)?; + let program = args_vec.first()?; + // Skip program to make indexing easier + let args_vec = &args_vec[1..]; - if arguments.is_empty() { - tracing::error!("command does not contain any arguments"); - return None; - } - - let mut commands = Vec::new(); - - let paths = path_opt + // Base Command instance(s) + // 1. We may need to launch multiple of the same process. + // 2. Each of those processes will need to be passed args from exec. + // 3. Each of those args may appear in any order. + // 4. Arg order should be preserved. + // + // So, we'll go through exec in two passes. The first pass handles paths (%f etc) and args up + // to the field code followed by the second which passes extra, non-% args to each processes. + // + // While it'd be marginally faster to process everything in one pass, that's problematic: + // 1. path_opt may need to be cloned because it may be moved on each iteration (borrowck + // doesn't know we'll only use it once) + // 2. We have to keep track of which modifier (%f etc) we've used/seen already + // 3. We have to keep track of which processes received non-modifier args which gets messy fast + // 4. `exec` is likely small so looping over it twice is not a big deal + let field_code_pos = args_vec .iter() - .map(AsRef::as_ref) - .map(Some) - // Add a single `None` if no path was given. - .chain(std::iter::repeat_n( - None, - if path_opt.is_empty() { 1 } else { 0 }, - )); + .position(|arg| EXEC_HANDLERS.contains(&arg.as_str())); + let args_handler = field_code_pos.and_then(|i| args_vec.get(i)); + // msrv + // .inspect(|handler| log::trace!("Found paths handler: {handler} for exec: {exec}")); + // Number of args before the field code. + // This won't be an off by one err below because take is not zero indexed. + let field_code_pos = field_code_pos.unwrap_or_default(); + let mut processes = match args_handler.map(String::as_str) { + Some("%f") => { + let mut processes = Vec::with_capacity(path_opt.len()); - for path in paths { - let mut batch_process = false; - let mut args = Vec::with_capacity(arguments.len()); - let mut field_code_used = false; - - for argument in arguments.iter().skip(1) { - let mut new_argument = BString::new(Vec::with_capacity(argument.capacity())); - let mut chars = argument.chars(); - while let Some(char) = chars.next() { - // https://specifications.freedesktop.org/desktop-entry/latest/exec-variables.html - if char == '%' { - match chars.next() { - Some('%') => new_argument.push_char(char), - Some('c') => new_argument.push_str(entry_name), - Some('k') => { - if let Some(path) = entry_path { - new_argument.push_str(path.as_os_str().as_bytes()); - } - } - - // %f and %u behave the same in a file manager. - Some('f' | 'u') => { - if let Some(path) = path - && !field_code_used - { - // TODO: files on remote file systems should be copied to a temporary local file. - batch_process = true; - field_code_used = true; - new_argument.push_str(path.as_bytes()); - } - } - - // %F and %U behave the same in a file manager. - Some('F') | Some('U') => { - if !field_code_used && new_argument.is_empty() { - field_code_used = true; - for path in path_opt.iter().map(AsRef::as_ref) { - args.push(BString::new(path.as_bytes().to_owned())); - } - } - } - - _ => (), - } - } else { - new_argument.push_char(char); + for path in path_opt.iter().map(AsRef::as_ref) { + // TODO: %f and %F need to handle non-file URLs (see spec) + if from_file_or_dir(path).is_none() { + log::warn!("Desktop file expects a file path instead of a URL: {path:?}"); } + + // Passing multiple paths to %f should open an instance per path + let mut process = process::Command::new(program); + process.args( + args_vec + .iter() + .map(AsRef::as_ref) + .take(field_code_pos) + .chain(std::iter::once(path)), + ); + processes.push(process); } - if !new_argument.is_empty() { - args.push(new_argument); - } + processes } + Some("%F") => { + // TODO: %f and %F need to handle non-file URLs (see spec) + for invalid in path_opt + .iter() + .map(AsRef::as_ref) + .filter(|&path| from_file_or_dir(path).is_none()) + { + log::warn!("Desktop file expects a file path instead of a URL: {invalid:?}"); + } - let mut command = process::Command::new(&arguments[0]); + // Launch one instance with all args + let mut process = process::Command::new(program); + process.args( + args_vec + .iter() + .map(OsStr::new) + .take(field_code_pos) + .chain(path_opt.iter().map(AsRef::as_ref)), + ); - for arg in args { - match arg.to_os_str() { - Ok(arg) => { - command.arg(arg); - } - Err(_) => { - tracing::error!("invalid string encoding in command"); + vec![process] + } + Some("%u") => path_opt + .iter() + .map(|path| { + let mut process = process::Command::new(program); + process.args( + args_vec + .iter() + .map(OsStr::new) + .take(field_code_pos) + .chain(std::iter::once(path.as_ref())), + ); + process + }) + .collect(), + Some("%U") => { + let mut process = process::Command::new(program); + process.args( + args_vec + .iter() + .map(OsStr::new) + .take(field_code_pos) + .chain(path_opt.iter().map(AsRef::as_ref)), + ); + vec![process] + } + Some(invalid) => unreachable!("All valid variants were checked; got: {invalid}"), + None => vec![process::Command::new(program)], + }; + + // Pass 2: Add remaining arguments that are not % to each process + for arg in args_vec.iter().skip(field_code_pos) { + match arg.as_str() { + // Consume path field codes or fail on codes we don't handle yet + field_code if arg.starts_with('%') => { + if !EXEC_HANDLERS.contains(&field_code) + && !DEPRECATED_HANDLERS.contains(&field_code) + { + log::warn!("unsupported Exec code {field_code:?} in {exec:?}"); return None; } } - } - - commands.push(command); - - if !batch_process { - break; + arg => { + for process in &mut processes { + process.arg(arg); + } + } } } #[cfg(debug_assertions)] - for command in &commands { + for command in &processes { log::debug!( "Parsed program {} with args: {:?}", command.get_program().to_string_lossy(), @@ -130,7 +156,13 @@ pub fn exec_to_command( ); } - Some(commands) + Some(processes) +} + +fn from_file_or_dir(path: impl AsRef) -> Option { + url::Url::from_file_path(&path) + .ok() + .or_else(|| url::Url::from_directory_path(&path).ok()) } #[derive(Clone, Debug)] @@ -146,12 +178,7 @@ pub struct MimeApp { impl MimeApp { //TODO: move to libcosmic, support multiple files pub fn command>(&self, path_opt: &[O]) -> Option> { - exec_to_command( - self.exec.as_deref()?, - &self.name, - self.path.as_deref(), - path_opt, - ) + exec_to_command(self.exec.as_deref()?, path_opt) } } @@ -450,43 +477,11 @@ impl Default for MimeAppCache { mod tests { use super::exec_to_command; - #[test] - fn keys_within_words() { - let exec = "/usr/bin/foo --option=%f"; - let paths = ["file1"]; - let commands = exec_to_command(exec, "keys_within_words", None, &paths) - .expect("Should parse valid exec"); - - assert_eq!(1, commands.len()); - let command = commands.first().unwrap(); - - assert_eq!("/usr/bin/foo", command.get_program().to_str().unwrap()); - assert_eq!( - "--option=file1", - command.get_args().next().unwrap().to_str().unwrap() - ); - } - - #[test] - fn no_path_f_field_code() { - let exec = "/usr/bin/foo %f"; - let paths: [&str; 0] = []; - let commands = exec_to_command(exec, "no_path_f_field_code", None, &paths) - .expect("Should parse valid exec"); - - assert_eq!(1, commands.len()); - let command = commands.first().unwrap(); - - assert_eq!("/usr/bin/foo", command.get_program().to_str().unwrap()); - assert_eq!(0, command.get_args().len()); - } - #[test] fn one_path_f_field_code() { let exec = "/usr/bin/foo %f"; let paths = ["file1"]; - let commands = exec_to_command(exec, "one_path_f_field_code", None, &paths) - .expect("Should parse valid exec"); + let commands = exec_to_command(exec, &paths).expect("Should parse valid exec"); assert_eq!(1, commands.len()); let command = commands.first().unwrap(); @@ -501,40 +496,31 @@ mod tests { #[test] #[allow(non_snake_case)] fn one_path_F_field_code() { - let exec = "/usr/bin/cosmic-term -w %F"; - let paths = ["/home/user"]; - let commands = exec_to_command(exec, "one_path_F_field_code", None, &paths) - .expect("Should parse valid exec"); + let exec = "/usr/bin/bar %F"; + let paths = ["cat"]; + let commands = exec_to_command(exec, &paths).expect("Should parse valid exec"); assert_eq!(1, commands.len()); let command = commands.first().unwrap(); - let mut args = command.get_args(); - assert_eq!( - "/usr/bin/cosmic-term", - command.get_program().to_str().unwrap() - ); - assert_eq!("-w", args.next().unwrap().to_str().unwrap()); - assert_eq!(paths[0], args.next().unwrap().to_str().unwrap()); + assert_eq!("/usr/bin/bar", command.get_program().to_str().unwrap()); + assert_eq!("cat", command.get_args().next().unwrap().to_str().unwrap()); } #[test] fn one_path_u_field_code() { - let exec = "/usr/bin/cosmic-term -w %u"; - let paths = ["/home/user"]; - let commands = exec_to_command(exec, "one_path_u_field_code", None, &paths) - .expect("Should parse valid exec"); + let exec = "/usr/bin/foobar %u"; + let paths = ["/home/josh/krumpli"]; + let commands = exec_to_command(exec, &paths).expect("Should parse valid exec"); assert_eq!(1, commands.len()); let command = commands.first().unwrap(); - let mut args = command.get_args(); + assert_eq!("/usr/bin/foobar", command.get_program().to_str().unwrap()); assert_eq!( - "/usr/bin/cosmic-term", - command.get_program().to_str().unwrap() + *paths.first().unwrap(), + command.get_args().next().unwrap().to_str().unwrap() ); - assert_eq!("-w", args.next().unwrap().to_str().unwrap()); - assert_eq!(paths[0], args.next().unwrap().to_str().unwrap()); } #[test] @@ -542,8 +528,7 @@ mod tests { fn one_path_U_field_code() { let exec = "/usr/bin/rmrfbye %U"; let paths = ["/"]; - let commands = exec_to_command(exec, "one_path_U_field_code", None, &paths) - .expect("Should parse valid exec"); + let commands = exec_to_command(exec, &paths).expect("Should parse valid exec"); assert_eq!(1, commands.len()); let command = commands.first().unwrap(); @@ -559,8 +544,7 @@ mod tests { "/usr/share/games/psp/miku.iso", "/usr/share/games/psp/eternia.iso", ]; - let commands = exec_to_command(exec, "mult_path_f_field_code", None, &paths) - .expect("Should parse valid exec"); + let commands = exec_to_command(exec, &paths).expect("Should parse valid exec"); assert_eq!(paths.len(), commands.len()); for (command, path) in commands.into_iter().zip(paths.iter()) { @@ -580,8 +564,7 @@ mod tests { "/usr/share/games/doom2/hr.wad", "/usr/share/games/doom2/hrmus.wad", ]; - let commands = exec_to_command(exec, "mult_path_F_field_code", None, &paths) - .expect("Should parse valid exec"); + let commands = exec_to_command(exec, &paths).expect("Should parse valid exec"); assert_eq!(1, commands.len()); let command = commands.first().unwrap(); @@ -603,8 +586,7 @@ mod tests { "https://redox-os.org/", "https://system76.com/", ]; - let commands = exec_to_command(exec, "mult_path_u_field_code", None, &paths) - .expect("Should parse valid exec"); + let commands = exec_to_command(exec, &paths).expect("Should parse valid exec"); assert_eq!(paths.len(), commands.len()); for (command, path) in commands.into_iter().zip(paths.iter()) { @@ -627,8 +609,7 @@ mod tests { "frieren01.mkv", "rtmp://example.org/this/video/doesnt/exist.avi", ]; - let commands = exec_to_command(exec, "mult_path_U_field_code", None, &paths) - .expect("Should parse valid exec"); + let commands = exec_to_command(exec, &paths).expect("Should parse valid exec"); assert_eq!(1, commands.len()); let command = commands.first().unwrap(); @@ -656,8 +637,7 @@ mod tests { "@@u", ]; let paths = ["file1.rs", "file2.rs"]; - let commands = exec_to_command(exec, "flatpak_style_exec", None, &paths) - .expect("Should parse valid exec"); + let commands = exec_to_command(exec, &paths).expect("Should parse valid exec"); assert_eq!(1, commands.len()); let command = commands.first().unwrap(); @@ -680,8 +660,7 @@ mod tests { "file:///usr/share/games/roguelike/mods/mod1", "file:///usr/share/games/roguelike/mods/mod2", ]; - let commands = exec_to_command(exec, "multiple_field_codes", None, &paths) - .expect("Should parse valid exec"); + let commands = exec_to_command(exec, &paths).expect("Should parse valid exec"); assert_eq!(1, commands.len()); let command = commands.first().unwrap(); @@ -714,8 +693,7 @@ mod tests { ]; let paths = ["rust_game_dev.pdf", "superhero_ferris.epub"]; let args_trailing = ["@@"]; - let commands = exec_to_command(exec, "sandwiched_field_code", None, &paths) - .expect("Should parse valid exec"); + let commands = exec_to_command(exec, &paths).expect("Should parse valid exec"); assert_eq!(1, commands.len()); let command = commands.first().unwrap(); diff --git a/src/mime_icon.rs b/src/mime_icon.rs index 64f007f..8d8d2e5 100644 --- a/src/mime_icon.rs +++ b/src/mime_icon.rs @@ -3,9 +3,11 @@ use cosmic::widget::icon; use mime_guess::Mime; use rustc_hash::FxHashMap; -use std::fs; -use std::path::Path; -use std::sync::{LazyLock, Mutex}; +use std::{ + fs, + path::Path, + sync::{LazyLock, Mutex}, +}; pub const FALLBACK_MIME_ICON: &str = "text-x-generic"; @@ -45,7 +47,7 @@ impl MimeIconCache { return None; } let icon_name = icon_names.remove(0); - let mut named = icon::from_name(icon_name).prefer_svg(true).size(key.size); + let mut named = icon::from_name(icon_name).size(key.size); if !icon_names.is_empty() { let fallback_names = icon_names.into_iter().map(std::borrow::Cow::from).collect(); @@ -112,10 +114,7 @@ pub fn mime_icon(mime: Mime, size: u16) -> icon::Handle { let mut mime_icon_cache = MIME_ICON_CACHE.lock().unwrap(); match mime_icon_cache.get(MimeIconKey { mime, size }) { Some(handle) => handle, - None => icon::from_name(FALLBACK_MIME_ICON) - .prefer_svg(true) - .size(size) - .handle(), + None => icon::from_name(FALLBACK_MIME_ICON).size(size).handle(), } } diff --git a/src/mounter/gvfs.rs b/src/mounter/gvfs.rs index 7c28668..d6294b6 100644 --- a/src/mounter/gvfs.rs +++ b/src/mounter/gvfs.rs @@ -1,20 +1,18 @@ -use cosmic::iced::futures::SinkExt; -use cosmic::iced::{Subscription, stream}; -use cosmic::{Task, widget}; -use gio::glib; -use gio::prelude::*; -use std::any::TypeId; -use std::cell::Cell; -use std::future::pending; -use std::hash::Hash; -use std::path::PathBuf; -use std::sync::Arc; +use cosmic::{ + Task, + iced::{Subscription, futures::SinkExt, stream}, + widget, +}; +use gio::{glib, prelude::*}; +use std::{any::TypeId, cell::Cell, future::pending, hash::Hash, path::PathBuf, sync::Arc}; use tokio::sync::mpsc; use super::{Mounter, MounterAuth, MounterItem, MounterItems, MounterMessage}; -use crate::config::IconSizes; -use crate::err_str; -use crate::tab::{self, DirSize, ItemMetadata, ItemThumbnail, Location}; +use crate::{ + config::IconSizes, + err_str, + tab::{self, DirSize, ItemMetadata, ItemThumbnail, Location}, +}; const TARGET_URI_ATTRIBUTE: &str = "standard::target-uri"; diff --git a/src/mounter/mod.rs b/src/mounter/mod.rs index a5ee75a..dc2ab92 100644 --- a/src/mounter/mod.rs +++ b/src/mounter/mod.rs @@ -1,13 +1,13 @@ -use cosmic::iced::Subscription; -use cosmic::{Task, widget}; -use std::collections::BTreeMap; -use std::fmt; -use std::path::PathBuf; -use std::sync::{Arc, LazyLock}; +use cosmic::{Task, iced::Subscription, widget}; +use std::{ + collections::BTreeMap, + fmt, + path::PathBuf, + sync::{Arc, LazyLock}, +}; use tokio::sync::mpsc; -use crate::config::IconSizes; -use crate::tab; +use crate::{config::IconSizes, tab}; #[cfg(feature = "gvfs")] mod gvfs; diff --git a/src/mouse_area.rs b/src/mouse_area.rs index 73bd710..a57b36a 100644 --- a/src/mouse_area.rs +++ b/src/mouse_area.rs @@ -3,17 +3,21 @@ use std::time::Instant; use crate::tab::DOUBLE_CLICK_DURATION; -use cosmic::iced::core::border::Border; -use cosmic::iced::core::event::Event; -use cosmic::iced::core::mouse::{self, click}; -use cosmic::iced::core::renderer::{self, Quad, Renderer as _}; -use cosmic::iced::core::widget::{Operation, Tree, tree}; -use cosmic::iced::core::{ - Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget, layout, - overlay, touch, +use cosmic::{ + Element, Renderer, Theme, + iced::core::{ + Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget, + border::Border, + event::Event, + layout, + mouse::{self, click}, + overlay, + renderer::{self, Quad, Renderer as _}, + touch, + widget::{Operation, Tree, tree}, + }, + widget::Id, }; -use cosmic::widget::Id; -use cosmic::{Element, Renderer, Theme}; /// Emit messages on mouse events. #[allow(missing_debug_implementations)] diff --git a/src/operation/mod.rs b/src/operation/mod.rs index c490655..b4a66e9 100644 --- a/src/operation/mod.rs +++ b/src/operation/mod.rs @@ -1,15 +1,20 @@ -use crate::app::{ArchiveType, DialogPage, Message, REPLACE_BUTTON_ID}; -use crate::config::IconSizes; -use crate::spawn_detached::spawn_detached; -use crate::{archive, fl, tab}; -use cosmic::iced::futures::channel::mpsc::Sender; -use cosmic::iced::futures::{self, SinkExt, StreamExt, stream}; -use std::borrow::Cow; -use std::fmt::Formatter; -use std::fs; -use std::io::{self, Read, Write}; -use std::path::{Path, PathBuf}; -use std::sync::Arc; +use crate::{ + app::{ArchiveType, DialogPage, Message, REPLACE_BUTTON_ID}, + archive, + config::IconSizes, + fl, + spawn_detached::spawn_detached, + tab, +}; +use cosmic::iced::futures::{self, SinkExt, StreamExt, channel::mpsc::Sender, stream}; +use std::{ + borrow::Cow, + fmt::Formatter, + fs, + io::{self, Read, Write}, + path::{Path, PathBuf}, + sync::Arc, +}; use tokio::sync::{Mutex as TokioMutex, mpsc}; use walkdir::WalkDir; use zip::AesMode::Aes256; @@ -34,7 +39,7 @@ async fn handle_replace( conflict_count: usize, ) -> ReplaceResult { let item_from = match tab::item_from_path(file_from, IconSizes::default()) { - Ok(ok) => Box::new(ok), + Ok(ok) => ok, Err(err) => { log::warn!("{err}"); return ReplaceResult::Cancel; @@ -42,7 +47,7 @@ async fn handle_replace( }; let item_to = match tab::item_from_path(file_to, IconSizes::default()) { - Ok(ok) => Box::new(ok), + Ok(ok) => ok, Err(err) => { log::warn!("{err}"); return ReplaceResult::Cancel; @@ -1238,23 +1243,28 @@ fn wrap_compio_spawn_error(err: Box) -> OperationError #[cfg(test)] mod tests { - use std::fs::{self, File}; - use std::io; - use std::path::PathBuf; + use std::{ + fs::{self, File}, + io, + path::PathBuf, + }; - use cosmic::iced::futures::channel::mpsc; - use cosmic::iced::futures::{StreamExt, future}; + use cosmic::iced::futures::{StreamExt, channel::mpsc, future}; use log::debug; use test_log::test; use tokio::sync; use super::{Controller, Operation, OperationError, OperationSelection, ReplaceResult}; - use crate::app::test_utils::{ - NAME_LEN, NUM_DIRS, NUM_FILES, NUM_HIDDEN, NUM_NESTED, empty_fs, filter_dirs, filter_files, - simple_fs, + use crate::{ + app::{ + DialogPage, Message, + test_utils::{ + NAME_LEN, NUM_DIRS, NUM_FILES, NUM_HIDDEN, NUM_NESTED, empty_fs, filter_dirs, + filter_files, simple_fs, + }, + }, + fl, }; - use crate::app::{DialogPage, Message}; - use crate::fl; /// Simple wrapper around `[Operation::Copy]` pub async fn operation_copy( diff --git a/src/operation/reader.rs b/src/operation/reader.rs index 9f45441..75088df 100644 --- a/src/operation/reader.rs +++ b/src/operation/reader.rs @@ -1,5 +1,4 @@ -use std::path::Path; -use std::{fs, io}; +use std::{fs, io, path::Path}; use crate::operation::OperationError; diff --git a/src/operation/recursive.rs b/src/operation/recursive.rs index 0f09bd6..9c9a1c3 100644 --- a/src/operation/recursive.rs +++ b/src/operation/recursive.rs @@ -6,21 +6,16 @@ use crate::operation::{OperationError, sync_to_disk}; use anyhow::Context as AnyhowContext; use compio::BufResult; use compio::buf::{IntoInner, IoBuf}; -use compio::driver::ToSharedFd; -use compio::driver::op::AsyncifyFd; +use compio::driver::{ToSharedFd, op::AsyncifyFd}; use compio::io::{AsyncReadAt, AsyncWriteAt}; use cosmic::iced::futures; #[cfg(feature = "gvfs")] use futures::{FutureExt, StreamExt}; -use std::cell::Cell; -use std::error::Error; -use std::fs; use std::future::Future; -use std::ops::ControlFlow; -use std::path::PathBuf; use std::pin::Pin; use std::rc::Rc; use std::time::Instant; +use std::{cell::Cell, error::Error, fs, ops::ControlFlow, path::PathBuf}; use walkdir::WalkDir; #[cfg(feature = "gvfs")] @@ -479,12 +474,12 @@ impl Op { progress.total_bytes = metadata.as_ref().map(|m| m.len()); (ctx.on_progress)(self, &progress); - if let Some(metadata) = metadata.as_ref() - && let Err(why) = to_file.set_permissions(metadata.permissions()).await - { - // This error is not propagated upwards as some filesystems do not support setting permissions - if !matches!(why.kind(), std::io::ErrorKind::Unsupported) { - tracing::warn!(?why, "failed to set permissions for {}", self.to.display(),); + if let Some(metadata) = metadata.as_ref() { + if let Err(why) = to_file.set_permissions(metadata.permissions()).await { + // This error is not propagated upwards as some filesystems do not support setting permissions + if !matches!(why.kind(), std::io::ErrorKind::Unsupported) { + tracing::warn!(?why, "failed to set permissions for {}", self.to.display(),); + } } } diff --git a/src/tab.rs b/src/tab.rs index d7339f8..ec2260a 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -1,72 +1,92 @@ #[cfg(feature = "desktop")] use cosmic::desktop::fde::{DesktopEntry, get_languages_from_env}; -use cosmic::iced::advanced::graphics; -use cosmic::iced::advanced::text::{self, Paragraph}; -use cosmic::iced::alignment::Vertical; -use cosmic::iced::clipboard::dnd::DndAction; -use cosmic::iced::core::mouse::ScrollDelta; -use cosmic::iced::core::widget::tree; -use cosmic::iced::futures::{self, SinkExt}; -use cosmic::iced::keyboard::Modifiers; -use cosmic::iced::widget::scrollable::{self, AbsoluteOffset, Viewport}; -use cosmic::iced::widget::{rule, stack}; -use cosmic::iced::{ - Alignment, Border, Color, ContentFit, Length, Point, Rectangle, Size, Subscription, Vector, - padding, stream, window, +use cosmic::{ + Apply, Element, cosmic_theme, font, + iced::core::{mouse::ScrollDelta, widget::tree}, + iced::{ + Alignment, Border, Color, ContentFit, Length, Point, Rectangle, Size, Subscription, Vector, + advanced::{ + graphics, + text::{self, Paragraph}, + }, + alignment::Vertical, + clipboard::dnd::DndAction, + futures::{self, SinkExt}, + keyboard::Modifiers, + padding, stream, + widget::{ + rule, + scrollable::{self, AbsoluteOffset, Viewport}, + stack, + }, + window, + }, + theme, + widget::{ + self, DndDestination, DndSource, Id, RcElementWrapper, Widget, + menu::{action::MenuAction, key_bind::KeyBind}, + space, + }, }; -use cosmic::widget::menu::action::MenuAction; -use cosmic::widget::menu::key_bind::KeyBind; -use cosmic::widget::{self, DndDestination, DndSource, Id, RcElementWrapper, Widget, space}; -use cosmic::{Apply, Element, cosmic_theme, font, theme}; #[cfg(feature = "desktop")] use i18n_embed::LanguageLoader; -use icu::datetime::input::DateTime; -use icu::datetime::options::TimePrecision; -use icu::datetime::{DateTimeFormatter, DateTimeFormatterPreferences, fieldsets}; -use icu::locale::preferences::extensions::unicode::keywords::HourCycle; +use icu::{ + datetime::{ + DateTimeFormatter, DateTimeFormatterPreferences, fieldsets, input::DateTime, + options::TimePrecision, + }, + locale::preferences::extensions::unicode::keywords::HourCycle, +}; use image::{DynamicImage, ImageReader}; use jiff_icu::ConvertFrom; use mime_guess::{Mime, mime}; use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; -use std::borrow::Cow; -use std::cell::Cell; -use std::cmp::{Ordering, Reverse}; -use std::collections::{BTreeMap, BTreeSet, HashMap}; -use std::error::Error; -use std::fmt::{self, Display}; -use std::fs::{self, File, Metadata}; -use std::hash::Hash; -use std::io::{BufRead, BufReader, Read}; #[cfg(unix)] use std::os::unix::fs::MetadataExt; -use std::path::{self, Path, PathBuf}; -use std::sync::{Arc, LazyLock, RwLock, atomic}; -use std::time::{Duration, Instant, SystemTime}; +use std::{ + borrow::Cow, + cell::Cell, + cmp::{Ordering, Reverse}, + collections::{BTreeMap, BTreeSet, HashMap}, + error::Error, + fmt::{self, Display}, + fs::{self, File, Metadata}, + hash::Hash, + io::{BufRead, BufReader}, + path::{self, Path, PathBuf}, + sync::{Arc, LazyLock, RwLock, atomic}, + time::{Duration, Instant, SystemTime}, +}; use tempfile::NamedTempFile; use tokio::sync::mpsc; use trash::{TrashItem, TrashItemMetadata, TrashItemSize}; use walkdir::WalkDir; -use crate::app::{Action, PreviewItem, PreviewKind}; -use crate::clipboard::{ClipboardCopy, ClipboardKind, ClipboardPaste}; -use crate::config::{ - ContextActionPreset, DesktopConfig, ICON_SCALE_MAX, ICON_SIZE_GRID, IconSizes, TabConfig, - ThumbCfg, +use crate::{ + FxOrderMap, + app::{Action, PreviewItem, PreviewKind}, + clipboard::{ClipboardCopy, ClipboardKind, ClipboardPaste}, + config::{ + ContextActionPreset, DesktopConfig, ICON_SCALE_MAX, ICON_SIZE_GRID, IconSizes, TabConfig, + ThumbCfg, + }, + dialog::DialogKind, + fl, + large_image::{ + LargeImageManager, decode_large_image, exceeds_memory_limit, should_use_dedicated_worker, + should_use_tiling, + }, + localize::{LANGUAGE_SORTER, LOCALE}, + menu, mime_app, + mime_icon::{mime_for_path, mime_icon}, + mounter::MOUNTERS, + mouse_area, + operation::{Controller, OperationError}, + thumbnail_cacher::{CachedThumbnail, ThumbnailCacher, ThumbnailSize}, + thumbnailer::thumbnailer, + trash::{Trash, TrashExt}, }; -use crate::dialog::DialogKind; -use crate::large_image::{ - LargeImageManager, decode_large_image, exceeds_memory_limit, should_use_dedicated_worker, - should_use_tiling, -}; -use crate::localize::{LANGUAGE_SORTER, LOCALE}; -use crate::mime_icon::{mime_for_path, mime_icon}; -use crate::mounter::MOUNTERS; -use crate::operation::{Controller, OperationError}; -use crate::thumbnail_cacher::{CachedThumbnail, ThumbnailCacher, ThumbnailSize}; -use crate::thumbnailer::thumbnailer; -use crate::trash::{Trash, TrashExt}; -use crate::{FxOrderMap, fl, menu, mime_app, mouse_area}; pub const DOUBLE_CLICK_DURATION: Duration = Duration::from_millis(500); pub const HOVER_DURATION: Duration = Duration::from_millis(1600); @@ -76,11 +96,6 @@ const MAX_SEARCH_LATENCY: Duration = Duration::from_millis(20); const MAX_SEARCH_RESULTS: usize = 200; //TODO: configurable thumbnail size? const THUMBNAIL_SIZE: u32 = (ICON_SIZE_GRID as u32) * (ICON_SCALE_MAX as u32); -/// Maximum bytes of text to pass to the editor for preview; caps shaping work to avoid blocking. -/// Files larger than this get a truncated preview (first N bytes only). -const TEXT_PREVIEW_MAX_BYTES: usize = 256 * 1024; // 256 KiB -/// Maximum file size (bytes) to attempt text preview; files larger than this are skipped entirely. -const TEXT_PREVIEW_MAX_FILE_BYTES: u64 = 8 * 1000 * 1000; // 8 MiB // Thumbnail generation semaphore - limits parallel thumbnail workers // Uses 4 workers for balanced throughput and memory usage @@ -275,7 +290,6 @@ fn button_style( pub fn folder_icon(path: &PathBuf, icon_size: u16) -> widget::icon::Handle { widget::icon::from_name(SPECIAL_DIRS.get(path).map_or("folder", |x| *x)) - .prefer_svg(true) .size(icon_size) .handle() } @@ -608,10 +622,7 @@ fn desktop_icon_handle(icon: &str, size: u16) -> widget::icon::Handle { if icon_path.is_absolute() && icon_path.exists() { widget::icon::from_path(icon_path.to_path_buf()) } else { - widget::icon::from_name(icon) - .prefer_svg(true) - .size(size) - .handle() + widget::icon::from_name(icon).size(size).handle() } } @@ -1562,7 +1573,7 @@ impl Location { } } - pub fn scan(&self, sizes: IconSizes) -> (Option>, Vec) { + pub fn scan(&self, sizes: IconSizes) -> (Option, Vec) { let items = match self { Self::Desktop(path, display, desktop_config) => { scan_desktop(path, display, *desktop_config, sizes) @@ -1578,7 +1589,7 @@ impl Location { }; let parent_item_opt = match self.path_opt() { Some(path) => match item_from_path(path, sizes) { - Ok(item) => Some(Box::new(item)), + Ok(item) => Some(item), Err(err) => { log::warn!("failed to get item for {}: {}", path.display(), err); None @@ -1687,7 +1698,6 @@ pub enum Command { ContextMenu(Option, Option), Delete(Vec), DropFiles(PathBuf, ClipboardPaste), - ClearRecents, EmptyTrash, #[cfg(feature = "desktop")] ExecEntryAction(cosmic::desktop::DesktopEntryData, usize), @@ -1727,7 +1737,6 @@ pub enum Message { EditLocationSubmit, EditLocationTab, OpenInNewTab(PathBuf), - ClearRecents, EmptyTrash, #[cfg(feature = "desktop")] ExecEntryAction(Option, usize), @@ -2090,37 +2099,17 @@ impl ItemThumbnail { log::warn!("failed to read {}: {}", path.display(), err); } } - } else if mime.type_() == mime::TEXT && check_size("text", TEXT_PREVIEW_MAX_FILE_BYTES) { - tried_supported_file = true; - if size > 0 { - // Reuse size from metadata above; cap allocation and read - let read_cap = (size.min(TEXT_PREVIEW_MAX_BYTES as u64)) as usize; - let mut buf = vec![0u8; read_cap]; - match File::open(path).and_then(|f| { - let n = Read::read(&mut f.take(read_cap as u64), &mut buf)?; - buf.truncate(n); - Ok(()) - }) { - Ok(()) => { - let text = match std::str::from_utf8(&buf) { - Ok(s) => s.to_string(), - Err(e) => { - // Use only the valid UTF-8 prefix (slice is guaranteed valid by valid_up_to()) - std::str::from_utf8(&buf[..e.valid_up_to()]) - .unwrap_or("") - .to_string() - } - }; - if !text.is_empty() { - return Self::Text(widget::text_editor::Content::with_text(&text)); - } - } - Err(err) => { - log::warn!("failed to read {}: {}", path.display(), err); - } + } else if mime.type_() == mime::TEXT && check_size("text", 8 * 1000 * 1000) { + /*TODO: fix performance issues, widget::text_editor::Content::with_text forces all text to shape, which blocks rendering + match fs::read_to_string(&path) { + Ok(data) => { + return ItemThumbnail::Text(widget::text_editor::Content::with_text(&data)); + } + Err(err) => { + log::warn!("failed to read {}: {}", path.display(), err); } } - // size == 0: empty file or unknown size; skip read and allocation + */ } // If we weren't able to create a thumbnail, but we should have @@ -2304,7 +2293,7 @@ impl Item { } fn preview(&self) -> Element<'_, Message> { - let spacing = cosmic::theme::spacing(); + let spacing = cosmic::theme::active().cosmic().spacing; // This loads the image only if thumbnailing worked let icon = widget::icon::icon(self.icon_handle_grid.clone()) .content_fit(ContentFit::Contain) @@ -2365,7 +2354,7 @@ impl Item { space_xxxs, space_m, .. - } = theme::spacing(); + } = theme::active().cosmic().spacing; let mut column = widget::column::with_capacity(4).spacing(space_m); @@ -2543,7 +2532,7 @@ impl Item { } pub fn replace_view(&self, heading: String, military_time: bool) -> Element<'_, Message> { - let cosmic_theme::Spacing { space_xxxs, .. } = theme::spacing(); + let cosmic_theme::Spacing { space_xxxs, .. } = theme::active().cosmic().spacing; let mut row = widget::row::with_capacity(2).spacing(space_xxxs); row = row.push(self.preview()); @@ -2683,7 +2672,7 @@ pub struct Tab { pub sort_name: HeadingOptions, pub sort_direction: bool, pub gallery: bool, - pub(crate) parent_item_opt: Option>, + pub(crate) parent_item_opt: Option, pub(crate) items_opt: Option>, pub dnd_hovered: Option<(Location, Instant)>, pub(crate) scrollable_id: widget::Id, @@ -3017,10 +3006,10 @@ impl Tab { return None; }; let search_items = after - .iter_mut() + .into_iter() .enumerate() .map(|(i, item)| (i + start, item)) - .chain(until.iter_mut().enumerate()); + .chain(until.into_iter().enumerate()); if forward { Self::select_first_prefix_match(prefix_lower, search_items) @@ -3037,7 +3026,7 @@ impl Tab { items: impl Iterator, ) -> Option { for (i, item) in items { - if item.name.to_lowercase().starts_with(prefix) { + if item.name.to_lowercase().starts_with(&prefix) { item.selected = true; return Some(i); } @@ -3633,7 +3622,7 @@ impl Tab { match item_from_path(&path, IconSizes::default()) { Ok(item) => { commands.push(Command::Preview(PreviewKind::Custom( - PreviewItem(Box::new(item)), + PreviewItem(item), ))); } Err(err) => { @@ -3723,9 +3712,6 @@ impl Tab { Message::OpenInNewTab(path) => { commands.push(Command::OpenInNewTab(path)); } - Message::ClearRecents => { - commands.push(Command::ClearRecents); - } Message::EmptyTrash => { commands.push(Command::EmptyTrash); } @@ -4361,7 +4347,7 @@ impl Tab { Message::ShiftPermissions(path_mode_opt, shift, bits) => match path_mode_opt { Some((path, mode)) => commands.push(Command::SetPermissions( path, - set_mode_part(mode, shift, bits), + set_mode_part(mode, shift, bits.try_into().unwrap()), )), // Shift permissions on all selected items None => { @@ -4372,9 +4358,13 @@ impl Tab { #[cfg(unix)] if let (Some(path), Some(mode)) = ( item.path_opt(), - item.file_metadata().map(|metadata| metadata.mode()), + item.file_metadata() + .and_then(|metadata| Some(metadata.mode())), ) { - permissions.push((path.clone(), set_mode_part(mode, shift, bits))); + permissions.push(( + path.clone(), + set_mode_part(mode, shift, bits.try_into().unwrap()), + )); } } commands.push(Command::SetMultiplePermissions(permissions)); @@ -4790,7 +4780,7 @@ impl Tab { space_xs, space_m, .. - } = theme::spacing(); + } = theme::active().cosmic().spacing; //TODO: display error messages when image not found? let mut name_opt = None; @@ -4988,7 +4978,7 @@ impl Tab { space_s, space_m, .. - } = theme::spacing(); + } = theme::active().cosmic().spacing; let size = self.size_opt.get().unwrap_or(Size::new(0.0, 0.0)); @@ -5323,7 +5313,7 @@ impl Tab { } pub fn empty_view(&self, has_hidden: bool) -> Element<'_, Message> { - let cosmic_theme::Spacing { space_xxs, .. } = theme::spacing(); + let cosmic_theme::Spacing { space_xxs, .. } = theme::active().cosmic().spacing; mouse_area::MouseArea::new(widget::column::with_children([widget::container( match self.mode { @@ -5363,7 +5353,7 @@ impl Tab { space_xxs, space_xxxs, .. - } = theme::spacing(); + } = theme::active().cosmic().spacing; let TabConfig { show_hidden, @@ -5473,7 +5463,8 @@ impl Tab { widget::button::custom( widget::icon::icon(item.icon_handle_grid.clone()) .content_fit(ContentFit::Contain) - .size(icon_sizes.grid()), + .size(icon_sizes.grid()) + .width(Length::Shrink), ) .padding(space_xxxs) .class(button_style( @@ -5708,7 +5699,7 @@ impl Tab { ) { let cosmic_theme::Spacing { space_s, space_xxs, .. - } = theme::spacing(); + } = theme::active().cosmic().spacing; let TabConfig { show_hidden, @@ -6110,7 +6101,7 @@ impl Tab { space_xxs, space_xs, .. - } = theme::spacing(); + } = theme::active().cosmic().spacing; let location_view_opt = if matches!(self.mode, Mode::Desktop) { None @@ -6240,24 +6231,6 @@ impl Tab { ); } } - Location::Recents | Location::Search(SearchLocation::Recents, ..) => { - if let Some(items) = self.items_opt() - && !items.is_empty() - { - tab_column = tab_column.push( - widget::layer_container(widget::row::with_children([ - widget::space::horizontal().into(), - widget::button::standard(fl!("clear-recents-history")) - .on_press(Message::ClearRecents) - .into(), - ])) - .padding([space_xxs, space_xs]) - .layer(cosmic_theme::Layer::Primary) - .apply(widget::container) - .padding([0, 0, 7, 0]), - ); - } - } Location::Network(uri, _display_name, _path) if uri == "network:///" => { tab_column = tab_column.push( widget::layer_container(widget::row::with_children([ @@ -6324,7 +6297,7 @@ impl Tab { space_xxxs, space_m, .. - } = theme::spacing(); + } = theme::active().cosmic().spacing; let mut column = widget::column::with_capacity(4).spacing(space_m); @@ -6457,31 +6430,33 @@ impl Tab { let mut settings = Vec::new(); // Only allow modifying open-with if all mime types are the same - if mime_types.len() == 1 - && let Some(mime) = mime_types - .first() + if mime_types.len() == 1 { + if let Some(mime) = mime_types + .get(0) .and_then(|(mime, _)| mime.parse::().ok()) - && let Some(mime_app_cache) = mime_app_cache_opt - { - let mime_apps = mime_app_cache.get(&mime); - if !mime_apps.is_empty() { - let mime_closure = mime.clone(); - settings.push( - widget::settings::item::builder(fl!("open-with")).control( - Element::from( - widget::dropdown( - mime_apps, - mime_apps.iter().position(|x| x.is_default), - move |index| (index, mime_closure.clone()), - ) - .icons(Cow::Borrowed(mime_app_cache.icons(&mime))), - ) - .map(|(index, mime)| { - let mime_app = &mime_apps[index]; - Message::SetOpenWith(mime, mime_app.id.clone()) - }), - ), - ); + { + if let Some(mime_app_cache) = mime_app_cache_opt { + let mime_apps = mime_app_cache.get(&mime); + if !mime_apps.is_empty() { + let mime_closure = mime.clone(); + settings.push( + widget::settings::item::builder(fl!("open-with")).control( + Element::from( + widget::dropdown( + mime_apps, + mime_apps.iter().position(|x| x.is_default), + move |index| (index, mime_closure.clone()), + ) + .icons(Cow::Borrowed(mime_app_cache.icons(&mime))), + ) + .map(|(index, mime)| { + let mime_app = &mime_apps[index]; + Message::SetOpenWith(mime, mime_app.id.clone()) + }), + ), + ); + } + } } } @@ -7086,25 +7061,21 @@ fn text_editor_class( #[cfg(test)] mod tests { - use std::path::PathBuf; - use std::{fs, io}; + use std::{fs, io, path::PathBuf}; - use cosmic::iced::mouse::ScrollDelta; - use cosmic::iced::runtime::keyboard::Modifiers; - use cosmic::widget; + use cosmic::{iced::mouse::ScrollDelta, iced::runtime::keyboard::Modifiers, widget}; use log::{debug, trace}; - use mime_guess::mime; use tempfile::TempDir; use test_log::test; - use super::{ - ItemMetadata, ItemThumbnail, Location, Message, Tab, respond_to_scroll_direction, scan_path, + use super::{Location, Message, Tab, respond_to_scroll_direction, scan_path}; + use crate::{ + app::test_utils::{ + NAME_LEN, NUM_DIRS, NUM_FILES, NUM_HIDDEN, NUM_NESTED, assert_eq_tab_path, empty_fs, + eq_path_item, filter_dirs, read_dir_sorted, simple_fs, tab_click_new, + }, + config::{IconSizes, TabConfig, ThumbCfg}, }; - use crate::app::test_utils::{ - NAME_LEN, NUM_DIRS, NUM_FILES, NUM_HIDDEN, NUM_NESTED, assert_eq_tab_path, empty_fs, - eq_path_item, filter_dirs, read_dir_sorted, simple_fs, tab_click_new, - }; - use crate::config::{IconSizes, TabConfig, ThumbCfg}; // Boilerplate for tab tests. Checks if simulated clicks selected items. fn tab_selects_item( @@ -7517,86 +7488,4 @@ mod tests { } } } - - #[test] - fn item_thumbnail_text_preview_small_utf8_returns_text() -> io::Result<()> { - let dir = TempDir::new()?; - let path = dir.path().join("preview.txt"); - fs::write(&path, "Hello, world!")?; - let metadata = fs::metadata(&path)?; - let item_metadata = ItemMetadata::Path { - metadata, - children_opt: None, - }; - let thumb = ItemThumbnail::new( - &path, - item_metadata, - mime::TEXT_PLAIN, - 128, - 100 * 1024 * 1024, - 1, - 8, - ); - assert!( - matches!(thumb, ItemThumbnail::Text(_)), - "small text file should produce Text thumbnail" - ); - Ok(()) - } - - #[test] - fn item_thumbnail_text_preview_empty_file_returns_not_image() -> io::Result<()> { - let dir = TempDir::new()?; - let path = dir.path().join("empty.txt"); - fs::File::create(&path)?; - let metadata = fs::metadata(&path)?; - let item_metadata = ItemMetadata::Path { - metadata, - children_opt: None, - }; - let thumb = ItemThumbnail::new( - &path, - item_metadata, - mime::TEXT_PLAIN, - 128, - 100 * 1024 * 1024, - 1, - 8, - ); - assert!( - matches!(thumb, ItemThumbnail::NotImage), - "empty text file should produce NotImage (no read)" - ); - Ok(()) - } - - #[test] - fn item_thumbnail_text_preview_invalid_utf8_uses_valid_prefix() -> io::Result<()> { - let dir = TempDir::new()?; - let path = dir.path().join("invalid_utf8.txt"); - // Valid UTF-8 "ab" then invalid byte sequence then "c" - fs::write(&path, b"ab\xff\xfe\xfdc")?; - let metadata = fs::metadata(&path)?; - let item_metadata = ItemMetadata::Path { - metadata, - children_opt: None, - }; - let thumb = ItemThumbnail::new( - &path, - item_metadata, - mime::TEXT_PLAIN, - 128, - 100 * 1024 * 1024, - 1, - 8, - ); - match &thumb { - ItemThumbnail::Text(content) => { - // Text editor content may add a trailing newline - assert_eq!(content.text().trim_end(), "ab"); - } - _ => panic!("expected Text thumbnail with valid prefix only, got {:?}", thumb), - } - Ok(()) - } } diff --git a/src/thumbnail_cacher.rs b/src/thumbnail_cacher.rs index e42d343..e4d616e 100644 --- a/src/thumbnail_cacher.rs +++ b/src/thumbnail_cacher.rs @@ -1,14 +1,16 @@ use image::DynamicImage; use md5::{Digest, Md5}; use rustc_hash::FxHashMap; -use std::error::Error; -use std::fs::{self, File}; -use std::io::{self, BufReader, BufWriter}; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; -use std::path::{Path, PathBuf}; -use std::sync::LazyLock; -use std::time::UNIX_EPOCH; +use std::{ + error::Error, + fs::{self, File}, + io::{self, BufReader, BufWriter}, + path::{Path, PathBuf}, + sync::LazyLock, + time::UNIX_EPOCH, +}; use tempfile::NamedTempFile; use url::Url; diff --git a/src/trash.rs b/src/trash.rs index 618e77c..cc39d02 100644 --- a/src/trash.rs +++ b/src/trash.rs @@ -1,10 +1,11 @@ use cosmic::widget; use regex::Regex; -use std::collections::HashSet; -use std::path::PathBuf; +use std::{collections::HashSet, path::PathBuf}; -use crate::config::IconSizes; -use crate::tab::{Item, SearchItem}; +use crate::{ + config::IconSizes, + tab::{Item, SearchItem}, +}; pub trait TrashExt { fn is_empty() -> bool { @@ -26,7 +27,7 @@ pub trait TrashExt { Vec::new() } - fn scan_search bool + Sync>(_callback: F, _regex: &Regex) {} + fn scan_search bool + Sync>(callback: F, regex: &Regex); fn icon(icon_size: u16) -> widget::icon::Handle { widget::icon::from_name(if Self::is_empty() { @@ -80,8 +81,7 @@ impl TrashExt for Trash { } fn scan(sizes: IconSizes) -> Vec { - use crate::localize::LANGUAGE_SORTER; - use crate::tab::item_from_trash_entry; + use crate::{localize::LANGUAGE_SORTER, tab::item_from_trash_entry}; use std::cmp::Ordering; let entries = match trash::os_limited::list() { diff --git a/src/zoom.rs b/src/zoom.rs index 89ae5b8..600d0b5 100644 --- a/src/zoom.rs +++ b/src/zoom.rs @@ -1,7 +1,6 @@ use std::num::NonZeroU16; -use crate::config::IconSizes; -use crate::tab::View; +use crate::{config::IconSizes, tab::View}; static DEFAULT_ZOOM: NonZeroU16 = NonZeroU16::new(100).unwrap(); static MIN_ZOOM: NonZeroU16 = NonZeroU16::new(50).unwrap();