Skip to content

Commit

Permalink
Add marks feature
Browse files Browse the repository at this point in the history
closes #132
  • Loading branch information
junglerobba committed Nov 13, 2024
1 parent 3839459 commit e92b12b
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 0 deletions.
8 changes: 8 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::{
configs::{Config, SearchDirectory, SessionSortOrderConfig},
dirty_paths::DirtyUtf8Path,
execute_command, get_single_selection,
marks::{marks_command, MarksCommand},
picker::Preview,
session::{create_sessions, SessionContainer},
tmux::Tmux,
Expand Down Expand Up @@ -58,6 +59,8 @@ pub enum CliCommand {
Bookmark(BookmarkCommand),
/// Open a session
OpenSession(OpenSessionCommand),
/// Manage list of sessions that can be instantly accessed by their index
Marks(MarksCommand),
}

#[derive(Debug, Args)]
Expand Down Expand Up @@ -225,6 +228,11 @@ impl Cli {
Ok(SubCommandGiven::Yes)
}

Some(CliCommand::Marks(args)) => {
marks_command(args, config, tmux)?;
Ok(SubCommandGiven::Yes)
}

None => Ok(SubCommandGiven::No(config.into())),
}
}
Expand Down
23 changes: 23 additions & 0 deletions src/configs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ pub struct Config {
pub shortcuts: Option<Keymap>,
pub bookmarks: Option<Vec<String>>,
pub session_configs: Option<HashMap<String, SessionConfig>>,
pub marks: Option<HashMap<String, String>>,
}

impl Config {
Expand Down Expand Up @@ -222,6 +223,28 @@ impl Config {
Vec::new()
}
}

pub fn add_mark(&mut self, path: String, index: usize) {
let marks = &mut self.marks;
match marks {
Some(ref mut marks) => {
marks.insert(index.to_string(), path);
}
None => {
self.marks = Some(HashMap::from([(index.to_string(), path)]));
}
}
}

pub fn delete_mark(&mut self, index: usize) {
if let Some(ref mut marks) = self.marks {
marks.remove(&index.to_string());
}
}

pub fn clear_marks(&mut self) {
self.marks = None;
}
}

#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Eq)]
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod configs;
pub mod dirty_paths;
pub mod error;
pub mod keymap;
pub mod marks;
pub mod picker;
pub mod repos;
pub mod session;
Expand Down
154 changes: 154 additions & 0 deletions src/marks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
use std::{env::current_dir, path::PathBuf};

use clap::{Args, Subcommand};
use error_stack::ResultExt;

use crate::{
configs::Config,
dirty_paths::DirtyUtf8Path,
error::{Result, TmsError},
session::Session,
tmux::Tmux,
};

#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true)]
pub struct MarksCommand {
/// The index of the mark to open
index: Option<usize>,
#[command(subcommand)]
cmd: Option<MarksSubCommand>,
}

#[derive(Debug, Subcommand)]
pub enum MarksSubCommand {
/// List all marks
List,
/// Add a session mark
Set(MarksSetCommand),
/// Open the session at index
Open(MarksOpenCommand),
/// Delete marks
Delete(MarksDeleteCommand),
}

#[derive(Debug, Args)]
pub struct MarksSetCommand {
/// Index of mark to set, if empty will append after the last item
index: Option<usize>,
#[arg(long, short)]
/// Path to project directory, if empty will use the current directory
path: Option<String>,
}

#[derive(Debug, Args)]
pub struct MarksOpenCommand {
/// The index of the mark to open
index: usize,
}

#[derive(Debug, Args)]
#[group(required = true, multiple = false)]
pub struct MarksDeleteCommand {
/// Index of mark to delete
index: Option<usize>,
#[arg(long, short)]
/// Delete all items
all: bool,
}

pub fn marks_command(args: &MarksCommand, config: Config, tmux: &Tmux) -> Result<()> {
match (&args.cmd, args.index) {
(None, None) => list(config),
(_, Some(index)) => open(index, &config, tmux),
(Some(MarksSubCommand::List), _) => list(config),
(Some(MarksSubCommand::Set(args)), _) => set(args, config),
(Some(MarksSubCommand::Open(args)), _) => open(args.index, &config, tmux),
(Some(MarksSubCommand::Delete(args)), _) => delete(args, config),
}
}

fn list(config: Config) -> Result<()> {
let items = get_marks(&config).unwrap_or_default();
items.iter().for_each(|(index, session)| {
println!("{index}: {} ({})", session.name, session.path().display());
});
Ok(())
}

fn set(args: &MarksSetCommand, mut config: Config) -> Result<()> {
let index = args.index.unwrap_or_else(|| {
let items = get_marks(&config).unwrap_or_default();
items
.iter()
.enumerate()
.take_while(|(i, (index, _))| i == index)
.count()
});

let path = if let Some(path) = &args.path {
path.to_owned()
} else {
current_dir()
.change_context(TmsError::IoError)?
.to_string()
.change_context(TmsError::IoError)?
};
config.add_mark(path, index);
config.save().change_context(TmsError::ConfigError)
}

fn get_marks(config: &Config) -> Option<Vec<(usize, Session)>> {
let items = config.marks.as_ref()?;
let mut items = items
.iter()
.filter_map(|(index, item)| {
let index = index.parse::<usize>().ok();
let session = path_to_session(item).ok();
index.zip(session)
})
.collect::<Vec<_>>();
items.sort_by(|(a, _), (b, _)| a.cmp(b));
Some(items)
}

fn open(index: usize, config: &Config, tmux: &Tmux) -> Result<()> {
let path = config
.marks
.as_ref()
.and_then(|items| items.get(&index.to_string()))
.ok_or(TmsError::ConfigError)
.attach_printable(format!("Session with index {} not found in marks", index))?;

let session = path_to_session(path)?;

session.switch_to(tmux, config)
}

fn path_to_session(path: &String) -> Result<Session> {
let path = shellexpand::full(path)
.change_context(TmsError::IoError)
.and_then(|p| {
PathBuf::from(p.to_string())
.canonicalize()
.change_context(TmsError::IoError)
})?;

let session_name = path
.file_name()
.expect("The file name doesn't end in `..`")
.to_string()?;
let session = Session::new(session_name, crate::session::SessionType::Bookmark(path));
Ok(session)
}

fn delete(args: &MarksDeleteCommand, mut config: Config) -> Result<()> {
if args.all {
config.clear_marks();
} else if let Some(index) = args.index {
config.delete_mark(index);
} else {
unreachable!("One of the args is required by clap");
}
config.save().change_context(TmsError::ConfigError)
}
1 change: 1 addition & 0 deletions tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ fn tms_config() -> anyhow::Result<()> {
shortcuts: None,
bookmarks: None,
session_configs: None,
marks: None,
};

let mut tms = Command::cargo_bin("tms")?;
Expand Down

0 comments on commit e92b12b

Please sign in to comment.