Merge branch 'main' into issue-151-ssh-remote-port-proxying
# Conflicts: # CLI/cmux.swift # Sources/ContentView.swift # Sources/GhosttyTerminalView.swift # Sources/Panels/BrowserPanel.swift # Sources/Panels/BrowserPanelView.swift # Sources/TabManager.swift # Sources/TerminalController.swift # Sources/Workspace.swift # Sources/WorkspaceContentView.swift # ghostty
This commit is contained in:
commit
bdebc8ecc9
205 changed files with 107859 additions and 6333 deletions
|
|
@ -13,7 +13,9 @@
|
|||
# - CMUX_ZSH_ZDOTDIR (set by cmux when it overwrote a user-provided ZDOTDIR)
|
||||
# - unset (zsh treats unset ZDOTDIR as $HOME)
|
||||
|
||||
builtin typeset _cmux_had_ghostty_zdotdir=0
|
||||
if [[ -n "${GHOSTTY_ZSH_ZDOTDIR+X}" ]]; then
|
||||
_cmux_had_ghostty_zdotdir=1
|
||||
builtin export ZDOTDIR="$GHOSTTY_ZSH_ZDOTDIR"
|
||||
builtin unset GHOSTTY_ZSH_ZDOTDIR
|
||||
elif [[ -n "${CMUX_ZSH_ZDOTDIR+X}" ]]; then
|
||||
|
|
@ -31,7 +33,9 @@ fi
|
|||
if [[ -o interactive ]]; then
|
||||
# We overwrote GhosttyKit's injected ZDOTDIR, so manually load Ghostty's
|
||||
# zsh integration if available.
|
||||
if [[ -n "${GHOSTTY_RESOURCES_DIR:-}" ]]; then
|
||||
# Guard on GHOSTTY_ZSH_ZDOTDIR being set by Ghostty. When users configure
|
||||
# shell-integration=none, Ghostty does not set this and we must skip.
|
||||
if [[ "$_cmux_had_ghostty_zdotdir" == "1" && -n "${GHOSTTY_RESOURCES_DIR:-}" ]]; then
|
||||
builtin typeset _cmux_ghostty="$GHOSTTY_RESOURCES_DIR/shell-integration/zsh/ghostty-integration"
|
||||
[[ -r "$_cmux_ghostty" ]] && builtin source -- "$_cmux_ghostty"
|
||||
fi
|
||||
|
|
@ -43,5 +47,5 @@ fi
|
|||
fi
|
||||
fi
|
||||
|
||||
builtin unset _cmux_file _cmux_ghostty _cmux_integ
|
||||
builtin unset _cmux_file _cmux_ghostty _cmux_integ _cmux_had_ghostty_zdotdir
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ _CMUX_GIT_LAST_PWD="${_CMUX_GIT_LAST_PWD:-}"
|
|||
_CMUX_GIT_LAST_RUN="${_CMUX_GIT_LAST_RUN:-0}"
|
||||
_CMUX_GIT_JOB_PID="${_CMUX_GIT_JOB_PID:-}"
|
||||
_CMUX_GIT_JOB_STARTED_AT="${_CMUX_GIT_JOB_STARTED_AT:-0}"
|
||||
_CMUX_GIT_HEAD_LAST_PWD="${_CMUX_GIT_HEAD_LAST_PWD:-}"
|
||||
_CMUX_GIT_HEAD_PATH="${_CMUX_GIT_HEAD_PATH:-}"
|
||||
_CMUX_GIT_HEAD_SIGNATURE="${_CMUX_GIT_HEAD_SIGNATURE:-}"
|
||||
_CMUX_PR_LAST_PWD="${_CMUX_PR_LAST_PWD:-}"
|
||||
_CMUX_PR_LAST_RUN="${_CMUX_PR_LAST_RUN:-0}"
|
||||
_CMUX_PR_JOB_PID="${_CMUX_PR_JOB_PID:-}"
|
||||
|
|
@ -51,6 +54,41 @@ _CMUX_PORTS_LAST_RUN="${_CMUX_PORTS_LAST_RUN:-0}"
|
|||
_CMUX_TTY_NAME="${_CMUX_TTY_NAME:-}"
|
||||
_CMUX_TTY_REPORTED="${_CMUX_TTY_REPORTED:-0}"
|
||||
|
||||
_cmux_git_resolve_head_path() {
|
||||
# Resolve the HEAD file path without invoking git (fast; works for worktrees).
|
||||
local dir="$PWD"
|
||||
while :; do
|
||||
if [[ -d "$dir/.git" ]]; then
|
||||
printf '%s\n' "$dir/.git/HEAD"
|
||||
return 0
|
||||
fi
|
||||
if [[ -f "$dir/.git" ]]; then
|
||||
local line gitdir
|
||||
IFS= read -r line < "$dir/.git" || line=""
|
||||
if [[ "$line" == gitdir:* ]]; then
|
||||
gitdir="${line#gitdir:}"
|
||||
gitdir="${gitdir## }"
|
||||
gitdir="${gitdir%% }"
|
||||
[[ -n "$gitdir" ]] || return 1
|
||||
[[ "$gitdir" != /* ]] && gitdir="$dir/$gitdir"
|
||||
printf '%s\n' "$gitdir/HEAD"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
[[ "$dir" == "/" || -z "$dir" ]] && break
|
||||
dir="$(dirname "$dir")"
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
_cmux_git_head_signature() {
|
||||
local head_path="$1"
|
||||
[[ -n "$head_path" && -r "$head_path" ]] || return 1
|
||||
local line
|
||||
IFS= read -r line < "$head_path" || return 1
|
||||
printf '%s\n' "$line"
|
||||
}
|
||||
|
||||
_cmux_report_tty_once() {
|
||||
# Send the TTY name to the app once per session so the batched port scanner
|
||||
# knows which TTY belongs to this panel.
|
||||
|
|
@ -62,7 +100,7 @@ _cmux_report_tty_once() {
|
|||
_CMUX_TTY_REPORTED=1
|
||||
{
|
||||
_cmux_send "report_tty $_CMUX_TTY_NAME --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID"
|
||||
} >/dev/null 2>&1 &
|
||||
} >/dev/null 2>&1 & disown
|
||||
}
|
||||
|
||||
_cmux_ports_kick() {
|
||||
|
|
@ -74,7 +112,7 @@ _cmux_ports_kick() {
|
|||
_CMUX_PORTS_LAST_RUN=$SECONDS
|
||||
{
|
||||
_cmux_send "ports_kick --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID"
|
||||
} >/dev/null 2>&1 &
|
||||
} >/dev/null 2>&1 & disown
|
||||
}
|
||||
|
||||
_cmux_prompt_command() {
|
||||
|
|
@ -123,7 +161,26 @@ _cmux_prompt_command() {
|
|||
{
|
||||
local qpwd="${pwd//\"/\\\"}"
|
||||
_cmux_send "report_pwd \"${qpwd}\" --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID"
|
||||
} >/dev/null 2>&1 &
|
||||
} >/dev/null 2>&1 & disown
|
||||
fi
|
||||
|
||||
# Branch can change via aliases/tools while an older probe is still in flight.
|
||||
# Track .git/HEAD content so we can restart stale probes immediately.
|
||||
local git_head_changed=0
|
||||
if [[ "$pwd" != "$_CMUX_GIT_HEAD_LAST_PWD" ]]; then
|
||||
_CMUX_GIT_HEAD_LAST_PWD="$pwd"
|
||||
_CMUX_GIT_HEAD_PATH="$(_cmux_git_resolve_head_path 2>/dev/null || true)"
|
||||
_CMUX_GIT_HEAD_SIGNATURE=""
|
||||
fi
|
||||
if [[ -n "$_CMUX_GIT_HEAD_PATH" ]]; then
|
||||
local head_signature
|
||||
head_signature="$(_cmux_git_head_signature "$_CMUX_GIT_HEAD_PATH" 2>/dev/null || true)"
|
||||
if [[ -n "$head_signature" && "$head_signature" != "$_CMUX_GIT_HEAD_SIGNATURE" ]]; then
|
||||
_CMUX_GIT_HEAD_SIGNATURE="$head_signature"
|
||||
git_head_changed=1
|
||||
# Also invalidate the PR probe so it refreshes with the new branch.
|
||||
_CMUX_PR_LAST_RUN=0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Git branch/dirty can change without a directory change (e.g. `git checkout`),
|
||||
|
|
@ -131,7 +188,7 @@ _cmux_prompt_command() {
|
|||
# When pwd changes (cd into a different repo), kill the old probe and start fresh
|
||||
# so the sidebar picks up the new branch immediately.
|
||||
if [[ -n "$_CMUX_GIT_JOB_PID" ]] && kill -0 "$_CMUX_GIT_JOB_PID" 2>/dev/null; then
|
||||
if [[ "$pwd" != "$_CMUX_GIT_LAST_PWD" ]]; then
|
||||
if [[ "$pwd" != "$_CMUX_GIT_LAST_PWD" || "$git_head_changed" == "1" ]]; then
|
||||
kill "$_CMUX_GIT_JOB_PID" >/dev/null 2>&1 || true
|
||||
_CMUX_GIT_JOB_PID=""
|
||||
_CMUX_GIT_JOB_STARTED_AT=0
|
||||
|
|
@ -154,20 +211,21 @@ _cmux_prompt_command() {
|
|||
fi
|
||||
} >/dev/null 2>&1 &
|
||||
_CMUX_GIT_JOB_PID=$!
|
||||
disown
|
||||
_CMUX_GIT_JOB_STARTED_AT=$now
|
||||
fi
|
||||
|
||||
# Pull request metadata (number/state/url):
|
||||
# refresh on cwd change and periodically to avoid stale status.
|
||||
# refresh on cwd change, HEAD change, and periodically to avoid stale status.
|
||||
if [[ -n "$_CMUX_PR_JOB_PID" ]] && kill -0 "$_CMUX_PR_JOB_PID" 2>/dev/null; then
|
||||
if [[ "$pwd" != "$_CMUX_PR_LAST_PWD" ]]; then
|
||||
if [[ "$pwd" != "$_CMUX_PR_LAST_PWD" || "$git_head_changed" == "1" ]]; then
|
||||
kill "$_CMUX_PR_JOB_PID" >/dev/null 2>&1 || true
|
||||
_CMUX_PR_JOB_PID=""
|
||||
_CMUX_PR_JOB_STARTED_AT=0
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$pwd" != "$_CMUX_PR_LAST_PWD" ]] || (( now - _CMUX_PR_LAST_RUN >= 60 )); then
|
||||
if [[ "$pwd" != "$_CMUX_PR_LAST_PWD" || "$git_head_changed" == "1" ]] || (( now - _CMUX_PR_LAST_RUN >= 60 )); then
|
||||
if [[ -z "$_CMUX_PR_JOB_PID" ]] || ! kill -0 "$_CMUX_PR_JOB_PID" 2>/dev/null; then
|
||||
_CMUX_PR_LAST_PWD="$pwd"
|
||||
_CMUX_PR_LAST_RUN=$now
|
||||
|
|
@ -197,6 +255,7 @@ _cmux_prompt_command() {
|
|||
fi
|
||||
} >/dev/null 2>&1 &
|
||||
_CMUX_PR_JOB_PID=$!
|
||||
disown
|
||||
_CMUX_PR_JOB_STARTED_AT=$now
|
||||
fi
|
||||
fi
|
||||
|
|
@ -205,6 +264,7 @@ _cmux_prompt_command() {
|
|||
if (( now - _CMUX_PORTS_LAST_RUN >= 10 )); then
|
||||
_cmux_ports_kick
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
_cmux_install_prompt_command() {
|
||||
|
|
|
|||
|
|
@ -45,8 +45,8 @@ typeset -g _CMUX_GIT_JOB_STARTED_AT=0
|
|||
typeset -g _CMUX_GIT_FORCE=0
|
||||
typeset -g _CMUX_GIT_HEAD_LAST_PWD=""
|
||||
typeset -g _CMUX_GIT_HEAD_PATH=""
|
||||
typeset -g _CMUX_GIT_HEAD_MTIME=0
|
||||
typeset -g _CMUX_HAVE_ZSTAT=0
|
||||
typeset -g _CMUX_GIT_HEAD_SIGNATURE=""
|
||||
typeset -g _CMUX_GIT_HEAD_WATCH_PID=""
|
||||
typeset -g _CMUX_PR_LAST_PWD=""
|
||||
typeset -g _CMUX_PR_LAST_RUN=0
|
||||
typeset -g _CMUX_PR_JOB_PID=""
|
||||
|
|
@ -155,19 +155,6 @@ _cmux_install_winch_guard() {
|
|||
}
|
||||
_cmux_install_winch_guard
|
||||
|
||||
_cmux_ensure_zstat() {
|
||||
# zstat is substantially cheaper than spawning external `stat`.
|
||||
if (( _CMUX_HAVE_ZSTAT != 0 )); then
|
||||
return 0
|
||||
fi
|
||||
if zmodload -F zsh/stat b:zstat 2>/dev/null; then
|
||||
_CMUX_HAVE_ZSTAT=1
|
||||
return 0
|
||||
fi
|
||||
_CMUX_HAVE_ZSTAT=-1
|
||||
return 1
|
||||
}
|
||||
|
||||
_cmux_git_resolve_head_path() {
|
||||
# Resolve the HEAD file path without invoking git (fast; works for worktrees).
|
||||
local dir="$PWD"
|
||||
|
|
@ -195,27 +182,15 @@ _cmux_git_resolve_head_path() {
|
|||
return 1
|
||||
}
|
||||
|
||||
_cmux_git_head_mtime() {
|
||||
_cmux_git_head_signature() {
|
||||
local head_path="$1"
|
||||
[[ -n "$head_path" && -f "$head_path" ]] || { print -r -- 0; return 0; }
|
||||
|
||||
if _cmux_ensure_zstat; then
|
||||
typeset -A st
|
||||
if zstat -H st +mtime -- "$head_path" 2>/dev/null; then
|
||||
print -r -- "${st[mtime]:-0}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Fallback for environments where zsh/stat isn't available.
|
||||
if command -v stat >/dev/null 2>&1; then
|
||||
local mtime
|
||||
mtime="$(stat -f %m "$head_path" 2>/dev/null || stat -c %Y "$head_path" 2>/dev/null || echo 0)"
|
||||
print -r -- "$mtime"
|
||||
[[ -n "$head_path" && -r "$head_path" ]] || return 1
|
||||
local line=""
|
||||
if IFS= read -r line < "$head_path"; then
|
||||
print -r -- "$line"
|
||||
return 0
|
||||
fi
|
||||
|
||||
print -r -- 0
|
||||
return 1
|
||||
}
|
||||
|
||||
_cmux_report_tty_once() {
|
||||
|
|
@ -244,6 +219,65 @@ _cmux_ports_kick() {
|
|||
} >/dev/null 2>&1 &!
|
||||
}
|
||||
|
||||
_cmux_report_git_branch_for_path() {
|
||||
local repo_path="$1"
|
||||
[[ -n "$repo_path" ]] || return 0
|
||||
[[ -S "$CMUX_SOCKET_PATH" ]] || return 0
|
||||
[[ -n "$CMUX_TAB_ID" ]] || return 0
|
||||
[[ -n "$CMUX_PANEL_ID" ]] || return 0
|
||||
|
||||
local branch dirty_opt="" first
|
||||
branch="$(git -C "$repo_path" branch --show-current 2>/dev/null)"
|
||||
if [[ -n "$branch" ]]; then
|
||||
first="$(git -C "$repo_path" status --porcelain -uno 2>/dev/null | head -1)"
|
||||
[[ -n "$first" ]] && dirty_opt="--status=dirty"
|
||||
_cmux_send "report_git_branch $branch $dirty_opt --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID"
|
||||
else
|
||||
_cmux_send "clear_git_branch --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID"
|
||||
fi
|
||||
}
|
||||
|
||||
_cmux_stop_git_head_watch() {
|
||||
if [[ -n "$_CMUX_GIT_HEAD_WATCH_PID" ]]; then
|
||||
kill "$_CMUX_GIT_HEAD_WATCH_PID" >/dev/null 2>&1 || true
|
||||
_CMUX_GIT_HEAD_WATCH_PID=""
|
||||
fi
|
||||
}
|
||||
|
||||
_cmux_start_git_head_watch() {
|
||||
[[ -S "$CMUX_SOCKET_PATH" ]] || return 0
|
||||
[[ -n "$CMUX_TAB_ID" ]] || return 0
|
||||
[[ -n "$CMUX_PANEL_ID" ]] || return 0
|
||||
|
||||
local watch_pwd="$PWD"
|
||||
local watch_head_path
|
||||
watch_head_path="$(_cmux_git_resolve_head_path 2>/dev/null || true)"
|
||||
[[ -n "$watch_head_path" ]] || return 0
|
||||
|
||||
local watch_head_signature
|
||||
watch_head_signature="$(_cmux_git_head_signature "$watch_head_path" 2>/dev/null || true)"
|
||||
|
||||
_CMUX_GIT_HEAD_LAST_PWD="$watch_pwd"
|
||||
_CMUX_GIT_HEAD_PATH="$watch_head_path"
|
||||
_CMUX_GIT_HEAD_SIGNATURE="$watch_head_signature"
|
||||
|
||||
_cmux_stop_git_head_watch
|
||||
{
|
||||
local last_signature="$watch_head_signature"
|
||||
while true; do
|
||||
sleep 1
|
||||
|
||||
local signature
|
||||
signature="$(_cmux_git_head_signature "$watch_head_path" 2>/dev/null || true)"
|
||||
if [[ -n "$signature" && "$signature" != "$last_signature" ]]; then
|
||||
last_signature="$signature"
|
||||
_cmux_report_git_branch_for_path "$watch_pwd"
|
||||
fi
|
||||
done
|
||||
} >/dev/null 2>&1 &!
|
||||
_CMUX_GIT_HEAD_WATCH_PID=$!
|
||||
}
|
||||
|
||||
_cmux_preexec() {
|
||||
if [[ -z "$_CMUX_TTY_NAME" ]]; then
|
||||
local t
|
||||
|
|
@ -265,9 +299,12 @@ _cmux_preexec() {
|
|||
# Register TTY + kick batched port scan for foreground commands (servers).
|
||||
_cmux_report_tty_once
|
||||
_cmux_ports_kick
|
||||
_cmux_start_git_head_watch
|
||||
}
|
||||
|
||||
_cmux_precmd() {
|
||||
_cmux_stop_git_head_watch
|
||||
|
||||
# Skip if socket doesn't exist yet
|
||||
[[ -S "$CMUX_SOCKET_PATH" ]] || return 0
|
||||
[[ -n "$CMUX_TAB_ID" ]] || return 0
|
||||
|
|
@ -328,6 +365,8 @@ _cmux_precmd() {
|
|||
fi
|
||||
|
||||
# Git branch/dirty: update immediately on directory change, otherwise every ~3s.
|
||||
# While a foreground command is running, _cmux_start_git_head_watch probes HEAD
|
||||
# once per second so agent-initiated git checkouts still surface quickly.
|
||||
local should_git=0
|
||||
|
||||
# Git branch can change without a `git ...`-prefixed command (aliases like `gco`,
|
||||
|
|
@ -335,13 +374,13 @@ _cmux_precmd() {
|
|||
if [[ "$pwd" != "$_CMUX_GIT_HEAD_LAST_PWD" ]]; then
|
||||
_CMUX_GIT_HEAD_LAST_PWD="$pwd"
|
||||
_CMUX_GIT_HEAD_PATH="$(_cmux_git_resolve_head_path 2>/dev/null || true)"
|
||||
_CMUX_GIT_HEAD_MTIME=0
|
||||
_CMUX_GIT_HEAD_SIGNATURE=""
|
||||
fi
|
||||
if [[ -n "$_CMUX_GIT_HEAD_PATH" ]]; then
|
||||
local head_mtime
|
||||
head_mtime="$(_cmux_git_head_mtime "$_CMUX_GIT_HEAD_PATH" 2>/dev/null || echo 0)"
|
||||
if [[ -n "$head_mtime" && "$head_mtime" != 0 && "$head_mtime" != "$_CMUX_GIT_HEAD_MTIME" ]]; then
|
||||
_CMUX_GIT_HEAD_MTIME="$head_mtime"
|
||||
local head_signature
|
||||
head_signature="$(_cmux_git_head_signature "$_CMUX_GIT_HEAD_PATH" 2>/dev/null || true)"
|
||||
if [[ -n "$head_signature" && "$head_signature" != "$_CMUX_GIT_HEAD_SIGNATURE" ]]; then
|
||||
_CMUX_GIT_HEAD_SIGNATURE="$head_signature"
|
||||
# Treat HEAD file change like a git command — force-replace any
|
||||
# running probe so the sidebar picks up the new branch immediately.
|
||||
_CMUX_GIT_FORCE=1
|
||||
|
|
@ -381,16 +420,7 @@ _cmux_precmd() {
|
|||
_CMUX_GIT_LAST_PWD="$pwd"
|
||||
_CMUX_GIT_LAST_RUN=$now
|
||||
{
|
||||
local branch dirty_opt=""
|
||||
branch=$(git branch --show-current 2>/dev/null)
|
||||
if [[ -n "$branch" ]]; then
|
||||
local first
|
||||
first=$(git status --porcelain -uno 2>/dev/null | head -1)
|
||||
[[ -n "$first" ]] && dirty_opt="--status=dirty"
|
||||
_cmux_send "report_git_branch $branch $dirty_opt --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID"
|
||||
else
|
||||
_cmux_send "clear_git_branch --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID"
|
||||
fi
|
||||
_cmux_report_git_branch_for_path "$pwd"
|
||||
} >/dev/null 2>&1 &!
|
||||
_CMUX_GIT_JOB_PID=$!
|
||||
_CMUX_GIT_JOB_STARTED_AT=$now
|
||||
|
|
@ -488,7 +518,12 @@ _cmux_fix_path() {
|
|||
add-zsh-hook -d precmd _cmux_fix_path
|
||||
}
|
||||
|
||||
_cmux_zshexit() {
|
||||
_cmux_stop_git_head_watch
|
||||
}
|
||||
|
||||
autoload -Uz add-zsh-hook
|
||||
add-zsh-hook preexec _cmux_preexec
|
||||
add-zsh-hook precmd _cmux_precmd
|
||||
add-zsh-hook precmd _cmux_fix_path
|
||||
add-zsh-hook zshexit _cmux_zshexit
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue