Compare commits

...

91 commits
v0.1.0 ... main

Author SHA1 Message Date
4f76590930 removed 'no sessions' text from list command with quiet flag set 2024-09-24 14:08:19 -04:00
d3a408ad33 added working directory flag 2024-09-23 09:02:40 -04:00
8449ae00d6 error functions now return a never type 2024-07-24 20:59:48 -04:00
ff30bc1052 minor 'list' optimization 2024-07-22 10:18:35 -04:00
8e2826b110 message helper now handles empty responses more gracefully 2024-07-22 10:18:10 -04:00
0fe3906578 'switch' command now supports detach flag, and updated manual documentation for 'switch' 2024-07-22 09:26:33 -04:00
fdf3114c04 'list' command now shows a symbol for the previous session 2024-07-17 11:30:50 -04:00
8ad16ad825 'switch' now handles empty case correctly 2024-07-17 11:21:05 -04:00
a9a73314af 'switch' command now defaults to previous session if present 2024-07-17 11:07:14 -04:00
af33e82415 renamed bash completion folder to be more appropriate 2024-07-17 09:24:31 -04:00
eaf72847b1 updated README 2024-07-17 09:23:34 -04:00
beb880ed43 Merge branch 'repo-var' into completion 2024-07-17 09:17:35 -04:00
449c460bbb repository search now tries to match the filename set by the REMUX_REPO_NAME environment variable 2024-07-17 09:15:11 -04:00
1b51633d4f added search and quiet mode to 'list' command and wrote a completion function 2024-07-16 11:46:56 -04:00
b7b893d55c errors now print to stderr instead of stdout 2024-07-12 10:48:03 -04:00
cdc68986fa inner tmux errors are now hidden 2024-07-06 21:10:39 -04:00
1acc7aeb7b updated README 2024-07-06 20:48:45 -04:00
449921656c added man instructions to README 2024-07-06 18:33:28 -04:00
638fce2331 minor man correction 2024-07-06 17:01:20 -04:00
b93d814f6f version bump 2024-07-01 12:05:29 -04:00
b24778113a 'new' now fails on duplicate names, reduced code reuse 2024-07-01 11:52:33 -04:00
21bd6f40ca created manual 2024-07-01 09:54:10 -04:00
1f143f00ad 'which' command is now 'title' 2024-07-01 09:53:52 -04:00
e930af1027 list command now shows a symbol in front of the current session 2024-07-01 09:12:38 -04:00
97f546a69c fixed 'add' command enforcing nest flag on detached sessions 2024-07-01 08:50:23 -04:00
44bad997cb context action now passes flags 2024-06-25 14:05:28 -04:00
441f1d3ef0 removed unused module reference 2024-06-24 17:19:01 -04:00
0232a9c650 updated README and help text 2024-06-24 17:17:40 -04:00
ace9627b79 major refactor; parameters are handled by a state struct 2024-06-24 16:32:41 -04:00
a915bfd9c5 updated README and help text 2024-06-18 12:02:47 -04:00
ac97336f13 added contextual action as default command 2024-06-18 11:57:08 -04:00
222f929fd4 merged in changes from 'switch' branch 2024-06-17 13:54:05 -04:00
0fc6cd66c6 renamed 'root' command to 'path' 2024-06-10 12:03:40 -04:00
1320b6f122 'h' is now shortened version of 'has' instead of help 2024-06-10 12:02:57 -04:00
26f6fd5dd0 made nest prevent message more helpful 2024-06-10 12:02:57 -04:00
19b12c682d updated README 2024-06-10 12:02:57 -04:00
28a7fc44f9 switched session enforce to new tmux detection helper 2024-06-10 12:02:57 -04:00
21eda33624 default window name now sets correctly 2024-06-10 12:02:57 -04:00
d776129b3e added help text for REMUX_NEW_WINDOW env var 2024-06-10 12:02:57 -04:00
62d72735da added REMUX_WINDOW_NAME var for add command 2024-06-10 12:02:57 -04:00
681e7427ba updated help text to include topics 2024-06-10 12:02:57 -04:00
d8aaf7feed corrected name of ATTACH_SYMBOL env var 2024-06-10 12:02:57 -04:00
b3403ffa36 wrote help topic for env vars 2024-06-10 12:02:57 -04:00
a07ae193b5 moved environment variable code to a new module 2024-06-10 12:02:02 -04:00
c8800a7f53 created env module 2024-06-10 12:01:15 -04:00
92d0010186 renamed detach flag and added detach support to 'new' 2024-06-10 12:01:15 -04:00
21f7b72298 removed incompatible disable_echo from session exist check 2024-06-10 12:01:15 -04:00
2e28acc3c3 version bump 2024-06-10 12:01:15 -04:00
e017d57a53 initial implementation of root command and made some commands allow being thrown through pipes 2024-06-10 12:01:15 -04:00
307ff23049 updated Cargo.toml 2024-03-14 17:27:12 -04:00
3e4346bd1d version bump and added data for auditing tools 2024-03-14 16:42:11 -04:00
7f5ad2fd1b fixed 'new' command setting group name instead of session name 2024-03-14 16:37:00 -04:00
6b7c84b673 merged in changes from dep-upgrade 2024-03-08 13:53:26 -05:00
782fb694d0 removed disable_echo flag from commands that don't support it 2024-03-08 13:50:27 -05:00
882e130121 fixed tmux dispatch echoing command information 2024-03-08 10:26:00 -05:00
4615777cae added help topic for switch 2024-03-08 10:23:02 -05:00
b00e15a037 switch command now must run in-session 2024-03-08 10:04:24 -05:00
5c3fb7df3f created switch command 2024-03-07 17:10:45 -05:00
41a64f039f revised incorrect help text for 'new' and 'attach' commands 2024-03-07 16:54:16 -05:00
8b00f58fde updated cargo-aur metadata 2024-03-06 16:49:25 -05:00
c776b63994 Merge remote-tracking branch 'refs/remotes/origin/main' 2024-03-06 16:44:54 -05:00
be593c82e7 list attach symbol is now configurable 2024-03-06 16:12:54 -05:00
9c814c43f4 updated README 2024-03-06 16:05:53 -05:00
63335916d9 updated README 2024-03-06 15:59:40 -05:00
aba802e77d upgraded tmux_interface to 0.3.2 and restructured command module 2024-03-06 15:54:41 -05:00
be5bace0ac updated README 2024-03-04 13:16:00 -05:00
640381820d added cargo-aur metadata 2024-03-04 10:53:10 -05:00
444964d4ef text section optimization 2024-03-03 22:48:20 -05:00
4fbb2b07c9 updated README 2024-03-03 22:28:32 -05:00
407d77812a version bump 2024-02-28 17:55:43 -05:00
e0ea051bf4 moved repo fallback into a helper function 2024-02-28 17:55:33 -05:00
2ede502884 added repo fallback to all relevant commands 2024-02-28 17:49:08 -05:00
90f3b9a999 'new' now attempts to fall back to repository root name 2024-02-28 14:42:24 -05:00
3dc0e82ad6 added helper function for finding repository root 2024-02-28 14:26:25 -05:00
7aa6b225de moved session exist check to a helper function 2024-02-28 09:35:30 -05:00
5abe1c827c updated README 2024-02-25 20:54:03 -05:00
1fbfad9364 updated README 2024-02-22 14:20:13 -05:00
5754c22e1a added binary information in preparation for crates.io publish 2024-02-19 21:17:48 -05:00
12d4b66fa3 updated README 2024-02-19 19:39:35 -05:00
0d7a411749 updated README 2024-02-13 19:36:38 -05:00
4b1ea9332f added nest flag to help text and README 2024-02-13 17:16:22 -05:00
29a51da6bb version bump for clarity 2024-02-12 15:22:53 -05:00
db33677596 added missing package info 2024-02-12 15:02:13 -05:00
fab7dec810 removed dead code and updated package information 2024-02-12 14:06:54 -05:00
a81226c79c merged in nesting flag 2024-02-04 16:14:34 -05:00
99531e785b added version flag 2024-02-04 16:11:32 -05:00
796d8744c5 added nesting flag 2024-02-04 16:07:24 -05:00
2dd4e35600 moved help into its own module 2024-01-23 16:45:58 -05:00
f77f768416 added terminal check 2023-08-15 21:30:10 -04:00
808591fb9b made README libraries section clearer 2023-06-20 12:33:38 -04:00
044736b535 help text is now used when the '-h' or '--help' flags are detected 2023-06-19 12:55:45 -04:00
18 changed files with 1385 additions and 306 deletions

View file

@ -1,15 +1,29 @@
[package]
name = "remux"
version = "0.1.0"
version = "0.4.0"
edition = "2021"
authors = [ "Valerie Wolfe <sleeplessval@gmail.com>" ]
description = "A friendly command shortener for tmux"
homepage = "https://git.vwolfe.io/valerie/remux"
repository = "https://git.vwolfe.io/valerie/remux"
license = "MIT"
categories = [ "command-line-utilities" ]
keywords = [ "tmux", "remux" ]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[package.metadata.aur]
name = "remux"
type = "source"
archive = "archive/v$pkgver.tar.gz"
depends = [ "tmux>=3.0" ]
[[bin]]
name = "remux"
path = "src/main.rs"
[dependencies]
pico-args = { version = "0.5.0", features = [ "combined-flags", "eq-separator" ] }
#ratatui = { version = "0.20.1", features = [ "termion" ] }
termion = "2.0.1"
tmux_interface = "0.2.1"
tmux_interface = "0.3.2"
[profile.release]
opt-level = 's'
@ -17,5 +31,5 @@ lto = true
debug = false
codegen-units = 1
panic = "abort"
strip = "debuginfo"
strip = "symbols"

19
LICENSE Normal file
View file

@ -0,0 +1,19 @@
Copyright (c) 2024 Valerie Wolfe
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -7,33 +7,111 @@ A tmux wrapper and command shortener written in Rust. ReMux's
goal is to wrap tmux commands to be both shorter, and oriented
around session names instead of session IDs.
To further simplify developer usage, the `attach`, `detach`, `has`, and `new`
commands can be used without a target field, and will default to the name of
the Git repository root directory, if one is found.
In their shortest forms, *every* ReMux command is as short or
shorter than its equivalent tmux command:
```sh
# new session
tmux new-session -t foo
tmux new-s -t foo
remux n foo
# lists
# list sessions
tmux ls
remux l
remux
# attach
tmux a -t foo
remux a foo
# has
tmux has -t foo
remux has foo
tmux h -t foo
remux h foo
# detach
tmux detach-client -t foo
tmux det -t foo
remux d foo
# nesting sessions with '-n' flag
TMUX='' tmux a -t foo
remux a -n foo
TMUX='' tmux new-session -t foo
remux n -n foo
# switch to another session
tmux swi -t foo
rmux s foo
# cd to session path
cd `tmux display-mes -p "#{session_path}"`
cd `rmux p`
```
ReMux is built using the [tmux_interface](https://crates.io/crates/tmux_interface) and [pico_args](https://crates.io/crates/pico_args) crates.
## Dependencies
ReMux depends on [tmux](https://github.com/tmux/tmux).
## Installation
### Manual Install
<details>
<summary>Release Binary</summary>
Copy the compiled binary from the <a href="https://git.vwolfe.io/valerie/remux/releases">releases page</a>
to a directory in <code>$PATH</code>, such as <code>/usr/bin/</code>.
</details>
<details>
<summary>Compile from Source</summary>
Compile using cargo with the command <code>cargo build --release</code> and copy
the file from <code>target/release/</code> to a directory in <code>$PATH</code>,
such as <code>/usr/bin/</code>.
</details>
<details>
<summary>makepkg (AUR)</summary>
Clone the <a href="https://aur.archlinux.org/remux.git">AUR Repository</a> and
run the command <code>makepkg --install</code>.
</details>
### Package Managers
<details>
<summary>Arch Linux (AUR): <code>remux</code></summary>
Install the package from the <a href="https://aur.archlinux.org/packages/remux"><code>remux</code> AUR Package</a>
using an AUR package manager such as <a href="https://github.com/Morganamilo/paru"><code>paru</code></a>.
</details>
<details>
<summary>Cargo: <code>tmux-remux</code></summary>
Install the package using Cargo with the command <code>cargo install tmux-remux</code>.
</details>
### Supplemental
<details>
<summary>Bash Completions</summary>
Copy <code>bash-completion/remux</code> to the appropriate directory, typically
<code>/usr/share/bash-completion</code>.
</details>
<details>
<summary>Man Page: Section 1</summary>
Copy <code>man/remux.1</code> into <code>/usr/share/man/man1/</code>.
</details>
## Configuration
The pretty-print attached symbol (default: `*`) can be set manually by setting `REMUX_ATTACH_SYMBOL`.
## Libraries
- [pico-args](https://crates.io/crates/pico_args) — argument parsing
- [termion](https://crates.io/crates/termion) — ANSI formatting
- [tmux_interface](https://crates.io/crates/tmux_interface) — tmux communication

19
bash-completion/remux Normal file
View file

@ -0,0 +1,19 @@
_remux() {
local word
COMPREPLY=()
word="${COMP_WORDS[COMP_CWORD]}"
case $COMP_CWORD in
1)
COMPREPLY=( `compgen -W 'attach detach has help list new path switch title' -- "$word"` )
;;
2)
COMPREPLY=( `compgen -W "$(remux l -q $word)"` )
;;
esac
return 0
}
complete -F _remux remux

4
deny.toml Normal file
View file

@ -0,0 +1,4 @@
[licenses]
allow = [ "MIT" ]

179
man/remux.1 Normal file
View file

@ -0,0 +1,179 @@
.Dd $Mdocdate$
.Dt REMUX 1
.Sh NAME
.Nm remux
.Nd a command shortener for
.Xr tmux 1
.Sh SYNOPSIS
.Nm remux
.Op Fl dhnqrtv
.Op Fl D Ar path
.Op Ar command
.Op args...
.Sh DESCRIPTION
.Nm
is a wrapper and command shortener for
.Xr tmux 1 ,
primarily focused on improving the ergonomics of using named sessions.
.Pp
If no command is provided, remux will use the context action. Inside a repository, remux will attach or create. If in a session or outside a repository, remux will list sessions.
.Sh COMMANDS
.Nm remux
commands are split into two categories: global commands, which can be used anywhere, and session commands, which can only be used from inside a session.
.Ss GLOBAL COMMANDS
.Bl -tag -width Ds
.It Xo Ic attach
.Op Fl dnr
.Op Ar title
.Op Ar window
.Xc
.Bd -literal -compact
aliases: a
Attaches to an existing session.
.Ed
.Pp
.Bl -tag -width Ds -compact
.It Fl d , Fl -detach
Detach all other connections to the session.
.It Fl D , Fl -dir Ar path
Sets the working directory for the given command.
.It Fl n , Fl -nest
Allow nesting (attaching a session from inside another session).
.It Fl r , Fl -read-only
Attach the session in read-only mode.
.It Ar title
The title of the target session. If not given, remux will try to use the name of the repository containing the current directory.
.It Ar window
The name of the window to attach to.
.El
.It Xo Ic detach
.Op Ar title
.Xc
.Bd -literal -compact
aliases: d
Detaches all clients from the target session.
.Ed
.It Xo Ic has
.Op Fl q
.Op Ar title
.Xc
.Bd -literal -compact
aliases: h
Checks whether or not a session exists.
.Ed
.Pp
.Bl -tag -width Ds -compact
.It Fl q , Fl -quiet
Run silently without printing to standard output.
.It Ar title
The title of the target session. If not given, remux will attempt to use the name of the repository containing the current directory.
.El
.It Ic list
.Bd -literal -compact
aliases: l, ls
Pretty-prints a list of tmux sessions.
.Ed
.It Xo Ic new
.Op Fl dn
.Op Fl t\ |\ --target Ar path
.Op Ar title
.Op Ar command
.Xc
.Bd -literal -compact
aliases: n
Creates a new session.
.Ed
.Pp
.Bl -tag -width Ds -compact
.It Fl d , Fl -detach
Creates the session without attaching.
.It Fl n , Fl -nest
Allow nesting (attaching a session from inside another session).
.It Fl t , Fl -target Ar path
Sets the session path to the provided directory.
.It Ar title
Create the session with the given title. If not given, remux will attempt to use the name of the repository containing the current directory.
.El
.El
.Ss SESSION COMMANDS
.Bl -tag -width Ds
.It Ic path
.Bd -literal -compact
aliases: p
Prints the session path.
.Ed
.It Xo Ic switch
.Op Fl rd
.Op Ar title
.Xc
.Bd -literal -compact
aliases: s
Switches from the current session to the target.
.Ed
.Pp
.Bl -tag -width Ds -compact
.It Fl d , Fl -detach
Detaches other clients from the target session.
.It Fl r , Fl -read-only
Switch to the target session in read-only mode.
.It Ar title
The title of the session to switch to. If blank, the previous session will be used.
.El
.It Ic title
.Bd -literal -compact
aliases: t, which
Prints the session title.
.El
.Sh ENVIRONMENT
.Bl -tag -width Ds
.It Ev REMUX_ATTACH_SYMBOL
Changes the symbol displayed for attached sessions in the
.Ic list
command.
Default: '*'
.It Ev REMUX_CURRENT_SYMBOL
Changes the symbol displayed for the current session in the
.Ic list
command.
Default: '>'
.It Ev REMUX_NEW_WINDOW
Provides a default windows name when creating a new session. Unused if empty.
Default: (unset)
.It Ev REMUX_PREVIOUS_SYMBOL
Changes the symbol displayed for the previous session in the
.Ic list
command.
Default: '-'
.It Ev REMUX_REPO_FILE
The filename to match on when trying to find the root of a repository.
Default: '.git'
.El
.Sh EXIT STATUS
.Bl -tag -Width Ds
.It 1
Unmatched command name.
.It 2
Unmatched target session.
.It 3
Unmatched help topic.
.It 4
Missing or invalid target; target was not given or is the same as the current session.
.It 5
.Nm remux
is not running from within a terminal.
.It 6
Nesting error; nest flag is missing or inappropriate.
.It 7
A session command was attempted outside a session.
.Sh EXAMPLES
Use
.Ic path
to navigate to the session path:
.Pp
.Dl $ cd `remux p`
.Pp
.Sh SEE ALSO
.Xr tmux 1
.Sh AUTHORS
.An -nosplit
.An Valerie Wolfe Aq Mt sleeplessval@gmail.com

260
sbom.xml Normal file
View file

@ -0,0 +1,260 @@
{
"SPDXID": "SPDXRef-DOCUMENT",
"creationInfo": {
"created": "2024-03-14T20:41:35.559Z",
"creators": [
"Tool: cargo-sbom-v0.8.4"
]
},
"dataLicense": "CC0-1.0",
"documentNamespace": "https://spdx.org/spdxdocs/remux-eb288ebb-1cdd-412b-87c2-b15fc96cd8bd",
"files": [
{
"SPDXID": "SPDXRef-File-remux",
"checksums": [],
"fileName": "remux",
"fileTypes": [
"BINARY"
]
}
],
"name": "remux",
"packages": [
{
"SPDXID": "SPDXRef-Package-libredox-0.0.2",
"description": "Redox stable ABI",
"downloadLocation": "registry+https://github.com/rust-lang/crates.io-index",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceLocator": "pkg:cargo/libredox@0.0.2",
"referenceType": "purl"
}
],
"licenseConcluded": "MIT",
"licenseDeclared": "MIT",
"name": "libredox",
"versionInfo": "0.0.2"
},
{
"SPDXID": "SPDXRef-Package-libc-0.2.153",
"description": "Raw FFI bindings to platform libraries like libc.\n",
"downloadLocation": "registry+https://github.com/rust-lang/crates.io-index",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceLocator": "pkg:cargo/libc@0.2.153",
"referenceType": "purl"
}
],
"homepage": "https://github.com/rust-lang/libc",
"licenseConcluded": "MIT OR Apache-2.0",
"licenseDeclared": "MIT OR Apache-2.0",
"name": "libc",
"versionInfo": "0.2.153"
},
{
"SPDXID": "SPDXRef-Package-numtoa-0.1.0",
"description": "Convert numbers into stack-allocated byte arrays",
"downloadLocation": "registry+https://github.com/rust-lang/crates.io-index",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceLocator": "pkg:cargo/numtoa@0.1.0",
"referenceType": "purl"
}
],
"licenseConcluded": "MIT OR Apache-2.0",
"licenseDeclared": "MIT OR Apache-2.0",
"name": "numtoa",
"versionInfo": "0.1.0"
},
{
"SPDXID": "SPDXRef-Package-termion-2.0.3",
"description": "A bindless library for manipulating terminals.",
"downloadLocation": "registry+https://github.com/rust-lang/crates.io-index",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceLocator": "pkg:cargo/termion@2.0.3",
"referenceType": "purl"
}
],
"licenseConcluded": "MIT",
"licenseDeclared": "MIT",
"name": "termion",
"versionInfo": "2.0.3"
},
{
"SPDXID": "SPDXRef-Package-pico-args-0.5.0",
"description": "An ultra simple CLI arguments parser.",
"downloadLocation": "registry+https://github.com/rust-lang/crates.io-index",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceLocator": "pkg:cargo/pico-args@0.5.0",
"referenceType": "purl"
}
],
"licenseConcluded": "MIT",
"licenseDeclared": "MIT",
"name": "pico-args",
"versionInfo": "0.5.0"
},
{
"SPDXID": "SPDXRef-Package-redox_termios-0.1.3",
"description": "A Rust library to access Redox termios functions",
"downloadLocation": "registry+https://github.com/rust-lang/crates.io-index",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceLocator": "pkg:cargo/redox_termios@0.1.3",
"referenceType": "purl"
}
],
"licenseConcluded": "MIT",
"licenseDeclared": "MIT",
"name": "redox_termios",
"versionInfo": "0.1.3"
},
{
"SPDXID": "SPDXRef-Package-remux-0.2.1",
"description": "A friendly command shortener for tmux",
"downloadLocation": "NONE",
"homepage": "https://git.vwolfe.io/valerie/remux",
"licenseConcluded": "MIT",
"licenseDeclared": "MIT",
"name": "remux",
"versionInfo": "0.2.1"
},
{
"SPDXID": "SPDXRef-Package-bitflags-2.4.2",
"description": "A macro to generate structures which behave like bitflags.\n",
"downloadLocation": "registry+https://github.com/rust-lang/crates.io-index",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceLocator": "pkg:cargo/bitflags@2.4.2",
"referenceType": "purl"
}
],
"homepage": "https://github.com/bitflags/bitflags",
"licenseConcluded": "MIT OR Apache-2.0",
"licenseDeclared": "MIT OR Apache-2.0",
"name": "bitflags",
"versionInfo": "2.4.2"
},
{
"SPDXID": "SPDXRef-Package-bitflags-1.3.2",
"description": "A macro to generate structures which behave like bitflags.\n",
"downloadLocation": "registry+https://github.com/rust-lang/crates.io-index",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceLocator": "pkg:cargo/bitflags@1.3.2",
"referenceType": "purl"
}
],
"homepage": "https://github.com/bitflags/bitflags",
"licenseConcluded": "MIT OR Apache-2.0",
"licenseDeclared": "MIT/Apache-2.0",
"name": "bitflags",
"versionInfo": "1.3.2"
},
{
"SPDXID": "SPDXRef-Package-redox_syscall-0.4.1",
"description": "A Rust library to access raw Redox system calls",
"downloadLocation": "registry+https://github.com/rust-lang/crates.io-index",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceLocator": "pkg:cargo/redox_syscall@0.4.1",
"referenceType": "purl"
}
],
"licenseConcluded": "MIT",
"licenseDeclared": "MIT",
"name": "redox_syscall",
"versionInfo": "0.4.1"
},
{
"SPDXID": "SPDXRef-Package-tmux_interface-0.2.1",
"description": "Rust language library for communication with TMUX via CLI",
"downloadLocation": "registry+https://github.com/rust-lang/crates.io-index",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceLocator": "pkg:cargo/tmux_interface@0.2.1",
"referenceType": "purl"
}
],
"licenseConcluded": "MIT",
"licenseDeclared": "MIT",
"name": "tmux_interface",
"versionInfo": "0.2.1"
}
],
"relationships": [
{
"relatedSpdxElement": "SPDXRef-Package-libc-0.2.153",
"relationshipType": "DEPENDS_ON",
"spdxElementId": "SPDXRef-Package-termion-2.0.3"
},
{
"relatedSpdxElement": "SPDXRef-Package-termion-2.0.3",
"relationshipType": "DEPENDS_ON",
"spdxElementId": "SPDXRef-Package-remux-0.2.1"
},
{
"relatedSpdxElement": "SPDXRef-Package-redox_syscall-0.4.1",
"relationshipType": "DEPENDS_ON",
"spdxElementId": "SPDXRef-Package-libredox-0.0.2"
},
{
"relatedSpdxElement": "SPDXRef-Package-numtoa-0.1.0",
"relationshipType": "DEPENDS_ON",
"spdxElementId": "SPDXRef-Package-termion-2.0.3"
},
{
"relatedSpdxElement": "SPDXRef-Package-tmux_interface-0.2.1",
"relationshipType": "DEPENDS_ON",
"spdxElementId": "SPDXRef-Package-remux-0.2.1"
},
{
"relatedSpdxElement": "SPDXRef-Package-pico-args-0.5.0",
"relationshipType": "DEPENDS_ON",
"spdxElementId": "SPDXRef-Package-remux-0.2.1"
},
{
"relatedSpdxElement": "SPDXRef-Package-redox_termios-0.1.3",
"relationshipType": "DEPENDS_ON",
"spdxElementId": "SPDXRef-Package-termion-2.0.3"
},
{
"relatedSpdxElement": "SPDXRef-Package-libc-0.2.153",
"relationshipType": "DEPENDS_ON",
"spdxElementId": "SPDXRef-Package-libredox-0.0.2"
},
{
"relatedSpdxElement": "SPDXRef-Package-bitflags-1.3.2",
"relationshipType": "DEPENDS_ON",
"spdxElementId": "SPDXRef-Package-redox_syscall-0.4.1"
},
{
"relatedSpdxElement": "SPDXRef-Package-remux-0.2.1",
"relationshipType": "GENERATED_FROM",
"spdxElementId": "SPDXRef-File-remux"
},
{
"relatedSpdxElement": "SPDXRef-Package-bitflags-2.4.2",
"relationshipType": "DEPENDS_ON",
"spdxElementId": "SPDXRef-Package-libredox-0.0.2"
},
{
"relatedSpdxElement": "SPDXRef-Package-libredox-0.0.2",
"relationshipType": "DEPENDS_ON",
"spdxElementId": "SPDXRef-Package-termion-2.0.3"
}
],
"spdxVersion": "SPDX-2.3"
}

View file

@ -1,251 +0,0 @@
use std::{
env::current_dir,
process::exit
};
use pico_args::Arguments;
use termion::{ color, style };
use tmux_interface::TmuxCommand;
use crate::error;
use crate::util;
pub fn help(pargs: &mut Arguments) {
let topic = pargs.subcommand().unwrap();
match topic.as_deref() {
None => {
println!("remux v{}", env!("CARGO_PKG_VERSION"));
println!("Valerie Wolfe <sleeplessval@gmail.com>");
println!("A command wrapper for tmux written in Rust.\n");
println!("usage: remux <command> [<args>]\n");
println!("Commands:");
println!(" help Show help text for remux or a specific command");
println!(" attach Attach to an existing tmux session");
println!(" detach Detach clients from a tmux session");
println!(" has Check if a tmux session exists");
println!(" list Pretty-print all tmux sessions");
println!(" new Create a new tmux session");
println!("\nUse 'remux help <command>' to see detailed help text for each command.");
},
Some("a" | "attach")
=> {
println!("remux attach");
println!("Attach to an existing session.\n");
println!("usage: remux attach [flags] <session> [window]\n");
println!("args:");
println!(" <session> The session to attach to");
println!(" [window] Optionally focus a window in the given session\n");
println!("flags:");
println!(" -d, --detach Detach other attached clients from the session");
println!(" -r, --readonly Attach the session as read-only");
},
Some("d" | "detach")
=> {
println!("remux detach");
println!("Detach all clients from a session.\n");
println!("usage: remux detach <session>\n");
println!("args:");
println!(" <session> The session name to detach clients from");
},
Some("has")
=> {
println!("remux has");
println!("Check if the target session exists.\n");
println!("usage: remux has [flags] <session>\n");
println!("args:");
println!(" <session> The session to check for\n");
println!("flags:");
println!(" -q, --quiet Display no text; exit code only");
},
Some("l" | "ls" | "list")
=> {
println!("remux list");
println!("Pretty-print all tmux sessions.\n");
println!("usage: remux list");
},
Some("n" | "new")
=> {
println!("remux new");
println!("Create a new tmux session.\n");
println!("usage: remux new [flags] <title> [command]\n");
println!("args:");
println!(" <title> The title of the new session");
println!(" [command] The shell command to run\n");
println!("flags:");
println!(" -t, --target <dir> Sets the target directory for the new session.");
},
// not found
_ => error::no_help(topic.unwrap())
}
}
pub fn attach(pargs: &mut Arguments) {
util::prevent_nest();
// get optional flags
let read_only = pargs.contains(["-r", "--readonly"]);
let detach_other = pargs.contains(["-d", "--detach"]);
// get target and error out if not provided
let args = pargs.clone().finish();
if args.len() < 1 { error::missing_target(); }
let target = args.get(0).unwrap().to_string_lossy();
let window = args.get(1);
// focus window if provided
if window.is_some() {
let target = window.unwrap().to_string_lossy();
let tmux = TmuxCommand::new();
tmux
.select_window()
.target_window(target)
.output().ok();
}
// command
let tmux = TmuxCommand::new();
let exists = tmux
.has_session()
.target_session(target.clone())
.output().unwrap();
if !exists.success() { error::no_target(target.to_string()); }
let mut attach = tmux.attach_session();
// handle optional flags
if read_only { attach.read_only(); }
if detach_other { attach.detach_other(); }
attach
.target_session(target)
.output().ok();
}
pub fn detach(pargs: &mut Arguments) {
// get target and error out if not provided
let args = pargs.clone().finish();
if args.len() < 1 { error::missing_target(); }
let target = args.get(0).unwrap().to_string_lossy();
// command
let tmux = TmuxCommand::new();
let exists = tmux
.has_session()
.target_session(target.clone())
.output().unwrap();
if !exists.success() { error::no_target(target.to_string()); }
tmux
.detach_client()
.target_session(target)
.output().ok();
}
pub fn has(pargs: &mut Arguments) {
// get optional flag
let quiet = pargs.contains(["-q", "--quiet"]);
// get target and error out if not provided
let args = pargs.clone().finish();
if args.len() < 1 { error::missing_target(); }
let target = args.get(0).unwrap().to_string_lossy();
// command
let tmux = TmuxCommand::new();
let exists = tmux
.has_session()
.target_session(target.clone())
.output().unwrap();
let success = exists.success();
// handle optional flag
// inverted; print text if NOT quiet
if !quiet { println!("session \"{target}\" {}.", if success { "exists" } else { "does not exist" }); }
// exit codes for scripts to use
exit( if success { 0 } else { 1 });
}
pub fn list() {
// get session list
let sessions = util::get_sessions().unwrap_or(Vec::new());
// handle empty case
if sessions.len() == 0 {
println!("no sessions");
return;
}
// iterate over pretty print
println!("sessions:");
for session in sessions.into_iter() {
let group = session.group.unwrap_or("[untitled]".to_string());
let id = session.id.unwrap();
let attached = session.attached.unwrap_or(0) > 0;
println!(
" {group} ({bold}{blue}{id}{reset}) {bold}{green}{attach_sym}{reset}",
// values
attach_sym = if attached { "󰌹" } else {""},
// formatting
bold = style::Bold,
blue = color::Fg(color::Blue),
green = color::Fg(color::LightGreen),
reset = style::Reset
);
}
}
pub fn new(pargs: &mut Arguments) {
use pico_args::Error;
// show nest message
util::prevent_nest();
// get optional flag
let target_dir: Result<String, Error> = pargs.value_from_str(["-t", "--target"]);
// get target and error out if not provided
let args = pargs.clone().finish();
if args.len() < 1 { error::missing_target(); }
// get target session and optional command
let title = args.get(0).unwrap().to_string_lossy();
let command = args.get(1);
let tmux = TmuxCommand::new();
let mut new = tmux.new_session();
if command.is_some() { new.shell_command(command.unwrap().to_string_lossy()); }
new
.group_name(title)
.attach()
.start_directory(target_dir.unwrap_or(current_dir().unwrap().to_string_lossy().to_string()))
.output().ok();
}

4
src/command/mod.rs Normal file
View file

@ -0,0 +1,4 @@
pub mod share;
pub mod session;

67
src/command/session.rs Normal file
View file

@ -0,0 +1,67 @@
//! commands accessible from within a session
use tmux_interface::{
Tmux,
commands
};
use crate::{
error,
state::State,
util::{
self,
message,
MSG_PREVIOUS, MSG_SESSION_PATH, NULL
}
};
pub fn path(state: &mut State) {
state.session_enforce("path");
if let Some(message) = message(MSG_SESSION_PATH) {
println!("{message}");
}
}
pub fn switch(state: &mut State) {
util::terminal_enforce();
// refuse to run outside a session
state.session_enforce("switch");
// consume optional flags
let read_only = state.flags.read_only;
let detach_other = state.flags.detached;
let args = state.args.clone().finish();
let target: String = match if let Some(inner) = args.get(0) { inner.to_str() } else { None } {
None |
Some("-") => if let Some(prev) = message(MSG_PREVIOUS) { prev }
else { error::missing_target() },
Some(inner) => inner.to_owned()
};
let exists = util::session_exists(target.clone());
if !exists { error::no_target(target.clone()); }
let mut tmux = Tmux::new();
if detach_other {
let detach = commands::DetachClient::new()
.target_session(&target);
tmux = tmux.add_command(detach);
}
let mut switch = commands::SwitchClient::new();
switch = switch.target_session(&target);
if read_only { switch.read_only = true; }
tmux.add_command(switch)
.stderr(NULL).output().ok();
}
pub fn title(state: State) {
state.session_enforce("title");
if let Some(title) = state.title { println!("{title}"); }
}

216
src/command/share.rs Normal file
View file

@ -0,0 +1,216 @@
//! globally available tmux commands.
use std::process::exit;
use pico_args::{ Arguments, Error };
use termion::{ color, style };
use tmux_interface::{
Tmux,
commands
};
use crate::{
env::{
self,
env_var,
SYMBOL_ATTACH, SYMBOL_CURRENT, SYMBOL_PREV
},
error,
flag,
state::State,
util::{
self,
message,
MSG_PREVIOUS, NULL
}
};
pub fn attach(state: &mut State) {
util::terminal_enforce();
state.nest_init();
// consume optional flags
let read_only = state.flags.read_only;
let detach_other = state.flags.detached;
// consume arguments
let target = state.target_title().unwrap();
let window = state.target();
// do not allow attaching to the same session
if state.session && target == state.title.clone().unwrap() { error::same_session(); }
// make sure the session exists
let exists = util::session_exists(target.clone());
if !exists { error::no_target(target.clone()); }
// build attach command
let mut attach = commands::AttachSession::new();
attach = attach.target_session(target);
if read_only { attach.read_only = true; }
if detach_other { attach.detach_other = true; }
let select_window: Option<commands::SelectWindow>;
if let Some(window) = window {
let mut command = commands::SelectWindow::new();
command.target_window = Some(window.into());
select_window = Some(command);
} else { select_window = None; }
// build dispatch
let mut tmux = Tmux::new().add_command(attach);
if let Some(select_window) = select_window { tmux = tmux.add_command(select_window); }
tmux.stderr(NULL).output().ok();
state.nest_deinit();
}
pub fn context_action(state: &mut State) {
if !state.session {
if let Some(repository) = &state.repository {
let target = repository.name.clone();
let mut args = Arguments::from_vec( vec![(&target).into()] );
let mut substate = State::new(&mut args);
substate.flags = state.flags.clone();
if util::session_exists(&target) {
attach(&mut substate);
} else {
new(&mut substate);
}
return;
}
}
// fallback behavior is list
list(state);
}
pub fn detach(state: &mut State) {
util::terminal_enforce();
let target = state.target_title().unwrap();
// make sure the session exists
let exists = util::session_exists(target.clone());
if !exists { error::no_target(target.clone()); }
// build and dispatch
let detach = commands::DetachClient::new()
.target_session(target);
Tmux::new()
.add_command(detach)
.disable_echo().output().ok();
}
pub fn has(state: &mut State) {
// consume optional flags
let quiet = state.flags.quiet;
// get target
let target = state.target_title().unwrap();
// run command
let success = util::session_exists(target.clone());
// print if not quiet
if !quiet {
println!("session \"{target}\" {}.",
if success { "exists" }
else { "does not exist" }
);
}
// emit exit code
exit( if success { 0 } else { 1 });
}
pub fn list(state: &mut State) {
// get session list
let sessions = util::get_sessions().unwrap_or(Vec::new());
let search = state.target();
let previous = message(MSG_PREVIOUS);
// handle empty case
if sessions.len() == 0 {
if !state.flags.quiet { println!("no sessions"); }
return;
}
// get attached session symbol
let attach_symbol = env_var(SYMBOL_ATTACH);
let current_symbol = env_var(SYMBOL_CURRENT);
let prev_symbol = env_var(SYMBOL_PREV);
// pretty print session list
if !state.flags.quiet { println!("sessions:"); }
for session in sessions {
let name = session.name.unwrap_or("[untitled]".to_string());
if search.is_some() && !name.starts_with(search.as_ref().unwrap()) { continue; }
if !state.flags.quiet {
let id = session.id.unwrap();
let attached = session.attached.unwrap_or(0) > 0;
let compare = Some(name.clone());
let marker =
if compare == state.title { current_symbol.clone() }
else if state.session && compare == previous { prev_symbol.clone() }
else { " ".to_string() };
println!(
" {marker} {name}{reset} ({bold}{blue}{id}{reset}) {bold}{green}{attach}{reset}",
// values
attach = if attached { attach_symbol.clone() } else { "".to_string() },
// formatting
bold = style::Bold,
blue = color::Fg(color::Blue),
green = color::Fg(color::LightGreen),
reset = style::Reset,
);
} else {
print!("{name} ");
}
}
}
pub fn new(state: &mut State) {
util::terminal_enforce();
// get optional flags
let detached = state.flags.detached;
let target_dir: Result<String, Error> = state.args.value_from_str(flag::TARGET);
// delayed nest_init; detached behavior conflicts with nest
if !detached { state.nest_init(); }
else if state.flags.nested { error::conflict_nest(Some("detached session is not nesting")); }
// get environment variables
let window_name = env_var(env::NEW_WINDOW_NAME);
// consume arguments
let title = state.target_title().unwrap();
let command = state.target();
// don't allow duplicate names
let exists = util::session_exists(title.clone());
if exists { error::target_exists(title.clone()); }
let mut new = commands::NewSession::new();
new = new.session_name(title);
if let Some(command) = command { new.shell_command = Some(command.into()); }
if detached { new.detached = true; }
if let Ok(target_dir) = target_dir { new = new.start_directory(target_dir); }
let mut tmux = Tmux::new().add_command(new);
// rename window if var not empty
if !window_name.is_empty() {
let auto_name = commands::RenameWindow::new()
.new_name(window_name);
tmux = tmux.add_command(auto_name);
}
tmux.stderr(NULL).output().ok();
}

17
src/env.rs Normal file
View file

@ -0,0 +1,17 @@
use std::env::var;
pub type EnvVar = (&'static str, &'static str);
pub const NEW_WINDOW_NAME: EnvVar = ("REMUX_NEW_WINDOW", "");
pub const REPO_FILE: EnvVar = ("REMUX_REPO_FILE", ".git");
pub const SYMBOL_ATTACH: EnvVar = ("REMUX_ATTACH_SYMBOL", "*");
pub const SYMBOL_CURRENT: EnvVar = ("REMUX_CURRENT_SYMBOL", ">");
pub const SYMBOL_PREV: EnvVar = ("REMUX_PREVIOUS_SYMBOL", "-");
pub const TMUX: &str = "TMUX";
/// get or default an environment variable
pub fn env_var(envvar: EnvVar) -> String {
var(envvar.0).unwrap_or(envvar.1.to_string())
}

View file

@ -1,32 +1,84 @@
use std::process::exit;
/// no subcommand that matches user input; code 1
pub fn no_subcommand(subcommand: String) {
println!("remux: no command match for \"{subcommand}\"");
pub fn no_subcommand(subcommand: String) -> ! {
eprintln!("remux: no command match for \"{subcommand}\"");
exit(1);
}
/// target session not found; code 2
pub fn no_target(target: String) {
println!("remux: no session \"{target}\" exists");
exit(2);
}
/// no sessions exist; code 2
pub fn no_sessions() {
println!("remux: no sessions running");
println!("use 'remux n <title>' to create a new session");
pub fn no_target<S: Into<String>>(target: S) -> ! {
let target = target.into();
eprintln!("remux: no session \"{target}\" exists");
exit(2);
}
/// help topic doesn't exist; code 3
pub fn no_help(topic: String) {
println!("remux: no help for \"{topic}\"");
pub fn no_help(topic: String) -> ! {
eprintln!("remux: no help for \"{topic}\"");
exit(3);
}
/// user provided no target; code 4
pub fn missing_target() {
println!("remux: no target provided");
pub fn missing_target() -> ! {
eprintln!("remux: no target provided");
exit(4);
}
/// refuse to attach to current session; code 4
pub fn same_session() -> ! {
eprintln!("remux: cannot attach to same session");
exit(4);
}
/// a session with the target name already exists; code 4
pub fn target_exists<S: Into<String>>(target: S) -> ! {
let target = target.into();
eprintln!("remux: session \"{target}\" already exists");
exit(4);
}
/// non-terminal environment prevention; code 5
pub fn not_terminal() -> ! {
eprintln!("remux: not running from a terminal");
exit(5);
}
/// tried to nest while not in a session; code 6
pub fn not_nesting() -> ! {
eprintln!("remux: inappropriate nesting flag (-n); not in a session");
exit(6);
}
/// operation requires nesting flag; code 6
pub fn prevent_nest() -> ! {
eprintln!("remux: the nesting flag (-n) is required for nesting operation");
exit(6);
}
/// operation conflicts with nesting flag; code 6
pub fn conflict_nest(reason: Option<&'static str>) -> ! {
if let Some(reason) = reason { eprintln!("remux: inappropriate nesting flag (-n): {reason}"); }
else { eprintln!("remux: nesting flag (-n) is inappropriate for this operation."); }
exit(6);
}
/// tried to run a session command outside a session; code 7
pub fn not_in_session(cmd: &'static str) -> ! {
eprintln!("remux: '{cmd}' must be run from within a session");
exit(7);
}
/// failed to set working directory; code 8
pub fn working_dir_fail(working_dir: &str) -> ! {
eprintln!("remux: failed to set working directory to '{working_dir}'");
exit(8);
}

56
src/flag.rs Normal file
View file

@ -0,0 +1,56 @@
use pico_args::Arguments;
type Flag = [&'static str;2];
pub static DETACH: Flag = [ "-d", "--detach" ];
pub static HELP: Flag = [ "-h", "--help" ];
pub static NEST: Flag = [ "-n", "--nest" ];
pub static QUIET: Flag = [ "-q", "--quiet" ];
pub static READ_ONLY: Flag = [ "-r", "--read-only" ];
pub static TARGET: Flag = [ "-t", "--target" ];
pub static VERSION: Flag = [ "-v", "--version" ];
pub static WORKING_DIR: Flag = [ "-D", "--dir" ];
pub struct Flags {
pub detached: bool,
pub nested: bool,
pub quiet: bool,
pub read_only: bool,
pub target: Option<String>,
pub working_dir: Option<String>
}
impl Flags {
pub fn from(args: &mut Arguments) -> Flags {
let detached = args.contains(DETACH);
let nested = args.contains(NEST);
let quiet = args.contains(QUIET);
let read_only = args.contains(READ_ONLY);
let target = args.value_from_str(TARGET).ok();
let working_dir = args.value_from_str(WORKING_DIR).ok();
Flags {
detached,
nested,
quiet,
read_only,
target,
working_dir
}
}
pub fn clone(&self) -> Flags {
Flags {
detached: self.detached,
nested: self.nested,
quiet: self.quiet,
read_only: self.read_only,
target: self.target.clone(),
working_dir: self.working_dir.clone()
}
}
}

169
src/help.rs Normal file
View file

@ -0,0 +1,169 @@
use pico_args::Arguments;
use crate::error;
use crate::VERSION;
pub fn help(pargs: &mut Arguments) {
let topic = pargs.subcommand().unwrap();
match topic.as_deref() {
None =>
println!("remux v{VERSION}
Valerie Wolfe <sleeplessval@gmail.com>
A command wrapper for tmux written in Rust.
usage: remux [command] [<args>]
commands:
help Show help text for remux, a command, or a help topic.
attach Attach to an existing tmux session
detach Detach clients from a tmux session
has Check if a tmux session exists
list Pretty-print all tmux sessions
new Create a new tmux session
path print session path (session)
switch switch to another session (session)
title print session title (session)
Use 'remux help <command>' to see detailed help text for each command.
help topics:
env Environment variables"),
// COMMAND HELP
Some("a" | "attach")
=>
println!("remux attach
Attach to an existing session.
usage: remux attach [flags] <session> [window]
remux a [flags] <session> [window]
args:
<session> The session to attach to
[window] Optionally focus a window in the given session
flags:
-d, --detach Detach other attached clients from the session
-n, --nest Attach the session inside another session.
-r, --read-only Attach the session as read-only"),
Some("d" | "detach")
=>
println!("remux detach
Detach all clients from a session.
usage: remux detach <session>
remux d <session>
args:
<session> The session name to detach clients from"),
Some("h" | "has")
=>
println!("remux has
Check if the target session exists.
usage: remux has [flags] <session>
rmux h [flags] session
args:
<session> The session to check for
flags:
-q, --quiet Display no text; exit code only"),
Some("l" | "ls" | "list")
=>
println!("remux list
Pretty-print all tmux sessions.
usage: remux list
remux ls
remux l"),
Some("n" | "new")
=>
println!("remux new
Create a new tmux session.
usage: remux new [flags] <title> [command]
remux n [flags] <title> [command]
args:
<title> The title of the new session
[command] The shell command to run
flags:
-n, --nest Create the session inside another session.
-t, --target <dir> Sets the target directory for the new session."),
Some("p" | "path")
=>
println!("remux path
Print the session path (#{{session_path}}) to standard output.
Must be run from inside a session.
usage: remux path
remux p"),
Some("s" | "switch")
=>
println!("remux switch
Switch to a different tmux session.
Must be run from inside a session.
usage: remux switch [flags] <title>
remux s [flags] <title>
args:
<title> The title of the session to switch to.
flags:
-r, --read-only Attach the target session as read-only."),
Some("w" | "which" | "title")
=>
println!("remux which
Print the title of the current session.
usage: remux which
remux w
remux title"),
// TOPIC HELP
Some("env" | "vars")
=>
println!("remux environment variables
REMUX_ATTACH_SYMBOL
Changes the symbol displayed for attached sessions displayed
by the 'list' command.
Default: '*'
REMUX_CURRENT_SYMBOL
Changes the symbol displayed to denote the current session
in the 'list' command.
Default: '>'
REMUX_NEW_WINDOW
Provides a default window name when creating a session with
the 'new' command, if not empty.
Default: (unset)"),
// not found
_ => error::no_help(topic.unwrap())
}
}
pub fn version() {
println!("remux v{VERSION}");
}

View file

@ -2,36 +2,71 @@
use pico_args::Arguments;
mod command;
mod env;
mod error;
mod flag;
mod help;
mod state;
mod util;
use help::{ help, version };
use state::State;
static VERSION: &str = env!("CARGO_PKG_VERSION");
fn main() {
// collect args
let mut args = Arguments::from_env();
let subcommand = args.subcommand().unwrap();
// consume flags
if args.contains(flag::HELP) {
help(&mut args);
return;
}
match subcommand.as_deref() {
Some("h" | "help")
=> command::help(&mut args),
if args.contains(flag::VERSION) {
version();
return;
}
let mut state = State::new(&mut args);
let target = state.target();
// invoke subcommand function
match target.as_deref() {
Some("help")
=> help(&mut args),
None
=> command::share::context_action(&mut state),
Some("a" | "attach")
=> command::attach(&mut args),
=> command::share::attach(&mut state),
Some("d" | "detach")
=> command::detach(&mut args),
=> command::share::detach(&mut state),
Some("has")
=> command::has(&mut args),
Some("h" | "has")
=> command::share::has(&mut state),
None |
Some("l" | "ls" | "list")
=> command::list(),
=> command::share::list(&mut state),
Some("n" | "new")
=> command::new(&mut args),
=> command::share::new(&mut state),
Some("p" | "path")
=> command::session::path(&mut state),
Some("s" | "switch")
=> command::session::switch(&mut state),
Some("t" | "title" | "which")
=> command::session::title(state),
_
=> error::no_subcommand(subcommand.unwrap())
=> error::no_subcommand(target.unwrap())
}
}

103
src/state.rs Normal file
View file

@ -0,0 +1,103 @@
use std::{
env,
path::PathBuf
};
use pico_args::Arguments;
use crate::{
env::{ env_var, REPO_FILE, TMUX },
error,
flag::Flags,
util::{ find, message, MSG_SESSION_NAME }
};
pub struct State<'a> {
pub args: &'a mut Arguments,
pub flags: Flags,
pub session: bool,
tmux_var: Option<String>,
pub title: Option<String>,
pub repository: Option<Repository>
}
impl State<'_> {
pub fn new(args: &mut Arguments) -> State {
let flags = Flags::from(args);
let tmux_var = env::var(TMUX).ok();
let session = tmux_var.is_some();
if let Some(ref path) = flags.working_dir { State::set_working_dir(&path); }
let title = if session { message(MSG_SESSION_NAME) } else { None };
let repository = Repository::find();
State {
args,
flags,
session,
tmux_var,
title,
repository
}
}
pub fn nest_init(&self) {
if self.flags.nested {
if self.session { env::set_var(TMUX, ""); } // nesting & session => ok
else { error::not_nesting(); } // nesting & !session => error
} else if self.session { error::prevent_nest(); } // !nesting & session => error
// !nesting & !session => ok
}
pub fn nest_deinit(&self) {
if self.flags.nested && self.session {
env::set_var(TMUX, self.tmux_var.as_ref().unwrap());
}
}
pub fn session_enforce(&self, cmd: &'static str) {
if !self.session { error::not_in_session(cmd); }
}
fn set_working_dir(path: &str) {
let result = env::set_current_dir(path);
if result.is_err() {
error::working_dir_fail(path);
}
}
pub fn target(&mut self) -> Option<String> { self.args.subcommand().unwrap_or(None) }
pub fn target_title(&mut self) -> Option<String> {
let from_args = self.target();
if from_args.is_some() { return from_args; }
else if let Some(repository) = &self.repository { Some(repository.name.clone()) }
else { error::missing_target() }
}
}
pub struct Repository {
pub path: PathBuf,
pub name: String
}
impl Repository {
pub fn find() -> Option<Repository> {
let path = find(&env_var(REPO_FILE), env::current_dir().unwrap());
if let Some(path) = path {
let name = path.file_name().unwrap().to_string_lossy().to_string();
let inner = Repository {
path,
name
};
Some(inner)
} else { None }
}
}

View file

@ -1,29 +1,67 @@
use std::{
env::var,
process::exit
io::{ stdout, IsTerminal },
path::PathBuf
};
use tmux_interface::{
Session, Sessions,
variables::session::session::SESSION_ALL
Session, StdIO, Tmux,
commands,
variables::session::SessionsCtl
};
use crate::error;
pub const NULL: Option<StdIO> = Some(StdIO::Null);
pub const MSG_PREVIOUS: &str = "#{client_last_session}";
pub const MSG_SESSION_NAME: &str = "#S";
pub const MSG_SESSION_PATH: &str = "#{session_path}";
pub const MSG_WINDOW_NAME: &str = "#{window_name}";
pub fn message(fstr: &str) -> Option<String> {
let message = commands::DisplayMessage::new().print().message(fstr);
let result = Tmux::new().add_command(message).output();
if let Ok(output) = result {
let text = String::from_utf8(output.0.stdout);
if let Ok(title) = text {
if title.len() > 0 { Some(title[0..title.len() - 1].to_owned()) }
else { None }
} else { None }
} else { None }
}
/// return a Vec of all sessions or None
pub fn get_sessions() -> Option<Vec<Session>> {
let i_sessions = Sessions::get(SESSION_ALL);
if i_sessions.is_err() { return None; }
let sessions = i_sessions.ok();
if sessions.is_none() { return None; }
Some(sessions.unwrap().0)
let sessions = SessionsCtl::new().get_all();
if let Ok(sessions) = sessions {
return Some(sessions.0);
} else { return None; }
}
/// show the tmux nest text if env var is not unset
pub fn prevent_nest() {
let tmux = var("TMUX").ok();
if tmux.is_some() && tmux.unwrap() != "" {
println!("Sessions should be nested with care; unset TMUX to allow.");
exit(1);
}
/// check whether a target session exists
pub fn session_exists<S: Into<String>>(target: S) -> bool {
let has_session = commands::HasSession::new()
.target_session(target.into());
Tmux::new().add_command(has_session)
.stderr(NULL)
.status()
.unwrap()
.success()
}
/// enforce a command is being run in a terminal
pub fn terminal_enforce() {
if !stdout().is_terminal() { error::not_terminal(); }
}
/// recursively propagate up directories to find a child
pub fn find(target: &str, path: PathBuf) -> Option<PathBuf> {
if path.join(target).exists() { return Some(path); }
let parent = path.parent();
if let Some(parent) = parent { return find(target, parent.to_path_buf()) }
else { None }
}