1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
use std::{fmt::Display, str::FromStr};

use clap::{Args, Subcommand};

#[derive(Debug, clap::Parser)]
#[clap(
    name = "sesh",
    version = "0.1.8",
    author = "Will Hopkins <willothyh@gmail.com>"
)]
#[group(required = false, multiple = true)]
/// A terminal session manager for unix systems. Run persistent, named tasks that you can
/// detach from and attach to at any time - both on your local machine, and over SSH.
pub struct Cli {
    #[command(subcommand)]
    pub command: Option<Command>,
    #[command(flatten)]
    pub args: CliArgs,
}

#[derive(Debug, Clone, Args)]
pub struct CliArgs {
    pub program: Option<String>,
    pub args: Vec<String>,
    #[arg(short, long)]
    pub name: Option<String>,
    #[arg(short, long)]
    pub detached: bool,
}

#[derive(Debug, Subcommand)]
pub enum Command {
    #[command(alias = "r", verbatim_doc_comment)]
    /// Resume the last used session [alias: r]
    ///
    /// Specify --create / -c to create a new session if one does not exist
    Resume {
        /// Create a new session if one does not exist
        #[arg(short, long)]
        create: bool,
    },
    /// Start a new session, optionally specifying a name [alias: s]
    ///
    /// If no program is specified, the default shell will be used.
    /// If no name is specified, the name will be [program name]-[n-1] where n is the number of sessions
    /// with that program name.
    /// If --detached / -d is present, the session will not be attached to the client on creation
    /// and will run in the background.
    #[command(alias = "s", verbatim_doc_comment)]
    Start {
        #[arg(short, long)]
        name: Option<String>,
        program: Option<String>,
        args: Vec<String>,
        #[arg(short, long)]
        detached: bool,
    },
    #[command(alias = "a", verbatim_doc_comment)]
    /// Attach to a session [alias: a]
    ///
    /// Select a session by index or name.
    /// If --create / -c is present, a new session will be created if one does not exist.
    /// If the session was selected by name and the session was not present, the new session
    /// created by --create will have the specified name.
    Attach {
        /// Id or name of session
        session: SessionSelector,
        /// Create a new session if one does not exist
        #[arg(short, long)]
        create: bool,
    },
    /// Fuzzy select a session to attach to [alias: f]
    ///
    /// Opens a fuzzy selection window provided by the dialoguer crate.
    /// Type to fuzzy find files, or use the Up/Down arrows to navigate.
    /// Press Enter to confirm your selection, or Escape to cancel.
    #[command(alias = "f", verbatim_doc_comment)]
    Select,
    /// Detach from a session [alias: d]
    ///
    /// If no session is specified, detaches from the current session (if it exists).
    /// Otherwise, detaches the specified session from its owning client.
    #[command(alias = "d", verbatim_doc_comment)]
    Detach {
        /// Id or name of session
        session: Option<SessionSelector>,
    },
    #[command(alias = "k", verbatim_doc_comment)]
    /// Kill a session [alias: k]
    ///
    /// Kills a session and the process it owns.
    /// Select a session by name or index.
    Kill {
        /// Id or name of session
        session: SessionSelector,
    },
    /// List sessions [alias: ls]
    ///
    /// Prints a compact list of session names and indexes.
    /// With the --info / -i option, prints a nicely formatted table with info about each session.
    #[command(alias = "ls", verbatim_doc_comment)]
    #[group(required = false, multiple = true)]
    List {
        /// Print detailed info about sessions
        #[arg(short, long)]
        info: bool,
        /// Print session info as JSON, to be processed by another tool
        #[arg(short, long)]
        json: bool,
    },
    /// Shutdown the server (kill all sessions)
    Shutdown,
}

#[derive(Debug, Clone)]
pub enum SessionSelector {
    Id(usize),
    Name(String),
}

impl SessionSelector {
    pub fn name(self) -> Option<String> {
        match self {
            SessionSelector::Id(_) => None,
            SessionSelector::Name(name) => Some(name),
        }
    }
}

impl Display for SessionSelector {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            SessionSelector::Id(id) => write!(f, "{}", id),
            SessionSelector::Name(name) => write!(f, "{}", name),
        }
    }
}

impl FromStr for SessionSelector {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if let Ok(id) = s.parse::<usize>() {
            Ok(SessionSelector::Id(id))
        } else {
            Ok(SessionSelector::Name(s.to_owned()))
        }
    }
}