Compare commits
91 commits
Author | SHA1 | Date | |
---|---|---|---|
4f76590930 | |||
d3a408ad33 | |||
8449ae00d6 | |||
ff30bc1052 | |||
8e2826b110 | |||
0fe3906578 | |||
fdf3114c04 | |||
8ad16ad825 | |||
a9a73314af | |||
af33e82415 | |||
eaf72847b1 | |||
beb880ed43 | |||
449c460bbb | |||
1b51633d4f | |||
b7b893d55c | |||
cdc68986fa | |||
1acc7aeb7b | |||
449921656c | |||
638fce2331 | |||
b93d814f6f | |||
b24778113a | |||
21bd6f40ca | |||
1f143f00ad | |||
e930af1027 | |||
97f546a69c | |||
44bad997cb | |||
441f1d3ef0 | |||
0232a9c650 | |||
ace9627b79 | |||
a915bfd9c5 | |||
ac97336f13 | |||
222f929fd4 | |||
0fc6cd66c6 | |||
1320b6f122 | |||
26f6fd5dd0 | |||
19b12c682d | |||
28a7fc44f9 | |||
21eda33624 | |||
d776129b3e | |||
62d72735da | |||
681e7427ba | |||
d8aaf7feed | |||
b3403ffa36 | |||
a07ae193b5 | |||
c8800a7f53 | |||
92d0010186 | |||
21f7b72298 | |||
2e28acc3c3 | |||
e017d57a53 | |||
307ff23049 | |||
3e4346bd1d | |||
7f5ad2fd1b | |||
6b7c84b673 | |||
782fb694d0 | |||
882e130121 | |||
4615777cae | |||
b00e15a037 | |||
5c3fb7df3f | |||
41a64f039f | |||
8b00f58fde | |||
c776b63994 | |||
be593c82e7 | |||
9c814c43f4 | |||
63335916d9 | |||
aba802e77d | |||
be5bace0ac | |||
640381820d | |||
444964d4ef | |||
4fbb2b07c9 | |||
407d77812a | |||
e0ea051bf4 | |||
2ede502884 | |||
90f3b9a999 | |||
3dc0e82ad6 | |||
7aa6b225de | |||
5abe1c827c | |||
1fbfad9364 | |||
5754c22e1a | |||
12d4b66fa3 | |||
0d7a411749 | |||
4b1ea9332f | |||
29a51da6bb | |||
db33677596 | |||
fab7dec810 | |||
a81226c79c | |||
99531e785b | |||
796d8744c5 | |||
2dd4e35600 | |||
f77f768416 | |||
808591fb9b | |||
044736b535 |
18 changed files with 1385 additions and 306 deletions
24
Cargo.toml
24
Cargo.toml
|
@ -1,15 +1,29 @@
|
||||||
[package]
|
[package]
|
||||||
name = "remux"
|
name = "remux"
|
||||||
version = "0.1.0"
|
version = "0.4.0"
|
||||||
edition = "2021"
|
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]
|
[dependencies]
|
||||||
pico-args = { version = "0.5.0", features = [ "combined-flags", "eq-separator" ] }
|
pico-args = { version = "0.5.0", features = [ "combined-flags", "eq-separator" ] }
|
||||||
#ratatui = { version = "0.20.1", features = [ "termion" ] }
|
|
||||||
termion = "2.0.1"
|
termion = "2.0.1"
|
||||||
tmux_interface = "0.2.1"
|
tmux_interface = "0.3.2"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = 's'
|
opt-level = 's'
|
||||||
|
@ -17,5 +31,5 @@ lto = true
|
||||||
debug = false
|
debug = false
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
strip = "debuginfo"
|
strip = "symbols"
|
||||||
|
|
||||||
|
|
19
LICENSE
Normal file
19
LICENSE
Normal 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.
|
92
README.md
92
README.md
|
@ -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
|
goal is to wrap tmux commands to be both shorter, and oriented
|
||||||
around session names instead of session IDs.
|
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
|
In their shortest forms, *every* ReMux command is as short or
|
||||||
shorter than its equivalent tmux command:
|
shorter than its equivalent tmux command:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|
||||||
# new session
|
# new session
|
||||||
tmux new-session -t foo
|
tmux new-s -t foo
|
||||||
remux n foo
|
remux n foo
|
||||||
|
|
||||||
# lists
|
# list sessions
|
||||||
tmux ls
|
tmux ls
|
||||||
remux l
|
remux l
|
||||||
remux
|
|
||||||
|
|
||||||
# attach
|
# attach
|
||||||
tmux a -t foo
|
tmux a -t foo
|
||||||
remux a foo
|
remux a foo
|
||||||
|
|
||||||
# has
|
# has
|
||||||
tmux has -t foo
|
tmux h -t foo
|
||||||
remux has foo
|
remux h foo
|
||||||
|
|
||||||
# detach
|
# detach
|
||||||
tmux detach-client -t foo
|
tmux det -t foo
|
||||||
remux d 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
19
bash-completion/remux
Normal 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
4
deny.toml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
[licenses]
|
||||||
|
allow = [ "MIT" ]
|
||||||
|
|
||||||
|
|
179
man/remux.1
Normal file
179
man/remux.1
Normal 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
260
sbom.xml
Normal 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"
|
||||||
|
}
|
251
src/command.rs
251
src/command.rs
|
@ -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
4
src/command/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
pub mod share;
|
||||||
|
pub mod session;
|
||||||
|
|
67
src/command/session.rs
Normal file
67
src/command/session.rs
Normal 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
216
src/command/share.rs
Normal 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
17
src/env.rs
Normal 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())
|
||||||
|
}
|
||||||
|
|
80
src/error.rs
80
src/error.rs
|
@ -1,32 +1,84 @@
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
|
||||||
/// no subcommand that matches user input; code 1
|
/// no subcommand that matches user input; code 1
|
||||||
pub fn no_subcommand(subcommand: String) {
|
pub fn no_subcommand(subcommand: String) -> ! {
|
||||||
println!("remux: no command match for \"{subcommand}\"");
|
eprintln!("remux: no command match for \"{subcommand}\"");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// target session not found; code 2
|
/// target session not found; code 2
|
||||||
pub fn no_target(target: String) {
|
pub fn no_target<S: Into<String>>(target: S) -> ! {
|
||||||
println!("remux: no session \"{target}\" exists");
|
let target = target.into();
|
||||||
exit(2);
|
eprintln!("remux: no session \"{target}\" exists");
|
||||||
}
|
|
||||||
/// no sessions exist; code 2
|
|
||||||
pub fn no_sessions() {
|
|
||||||
println!("remux: no sessions running");
|
|
||||||
println!("use 'remux n <title>' to create a new session");
|
|
||||||
exit(2);
|
exit(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// help topic doesn't exist; code 3
|
/// help topic doesn't exist; code 3
|
||||||
pub fn no_help(topic: String) {
|
pub fn no_help(topic: String) -> ! {
|
||||||
println!("remux: no help for \"{topic}\"");
|
eprintln!("remux: no help for \"{topic}\"");
|
||||||
exit(3);
|
exit(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// user provided no target; code 4
|
/// user provided no target; code 4
|
||||||
pub fn missing_target() {
|
pub fn missing_target() -> ! {
|
||||||
println!("remux: no target provided");
|
eprintln!("remux: no target provided");
|
||||||
exit(4);
|
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
56
src/flag.rs
Normal 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
169
src/help.rs
Normal 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}");
|
||||||
|
}
|
||||||
|
|
59
src/main.rs
59
src/main.rs
|
@ -2,36 +2,71 @@
|
||||||
use pico_args::Arguments;
|
use pico_args::Arguments;
|
||||||
|
|
||||||
mod command;
|
mod command;
|
||||||
|
mod env;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod flag;
|
||||||
|
mod help;
|
||||||
|
mod state;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
|
use help::{ help, version };
|
||||||
|
use state::State;
|
||||||
|
|
||||||
|
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
// collect args
|
||||||
let mut args = Arguments::from_env();
|
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() {
|
if args.contains(flag::VERSION) {
|
||||||
Some("h" | "help")
|
version();
|
||||||
=> command::help(&mut args),
|
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")
|
Some("a" | "attach")
|
||||||
=> command::attach(&mut args),
|
=> command::share::attach(&mut state),
|
||||||
|
|
||||||
Some("d" | "detach")
|
Some("d" | "detach")
|
||||||
=> command::detach(&mut args),
|
=> command::share::detach(&mut state),
|
||||||
|
|
||||||
Some("has")
|
Some("h" | "has")
|
||||||
=> command::has(&mut args),
|
=> command::share::has(&mut state),
|
||||||
|
|
||||||
None |
|
|
||||||
Some("l" | "ls" | "list")
|
Some("l" | "ls" | "list")
|
||||||
=> command::list(),
|
=> command::share::list(&mut state),
|
||||||
|
|
||||||
Some("n" | "new")
|
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
103
src/state.rs
Normal 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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
72
src/util.rs
72
src/util.rs
|
@ -1,29 +1,67 @@
|
||||||
use std::{
|
use std::{
|
||||||
env::var,
|
io::{ stdout, IsTerminal },
|
||||||
process::exit
|
path::PathBuf
|
||||||
};
|
};
|
||||||
|
|
||||||
use tmux_interface::{
|
use tmux_interface::{
|
||||||
Session, Sessions,
|
Session, StdIO, Tmux,
|
||||||
variables::session::session::SESSION_ALL
|
|
||||||
|
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
|
/// return a Vec of all sessions or None
|
||||||
pub fn get_sessions() -> Option<Vec<Session>> {
|
pub fn get_sessions() -> Option<Vec<Session>> {
|
||||||
let i_sessions = Sessions::get(SESSION_ALL);
|
let sessions = SessionsCtl::new().get_all();
|
||||||
if i_sessions.is_err() { return None; }
|
if let Ok(sessions) = sessions {
|
||||||
let sessions = i_sessions.ok();
|
return Some(sessions.0);
|
||||||
if sessions.is_none() { return None; }
|
} else { return None; }
|
||||||
|
|
||||||
Some(sessions.unwrap().0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// show the tmux nest text if env var is not unset
|
/// check whether a target session exists
|
||||||
pub fn prevent_nest() {
|
pub fn session_exists<S: Into<String>>(target: S) -> bool {
|
||||||
let tmux = var("TMUX").ok();
|
let has_session = commands::HasSession::new()
|
||||||
if tmux.is_some() && tmux.unwrap() != "" {
|
.target_session(target.into());
|
||||||
println!("Sessions should be nested with care; unset TMUX to allow.");
|
Tmux::new().add_command(has_session)
|
||||||
exit(1);
|
.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 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue