aboutsummaryrefslogtreecommitdiff
path: root/dot_config/hypr/scripts/executable_tmux-smart-nav
blob: 4275462268d15491bcc7da18736661c0d29062cf (plain) (blame)
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
#!/usr/bin/env bash

set -u

direction="${1:-}"
terminal_pid="${2:-}"

case "$direction" in
  left)
    tmux_key="C-h"
    ;;
  right)
    tmux_key="C-l"
    ;;
  up)
    tmux_key="C-k"
    ;;
  down)
    tmux_key="C-j"
    ;;
  *)
    printf 'usage: %s <left|right|up|down> <terminal-pid>\n' "$0" >&2
    exit 2
    ;;
esac

if [[ ! "$terminal_pid" =~ ^[0-9]+$ ]] || ! command -v tmux >/dev/null 2>&1; then
  printf 'hyprland\n'
  exit
fi

# A tmux client is a descendant of the terminal process that owns it. This
# avoids acting on a client in another Ghostty window.
is_descendant_of() {
  local pid="$1"
  local parent

  while [[ "$pid" =~ ^[0-9]+$ && "$pid" -gt 1 ]]; do
    if [[ "$pid" == "$terminal_pid" ]]; then
      return 0
    fi

    parent="$(ps -o ppid= -p "$pid" 2>/dev/null | tr -d '[:space:]')"
    [[ -n "$parent" && "$parent" != "$pid" ]] || return 1
    pid="$parent"
  done

  return 1
}

client_tty=""
while read -r client_pid tty; do
  if is_descendant_of "$client_pid"; then
    client_tty="$tty"
    break
  fi
done < <(tmux list-clients -F '#{client_pid} #{client_tty}' 2>/dev/null)

if [[ -z "$client_tty" ]]; then
  printf 'hyprland\n'
  exit
fi

pane_id="$(tmux display-message -p -c "$client_tty" '#{pane_id}' 2>/dev/null)"
pane_command="$(tmux display-message -p -c "$client_tty" '#{pane_current_command}' 2>/dev/null)"

# Ghostty has one title for the whole tmux window, so it cannot identify the
# focused pane's process. Ask Neovim through a pane-specific socket only when
# tmux says this exact pane is running it.
case "${pane_command,,}" in
  nvim|vim)
    safe_pane_id="${pane_id//[^a-zA-Z0-9_.-]/_}"
    socket_path="${XDG_RUNTIME_DIR:-/tmp}/nvim-hypr-nav-pane-${safe_pane_id}.sock"
    response="$(
      printf 'nav:%s\n' "$direction" |
        socat -T 0.25 -,ignoreeof "UNIX-CONNECT:$socket_path" 2>/dev/null || true
    )"

    if [[ "$response" == *success* ]]; then
      printf 'handled\n'
      exit
    fi
    ;;
esac

# Send the directional key through the matching client so tmux resolves it
# against that client's own active pane. Its existing C-h/j/k/l bindings also
# retain the configured Neovim handoff.
at_edge="$(tmux display-message -p -c "$client_tty" "#{pane_at_${direction}}" 2>/dev/null)"

if [[ -z "$pane_id" || "$at_edge" == "1" ]]; then
  printf 'hyprland\n'
  exit
fi

if tmux send-keys -c "$client_tty" -K "$tmux_key" >/dev/null 2>&1; then
  selected_pane_id="$(tmux display-message -p -c "$client_tty" '#{pane_id}' 2>/dev/null)"

  if [[ "$selected_pane_id" != "$pane_id" ]]; then
    printf 'handled\n'
    exit
  fi
fi

printf 'hyprland\n'