diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/config.rs | 6 | ||||
-rw-r--r-- | src/lib.rs | 1 | ||||
-rw-r--r-- | src/main.rs | 6 | ||||
-rw-r--r-- | src/project.rs | 6 | ||||
-rw-r--r-- | src/tmux.rs | 82 | ||||
-rw-r--r-- | src/tmux/error.rs | 49 |
6 files changed, 148 insertions, 2 deletions
diff --git a/src/config.rs b/src/config.rs index c45036c..d961f50 100644 --- a/src/config.rs +++ b/src/config.rs @@ -64,6 +64,12 @@ pub struct Projects { #[cfg(feature = "git")] #[arg(long, short)] pub git: bool, + + /// Add current tmux session directories. + /// + /// Uses last attached time as the timestamp. + #[arg(long, short)] + pub tmux: bool, } #[derive(Debug, Default, Clone, Args)] @@ -9,3 +9,4 @@ pub mod search; #[cfg(feature = "git")] pub mod git; pub mod path; +pub mod tmux; diff --git a/src/main.rs b/src/main.rs index bdc7998..383411b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use anyhow::Result; use clap::Parser; -use projectr::{config::Config, project::Projects, Search}; +use projectr::{config::Config, project::Projects, tmux::Tmux, Search}; fn main() -> Result<()> { let config = Config::parse(); @@ -19,5 +19,9 @@ fn main() -> Result<()> { projects.extend(search); } + if let Ok(sessions) = Tmux::sessions() { + projects.extend(sessions) + } + Ok(projects.write(std::io::stdout())?) } diff --git a/src/project.rs b/src/project.rs index 5f98ef6..d05da37 100644 --- a/src/project.rs +++ b/src/project.rs @@ -9,7 +9,7 @@ use std::{ use tracing::trace; -use crate::{parser::FilterMap, path::PathMatcher}; +use crate::{parser::FilterMap, path::PathMatcher, tmux::Tmux}; #[derive(Default)] pub struct Projects { @@ -111,6 +111,10 @@ impl From<crate::config::Projects> for Projects { projects.add_filter(PathMatcher(pattern.to_owned())); } + if value.tmux { + projects.add_filter(Tmux); + } + #[cfg(feature = "git")] if value.git { projects.add_filter(crate::git::Git); diff --git a/src/tmux.rs b/src/tmux.rs new file mode 100644 index 0000000..559242b --- /dev/null +++ b/src/tmux.rs @@ -0,0 +1,82 @@ +use std::{ffi::OsString, path::PathBuf, process::Command, time::Duration}; + +use crate::{parser::Parser, project::Project}; + +use self::error::Error; + +pub mod error; + +#[derive(Debug)] +pub struct Tmux; + +impl Tmux { + pub fn sessions() -> Result<Vec<PathBuf>, Error> { + let stdout = Command::new("tmux") + .arg("list-sessions") + .arg("-F") + .arg("#{session_path}") + .output()? + .stdout; + + Ok(std::str::from_utf8(&stdout)? + .lines() + .map(Into::into) + .collect()) + } + + pub fn get_session(&self, path_buf: PathBuf) -> Result<Project, Error> { + let mut filter = OsString::from("#{==:#{session_path},"); + filter.push(path_buf.as_os_str()); + filter.push("}"); + + // tmux list-sessions -f '#{==:#{session_path},/home/tobyv/src/projectr}' + let stdout = Command::new("tmux") + .arg("list-sessions") + .arg("-F") + .arg("#{session_path},#{session_last_attached},#{session_created}") + .arg("-f") + .arg(filter) + .output()? + .stdout; + + std::str::from_utf8(&stdout)? + .lines() + .map(Self::parse_project) + .inspect(|res| { + if let Err(err) = res { + tracing::warn!(%err, "Skipping invalid format") + } + }) + .flatten() + .max() + .ok_or(Error::NotFound) + } + + fn parse_project<T: AsRef<str>>(fmt_str: T) -> Result<Project, Error> { + let mut split = fmt_str.as_ref().split(','); + + let path_buf = split + .next() + .ok_or(Error::PathFormat(fmt_str.as_ref().to_string()))? + .into(); + + let timestamp = split + .next() + .ok_or(Error::TimestampFormat(fmt_str.as_ref().to_string()))? + .parse() + .map(Duration::from_secs)?; + + Ok(Project { + timestamp, + path_buf, + }) + } +} + +impl Parser for Tmux { + type Error = Error; + + fn parse(&self, path_buf: PathBuf) -> Result<Project, Self::Error> { + self.get_session(path_buf) + } +} diff --git a/src/tmux/error.rs b/src/tmux/error.rs new file mode 100644 index 0000000..c857e5b --- /dev/null +++ b/src/tmux/error.rs @@ -0,0 +1,49 @@ +#[derive(Debug)] +pub enum FormatError { + Path(String), + Timestamp(String), +} + +#[derive(Debug)] +pub enum Error { + IO(std::io::Error), + Utf8(std::str::Utf8Error), + PathFormat(String), + TimestampFormat(String), + ParseInt(std::num::ParseIntError), + NotFound, +} + +impl std::error::Error for Error {} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::IO(err) => write!(f, "IO error: {}", err), + Error::Utf8(err) => write!(f, "Failed to parse UTF8: {}", err), + + Error::PathFormat(s) => write!(f, "Invalid path format: {}", s), + Error::TimestampFormat(s) => write!(f, "Invalid timestamp format: {}", s), + Error::ParseInt(err) => write!(f, "Failed to parse int: {}", err), + Error::NotFound => write!(f, "Tmux session not found"), + } + } +} + +impl From<std::io::Error> for Error { + fn from(value: std::io::Error) -> Self { + Self::IO(value) + } +} + +impl From<std::str::Utf8Error> for Error { + fn from(value: std::str::Utf8Error) -> Self { + Self::Utf8(value) + } +} + +impl From<std::num::ParseIntError> for Error { + fn from(value: std::num::ParseIntError) -> Self { + Self::ParseInt(value) + } +} |