Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: RobertAudi/tsm
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.1.2
Choose a base ref
...
head repository: RobertAudi/tsm
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref

Commits on Mar 26, 2017

  1. Unverified

    This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
    Copy the full SHA
    fc61aa0 View commit details

Commits on Mar 31, 2019

  1. Unverified

    This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
    Copy the full SHA
    ceca790 View commit details
  2. Unverified

    This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
    Copy the full SHA
    4c0f14c View commit details
  3. Bump version to 0.1.3

    RobertAudi committed Mar 31, 2019

    Unverified

    This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
    Copy the full SHA
    f1ce9dd View commit details
  4. Unverified

    This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
    Copy the full SHA
    914f5da View commit details
  5. Bump version to 0.1.4

    RobertAudi committed Mar 31, 2019

    Unverified

    This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
    Copy the full SHA
    ec0f425 View commit details
  6. Unverified

    This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
    Copy the full SHA
    03f9e10 View commit details
  7. Unverified

    This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
    Copy the full SHA
    ca0c529 View commit details
  8. Unverified

    This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
    Copy the full SHA
    fb861c1 View commit details
  9. Unverified

    This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
    Copy the full SHA
    b4bc7a5 View commit details
  10. Unverified

    This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
    Copy the full SHA
    a4f046c View commit details
  11. Deprecate the open command

    RobertAudi committed Mar 31, 2019

    Unverified

    This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
    Copy the full SHA
    1a24f86 View commit details
  12. Unverified

    This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
    Copy the full SHA
    b104f0e View commit details
  13. Merge branch 'deprecate-open-command'

    * deprecate-open-command:
      Deprecate the open command
    RobertAudi committed Mar 31, 2019

    Unverified

    This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
    Copy the full SHA
    06a4265 View commit details

Commits on Jun 2, 2019

  1. Remove the open command

    RobertAudi committed Jun 2, 2019

    Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    RobertAudi Robert Audi
    Copy the full SHA
    223f003 View commit details
  2. Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    RobertAudi Robert Audi
    Copy the full SHA
    c7217e1 View commit details
  3. Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    RobertAudi Robert Audi
    Copy the full SHA
    72e9ad0 View commit details
  4. Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    RobertAudi Robert Audi
    Copy the full SHA
    09f592e View commit details
  5. Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    RobertAudi Robert Audi
    Copy the full SHA
    7fa2172 View commit details
  6. Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    RobertAudi Robert Audi
    Copy the full SHA
    ec5da36 View commit details
  7. Bump version to 0.1.5

    RobertAudi committed Jun 2, 2019

    Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    RobertAudi Robert Audi
    Copy the full SHA
    61b4164 View commit details
  8. Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    RobertAudi Robert Audi
    Copy the full SHA
    21c4cf4 View commit details
  9. Bump version to 0.1.6

    RobertAudi committed Jun 2, 2019

    Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    RobertAudi Robert Audi
    Copy the full SHA
    fa2415e View commit details
  10. Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    RobertAudi Robert Audi
    Copy the full SHA
    b9d20be View commit details
  11. Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    RobertAudi Robert Audi
    Copy the full SHA
    1fedd7e View commit details
  12. Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    RobertAudi Robert Audi
    Copy the full SHA
    4c91ede View commit details
  13. Update the release script

    RobertAudi committed Jun 2, 2019

    Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    RobertAudi Robert Audi
    Copy the full SHA
    1a7a88e View commit details
  14. Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    RobertAudi Robert Audi
    Copy the full SHA
    f4d0ba6 View commit details
  15. Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    RobertAudi Robert Audi
    Copy the full SHA
    5b2079d View commit details
  16. Bump version to 0.2.0

    RobertAudi committed Jun 2, 2019

    Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    RobertAudi Robert Audi
    Copy the full SHA
    829d3e2 View commit details
  17. Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    RobertAudi Robert Audi
    Copy the full SHA
    4635e8f View commit details
  18. Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    RobertAudi Robert Audi
    Copy the full SHA
    6c99338 View commit details
  19. Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    RobertAudi Robert Audi
    Copy the full SHA
    01f9a06 View commit details

Commits on Jun 6, 2019

  1. Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    RobertAudi Robert Audi
    Copy the full SHA
    d91f158 View commit details
  2. Bump version to 0.2.1

    RobertAudi committed Jun 6, 2019

    Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    RobertAudi Robert Audi
    Copy the full SHA
    97a8ade View commit details

Commits on Apr 12, 2023

  1. Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    RobertAudi Robert Audi
    Copy the full SHA
    342e26f View commit details
  2. Bump version to 0.2.2

    RobertAudi committed Apr 12, 2023

    Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    RobertAudi Robert Audi
    Copy the full SHA
    8029883 View commit details

Commits on Apr 16, 2023

  1. Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    RobertAudi Robert Audi
    Copy the full SHA
    e484c2c View commit details
  2. Bump version to 0.2.2

    RobertAudi committed Apr 16, 2023

    Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    RobertAudi Robert Audi
    Copy the full SHA
    ce7f19c View commit details
32 changes: 32 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
SHELL = /bin/bash

BUILD_DIR := ./build
DIST_DIR := ./dist
SRC_DIR := ./src
BIN_NAME := tsm
BIN_DIR := $(DIST_DIR)/bin
FUNCTIONS_DIR := $(DIST_DIR)/functions

FILES := shebang logo setup $(addsuffix /*,core utils helpers commands) main tsm
SRC_FILES := $(addsuffix .zsh,$(addprefix $(SRC_DIR)/,$(FILES)))
COMPLETION_FILE := ./share/zsh/_tsm
VERSION_FILE := VERSION.txt

all: build

.PHONY: release
release: build
@mkdir -p -m 700 $(BIN_DIR) $(FUNCTIONS_DIR)
@cp -f $(BUILD_DIR)/$(BIN_NAME) $(BIN_DIR)/$(BIN_NAME)
@cp -f $(COMPLETION_FILE) $(FUNCTIONS_DIR)/
@$(BIN_DIR)/$(BIN_NAME) version > $(VERSION_FILE)

.PHONY: build
build: $(SRC_FILES)
@mkdir -p -m 700 $(BUILD_DIR)
@awk 'FNR==1 && NR > 1 {print ""}{print}' $(SRC_FILES) > $(BUILD_DIR)/$(BIN_NAME)
@chmod a+x $(BUILD_DIR)/$(BIN_NAME)

.PHONY: clean
clean:
rm -rf $(BUILD_DIR)
29 changes: 23 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -4,15 +4,33 @@ Tmux Session Manager
Installation
------------

### zplug
### [Zplugin](https://github.com/zdharma/zplugin)

```sh
zplugin light "RobertAudi/tsm"
```

### [zplug](https://github.com/zplug/zplug)

```sh
zplug "RobertAudi/tsm"
```

### [zgen](https://github.com/tarjoilija/zgen)

```sh
zgen load RobertAudi/tsm
```

### [Antigen](http://antigen.sharats.me/)

```sh
antigen bundle RobertAudi/tsm
```

### As a plugin

```shell
```console
$ source "tsm.plugin.zsh"
```

@@ -25,7 +43,7 @@ For a "stable" version:

For the latest version:

- Run `./build.zsh`
- Run `make`
- Put `build/tsm` in a directory in your `$PATH`
- Put `share/zsh/_tsm` in a directory in your `$FPATH` (optional)

@@ -40,14 +58,14 @@ Requirements
Usage
-----

```shell
```console
$ tsm <command> [args...]
```

Configuration
-------------

- `$TSM_HOME` (**default:** `$XDG_DATA_HOME/tmux-sessions`)
- `$TSM_HOME` (**default:** `$XDG_DATA_HOME/tsm`)
- `$TSM_SESSIONS_DIR` (**default:** `$TSM_HOME/sessions`)
- `$TSM_BACKUPS_DIR` (**default:** `$TSM_HOME/backups`)
- `$TSM_DEFAULT_SESSION_FILE` (**default:** `$TSM_HOME/default-session.txt`)
@@ -75,7 +93,6 @@ Commands
- [x] `rename`
- [ ] `migrate`
- [ ] `env`
- [ ] `doctor`

TODO
----
2 changes: 1 addition & 1 deletion VERSION.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.1.2
0.2.3
226 changes: 118 additions & 108 deletions dist/bin/tsm
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@ emulate -LR zsh

zmodload zsh/parameter
zmodload zsh/datetime
zmodload -F zsh/stat b:zstat

setopt extended_glob
setopt typeset_silent
@@ -31,10 +32,7 @@ setopt NO_clobber
# |Configuration| {{{
# ------------------------------------------------------------------------------

: ${TSM_LEGACY_HOME=$HOME/.tmux/tmux-sessions}
: ${TSM_LEGACY_SESSIONS_DIR:=$TSM_LEGACY_HOME/sessions}

: ${TSM_HOME:=${XDG_DATA_HOME:-$HOME/.local/share}/tmux-sessions}
: ${TSM_HOME:=${XDG_DATA_HOME:-$HOME/.local/share}/tsm}
: ${TSM_SESSIONS_DIR:=$TSM_HOME/sessions}
: ${TSM_BACKUPS_DIR:=$TSM_HOME/backups}
: ${TSM_DEFAULT_SESSION_FILE:=$TSM_HOME/default-session.txt}
@@ -70,23 +68,36 @@ readonly -l colors

local -A __tsm_commands
__tsm_commands=(
list "List saved sessions"
show "Show details about a session"
save "Save the current session"
remove "Remove a saved session"
rename "Rename a saved session"
copy "Copy a saved session"
restore "Restore a saved session"
resume "Restore and attach a saved session"
quit "Quit tmux"
version "Show version"
help "Show usage information"
list "List saved sessions"
show "Show details about a session"
save "Save the current session"
remove "Remove a saved session"
rename "Rename a saved session"
copy "Copy a saved session"
restore "Restore a saved session"
resume "Restore and attach a saved session"
quit "Quit tmux"
version "Show version"
help "Show usage information"
)
readonly -l __tsm_commands

local __tsm_tmux_delimiter
__tsm_tmux_delimiter=$'\t'
readonly __tsm_tmux_delimiter

local -A __tsm_tmux_formats
__tsm_tmux_formats=(
pane "#{session_name}${__tsm_tmux_delimiter}#{window_name}${__tsm_tmux_delimiter}#{pane_current_path}"
window "#{session_name}${__tsm_tmux_delimiter}#{window_index}${__tsm_tmux_delimiter}#{window_active}:#{window_flags}${__tsm_tmux_delimiter}#{window_layout}"
grouped_sessions "#{session_grouped}${__tsm_tmux_delimiter}#{session_group}${__tsm_tmux_delimiter}#{session_id}${__tsm_tmux_delimiter}#{session_name}"
state "#{client_session}${__tsm_tmux_delimiter}#{client_last_session}"
)
readonly -l __tsm_tmux_formats

# -------------------------------------------------------------------------- }}}

local __tsm_version="0.1.2"
local __tsm_version="0.2.3"
readonly __tsm_version

# Usage: command1 | and-pipe command2
@@ -169,6 +180,11 @@ function __tsm::utils::datetime() {
builtin printf "%s.%03d" "$(builtin strftime "%Y-%m-%dT%H:%M:%S" $epochtime[1])" "$(($epochtime[2] / 1000000))"
}

# Return the creation time of a file
function __tsm::utils::datetime::ctime() {
builtin zstat -F "%Y-%m-%dT%H:%M:%S" +ctime "$1"
}

# This function will return width/height parameters
# that will be passed to the `tmux new-session` command
function __tsm::utils::dimensions_parameters() {
@@ -183,14 +199,6 @@ function __tsm::utils::filename() {
}


# Check if tmux is running AND active
# (ie: we are inside a tmux session)
function __tsm::utils::inside_tmux() {
{ __tsm::utils::tmux_running && [[ -n "$TMUX" ]] } || return 1
local -a tmux_info ; tmux_info=("${(s:,:)TMUX}")
[[ -S "${tmux_info[1]}" ]] && builtin kill -s 0 "${tmux_info[2]}" &>/dev/null
}

# Log a message to STDERR.
# Messages are printed to STDERR instead of STDOUT
# so that logging can be silenced without hiding output
@@ -253,20 +261,6 @@ function __tsm::utils::separator() {
builtin print -r -- "${(pl:$width::$sep:)}"
}

# Check if a tmux session exists.
function __tsm::utils::session_exists() {
command tmux has-session -t "$1" 2>/dev/null
}

# Check if the tmux server is running.
# I am not a fan of the approach used
# but it will have to do until I find
# a better way of doing it.
function __tsm::utils::tmux_running() {
command tmux info &> /dev/null
}


function __tsm::utils::trim() {
local -A opts
zparseopts -D -A opts -- l t b
@@ -300,23 +294,66 @@ function __tsm::utils::trim::both() {
# Usage: __tsm::helpers::add_window <session_name> <window_name> <window_working_directory>
function __tsm::helpers::add_window() {
local session_name="$1" window_name="$2" working_directory="$3"
command tmux new-window -d -t "$session_name:" -n "$window_name" -c "$working_directory"
command tmux new-window -d -S -t "$session_name:" -n "$window_name" -c "$working_directory"
}

# Dump the list of tmux windows using the following
# three components separated by a tab character `\t`:
# - Session name
# - Window name
# - Window working directory path
#
# Caveat: Window panes are ignored.
function __tsm::helpers::dump() {
local d=$'\t'
# FIXME: Fail if tmux is not running
# TODO: Find a way to dump all panes including enough info to be able to restore them
command tmux list-panes -a -F "#S${d}#W${d}#{pane_current_path}"
# Dump the list of tmux panes
function __tsm::helpers::dump_panes() {
command tmux list-panes -a -F "${__tsm_tmux_formats[pane]}"
}

# Dump the list of tmux panes prefixed with the list type (pane)
function __tsm::helpers::dump_panes::annotated() {
command tmux list-panes -a -F "pane${__tsm_tmux_delimiter}${__tsm_tmux_formats[pane]}"
}

# Dump the list of tmux windows
function __tsm::helpers::dump_windows() {
command tmux list-windows -a -F "${__tsm_tmux_formats[window]}"
}

# Dump the list of tmux windows prefixed with the list type (window)
function __tsm::helpers::dump_windows::annotated() {
command tmux list-windows -a -F "window${__tsm_tmux_delimiter}${__tsm_tmux_formats[window]}"
}

function __tsm::helpers::get_active_window_index() {
command tmux list-windows -t "$1" -F "#{window_flags} #{window_index}" \
| command awk '$1 ~ /\*/ { print $2; }'
}

function __tsm::helpers::get_alternate_window_index() {
command tmux list-windows -t "$1" -F "#{window_flags} #{window_index}" \
| command awk '$1 ~ /-/ { print $2; }'
}

# Check if tmux is running AND active
# (ie: we are inside a tmux session)
function __tsm::helpers::inside_tmux() {
{ __tsm::helpers::tmux_running && [[ -n "$TMUX" ]] } || return 1
local -a tmux_info ; tmux_info=("${(s:,:)TMUX}")
[[ -S "${tmux_info[1]}" ]] && builtin kill -s 0 "${tmux_info[2]}" &>/dev/null
}

# ------------------------------------------------------------------------------
# When dumping windows and panes, the line type is prepended to each line
# These functions help identify the type of a line

function __tsm::helpers::is_line_type() {
local line_type="$1" line="$2"
[[ "$line" =~ "^$line_type" ]]
}

function __tsm::helpers::is_line_type::pane() {
__tsm::helpers::is_line_type "pane" "$1"
}

function __tsm::helpers::is_line_type::window() {
__tsm::helpers::is_line_type "window" "$1"
}

# ------------------------------------------------------------------------------

# Create a new tmux session.
# A dummy window is created so that the working
# directory of new windows will default to "$HOME".
@@ -325,16 +362,32 @@ function __tsm::helpers::dump() {
function __tsm::helpers::new_session() {
local session_name="$1" window_name="$2" window_working_directory="$3"
local dimensions="${4:-$(__tsm::utils::dimensions_parameters)}"
local session_working_directory dummy_window
local session_working_directory dummy_window dummy_window_index
session_working_directory="$HOME"
dummy_window="__dummy-window-${EPOCHREALTIME/./}-$(__tsm::utils::random)__"

command tmux new-session -d -s "$session_name" -n "$dummy_window" -c "$HOME" $=dimensions
dummy_window_index="$(command tmux list-windows -t "$session_name" | command grep "$dummy_window" | command cut -d: -f1)"

__tsm::helpers::add_window "$session_name" "$window_name" "$window_working_directory"
command tmux kill-window -t "$dummy_window"

command tmux kill-window -t "${session_name}:${dummy_window_index}"
}


# Check if a tmux session exists.
function __tsm::helpers::session_exists() {
command tmux has-session -t "$1" 2>/dev/null
}

# Check if the tmux server is running.
# I am not a fan of the approach used
# but it will have to do until I find
# a better way of doing it.
function __tsm::helpers::tmux_running() {
command tmux info &> /dev/null
}

# |Backup| {{{
# ------------------------------------------------------------------------------

@@ -376,19 +429,14 @@ function __tsm::commands::backup::session() {
return 1
fi

local session_dump
session_dump="$(__tsm::helpers::dump)" || return $status

local filename="$(__tsm::utils::filename).$(__tsm::utils::random).txt"
[[ -n "$session_file" ]] && filename="${session_file:A:t:r}.${filename}"

builtin print -- "$session_dump" > "${TSM_BACKUPS_DIR}/$filename" \
local filename="${session_file:A:t:r}.$(__tsm::utils::datetime::ctime "$session_file").$(__tsm::utils::random).txt"
command cp -f "$session_file" "${TSM_BACKUPS_DIR}/$filename" >/dev/null \
&& __tsm::commands::backup::clean
}

function __tsm::commands::backup() {
local session_dump
session_dump="$(__tsm::helpers::dump)" || return $status
session_dump="$(__tsm::helpers::dump_panes)" || return $status

local filename="$(__tsm::utils::filename).$(__tsm::utils::random).txt"
[[ -n "$1" ]] && filename="${1}.${filename}"
@@ -450,44 +498,6 @@ function __tsm::commands::copy() {
command cp -f "$session_file" "$new_session_file" >/dev/null
}

# TODO: Refactor. This code is worse than puke.
function __tsm::commands::doctor::legacy() {
[[ -d "$TSM_LEGACY_SESSIONS_DIR" ]] || return

local -a legacy_session_files
legacy_session_files=("${TSM_LEGACY_SESSIONS_DIR}"/*.txt(.NOmf:gu+r:))

# No legacy session files, no point testing anything else...
(( ${#legacy_session_files} == 0 )) && return

__tsm::utils::log warn "Legacy sessions found"

local -a session_files
session_files=("${TSM_SESSIONS_DIR}"/*.txt(.NOmf:gu+r:))

if (( ${#session_files} == 0 )); then
builtin printf "$(__tsm::utils::colorize blue "%s")" " --> " >&2
builtin printf "%s\n" "There aren't any saved sessions. You can import the legacy sessions with the following command:" >&2

builtin printf "$(__tsm::utils::colorize blue "%s")\n" " >>> " >&2
builtin printf "$(__tsm::utils::colorize blue "%s")" " >>> " >&2
builtin printf "%*s$(__tsm::utils::colorize dimmed "%s")\n" 2 "" "cp -v ${TSM_LEGACY_SESSIONS_DIR:A}/*.txt ${TSM_SESSIONS_DIR:A}/" >&2
builtin printf "$(__tsm::utils::colorize blue "%s")\n" " >>> " >&2
else
# FIXME: Only show the following output if a `-v|--versbose` flag was passed
builtin printf "$(__tsm::utils::colorize blue "%s")" " --> " >&2
builtin printf "%s\n" "You can remove the legacy sessions with the following command:" >&2

builtin printf "$(__tsm::utils::colorize blue "%s")\n" " >>> " >&2
builtin printf "$(__tsm::utils::colorize blue "%s")" " >>> " >&2
builtin printf "%*s$(__tsm::utils::colorize dimmed "%s")\n" 2 "" "rm -v ${TSM_LEGACY_SESSIONS_DIR:A}/*.txt" >&2
builtin printf "$(__tsm::utils::colorize blue "%s")\n" " >>> " >&2
fi

__tsm::utils::separator "-"
builtin print
}

function __tsm::commands::duplicate() {
local session_name="$1"
if [[ -z "$session_name" ]]; then
@@ -638,7 +648,7 @@ function __tsm::commands::list() {
local -A session_registry

for f in $session_files; do
while IFS=$'\t' read session_name window_name dir; do
while IFS=$__tsm_tmux_delimiter read session_name window_name dir; do
windows_count+=1
if ! (( ${+session_registry[$session_name]} )); then
session_registry[$session_name]=$((session_registry[$session_name] + 1))
@@ -807,9 +817,9 @@ function __tsm::commands::restore() {

dimensions="$(__tsm::utils::dimensions_parameters)"

while IFS=$'\t' read session_name window_name dir; do
while IFS=$__tsm_tmux_delimiter read session_name window_name dir; do
if [[ -d "$dir" && "$window_name" != "log" && "$window_name" != "man" ]]; then
if __tsm::utils::session_exists "$session_name"; then
if __tsm::helpers::session_exists "$session_name"; then
__tsm::helpers::add_window "$session_name" "$window_name" "$dir"
else
__tsm::helpers::new_session "$session_name" "$window_name" "$dir" "$dimensions"
@@ -825,9 +835,10 @@ function __tsm::commands::restore() {
# -------------------------------------------------------------------------- }}}

# Restore a session and attach to one
# Also alias as: __tsm::commands::resume
# TODO: Specify which tmux session to attach to
function __tsm::commands::resume() {
__tsm::commands::restore "$@" && { __tsm::utils::inside_tmux || command tmux attach }
__tsm::commands::restore "$@" && { __tsm::helpers::inside_tmux || command tmux attach }
}

# Save the current session. If a name is not specified
@@ -836,7 +847,7 @@ function __tsm::commands::resume() {
# asked to confirm before override the existing one.
function __tsm::commands::save() {
local session_dump
session_dump="$(__tsm::helpers::dump)" || return $status
session_dump="$(__tsm::helpers::dump_panes)" || return $status

local filename="${1:-$(__tsm::utils::filename)}.txt"
local session_file="${TSM_SESSIONS_DIR}/$filename"
@@ -888,7 +899,7 @@ function __tsm::commands::show() {

integer -l sessions_count windows_count
local -A session_registry
while IFS=$'\t' read session_name window_name dir; do
while IFS=$__tsm_tmux_delimiter read session_name window_name dir; do
if (( ${+session_registry[$session_name]} )); then
session_registry[$session_name]=$((session_registry[$session_name] + 1))
else
@@ -932,16 +943,15 @@ function __tsm::commands::version() {
function __tsm::main() {
local cmd="$1"
[[ "$cmd" == "tsm" ]] && { __tsm::commands::tsm ; return $status }
if [[ -n "$cmd" ]] && (( ${+__tsm_commands[(k)$cmd]} )); then
if [[ -n "$cmd" ]] && (( ${+__tsm_commands[$cmd]} )); then
__tsm::commands::"$cmd" "${@:2}"
else
[[ -n "$cmd" ]] && __tsm::utils::log error "Command not found: $(__tsm::utils::colorize bold,white "$cmd")"
__tsm::commands::help
return 1
fi
}

# ------------------------------------------------------------------------------

__tsm::commands::doctor::legacy

__tsm::main "$@"
41 changes: 35 additions & 6 deletions dist/functions/_tsm
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#compdef tsm
#autoload

_tsm_commands=(
local context state line curcontext="$curcontext" ret=1
local -a cmds
cmds=(
'list:List saved sessions'
'show:Show details about a session'
'save:Save the current session'
@@ -16,9 +18,36 @@ _tsm_commands=(
'help:Show usage information'
)

_arguments '*:: :->command'
_arguments -C \
'1:tsm command:->subcommand' \
'*:: :->args' \
&& ret=0

if (( CURRENT == 1 )); then
_describe -t commands "tsm commands" _tsm_commands
return
fi
case $state in
subcommand)
_describe -t commands 'tsm commands' cmds && ret=0
;;
esac

case "$line[1]" in
show|remove|rename|copy|duplicate|restore|resume)
local sessions_dir
local -a session_files
sessions_dir="${TSM_SESSIONS_DIR:-${TSM_HOME:-${XDG_DATA_HOME:-$HOME/.local/share}/tsm}/sessions}"
session_files=("${sessions_dir}"/*.txt(.NOmf:gu+r::t:r))

if (( ${#session_files} == 0 )); then
_message -e 'no sessions found' && ret=1
else
_values 'Sessions' ${session_files//:/\\:} && ret=0
fi
;;
help)
_describe -t commands 'tsm commands' cmds && ret=0
;;
list|quit|version)
_message -e 'no more arguments' && ret=1
;;
esac

return ret
41 changes: 35 additions & 6 deletions share/zsh/_tsm
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#compdef tsm
#autoload

_tsm_commands=(
local context state line curcontext="$curcontext" ret=1
local -a cmds
cmds=(
'list:List saved sessions'
'show:Show details about a session'
'save:Save the current session'
@@ -16,9 +18,36 @@ _tsm_commands=(
'help:Show usage information'
)

_arguments '*:: :->command'
_arguments -C \
'1:tsm command:->subcommand' \
'*:: :->args' \
&& ret=0

if (( CURRENT == 1 )); then
_describe -t commands "tsm commands" _tsm_commands
return
fi
case $state in
subcommand)
_describe -t commands 'tsm commands' cmds && ret=0
;;
esac

case "$line[1]" in
show|remove|rename|copy|duplicate|restore|resume)
local sessions_dir
local -a session_files
sessions_dir="${TSM_SESSIONS_DIR:-${TSM_HOME:-${XDG_DATA_HOME:-$HOME/.local/share}/tsm}/sessions}"
session_files=("${sessions_dir}"/*.txt(.NOmf:gu+r::t:r))

if (( ${#session_files} == 0 )); then
_message -e 'no sessions found' && ret=1
else
_values 'Sessions' ${session_files//:/\\:} && ret=0
fi
;;
help)
_describe -t commands 'tsm commands' cmds && ret=0
;;
list|quit|version)
_message -e 'no more arguments' && ret=1
;;
esac

return ret
11 changes: 3 additions & 8 deletions src/commands/backup.zsh
Original file line number Diff line number Diff line change
@@ -39,19 +39,14 @@ function __tsm::commands::backup::session() {
return 1
fi

local session_dump
session_dump="$(__tsm::helpers::dump)" || return $status

local filename="$(__tsm::utils::filename).$(__tsm::utils::random).txt"
[[ -n "$session_file" ]] && filename="${session_file:A:t:r}.${filename}"

builtin print -- "$session_dump" > "${TSM_BACKUPS_DIR}/$filename" \
local filename="${session_file:A:t:r}.$(__tsm::utils::datetime::ctime "$session_file").$(__tsm::utils::random).txt"
command cp -f "$session_file" "${TSM_BACKUPS_DIR}/$filename" >/dev/null \
&& __tsm::commands::backup::clean
}

function __tsm::commands::backup() {
local session_dump
session_dump="$(__tsm::helpers::dump)" || return $status
session_dump="$(__tsm::helpers::dump_panes)" || return $status

local filename="$(__tsm::utils::filename).$(__tsm::utils::random).txt"
[[ -n "$1" ]] && filename="${1}.${filename}"
37 changes: 0 additions & 37 deletions src/commands/doctor.zsh

This file was deleted.

2 changes: 1 addition & 1 deletion src/commands/list.zsh
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ function __tsm::commands::list() {
local -A session_registry

for f in $session_files; do
while IFS=$'\t' read session_name window_name dir; do
while IFS=$__tsm_tmux_delimiter read session_name window_name dir; do
windows_count+=1
if ! (( ${+session_registry[$session_name]} )); then
session_registry[$session_name]=$((session_registry[$session_name] + 1))
4 changes: 2 additions & 2 deletions src/commands/restore.zsh
Original file line number Diff line number Diff line change
@@ -52,9 +52,9 @@ function __tsm::commands::restore() {

dimensions="$(__tsm::utils::dimensions_parameters)"

while IFS=$'\t' read session_name window_name dir; do
while IFS=$__tsm_tmux_delimiter read session_name window_name dir; do
if [[ -d "$dir" && "$window_name" != "log" && "$window_name" != "man" ]]; then
if __tsm::utils::session_exists "$session_name"; then
if __tsm::helpers::session_exists "$session_name"; then
__tsm::helpers::add_window "$session_name" "$window_name" "$dir"
else
__tsm::helpers::new_session "$session_name" "$window_name" "$dir" "$dimensions"
3 changes: 2 additions & 1 deletion src/commands/resume.zsh
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Restore a session and attach to one
# Also alias as: __tsm::commands::resume
# TODO: Specify which tmux session to attach to
function __tsm::commands::resume() {
__tsm::commands::restore "$@" && { __tsm::utils::inside_tmux || command tmux attach }
__tsm::commands::restore "$@" && { __tsm::helpers::inside_tmux || command tmux attach }
}
2 changes: 1 addition & 1 deletion src/commands/save.zsh
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@
# asked to confirm before override the existing one.
function __tsm::commands::save() {
local session_dump
session_dump="$(__tsm::helpers::dump)" || return $status
session_dump="$(__tsm::helpers::dump_panes)" || return $status

local filename="${1:-$(__tsm::utils::filename)}.txt"
local session_file="${TSM_SESSIONS_DIR}/$filename"
2 changes: 1 addition & 1 deletion src/commands/show.zsh
Original file line number Diff line number Diff line change
@@ -26,7 +26,7 @@ function __tsm::commands::show() {

integer -l sessions_count windows_count
local -A session_registry
while IFS=$'\t' read session_name window_name dir; do
while IFS=$__tsm_tmux_delimiter read session_name window_name dir; do
if (( ${+session_registry[$session_name]} )); then
session_registry[$session_name]=$((session_registry[$session_name] + 1))
else
5 changes: 1 addition & 4 deletions src/core/configuration.zsh
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
# |Configuration| {{{
# ------------------------------------------------------------------------------

: ${TSM_LEGACY_HOME=$HOME/.tmux/tmux-sessions}
: ${TSM_LEGACY_SESSIONS_DIR:=$TSM_LEGACY_HOME/sessions}

: ${TSM_HOME:=${XDG_DATA_HOME:-$HOME/.local/share}/tmux-sessions}
: ${TSM_HOME:=${XDG_DATA_HOME:-$HOME/.local/share}/tsm}
: ${TSM_SESSIONS_DIR:=$TSM_HOME/sessions}
: ${TSM_BACKUPS_DIR:=$TSM_HOME/backups}
: ${TSM_DEFAULT_SESSION_FILE:=$TSM_HOME/default-session.txt}
35 changes: 24 additions & 11 deletions src/core/constants.zsh
Original file line number Diff line number Diff line change
@@ -21,18 +21,31 @@ readonly -l colors

local -A __tsm_commands
__tsm_commands=(
list "List saved sessions"
show "Show details about a session"
save "Save the current session"
remove "Remove a saved session"
rename "Rename a saved session"
copy "Copy a saved session"
restore "Restore a saved session"
resume "Restore and attach a saved session"
quit "Quit tmux"
version "Show version"
help "Show usage information"
list "List saved sessions"
show "Show details about a session"
save "Save the current session"
remove "Remove a saved session"
rename "Rename a saved session"
copy "Copy a saved session"
restore "Restore a saved session"
resume "Restore and attach a saved session"
quit "Quit tmux"
version "Show version"
help "Show usage information"
)
readonly -l __tsm_commands

local __tsm_tmux_delimiter
__tsm_tmux_delimiter=$'\t'
readonly __tsm_tmux_delimiter

local -A __tsm_tmux_formats
__tsm_tmux_formats=(
pane "#{session_name}${__tsm_tmux_delimiter}#{window_name}${__tsm_tmux_delimiter}#{pane_current_path}"
window "#{session_name}${__tsm_tmux_delimiter}#{window_index}${__tsm_tmux_delimiter}#{window_active}:#{window_flags}${__tsm_tmux_delimiter}#{window_layout}"
grouped_sessions "#{session_grouped}${__tsm_tmux_delimiter}#{session_group}${__tsm_tmux_delimiter}#{session_id}${__tsm_tmux_delimiter}#{session_name}"
state "#{client_session}${__tsm_tmux_delimiter}#{client_last_session}"
)
readonly -l __tsm_tmux_formats

# -------------------------------------------------------------------------- }}}
2 changes: 1 addition & 1 deletion src/core/version.zsh
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
local __tsm_version="0.1.2"
local __tsm_version="0.2.3"
readonly __tsm_version
2 changes: 1 addition & 1 deletion src/helpers/add_window.zsh
Original file line number Diff line number Diff line change
@@ -2,5 +2,5 @@
# Usage: __tsm::helpers::add_window <session_name> <window_name> <window_working_directory>
function __tsm::helpers::add_window() {
local session_name="$1" window_name="$2" working_directory="$3"
command tmux new-window -d -t "$session_name:" -n "$window_name" -c "$working_directory"
command tmux new-window -d -S -t "$session_name:" -n "$window_name" -c "$working_directory"
}
30 changes: 18 additions & 12 deletions src/helpers/dump.zsh
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
# Dump the list of tmux windows using the following
# three components separated by a tab character `\t`:
# - Session name
# - Window name
# - Window working directory path
#
# Caveat: Window panes are ignored.
function __tsm::helpers::dump() {
local d=$'\t'
# FIXME: Fail if tmux is not running
# TODO: Find a way to dump all panes including enough info to be able to restore them
command tmux list-panes -a -F "#S${d}#W${d}#{pane_current_path}"
# Dump the list of tmux panes
function __tsm::helpers::dump_panes() {
command tmux list-panes -a -F "${__tsm_tmux_formats[pane]}"
}

# Dump the list of tmux panes prefixed with the list type (pane)
function __tsm::helpers::dump_panes::annotated() {
command tmux list-panes -a -F "pane${__tsm_tmux_delimiter}${__tsm_tmux_formats[pane]}"
}

# Dump the list of tmux windows
function __tsm::helpers::dump_windows() {
command tmux list-windows -a -F "${__tsm_tmux_formats[window]}"
}

# Dump the list of tmux windows prefixed with the list type (window)
function __tsm::helpers::dump_windows::annotated() {
command tmux list-windows -a -F "window${__tsm_tmux_delimiter}${__tsm_tmux_formats[window]}"
}
9 changes: 9 additions & 0 deletions src/helpers/get_window_index.zsh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
function __tsm::helpers::get_active_window_index() {
command tmux list-windows -t "$1" -F "#{window_flags} #{window_index}" \
| command awk '$1 ~ /\*/ { print $2; }'
}

function __tsm::helpers::get_alternate_window_index() {
command tmux list-windows -t "$1" -F "#{window_flags} #{window_index}" \
| command awk '$1 ~ /-/ { print $2; }'
}
4 changes: 2 additions & 2 deletions src/utils/inside_tmux.zsh → src/helpers/inside_tmux.zsh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Check if tmux is running AND active
# (ie: we are inside a tmux session)
function __tsm::utils::inside_tmux() {
{ __tsm::utils::tmux_running && [[ -n "$TMUX" ]] } || return 1
function __tsm::helpers::inside_tmux() {
{ __tsm::helpers::tmux_running && [[ -n "$TMUX" ]] } || return 1
local -a tmux_info ; tmux_info=("${(s:,:)TMUX}")
[[ -S "${tmux_info[1]}" ]] && builtin kill -s 0 "${tmux_info[2]}" &>/dev/null
}
18 changes: 18 additions & 0 deletions src/helpers/line_type.zsh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# ------------------------------------------------------------------------------
# When dumping windows and panes, the line type is prepended to each line
# These functions help identify the type of a line

function __tsm::helpers::is_line_type() {
local line_type="$1" line="$2"
[[ "$line" =~ "^$line_type" ]]
}

function __tsm::helpers::is_line_type::pane() {
__tsm::helpers::is_line_type "pane" "$1"
}

function __tsm::helpers::is_line_type::window() {
__tsm::helpers::is_line_type "window" "$1"
}

# ------------------------------------------------------------------------------
7 changes: 5 additions & 2 deletions src/helpers/new_session.zsh
Original file line number Diff line number Diff line change
@@ -6,12 +6,15 @@
function __tsm::helpers::new_session() {
local session_name="$1" window_name="$2" window_working_directory="$3"
local dimensions="${4:-$(__tsm::utils::dimensions_parameters)}"
local session_working_directory dummy_window
local session_working_directory dummy_window dummy_window_index
session_working_directory="$HOME"
dummy_window="__dummy-window-${EPOCHREALTIME/./}-$(__tsm::utils::random)__"

command tmux new-session -d -s "$session_name" -n "$dummy_window" -c "$HOME" $=dimensions
dummy_window_index="$(command tmux list-windows -t "$session_name" | command grep "$dummy_window" | command cut -d: -f1)"

__tsm::helpers::add_window "$session_name" "$window_name" "$window_working_directory"
command tmux kill-window -t "$dummy_window"

command tmux kill-window -t "${session_name}:${dummy_window_index}"
}

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Check if a tmux session exists.
function __tsm::utils::session_exists() {
function __tsm::helpers::session_exists() {
command tmux has-session -t "$1" 2>/dev/null
}
3 changes: 1 addition & 2 deletions src/utils/tmux_running.zsh → src/helpers/tmux_running.zsh
Original file line number Diff line number Diff line change
@@ -2,7 +2,6 @@
# I am not a fan of the approach used
# but it will have to do until I find
# a better way of doing it.
function __tsm::utils::tmux_running() {
function __tsm::helpers::tmux_running() {
command tmux info &> /dev/null
}

3 changes: 2 additions & 1 deletion src/main.zsh
Original file line number Diff line number Diff line change
@@ -3,9 +3,10 @@
function __tsm::main() {
local cmd="$1"
[[ "$cmd" == "tsm" ]] && { __tsm::commands::tsm ; return $status }
if [[ -n "$cmd" ]] && (( ${+__tsm_commands[(k)$cmd]} )); then
if [[ -n "$cmd" ]] && (( ${+__tsm_commands[$cmd]} )); then
__tsm::commands::"$cmd" "${@:2}"
else
[[ -n "$cmd" ]] && __tsm::utils::log error "Command not found: $(__tsm::utils::colorize bold,white "$cmd")"
__tsm::commands::help
return 1
fi
1 change: 1 addition & 0 deletions src/setup.zsh
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ emulate -LR zsh

zmodload zsh/parameter
zmodload zsh/datetime
zmodload -F zsh/stat b:zstat

setopt extended_glob
setopt typeset_silent
2 changes: 0 additions & 2 deletions src/tsm.zsh
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
__tsm::commands::doctor::legacy

__tsm::main "$@"
5 changes: 5 additions & 0 deletions src/utils/datetime.zsh
Original file line number Diff line number Diff line change
@@ -5,3 +5,8 @@ function __tsm::utils::datetime() {
# then it's definitely not mine. Don't be a PITA.
builtin printf "%s.%03d" "$(builtin strftime "%Y-%m-%dT%H:%M:%S" $epochtime[1])" "$(($epochtime[2] / 1000000))"
}

# Return the creation time of a file
function __tsm::utils::datetime::ctime() {
builtin zstat -F "%Y-%m-%dT%H:%M:%S" +ctime "$1"
}
16 changes: 0 additions & 16 deletions tools/build.zsh

This file was deleted.

38 changes: 0 additions & 38 deletions tools/release.zsh

This file was deleted.

8 changes: 8 additions & 0 deletions tsm.plugin.zsh
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
typeset -gx TSM_HOME TSM_SESSIONS_DIR TSM_BACKUPS_DIR TSM_DEFAULT_SESSION_FILE TSM_BACKUPS_COUNT

: ${TSM_HOME:=${XDG_DATA_HOME:-$HOME/.local/share}/tsm}
: ${TSM_SESSIONS_DIR:=$TSM_HOME/sessions}
: ${TSM_BACKUPS_DIR:=$TSM_HOME/backups}
: ${TSM_DEFAULT_SESSION_FILE:=$TSM_HOME/default-session.txt}
: ${TSM_BACKUPS_COUNT:=20}

typeset __tsm_dist_dir="${${(%):-%x}:A:h}/dist"
path=("${__tsm_dist_dir}/"bin $path)
fpath=("${__tsm_dist_dir}/"functions $fpath)