summaryrefslogtreecommitdiff
path: root/kitty/ssh_picker.sh
blob: 6ad6e3a9033eecee942db04b6c7d489677e7a82d (plain)
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
#!/usr/bin/env bash
# ~/.config/kitty/ssh_picker.sh
# Launched by kitty as an overlay window.
# Reads servers from ~/.ssh/config (Host entries) and lets
# you fuzzy-filter with arrow keys, then opens kitten ssh.
#
# Dependencies: none beyond bash + standard coreutils.
# Optional: fzf — if present it takes over the picker UI.

set -euo pipefail

# ------------------------------------------------------------
# config — edit these or let them fall through to ~/.ssh/config
# ------------------------------------------------------------
SSH_CONFIG="${HOME}/.ssh/config"
TERM_FALLBACK="xterm-256color"

# ------------------------------------------------------------
# gruvbox dark ANSI helpers
# ------------------------------------------------------------
RESET=$'\e[0m'
BOLD=$'\e[1m'
DIM=$'\e[2m'

GB_BG=$'\e[48;2;40;40;40m'          # #282828
GB_BG1=$'\e[48;2;60;56;54m'         # #3c3836
GB_BG2=$'\e[48;2;80;73;69m'         # #504945
GB_YELLOW=$'\e[38;2;250;189;47m'    # #fabd2f
GB_BLUE=$'\e[38;2;131;165;152m'     # #83a598
GB_GREEN=$'\e[38;2;184;187;38m'     # #b8bb26
GB_RED=$'\e[38;2;251;73;52m'        # #fb4934
GB_ORANGE=$'\e[38;2;254;128;25m'    # #fe8019
GB_FG=$'\e[38;2;235;219;178m'       # #ebdbb2
GB_DIM=$'\e[38;2;168;153;132m'      # #a89984

# ------------------------------------------------------------
# parse servers
# ------------------------------------------------------------
get_servers() {
    local servers=()

    # pull Host entries from ~/.ssh/config (skip wildcards)
    if [[ -f "$SSH_CONFIG" ]]; then
        while IFS= read -r line; do
            if [[ "$line" =~ ^[Hh]ost[[:space:]]+([^*?]+)$ ]]; then
                local host="${BASH_REMATCH[1]// /}"
                [[ -n "$host" ]] && servers+=("$host")
            fi
        done < "$SSH_CONFIG"
    fi

    # fallback examples if config is empty
    if [[ ${#servers[@]} -eq 0 ]]; then
        servers=(
            "contabo server"
            )
    fi

    printf '%s\n' "${servers[@]}"
}

# ------------------------------------------------------------
# fzf path (preferred)
# ------------------------------------------------------------
if command -v fzf &>/dev/null; then
    selected=$(get_servers | fzf \
        --prompt="  ssh  " \
        --pointer=">" \
        --marker="*" \
        --height=40% \
        --layout=reverse \
        --border=rounded \
        --info=inline \
        --color="dark,\
bg:#282828,\
bg+:#3c3836,\
fg:#ebdbb2,\
fg+:#fbf1c7,\
hl:#fabd2f,\
hl+:#fabd2f,\
border:#504945,\
prompt:#83a598,\
pointer:#fe8019,\
marker:#b8bb26,\
spinner:#d3869b,\
header:#a89984,\
info:#a89984" \
        --header="  ctrl+c to cancel" \
        --preview="echo {} | xargs -I% sh -c 'grep -A10 \"^Host %$\" ~/.ssh/config 2>/dev/null || echo \"no config entry\"'" \
        --preview-window="right:40%:wrap" \
        2>/dev/null) || exit 0

    [[ -z "$selected" ]] && exit 0
    TERM="$TERM_FALLBACK" kitten ssh "$selected"
    exit 0
fi

# ------------------------------------------------------------
# built-in picker (no fzf)
# ------------------------------------------------------------
mapfile -t SERVERS < <(get_servers)
TOTAL=${#SERVERS[@]}
SELECTED=0
QUERY=""
FILTERED=("${SERVERS[@]}")

draw() {
    clear
    printf '%s' "${GB_BG}"
    printf '\n'
    printf '  %s%s ssh picker%s\n' "${GB_YELLOW}${BOLD}" "" "${RESET}"
    printf '  %s%s%s\n\n' "${GB_DIM}" "arrow keys to navigate, enter to connect, esc to quit" "${RESET}"

    # search bar
    printf '  %ssearch:%s %s%s%s\n\n' \
        "${GB_DIM}" "${RESET}" \
        "${GB_FG}${BOLD}" "${QUERY}" "${RESET}"

    # server list
    local i=0
    for srv in "${FILTERED[@]}"; do
        if [[ $i -eq $SELECTED ]]; then
            printf '  %s%s > %s%s\n' "${GB_BG1}${GB_ORANGE}${BOLD}" "" "$srv" "${RESET}"
        else
            printf '  %s   %s%s\n' "${GB_DIM}" "$srv" "${RESET}"
        fi
        (( i++ ))
    done

    printf '\n  %s%s hosts found  •  install fzf for preview support%s\n' \
        "${GB_DIM}" "${#FILTERED[@]}" "${RESET}"
}

filter() {
    FILTERED=()
    SELECTED=0
    for srv in "${SERVERS[@]}"; do
        if [[ -z "$QUERY" || "${srv,,}" == *"${QUERY,,}"* ]]; then
            FILTERED+=("$srv")
        fi
    done
}

# hide cursor
tput civis
trap 'tput cnorm; clear' EXIT

while true; do
    draw

    # read a single keypress (handles escape sequences)
    IFS= read -r -s -N1 key

    # check for escape sequences (arrow keys)
    if [[ "$key" == $'\e' ]]; then
        IFS= read -r -s -N1 -t 0.1 k2 || true
        if [[ "$k2" == "[" ]]; then
            IFS= read -r -s -N1 -t 0.1 k3 || true
            case "$k3" in
                A)  # up
                    (( SELECTED > 0 )) && (( SELECTED-- ))
                    ;;
                B)  # down
                    (( SELECTED < ${#FILTERED[@]} - 1 )) && (( SELECTED++ ))
                    ;;
            esac
        else
            # plain escape — quit
            exit 0
        fi
        continue
    fi

    case "$key" in
        $'\n'|$'\r')
            [[ ${#FILTERED[@]} -eq 0 ]] && continue
            tput cnorm
            clear
            host="${FILTERED[$SELECTED]}"
            printf '%sconnecting to %s%s%s...\n\n' \
                "${GB_DIM}" "${GB_BLUE}${BOLD}" "$host" "${RESET}"
            TERM="$TERM_FALLBACK" kitten ssh "$host"
            exit 0
            ;;
        $'\x7f'|$'\b')
            # backspace
            QUERY="${QUERY%?}"
            filter
            ;;
        $'\x03')
            # ctrl+c
            exit 0
            ;;
        *)
            # printable character — append to query
            if [[ "$key" =~ ^[[:print:]]$ ]]; then
                QUERY+="$key"
                filter
            fi
            ;;
    esac
done