diff options
author | Manos Pitsidianakis <el13635@mail.ntua.gr> | 2020-01-21 02:42:18 +0200 |
---|---|---|
committer | Manos Pitsidianakis <el13635@mail.ntua.gr> | 2020-01-21 12:02:21 +0200 |
commit | 1e2acd3b296bd8afb480fee0f9d70db5f5542f3e (patch) | |
tree | 974aa8d70d749270d3e849d141adcc4fe1d30737 /melib/src/lib.rs | |
parent | 6d9f584de3430aa65f9a3e8a5907d0bb4bcd637a (diff) |
melib: add complete() method to ShellExpandTrait
complete(force: bool) returns String path segments that when appended to
the path will form a valid location. Example:
- User types: save-attachment 1 /t
- User presses <TAB>.
- complete() returns the suggestion: "mp/"
- User sees: save-attachment 1 /tmp/
complete() uses openat() and getdents64 syscalls hoping it's faster than
using stdlib.
Diffstat (limited to 'melib/src/lib.rs')
-rw-r--r-- | melib/src/lib.rs | 120 |
1 files changed, 119 insertions, 1 deletions
diff --git a/melib/src/lib.rs b/melib/src/lib.rs index 468bfc14..2b492105 100644 --- a/melib/src/lib.rs +++ b/melib/src/lib.rs @@ -150,10 +150,12 @@ pub use crate::addressbook::*; pub use shellexpand::ShellExpandTrait; pub mod shellexpand { - use std::path::*; + use smallvec::SmallVec; + use std::path::{Path, PathBuf}; pub trait ShellExpandTrait { fn expand(&self) -> PathBuf; + fn complete(&self, force: bool) -> SmallVec<[String; 128]>; } impl ShellExpandTrait for Path { @@ -188,5 +190,121 @@ pub mod shellexpand { } ret } + + fn complete(&self, force: bool) -> SmallVec<[String; 128]> { + use libc::dirent64; + use nix::fcntl::OFlag; + use std::ffi::CString; + use std::ffi::OsStr; + use std::os::unix::ffi::OsStrExt; + use std::os::unix::io::AsRawFd; + const BUF_SIZE: ::libc::size_t = 8 << 10; + + let (prefix, _match) = if self.as_os_str().as_bytes().ends_with(b"/.") { + (self.components().as_path(), OsStr::from_bytes(b".")) + } else { + if self.exists() && (!force || self.as_os_str().as_bytes().ends_with(b"/")) { + // println!("{} {:?}", self.display(), self.components().last()); + return SmallVec::new(); + } else { + let last_component = self + .components() + .last() + .map(|c| c.as_os_str()) + .unwrap_or(OsStr::from_bytes(b"")); + let prefix = if let Some(p) = self.parent() { + p + } else { + return SmallVec::new(); + }; + (prefix, last_component) + } + }; + + let dir = match ::nix::dir::Dir::openat( + ::libc::AT_FDCWD, + prefix, + OFlag::O_DIRECTORY | OFlag::O_NOATIME | OFlag::O_RDONLY | OFlag::O_CLOEXEC, + ::nix::sys::stat::Mode::S_IRUSR | ::nix::sys::stat::Mode::S_IXUSR, + ) + .or_else(|_| { + ::nix::dir::Dir::openat( + ::libc::AT_FDCWD, + prefix, + OFlag::O_DIRECTORY | OFlag::O_RDONLY | OFlag::O_CLOEXEC, + ::nix::sys::stat::Mode::S_IRUSR | ::nix::sys::stat::Mode::S_IXUSR, + ) + }) { + Ok(dir) => dir, + Err(err) => { + debug!(prefix); + debug!(err); + return SmallVec::new(); + } + }; + + let mut buf: Vec<u8> = Vec::with_capacity(BUF_SIZE); + let mut entries = SmallVec::new(); + loop { + let n: i64 = unsafe { + ::libc::syscall( + ::libc::SYS_getdents64, + dir.as_raw_fd(), + buf.as_ptr(), + BUF_SIZE - 256, + ) + }; + if n < 0 { + return SmallVec::new(); + } else if n == 0 { + break; + } + + let n = n as usize; + unsafe { + buf.set_len(n); + } + let mut pos = 0; + while pos < n { + let dir = unsafe { std::mem::transmute::<&[u8], &[dirent64]>(&buf[pos..]) }; + let entry = unsafe { std::ffi::CStr::from_ptr(dir[0].d_name.as_ptr()) }; + if entry.to_bytes() != b"." && entry.to_bytes() != b".." { + if entry.to_bytes().starts_with(_match.as_bytes()) { + if dir[0].d_type == ::libc::DT_DIR && !entry.to_bytes().ends_with(b"/") + { + let mut s = unsafe { + String::from_utf8_unchecked( + entry.to_bytes()[_match.as_bytes().len()..].to_vec(), + ) + }; + s.push('/'); + entries.push(s); + } else { + entries.push(unsafe { + String::from_utf8_unchecked( + entry.to_bytes()[_match.as_bytes().len()..].to_vec(), + ) + }); + } + } + } + pos += dir[0].d_reclen as usize; + } + // https://github.com/romkatv/gitstatus/blob/caf44f7aaf33d0f46e6749e50595323c277e0908/src/dir.cc + // "It's tempting to bail here if n + sizeof(linux_dirent64) + 512 <= n. After all, there + // was enough space for another entry but SYS_getdents64 didn't write it, so this must be + // the end of the directory listing, right? Unfortunately, no. SYS_getdents64 is finicky. + // It sometimes writes a partial list of entries even if the full list would fit." + } + return entries; + } + } + + #[test] + fn test_shellexpandtrait() { + let path = &Path::new("~/.v"); + //println!("ret = {:?}", path.expand().complete(false)); + assert!(Path::new("~").expand().complete(false).is_empty()); + assert!(!Path::new("~").expand().complete(true).is_empty()); } } |