From 6db70b46eac79cd6cdae539da38f9e314619e463 Mon Sep 17 00:00:00 2001 From: William McGann Date: Fri, 9 Feb 2024 14:27:26 -0500 Subject: [PATCH] Initial Windows root navigation --- Cargo.lock | 1 + Cargo.toml | 3 +++ src/app_state.rs | 16 +++++++++++--- src/ui/mod.rs | 55 +++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 71 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f6d370e6..22777656 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -646,6 +646,7 @@ dependencies = [ "tempfile", "textwrap", "unicode-segmentation", + "winapi", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index aea0d934..80720ec5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,3 +34,6 @@ rexpect = "0.5" [profile.release] lto = true strip = "debuginfo" + +[target.'cfg(windows)'.dependencies] +winapi = { version = "0.3", features = ["fileapi"] } diff --git a/src/app_state.rs b/src/app_state.rs index eb17465b..2677d393 100644 --- a/src/app_state.rs +++ b/src/app_state.rs @@ -32,7 +32,7 @@ pub type MatchesLocType = Vec<(usize, usize)>; /// A vector that keeps track of items that are 'filtered'. It offers indexing/viewing /// both the vector of filtered items and the whole unfiltered vector. -struct MatchesVec { +pub struct MatchesVec { all_items: Vec, // Each key-value pair in this map corresponds to an item in `all_items` that matches the // current search. The key is the item's index in `all_items`, while the value contains the @@ -112,6 +112,16 @@ pub struct CustomDirEntry { } impl CustomDirEntry { + pub fn custom(path_buf: PathBuf) -> CustomDirEntry { + Self{ + _path: path_buf.clone(), + // TODO: Don't let this hack survive! But we need it to properly respond for `is_dir` + metadata: Some(std::fs::metadata("/").unwrap()), + symlink_target: None, + _file_name: std::ffi::OsString::from(path_buf.clone()), + } + } + /// Return the file name of this directory entry. The file name is an OsString, /// which may not be possible to convert to a String. In this case, this /// function returns an empty string. @@ -169,7 +179,7 @@ impl From<&Path> for CustomDirEntry { } /// The type of the `ls_output_buf` buffer of the app state -type LsBufType = MatchesVec; +pub type LsBufType = MatchesVec; /// Possible non-error results of 'change directory' operation #[derive(Debug)] @@ -196,7 +206,7 @@ pub struct TereAppState { // This vector will hold the list of files/folders in the current directory, // including ".." (the parent folder). - ls_output_buf: LsBufType, + pub ls_output_buf: LsBufType, // Have to manually keep track of the logical absolute path of our app, see https://stackoverflow.com/a/70309860/5208725 pub current_path: PathBuf, diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 715378fe..7bd5cdb8 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -2,12 +2,17 @@ mod action; pub mod help_window; pub mod markup_render; +#[cfg(windows)] +extern crate winapi; + use std::convert::TryFrom; +use std::error::Error; +use std::ffi::CString; use std::fmt::Write as _; use std::io::{Stderr, Write}; use std::path::PathBuf; -use crate::app_state::{CdResult, TereAppState}; +use crate::app_state::{CdResult, CustomDirEntry, TereAppState}; use crate::error::TereError; use crate::settings::{CaseSensitiveMode, GapSearchMode, SortMode}; pub use action::{Action, ActionContext}; @@ -606,6 +611,54 @@ impl<'a> TereTui<'a> { Ok(()) } + #[cfg(windows)] + fn get_roots() -> CTResult> { + use winapi::um::fileapi::{ + GetDriveTypeA, + GetLogicalDrives, + }; + + let mut results: Vec = Vec::new(); + + unsafe { + let mut drives = GetLogicalDrives(); + let mut drive_letter = 'A'; + + while drives != 0 { + if drives & 1 == 1 { + let str = drive_letter.to_string() + ":\\"; + let cstr = CString::new(str.clone())?; + let x = GetDriveTypeA(cstr.as_ptr()); + if x == 3 || x == 2 { + results.push(str); + } + } + drive_letter = std::char::from_u32((drive_letter as u32) + 1).unwrap(); + drives >>= 1; + } + } + + Ok(results) + } + + #[cfg(windows)] + fn on_go_to_root(&mut self) -> CTResult<()> { + let drive_letters: Vec = Self::get_roots()? + .iter() + .map(|r| PathBuf::from(r)) + .map(|p| CustomDirEntry::custom(p)) + .collect(); + + self.info_message("Select drive to browse")?; + self.app_state.ls_output_buf = drive_letters.into(); + + self.redraw_header()?; + self.redraw_main_window()?; + self.redraw_footer()?; + Ok(()) + } + + #[cfg(not(windows))] fn on_go_to_root(&mut self) -> CTResult<()> { // note: this is the same as std::path::Component::RootDir // using a temporary buffer to avoid allocating on the heap, because MAIN_SEPARATOR_STR is