From b673c6ae91acda474a0c70a0765f5fe0a88faae6 Mon Sep 17 00:00:00 2001 From: Araxeus Date: Fri, 6 Jan 2023 16:31:20 +0200 Subject: [PATCH] Add windows drive view (#27) --- Cargo.lock | 62 ++++++++++++++++--------------- Cargo.toml | 3 ++ src/main.rs | 68 ++++++++++++++++++++++++++-------- src/structs/entry.rs | 12 ++++++ src/structs/filetype.rs | 6 ++- src/structs/icons.rs | 13 ++++++- src/structs/prompt.rs | 7 +++- src/structs/prompt_renderer.rs | 6 ++- src/utils.rs | 31 +++++++++++++++- 9 files changed, 156 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index de3eec7..a264ddd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,26 +28,15 @@ dependencies = [ [[package]] name = "async-broadcast" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d26004fe83b2d1cd3a97609b21e39f9a31535822210fe83205d2ce48866ea61" +checksum = "1b19760fa2b7301cf235360ffd6d3558b1ed4249edd16d6cca8d690cee265b95" dependencies = [ "event-listener", "futures-core", "parking_lot", ] -[[package]] -name = "async-channel" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" -dependencies = [ - "concurrent-queue", - "event-listener", - "futures-core", -] - [[package]] name = "async-executor" version = "1.5.0" @@ -558,6 +547,7 @@ dependencies = [ "open", "tiny_update_notifier", "unicode-segmentation", + "windows 0.43.0", ] [[package]] @@ -697,9 +687,9 @@ dependencies = [ [[package]] name = "object" -version = "0.30.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239da7f290cfa979f43f85a8efeee9a8a76d0827c356d37f9d3d7254d6b537fb" +checksum = "8d864c91689fdc196779b98dba0aceac6118594c2df6ee5d943eb6a8df4d107a" dependencies = [ "memchr", ] @@ -722,9 +712,9 @@ dependencies = [ [[package]] name = "ordered-stream" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ca8c99d73c6e92ac1358f9f692c22c0bfd9c4701fa086f5d365c0d4ea818ea" +checksum = "d4eb9ba3f3e42dbdd3b7b122de5ca169c81e93d561eb900da3a8c99bcfcf381a" dependencies = [ "futures-core", "pin-project-lite", @@ -1092,7 +1082,7 @@ checksum = "c58de036c4d2e20717024de2a3c4bf56c301f07b21bc8ef9b57189fce06f1f3b" dependencies = [ "quick-xml", "strum", - "windows", + "windows 0.39.0", ] [[package]] @@ -1329,6 +1319,21 @@ dependencies = [ "windows_x86_64_msvc 0.39.0", ] +[[package]] +name = "windows" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04662ed0e3e5630dfa9b26e4cb823b817f1a9addda855d973a9458c236556244" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.0", +] + [[package]] name = "windows-sys" version = "0.42.0" @@ -1418,12 +1423,11 @@ checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" [[package]] name = "zbus" -version = "3.6.2" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938ea6da98c75c2c37a86007bd17fd8e208cbec24e086108c87ece98e9edec0d" +checksum = "379d587c0ccb632d1179cf44082653f682842f0535f0fdfaefffc34849cc855e" dependencies = [ "async-broadcast", - "async-channel", "async-executor", "async-io", "async-lock", @@ -1457,9 +1461,9 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "3.6.2" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45066039ebf3330820e495e854f8b312abb68f0a39e97972d092bd72e8bb3e8e" +checksum = "66492a2e90c0df7190583eccb8424aa12eb7ff06edea415a4fff6688fae18cf8" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -1470,9 +1474,9 @@ dependencies = [ [[package]] name = "zbus_names" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c737644108627748a660d038974160e0cbb62605536091bdfa28fd7f64d43c8" +checksum = "f34f314916bd89bdb9934154627fab152f4f28acdda03e7c4c68181b214fe7e3" dependencies = [ "serde", "static_assertions", @@ -1481,9 +1485,9 @@ dependencies = [ [[package]] name = "zvariant" -version = "3.9.0" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f8c89c183461e11867ded456db252eae90874bc6769b7adbea464caa777e51" +checksum = "576cc41e65c7f283e5460f5818073e68fb1f1631502b969ef228c2e03c862efb" dependencies = [ "byteorder", "enumflags2", @@ -1495,9 +1499,9 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "3.9.0" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "155247a5d1ab55e335421c104ccd95d64f17cebbd02f50cdbc1c33385f9c4d81" +checksum = "0fd4aafc0dee96ae7242a24249ce9babf21e1562822f03df650d4e68c20e41ed" dependencies = [ "proc-macro-crate", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 97d8481..dccb8b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,6 @@ fuzzy-matcher = "0.3.7" crossterm = "0.25.0" unicode-segmentation = "1.10.0" tiny_update_notifier = "2.1.0" + +[target.'cfg(windows)'.dependencies] +windows = { version = "0.43.0", features = ["Win32_Storage_FileSystem"] } diff --git a/src/main.rs b/src/main.rs index 1f3d01b..9f080a2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,9 @@ use std::{fs, path::Path}; use structs::{Entry, Filetype, Icons}; use utils::{display_choices, err, get_first_arg, pretty_path, resolve_lnk, KeyModifiers}; +#[cfg(windows)] +use utils::get_logical_drives; + use tiny_update_notifier::check_github; fn main() { @@ -27,13 +30,17 @@ fn main() { } fn main_loop(initial_path: String) { - let mut path = initial_path; + let mut selected_entry = Entry { + path: initial_path, + ..Default::default() + }; + loop { - let choices = get_choices(&path); + let choices = get_choices(&selected_entry); - let (index, modifier) = display_choices(&choices, &path); + let (index, modifier) = display_choices(&choices, &selected_entry.path); - let entry = &choices[index]; + let entry = choices[index].clone(); // exec file if entry.filetype.should_exec() || modifier == KeyModifiers::CONTROL { @@ -47,25 +54,45 @@ fn main_loop(initial_path: String) { )), } } - // browse directory by continuing loop with new path - path = if entry.filetype == Filetype::Lnk { - resolve_lnk(&entry.path) - } else { - entry.path.to_string() - }; if modifier == KeyModifiers::SHIFT || modifier == KeyModifiers::ALT { - print!("{}", pretty_path(&path)); + print!("{}", pretty_path(&selected_entry.path)); break; } + + // browse directory by continuing loop with new path + selected_entry = entry; + + if selected_entry.filetype == Filetype::Lnk { + selected_entry.path = resolve_lnk(&selected_entry.path); + } } } -fn get_choices(path: &str) -> Vec { +fn get_choices(entry: &Entry) -> Vec { let mut result_vector: Vec = Vec::new(); + #[cfg(windows)] + // Open Drives View on Windows + if entry.filetype == Filetype::DriveView { + match get_logical_drives() { + Ok(drives) => { + for drive in drives { + result_vector.push(Entry { + name: format!("{drive}:\\"), + path: format!("{drive}:\\"), + icon: &Icons::DRIVE, + filetype: Filetype::Directory, + }); + } + return result_vector; + } + Err(_) => err("Failed to get drives"), + } + } + // .. Open parent directory - if let Some(parent) = Path::new(path).parent() { + if let Some(parent) = Path::new(&entry.path).parent() { result_vector.push(Entry { name: String::from(".."), path: parent.to_string_lossy().to_string(), @@ -74,8 +101,19 @@ fn get_choices(path: &str) -> Vec { }); } + #[cfg(windows)] + if result_vector.is_empty() { + // .. Open Drives View on Windows + result_vector.push(Entry { + name: String::from(".."), + path: env!("COMPUTERNAME").to_string(), + icon: &Icons::PC, + filetype: Filetype::DriveView, + }); + } + // Get files in directory - if let Ok(entries) = fs::read_dir(path) { + if let Ok(entries) = fs::read_dir(&entry.path) { for entry in entries.flatten() { result_vector.push(Entry::from_dir_entry(&entry)); } @@ -85,7 +123,7 @@ fn get_choices(path: &str) -> Vec { if result_vector.len() < 2 { result_vector.push(Entry { name: String::from(""), - path: path.to_string(), + path: entry.path.clone(), icon: &Icons::EXPLORER, filetype: Filetype::Executable, }); diff --git a/src/structs/entry.rs b/src/structs/entry.rs index d7050ba..1514466 100644 --- a/src/structs/entry.rs +++ b/src/structs/entry.rs @@ -2,6 +2,7 @@ use super::{Filetype, Icon, Icons}; use std::{fmt, fs}; +#[derive(Clone)] pub struct Entry { pub name: String, pub path: String, @@ -9,6 +10,17 @@ pub struct Entry { pub filetype: Filetype, } +impl Default for Entry { + fn default() -> Self { + Self { + name: String::new(), + path: String::new(), + icon: Icons::from_filetype(&Filetype::Unknown), + filetype: Filetype::Unknown, + } + } +} + impl fmt::Display for Entry { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} {}", self.icon, self.name) diff --git a/src/structs/filetype.rs b/src/structs/filetype.rs index e2728c6..296e5f4 100644 --- a/src/structs/filetype.rs +++ b/src/structs/filetype.rs @@ -1,6 +1,6 @@ use std::{fs, path::Path}; -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Clone)] pub enum Filetype { Text, // (default) Executable, @@ -17,6 +17,8 @@ pub enum Filetype { Symlink, Lnk, Unknown, + #[allow(dead_code)] + DriveView, } impl Filetype { @@ -28,7 +30,7 @@ impl Filetype { pub const fn should_exec(&self) -> bool { !matches!( self, - Self::Directory | Self::Symlink | Self::Lnk | Self::Unknown + Self::Directory | Self::Symlink | Self::Lnk | Self::Unknown | Self::DriveView ) } diff --git a/src/structs/icons.rs b/src/structs/icons.rs index b597057..ca6ac99 100644 --- a/src/structs/icons.rs +++ b/src/structs/icons.rs @@ -4,6 +4,12 @@ use std::fmt; pub struct Icon(&'static str); +impl Icon { + pub const fn str(&self) -> &'static str { + self.0 + } +} + impl fmt::Display for Icon { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) @@ -14,7 +20,7 @@ impl fmt::Display for Icon { pub struct Icons; impl Icons { - pub const EXE: Icon = Icon("๐Ÿ’ฟ"); // ๐Ÿ’ฝ ๐Ÿ“€ ๐Ÿ’พ + pub const EXE: Icon = Icon("๐Ÿ’ฟ"); // ๐Ÿ“€ ๐Ÿ’พ pub const TXT: Icon = Icon("๐Ÿ“„"); // ๐Ÿ“ฐ ๐Ÿ“ ๐Ÿ“– ๐Ÿ“œ ๐Ÿ“’ ๐Ÿ““ ๐Ÿ“‘ ๐Ÿงพ ๐Ÿ“‹ ๐Ÿ“‡ pub const PIC: Icon = Icon("๐Ÿ–ผ๏ธ"); // ๐Ÿ“ท ๐Ÿ“ธ ๐ŸŽจ pub const VID: Icon = Icon("๐ŸŽฌ"); // ๐ŸŽž๏ธ ๐Ÿ“บ ๐Ÿ“น ๐Ÿ“ฝ๏ธ ๐ŸŽฅ ๐Ÿ“ผ @@ -31,6 +37,10 @@ impl Icons { pub const JS: Icon = Icon("๐Ÿ“’"); // ๐Ÿ‡ฏ ๐Ÿ "\x1b[30;43m๐Ÿ‡ฏ\x1b[0m" = black on yellow pub const CSS: Icon = Icon("๐Ÿ’„"); // ๐Ÿ’… + // Windows Only + pub const PC: Icon = Icon("๐Ÿ–ฅ๏ธ"); + pub const DRIVE: Icon = Icon("๐Ÿ’ฝ"); + // TODO // Packages: ๐Ÿ“ฆ (zip, tar, gz, bz2, xz, 7z, rar) // Typescript: ๐Ÿ“˜ @@ -61,6 +71,7 @@ impl Icons { Filetype::Rust => &Self::RUST, Filetype::Javascript => &Self::JS, Filetype::Css => &Self::CSS, + Filetype::DriveView => &Self::PC, Filetype::Unknown => &Self::UNKNOWN, } } diff --git a/src/structs/prompt.rs b/src/structs/prompt.rs index de3a38e..23ca64e 100644 --- a/src/structs/prompt.rs +++ b/src/structs/prompt.rs @@ -9,6 +9,7 @@ use fuzzy_matcher::FuzzyMatcher; use std::io; use unicode_segmentation::UnicodeSegmentation; +use crate::Icons; /// Renders a selection menu that user can fuzzy match to reduce set. /// /// User can use fuzzy search to limit selectable items. @@ -169,7 +170,7 @@ impl Prompt<'_> { if cursor_pos > 0 { cursor_pos -= 1; term.flush()?; - } else if search_term.is_empty() { + } else if search_term.is_empty() && self.items[0].ends_with("..") { if self.clear { render.clear()?; } @@ -186,7 +187,9 @@ impl Prompt<'_> { cursor_pos += 1; term.flush()?; } else if search_term.is_empty() - && filtered_list[sel].0.find('๐Ÿ“').is_some() + && (filtered_list[sel].0.contains(Icons::DIR.str()) + || (cfg!(windows) + && filtered_list[sel].0.contains(Icons::DRIVE.str()))) { if self.clear { render.clear()?; diff --git a/src/structs/prompt_renderer.rs b/src/structs/prompt_renderer.rs index 07ecf67..cd5025b 100644 --- a/src/structs/prompt_renderer.rs +++ b/src/structs/prompt_renderer.rs @@ -240,6 +240,9 @@ impl Theme for ColorfulTheme { if highlight_matches { if let Some((_score, indices)) = matcher.fuzzy_indices(text, search_term) { for (idx, c) in text.chars().into_iter().enumerate() { + if text.starts_with('\u{1f5a5}') && c == ' ' && active { + continue; // fix `๐Ÿ–ฅ๏ธ ..` is printed as `๐Ÿ–ฅ๏ธ ..` + }; if indices.contains(&idx) && !is_rtl(c) { if active { write!( @@ -251,7 +254,8 @@ impl Theme for ColorfulTheme { } else { write!(f, "{}", self.fuzzy_match_highlight_style.apply_to(c))?; } - } else if active { + } else if active && c != '\u{1f5a5}' && c != '\u{fe0f}' { + // Fix `๐Ÿ–ฅ๏ธ` is printed as `๐Ÿ–ฅ ` (because painting those unicode chars breaks the emoji) write!(f, "{}", self.active_item_style.apply_to(c))?; } else { write!(f, "{c}")?; diff --git a/src/utils.rs b/src/utils.rs index b0e6e7f..ba49859 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -36,7 +36,6 @@ pub fn resolve_lnk(path: &String) -> String { ) } -// dialoguer select from a list of choices // returns the index of the selected choice pub fn display_choices(items: &[Entry], path: &str) -> (usize, KeyModifiers) { Prompt::with_theme(&ColorfulTheme::default()) @@ -60,8 +59,36 @@ pub fn get_first_arg() -> Option { pub fn pretty_path(path: &str) -> &str { if cfg!(windows) { - &path[4..] + path.trim_start_matches("\\\\?\\") } else { path } } + +/**** WINDOWS ONLY ****/ + +#[cfg(windows)] +use std::io::Error; +#[cfg(windows)] +use windows::Win32::Storage::FileSystem::GetLogicalDrives; + +#[cfg(windows)] +pub fn get_logical_drives() -> Result, Error> { + let bitmask = unsafe { GetLogicalDrives() }; + if bitmask == 0 { + return Err(Error::last_os_error()); + } + + Ok(bitmask_to_vec(bitmask)) +} + +#[cfg(windows)] +fn bitmask_to_vec(bitmask: u32) -> Vec { + let mut vec = Vec::new(); + for i in 0..32 { + if bitmask & (1 << i) != 0 { + vec.push((b'A' + i) as char); + } + } + vec +}