-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall-get_git_diff.sh
More file actions
executable file
·285 lines (245 loc) · 8.38 KB
/
install-get_git_diff.sh
File metadata and controls
executable file
·285 lines (245 loc) · 8.38 KB
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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
#!/usr/bin/env bash
set -euo pipefail
# github: @iyiolacak
[[ "${DEBUG:-0}" == "1" ]] && set -x
APP_ID="dropgitdiff"
BIN_NAME="$APP_ID"
BIN_PATH="$HOME/.local/bin/$BIN_NAME"
HYPR_DIR="$HOME/.config/hypr"
CONF_MAIN="$HYPR_DIR/hyprland.conf"
CONF_D_DIR="$HYPR_DIR/conf.d"
CONF_SNIPPET="$CONF_D_DIR/99-${APP_ID}.conf"
BACKUP_MAIN="${CONF_MAIN}.bak.${APP_ID}"
MARK_START="# >>> ${APP_ID} start"
MARK_END="# <<< ${APP_ID} end"
BIND_CMD='bash -lc "~/.local/bin/dropgitdiff"'
BIND_LINE="bind = SUPER SHIFT, D, exec, ${BIND_CMD}"
FILES_ONLY=0
UNINSTALL=0
for a in "$@"; do
case "$a" in
--files-only) FILES_ONLY=1 ;;
--uninstall) UNINSTALL=1 ;;
*) echo "Unknown arg: $a" >&2; exit 2 ;;
esac
done
say() { printf '[%s] %s\n' "$APP_ID" "$*"; }
warn() { printf '[%s] WARN: %s\n' "$APP_ID" "$*" >&2; }
ensure_dirs() {
mkdir -p "$(dirname "$BIN_PATH")"
mkdir -p "$HYPR_DIR"
}
conf_d_is_sourced() {
# true if main config sources any conf.d path
[ -f "$CONF_MAIN" ] && grep -Ei '^\s*source\s*=.*conf\.d' "$CONF_MAIN" >/dev/null
}
apply_live_bind() {
if command -v hyprctl >/dev/null 2>&1; then
hyprctl keyword bind "SUPER_SHIFT, D, exec, ${BIND_CMD}" >/dev/null 2>&1 || warn "live bind failed"
say "Applied live binding: SUPER+SHIFT+D"
else
warn "hyprctl not found; skipping live apply"
fi
}
persist_bind() {
# Prefer conf.d drop-in for cleanliness, only if it's actually sourced from the main config
if [ -d "$CONF_D_DIR" ] && conf_d_is_sourced; then
printf '%s\n' "$BIND_LINE" > "$CONF_SNIPPET"
say "Wrote bind to $CONF_SNIPPET"
return
fi
# Fallback: append to main config with markers (idempotent)
if [ ! -e "$CONF_MAIN" ]; then
warn "No $CONF_MAIN found; creating it"
: > "$CONF_MAIN"
fi
if grep -Fq "$MARK_START" "$CONF_MAIN"; then
awk -v s="$MARK_START" -v e="$MARK_END" -v line="$BIND_LINE" '
$0==s{inblk=1; print; print line; next}
$0==e{inblk=0; print; next}
inblk!=1{print}
' "$CONF_MAIN" > "${CONF_MAIN}.new"
mv "${CONF_MAIN}.new" "$CONF_MAIN"
say "Refreshed bind block in $CONF_MAIN"
else
cp -n "$CONF_MAIN" "$BACKUP_MAIN" || true
{
echo
echo "$MARK_START"
echo "$BIND_LINE"
echo "$MARK_END"
echo
} >> "$CONF_MAIN"
say "Appended bind to $CONF_MAIN (backup: $BACKUP_MAIN)"
fi
}
remove_bind() {
if [ -e "$CONF_SNIPPET" ]; then
rm -f "$CONF_SNIPPET"
say "Removed $CONF_SNIPPET"
fi
if [ -e "$CONF_MAIN" ] && grep -Fq "$MARK_START" "$CONF_MAIN"; then
awk -v s="$MARK_START" -v e="$MARK_END" '
$0==s{inblk=1; next}
$0==e{inblk=0; next}
inblk!=1{print}
' "$CONF_MAIN" > "${CONF_MAIN}.new"
mv "${CONF_MAIN}.new" "$CONF_MAIN"
say "Removed marked bind block from $CONF_MAIN"
fi
}
write_script_verbatim() {
cat > "$BIN_PATH" <<'EOF_SCRIPT'
#!/usr/bin/env bash
set -euo pipefail
# DEBUG=1 to print trace
if [[ "${DEBUG:-0}" == "1" ]]; then set -x; fi
OUTFILE="$HOME/git_diff.md"
# Optional: edit these for deterministic fallback
SEARCH_PATHS=(
"$HOME/Projects"
"$HOME/Projects/job"
"$HOME/Projects/personal"
"$HOME/projects"
"$HOME/work"
)
FIND_MAXDEPTH=3
# --- active window (Hyprland)
active_json="$(hyprctl activewindow -j 2>/dev/null || true)"
[ -n "$active_json" ] || { notify-send "Git Diff" "No active window info (hyprctl)."; exit 1; }
title="$(printf '%s' "$active_json" | jq -r '.title // empty')"
class="$(printf '%s' "$active_json" | jq -r '.class // empty')"
pid="$(printf '%s' "$active_json" | jq -r '.pid // empty')"
case "$class" in
*Code*|*code-oss*|*VSCodium*) ;; # OK
*) notify-send "Git Diff" "Focused window is not VS Code."; exit 1 ;;
esac
# --- parse ${rootName} from title
norm="$(sed -E 's/[[:space:]]+—[[:space:]]+/ - /g' <<<"$title")"
p1="$(awk -F' - ' '{print $1}' <<<"$norm")"
p2="$(awk -F' - ' '{print $2}' <<<"$norm")"
p3="$(awk -F' - ' '{print $3}' <<<"$norm")"
if [[ "${p3:-}" =~ [Vv]isual[[:space:]]+Studio[[:space:]]+Code|VSCodium|Code[[:space:]]-?[[:space:]]OSS ]]; then
root_name="$p2"
else
root_name="$(sed -E 's/[[:space:]]+-[[:space:]]+(Visual Studio Code|VSCodium|Code - OSS)$//' <<<"$norm")"
fi
root_name="$(sed -E 's/^[[:space:]]+|[[:space:]]+$//g' <<<"$root_name")"
[ -n "$root_name" ] || { notify-send "Git Diff" "Could not parse folder from title: $title"; exit 1; }
# --- Resolver 0: strict zoxide (prefer exact tail match)
workspace=""
if command -v zoxide >/dev/null 2>&1; then
# exact tail match first (…/root_name)
z_exact="$(zoxide query -l | awk -v rn="$root_name" -F/ '$(NF)==rn {print}' | head -n1 || true)"
if [ -n "$z_exact" ]; then
workspace="$z_exact"
else
# relaxed contains as a fallback (may hit subdirs; we will re-check .git later)
z_relaxed="$(zoxide query -l | grep -F "$root_name" | head -n1 || true)"
[ -n "$z_relaxed" ] && workspace="$z_relaxed"
fi
fi
# If zoxide gave us a subfolder, prefer its repo root later via git rev-parse.
# --- Resolver 1: VS Code --status by PID (only if not already resolved)
if [ -z "$workspace" ] || [ ! -e "$workspace" ]; then
CODEBIN=""
for c in code codium code-oss; do
if command -v "$c" >/dev/null 2>&1; then CODEBIN="$c"; break; fi
done
if [ -n "$CODEBIN" ]; then
status="$("$CODEBIN" --status 2>/dev/null || true)"
if [ -n "$status" ]; then
block_for_pid="$(awk -v pid="$pid" '
$0 ~ pid {print; cap=1; next}
cap && NF==0 {cap=0}
cap {print}
' <<<"$status")"
if [ -n "$block_for_pid" ]; then
ws_hint="$(printf '%s\n' "$block_for_pid" \
| sed -nE 's/.*(folder|workspace|uri|path)[^:]*:[[:space:]]+//Ip;T; p' \
| head -n1 \
| sed -E 's/^"//; s/"$//')"
if [ -z "$ws_hint" ]; then
ws_hint="$(printf '%s\n' "$block_for_pid" | grep -Eo '(/[^[:space:]]+)+' | head -n1 || true)"
fi
[ -n "$ws_hint" ] && workspace="$ws_hint"
fi
# If still empty, disambiguate by root_name among all paths listed
if [ -z "$workspace" ] || [ ! -e "$workspace" ]; then
mapfile -t hits < <(printf '%s\n' "$status" \
| grep -Eo '(/[^[:space:]]+)+' \
| awk -v rn="$root_name" -F/ '$(NF)==rn {print}' \
| sort -u)
if [ ${#hits[@]} -eq 1 ]; then
workspace="${hits[0]}"
elif [ ${#hits[@]} -gt 1 ] && [ -n "${block_for_pid:-}" ]; then
for h in "${hits[@]}"; do
if grep -Fq -- "$h" <<<"$block_for_pid"; then workspace="$h"; break; fi
done
[ -z "$workspace" ] && workspace="${hits[0]}"
fi
fi
fi
fi
fi
# --- Resolver 2: deterministic bases (.../root_name)
if [ -z "$workspace" ] || [ ! -e "$workspace" ]; then
for base in "${SEARCH_PATHS[@]}"; do
cand="$base/$root_name"
if [ -d "$cand" ]; then workspace="$cand"; break; fi
done
fi
# --- Resolver 3: bounded find of git dirs named root_name
if [ -z "$workspace" ] || [ ! -e "$workspace" ]; then
for base in "${SEARCH_PATHS[@]}"; do
[ -d "$base" ] || continue
cand="$(find "$base" -maxdepth "$FIND_MAXDEPTH" -type d -name "$root_name" -exec test -d "{}/.git" ';' -print -quit 2>/dev/null || true)"
if [ -n "$cand" ]; then workspace="$cand"; break; fi
done
fi
# Normalize URI/workspace-file
if [[ "${workspace:-}" =~ ^file:// ]]; then
workspace="${workspace#file://}"
workspace="$(printf '%b' "${workspace//%/\\x}")"
fi
if [ -f "${workspace:-}" ] && [[ "$workspace" =~ \.code-workspace$ ]]; then
workspace="$(dirname "$workspace")"
fi
[ -n "${workspace:-}" ] || { notify-send "Git Diff" "Cannot resolve path for '$root_name'."; exit 1; }
# --- repo root & diff
cd "$workspace"
repo_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
[ -n "$repo_root" ] || { notify-send "Git Diff" "Not a git repo: $workspace"; exit 1; }
cd "$repo_root"
git diff > "$OUTFILE"
# Copy the FILE itself, not the text
if command -v wl-copy >/dev/null 2>&1; then
wl-copy --type text/uri-list "file://$OUTFILE"
notify-send "Git Diff" "Copied file: $OUTFILE"
else
notify-send "Git Diff" "Saved $OUTFILE (wl-copy not found)."
fi
EOF_SCRIPT
chmod +x "$BIN_PATH"
say "Installed script to $BIN_PATH"
}
uninstall_all() {
remove_bind
rm -f "$BIN_PATH"
say "Removed $BIN_PATH"
say "Uninstall complete."
}
# ---------------- Main ----------------
if (( UNINSTALL )); then
uninstall_all
exit 0
fi
ensure_dirs
write_script_verbatim
if (( FILES_ONLY )); then
say "Installed script only (--files-only)."
exit 0
fi
persist_bind
apply_live_bind
say "Done. Use SUPER+SHIFT+D."