Skip to content

Commit

Permalink
Add support for session preview
Browse files Browse the repository at this point in the history
  • Loading branch information
jrmoulton committed Apr 5, 2023
1 parent cb59950 commit fada691
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 44 deletions.
56 changes: 39 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,49 @@ Tmux Sessionizer is a tmux session manager that is based on ThePrimeagen's
but is opinionated and personalized to my specific tmux workflow. And it's awesome. Git worktrees
are automatically opened as new windows, specific directories can be excluded, a default session can
be set, killing a session jumps you to a default, and it is a goal to handle more edge case
scenarios.
scenarios.

Tmux has keybindings built-in to allow you to switch between sessions. By default these are `leader-(` and `leader-)`
Tmux has keybindings built-in to allow you to switch between sessions. By default these are
`leader-(` and `leader-)`

Switching between windows is done by default with `leader-p` and `leader-n`

![tms-gif](images/tms-v0_1_1.gif)

## Usage

### The `tms` command

Running `tms` will find the repos and fuzzy find on them

### The `tms switch` command

There is also the `tms switch` command that will show other active sessions with a fuzzy finder and
a preview window. This can be very useful when used with the tmux `display-popup` which can open a
popup window above the current session. That popup window with a command can have a keybinding. The
config could look like this `bind C-j display-popup -E "tms switch"`. Then when using leader+C-j the
popup is displayed (and it's fast)

![tms-switch](images/tms_switch-v2_1.png)

Use `tms --help`

```
USAGE:
tms [SUBCOMMAND]
Scan for all git folders in specified directories, select one and open it as a new tmux session
OPTIONS:
-h, --help Print help information
-V, --version Print version information
SUBCOMMANDS:
config Configure the defaults for search paths and excluded directories
help Print this message or the help of the given subcommand(s)
kill Kill the current tmux session and jump to another
sessions Show running tmux sessions with asterisk on the current session
Usage: tms [COMMAND]
Commands:
config Configure the defaults for search paths and excluded directories
start Initialize tmux with the default sessions
switch Display other sessions with a fuzzy finder and a preview window
kill Kill the current tmux session and jump to another
sessions Show running tmux sessions with asterisk on the current session
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
-V, --version Print version
```

### Configuring defaults
Expand Down Expand Up @@ -71,16 +88,21 @@ Install with `cargo install tmux-sessionizer` or

### From source

Clone the repository and install using ```cargo install --path . --force```
Clone the repository and install using `cargo install --path . --force`

## Usage Notes

The 'tms sessions' command can be used to get a styled output of the active sessions with an asterisk on the current session. The configuration would look something like this
The 'tms sessions' command can be used to get a styled output of the active sessions with an
asterisk on the current session. The configuration would look something like this

```
set -g status-right " #(tms sessions)"
```
E.g. ![tmux status bar](images/tmux-status-bar.png)
If this configuration is used it can be helpful to rebind the default tmux keys for switching sessions so that the status bar is refreshed on every session switch. This can be configured with settings like this.

E.g. ![tmux status bar](images/tmux-status-bar.png) If this configuration is used it can be helpful
to rebind the default tmux keys for switching sessions so that the status bar is refreshed on every
session switch. This can be configured with settings like this.

```
bind -r '(' switch-client -p\; refresh-client -S
bind -r ')' switch-client -n\; refresh-client -S
Expand Down
Binary file added images/tms_switch-v2_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
53 changes: 31 additions & 22 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use std::{error::Error, fmt::Display};

use crate::{configs::Config, execute_tmux_command, ConfigError};
use crate::{configs::Config, execute_tmux_command, get_single_selection, ConfigError, TmsError};
use clap::{Arg, ArgMatches, Command};
use error_stack::{IntoReport, Result, ResultExt};

Expand Down Expand Up @@ -54,6 +52,7 @@ pub(crate) fn create_app() -> ArgMatches {
)
)
.subcommand(Command::new("start").about("Initialize tmux with the default sessions"))
.subcommand(Command::new("switch").about("Display other sessions with a fuzzy finder and a preview window"))
.subcommand(Command::new("kill")
.about("Kill the current tmux session and jump to another")
)
Expand All @@ -63,11 +62,11 @@ pub(crate) fn create_app() -> ArgMatches {
.get_matches()
}

pub(crate) fn handle_sub_commands(cli_args: ArgMatches) -> Result<SubCommandGiven, CliError> {
pub(crate) fn handle_sub_commands(cli_args: ArgMatches) -> Result<SubCommandGiven, TmsError> {
// Get the configuration from the config file
let config = confy::load::<Config>("tms", None)
.into_report()
.change_context(CliError::ConfigError)?;
.change_context(TmsError::ConfigError)?;
match cli_args.subcommand() {
Some(("start", _sub_cmd_matches)) => {
if let Some(sessions) = config.sessions {
Expand All @@ -81,7 +80,7 @@ pub(crate) fn handle_sub_commands(cli_args: ArgMatches) -> Result<SubCommandGive
" -c {}",
shellexpand::full(&session_path)
.into_report()
.change_context(CliError::IoError)?
.change_context(TmsError::IoError)?
))
}
execute_tmux_command(&sesssion_start_string);
Expand All @@ -97,7 +96,7 @@ pub(crate) fn handle_sub_commands(cli_args: ArgMatches) -> Result<SubCommandGive
" -c {}",
shellexpand::full(&window_path)
.into_report()
.change_context(CliError::IoError)?
.change_context(TmsError::IoError)?
));
}
execute_tmux_command(&window_start_string);
Expand All @@ -116,11 +115,33 @@ pub(crate) fn handle_sub_commands(cli_args: ArgMatches) -> Result<SubCommandGive
}
Ok(SubCommandGiven::Yes)
}

Some(("switch", _sub_cmd_matches)) => {
let mut sessions = String::from_utf8(
execute_tmux_command(
"tmux list-sessions -F '#{?session_attached,,#{session_name}}",
)
.stdout,
)
.unwrap();
sessions = sessions
.replace('\'', "")
.replace("\n\n", "\n")
.trim()
.to_string();
let target_session = get_single_selection(sessions, Some("tmux capture-pane -ept {}"))?;
execute_tmux_command(&format!(
"tmux switch-client -t {}",
target_session.replace('.', "_")
));

Ok(SubCommandGiven::Yes)
}
// Handle the config subcommand
Some(("config", sub_cmd_matches)) => {
let mut defaults = confy::load::<Config>("tms", None)
.into_report()
.change_context(CliError::ConfigError)?;
.change_context(TmsError::ConfigError)?;
defaults.search_paths = match sub_cmd_matches.get_many::<String>("search paths") {
Some(paths) => {
let mut paths = paths.map(|x| x.to_string()).collect::<Vec<String>>();
Expand Down Expand Up @@ -181,7 +202,7 @@ pub(crate) fn handle_sub_commands(cli_args: ArgMatches) -> Result<SubCommandGive
.into_report()
.change_context(ConfigError::WriteFailure)
.attach_printable("Failed to write the config file")
.change_context(CliError::ConfigError)?;
.change_context(TmsError::ConfigError)?;
println!("Configuration has been stored");
Ok(SubCommandGiven::Yes)
}
Expand All @@ -192,7 +213,7 @@ pub(crate) fn handle_sub_commands(cli_args: ArgMatches) -> Result<SubCommandGive
.into_report()
.change_context(ConfigError::LoadError)
.attach_printable("Failed to load the config file")
.change_context(CliError::ConfigError)?;
.change_context(TmsError::ConfigError)?;
let mut current_session =
String::from_utf8(execute_tmux_command("tmux display-message -p '#S'").stdout)
.expect("The tmux command static string should always be valid utf-9");
Expand Down Expand Up @@ -250,18 +271,6 @@ pub(crate) fn handle_sub_commands(cli_args: ArgMatches) -> Result<SubCommandGive
}
}

#[derive(Debug)]
pub(crate) enum CliError {
ConfigError,
IoError,
}
impl Display for CliError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("{self:?}"))
}
}
impl Error for CliError {}

pub enum SubCommandGiven {
Yes,
No(Config),
Expand Down
11 changes: 6 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ fn main() -> Result<(), TmsError> {

// Use CLAP to parse the command line arguments
let cli_args = create_app();
let config = match handle_sub_commands(cli_args).change_context(TmsError::CliError)? {
let config = match handle_sub_commands(cli_args)? {
SubCommandGiven::Yes => return Ok(()),
SubCommandGiven::No(config) => config, // continue
};
Expand All @@ -51,7 +51,7 @@ fn main() -> Result<(), TmsError> {
config.excluded_dirs,
config.display_full_path,
)?;
let repo_name = get_single_selection(&repos)?;
let repo_name = get_single_selection(repos.repo_string(), None)?;
let found_repo = repos
.find_repo(&repo_name)
.expect("The internal represenation of the selected repository should be present");
Expand Down Expand Up @@ -164,15 +164,16 @@ pub fn execute_tmux_command(command: &str) -> process::Output {
.unwrap_or_else(|_| panic!("Failed to execute the tmux command `{command}`"))
}

fn get_single_selection(repos: &impl RepoContainer) -> Result<String, TmsError> {
fn get_single_selection(list: String, preview: Option<&str>) -> Result<String, TmsError> {
let options = SkimOptionsBuilder::default()
.height(Some("50%"))
.preview(preview)
.multi(false)
.color(Some("dark"))
.build()
.map_err(TmsError::FuzzyFindError)?;
let item_reader = SkimItemReader::default();
let item = item_reader.of_bufread(Cursor::new(repos.repo_string()));
let item = item_reader.of_bufread(Cursor::new(list));
let skim_output = Skim::run_with(&options, Some(item))
.ok_or_else(|| TmsError::FuzzyFindError("Fuzzy finder internal errors".into()))?;
if skim_output.is_abort {
Expand Down Expand Up @@ -255,11 +256,11 @@ impl Display for Suggestion {
#[derive(Debug)]
pub(crate) enum TmsError {
CliError,
ConfigError,
GitError,
NonUtf8Path,
FuzzyFindError(String),
IoError,
ConfigError,
}
impl Display for TmsError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Expand Down

0 comments on commit fada691

Please sign in to comment.