Skip to content

Commit

Permalink
Add support for bookmarking a directory
Browse files Browse the repository at this point in the history
Any directory can be bookmarked, doesn't need to contain a git repo.

Fixes #93
  • Loading branch information
petersimonsson committed May 18, 2024
1 parent a6b879d commit 70759eb
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 13 deletions.
39 changes: 38 additions & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{collections::HashMap, fs::canonicalize, path::Path};
use std::{collections::HashMap, env::current_dir, fs::canonicalize, path::Path};

use crate::{
configs::{Config, SearchDirectory, SessionSortOrderConfig},
Expand Down Expand Up @@ -44,6 +44,8 @@ pub enum CliCommand {
Refresh(RefreshCommand),
/// Clone repository into the first search path and create a new session for it
CloneRepo(CloneRepoCommand),
/// Bookmark a directory so it is available to select along with the Git repositories
Bookmark(BookmarkCommand),
}

#[derive(Debug, Args)]
Expand Down Expand Up @@ -114,6 +116,15 @@ pub struct CloneRepoCommand {
repository: String,
}

#[derive(Debug, Args)]
pub struct BookmarkCommand {
#[arg(long, short)]
/// Delete instead of add a bookmark
delete: bool,
/// Path to bookmark, if left empty bookmark the current directory.
path: Option<String>,
}

impl Cli {
pub fn handle_sub_commands(&self, tmux: &Tmux) -> Result<SubCommandGiven> {
// Get the configuration from the config file
Expand Down Expand Up @@ -169,6 +180,11 @@ impl Cli {
Ok(SubCommandGiven::Yes)
}

Some(CliCommand::Bookmark(args)) => {
bookmark_command(args, config)?;
Ok(SubCommandGiven::Yes)
}

None => Ok(SubCommandGiven::No(config.into())),
}
}
Expand Down Expand Up @@ -642,6 +658,27 @@ fn git_credentials_callback(
git2::Cred::ssh_key_from_agent(user)
}

fn bookmark_command(args: &BookmarkCommand, mut config: Config) -> Result<()> {
let path = if let Some(path) = &args.path {
path.to_owned()
} else {
current_dir()
.change_context(TmsError::IoError)?
.to_string()
.change_context(TmsError::IoError)?
};

if !args.delete {
config.add_bookmark(path);
} else {
config.delete_bookmark(path);
}

config.save().change_context(TmsError::ConfigError)?;

Ok(())
}

pub enum SubCommandGiven {
Yes,
No(Box<Config>),
Expand Down
52 changes: 50 additions & 2 deletions src/configs.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use clap::ValueEnum;
use error_stack::ResultExt;
use serde_derive::{Deserialize, Serialize};
use std::{env, fmt::Display, fs::canonicalize, io::Write, path::PathBuf};
use std::{collections::HashMap, env, fmt::Display, fs::canonicalize, io::Write, path::PathBuf};

use ratatui::style::{Color, Style};

use crate::{keymap::Keymap, Suggestion};
use crate::{dirty_paths::DirtyUtf8Path, keymap::Keymap, Suggestion};

type Result<T> = error_stack::Result<T, ConfigError>;

Expand Down Expand Up @@ -46,6 +46,7 @@ pub struct Config {
pub sessions: Option<Vec<Session>>,
pub picker_colors: Option<PickerColorConfig>,
pub shortcuts: Option<Keymap>,
pub bookmarks: Option<Vec<String>>,
}

impl Config {
Expand Down Expand Up @@ -180,6 +181,53 @@ impl Config {

Ok(search_dirs)
}

pub fn add_bookmark(&mut self, path: String) {
let bookmarks = &mut self.bookmarks;
match bookmarks {
Some(ref mut bookmarks) => {
if !bookmarks.contains(&path) {
bookmarks.push(path);
}
}
None => {
self.bookmarks = Some(vec![path]);
}
}
}

pub fn delete_bookmark(&mut self, path: String) {
if let Some(ref mut bookmarks) = self.bookmarks {
if let Some(idx) = bookmarks.iter().position(|bookmark| *bookmark == path) {
bookmarks.remove(idx);
}
}
}

pub fn bookmark_paths(&self) -> HashMap<String, PathBuf> {
let mut ret = HashMap::new();

if let Some(bookmarks) = &self.bookmarks {
for bookmark in bookmarks {
if let Ok(path) = PathBuf::from(bookmark).canonicalize() {
let name = if let Some(true) = self.display_full_path {
Some(path.display().to_string())
} else {
path.file_name()
.expect("should end with a directory")
.to_string()
.ok()
};

if let Some(name) = name {
ret.insert(name, path);
}
}
}
}

ret
}
}

#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Eq)]
Expand Down
62 changes: 52 additions & 10 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::{collections::HashMap, path::PathBuf};

use clap::Parser;
use error_stack::{Report, ResultExt};

use git2::Repository;
use tms::{
cli::{Cli, SubCommandGiven},
dirty_paths::DirtyUtf8Path,
Expand Down Expand Up @@ -32,6 +35,8 @@ fn main() -> Result<()> {
SubCommandGiven::No(config) => config, // continue
};

let bookmarks = config.bookmark_paths();

// Find repositories and present them with the fuzzy finder
let repos = find_repos(
config.search_dirs().change_context(TmsError::ConfigError)?,
Expand All @@ -41,8 +46,12 @@ fn main() -> Result<()> {
config.recursive_submodules,
)?;

let repo_name = if let Some(str) = get_single_selection(
&repos.list(),
let mut dirs = repos.list();

dirs.append(&mut bookmarks.keys().map(|b| b.to_string()).collect());

let selected_str = if let Some(str) = get_single_selection(
&dirs,
Preview::None,
config.picker_colors,
config.shortcuts,
Expand All @@ -53,9 +62,21 @@ fn main() -> Result<()> {
return Ok(());
};

let found_repo = repos
.find_repo(&repo_name)
.expect("The internal representation of the selected repository should be present");
if let Some(found_repo) = repos.find_repo(&selected_str) {
switch_to_repo_session(selected_str, found_repo, &tmux, config.display_full_path)?;
} else {
switch_to_bookmark_session(selected_str, &tmux, bookmarks)?;
}

Ok(())
}

fn switch_to_repo_session(
selected_str: String,
found_repo: &Repository,
tmux: &Tmux,
display_full_path: Option<bool>,
) -> Result<()> {
let path = if found_repo.is_bare() {
found_repo.path().to_string()?
} else {
Expand All @@ -66,22 +87,43 @@ fn main() -> Result<()> {
.change_context(TmsError::IoError)?
.to_string()?
};
let repo_short_name = (if config.display_full_path == Some(true) {
std::path::PathBuf::from(&repo_name)
let repo_short_name = (if display_full_path == Some(true) {
std::path::PathBuf::from(&selected_str)
.file_name()
.expect("None of the paths here should terminate in `..`")
.to_string()?
} else {
repo_name
selected_str
})
.replace('.', "_");

if !session_exists(&repo_short_name, &tmux) {
if !session_exists(&repo_short_name, tmux) {
tmux.new_session(Some(&repo_short_name), Some(&path));
set_up_tmux_env(found_repo, &repo_short_name, &tmux)?;
set_up_tmux_env(found_repo, &repo_short_name, tmux)?;
}

switch_to_session(&repo_short_name, &tmux);

Ok(())
}

fn switch_to_bookmark_session(
selected_str: String,
tmux: &Tmux,
bookmarks: HashMap<String, PathBuf>,
) -> Result<()> {
let path = &bookmarks[&selected_str];
let session_name = path
.file_name()
.expect("Bookmarks should not end in `..`")
.to_string()?
.replace('.', "_");

if !session_exists(&session_name, &tmux) {
tmux.new_session(Some(&session_name), path.to_str());
}

switch_to_session(&session_name, &tmux);

Ok(())
}
1 change: 1 addition & 0 deletions tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ fn tms_config() -> anyhow::Result<()> {
prompt_color: Some(picker_prompt_color.clone()),
}),
shortcuts: None,
bookmarks: None,
};

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

0 comments on commit 70759eb

Please sign in to comment.