Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 88 additions & 48 deletions bin/easywork
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,30 @@ _detect_rc_file() {
esac
}

# ─── Multi-shell rc file detection for completion injection ─────
# Returns all rc files that should receive completion source lines.
# Format: "shell_type:absolute_path" (one per line)
_detect_completion_rc_files() {
local os_type
os_type="$(detect_os)"
local files=()

# zsh always uses .zshrc
files+=("zsh:$HOME/.zshrc")

# bash: platform-specific primary rc
if [[ "$os_type" == "macos" ]]; then
files+=("bash:$HOME/.bash_profile")
# .bashrc is a secondary file on macOS — include if it exists
[[ -f "$HOME/.bashrc" ]] && files+=("bash:$HOME/.bashrc")
else
files+=("bash:$HOME/.bashrc")
[[ -f "$HOME/.bash_profile" ]] && files+=("bash:$HOME/.bash_profile")
fi

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

_setup_completions() {
if [[ "${DRY_RUN:-false}" == "true" ]]; then
log_info "[DRY-RUN] 将配置 shell 补全"
Expand All @@ -286,8 +310,11 @@ _setup_completions() {
local mod_list
mod_list="$(list_modules_sorted 2> /dev/null | tr '\n' ' ' || true)"

# Bash completion — context-aware: flags, subcommands, module names
# ── Bash completion — context-aware: flags, subcommands, module names ──
cat > "$comp_dir/easywork.bash" << BASH_COMP
# EasyWork bash completion — generated by easywork ${EASYWORK_VERSION}
# DO NOT EDIT — regenerated on 'easywork install'

_easywork() {
COMPREPLY=()
local cur
Expand Down Expand Up @@ -324,47 +351,63 @@ _easywork() {
complete -F _easywork easywork
BASH_COMP

# Zsh completion — context-aware with flag descriptions and subcommand routing
# ── Zsh completion — explicit compdef registration (sourced directly) ──
cat > "$comp_dir/_easywork" << ZSH_COMP
#compdef easywork
# EasyWork zsh completion — generated by easywork ${EASYWORK_VERSION}
# DO NOT EDIT — regenerated on 'easywork install'

_arguments -C \\
'--yes[跳过交互确认]' \\
'-y[跳过交互确认]' \\
'--dry-run[预览变更不执行]' \\
'--verbose[详细输出]' \\
'-v[详细输出]' \\
'--keep-config[卸载时保留配置文件]' \\
'--remove-config[卸载时删除配置文件]' \\
"1:命令:(install uninstall config version update help ${mod_list})" \\
'*::参数:->args'

case "\$state" in
args)
case "\${line[1]}" in
install | uninstall) compadd ${mod_list} ;;
config) compadd edit show ;;
esac
;;
esac
_easywork() {
local -a commands modules
commands=(install uninstall config version update help)
modules=(${mod_list})

_arguments -C \\
'--yes[跳过交互确认]' \\
'-y[跳过交互确认]' \\
'--dry-run[预览变更不执行]' \\
'--verbose[详细输出]' \\
'-v[详细输出]' \\
'--keep-config[卸载时保留配置文件]' \\
'--remove-config[卸载时删除配置文件]' \\
"1:命令:(\$commands \$modules)" \\
'*::参数:->args'

case "\$state" in
args)
case "\${line[1]}" in
install | uninstall) compadd \$modules ;;
config) compadd edit show ;;
esac
;;
esac
}

# Register explicitly — this file is sourced directly, not autoloaded via fpath
compdef _easywork easywork
ZSH_COMP

local rc_file
rc_file="$(_detect_rc_file)"
[[ -f "$rc_file" ]] || touch "$rc_file"
# ── Inject source lines into all detected shell rc files ──
local injected_any=false
local sh_type rc_file source_line
while IFS=: read -r sh_type rc_file; do
[[ -f "$rc_file" ]] || touch "$rc_file"

local sh_type source_line
sh_type="$(detect_shell)"
if [[ "$sh_type" == "zsh" ]]; then
source_line="fpath=($comp_dir \$fpath)"
else
source_line="source $comp_dir/easywork.bash # EasyWork completion"
fi
if [[ "$sh_type" == "zsh" ]]; then
source_line="source $comp_dir/_easywork # EasyWork completion"
else
source_line="source $comp_dir/easywork.bash # EasyWork completion"
fi

if ! grep -qF "$source_line" "$rc_file" 2> /dev/null; then
echo "" >> "$rc_file"
echo "$source_line" >> "$rc_file"
log_success "Shell 补全已配置 (${rc_file})"
if ! grep -qF "# EasyWork completion" "$rc_file" 2> /dev/null; then
printf '\n%s\n' "$source_line" >> "$rc_file"
injected_any=true
log_info "Shell 补全已注入: ${rc_file}"
fi
done < <(_detect_completion_rc_files)

if $injected_any; then
log_success "Shell 补全已配置(重启 shell 或运行 source 后生效)"
fi
}

Expand All @@ -375,20 +418,17 @@ _remove_completions() {
fi

local comp_dir="$HOME/.easywork/completions"
local rc_file source_line sh_type
rc_file="$(_detect_rc_file)"
sh_type="$(detect_shell)"

if [[ -f "$rc_file" ]]; then
if [[ "$sh_type" == "zsh" ]]; then
source_line="fpath=($comp_dir \$fpath)"
else
source_line="source $comp_dir/easywork.bash # EasyWork completion"
local sh_type rc_file tmpfile

# Remove completion source lines from all detected rc files
while IFS=: read -r sh_type rc_file; do
if [[ -f "$rc_file" ]]; then
tmpfile="${rc_file}.tmp.$$"
grep -vF "# EasyWork completion" "$rc_file" > "$tmpfile" 2> /dev/null || true
mv "$tmpfile" "$rc_file"
log_info "已从 ${rc_file} 移除补全配置"
fi
local tmpfile="${rc_file}.tmp.$$"
grep -vF "$source_line" "$rc_file" > "$tmpfile" 2> /dev/null || true
mv "$tmpfile" "$rc_file"
fi
done < <(_detect_completion_rc_files)

rm -f "$comp_dir/easywork.bash" "$comp_dir/_easywork"
rmdir "$comp_dir" 2> /dev/null || true
Expand Down
85 changes: 84 additions & 1 deletion tests/test_modules.bats
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,15 @@ EOF
grep -q "_easywork()" "$comp_dir/easywork.bash"
grep -q "complete -F _easywork easywork" "$comp_dir/easywork.bash"

# Zsh completion must contain #compdef
# Zsh completion must contain #compdef and explicit compdef registration
grep -q "#compdef easywork" "$comp_dir/_easywork"
grep -q "compdef _easywork easywork" "$comp_dir/_easywork"

# .zshrc source line must use 'source', not the broken 'fpath=' approach
# (check .zshrc was injected with the correct source line)
if [[ -f "${TEST_HOME}/.zshrc" ]]; then
! grep -q "fpath=" "${TEST_HOME}/.zshrc" 2> /dev/null || true
fi
}

@test "completions: generated bash completion is context-aware" {
Expand Down Expand Up @@ -321,3 +328,79 @@ EOF
[[ "$found_edit" == "true" ]]
[[ "$found_show" == "true" ]]
}

@test "completions: zsh completion file uses explicit compdef not fpath autoload" {
run "${EASYWORK_ROOT}/bin/easywork" install --yes
local comp_dir="${TEST_HOME}/.easywork/completions"

# Must contain the explicit compdef registration
grep -q "compdef _easywork easywork" "$comp_dir/_easywork"

# The _easywork file defines its own function (not just an autoload stub)
grep -q "_easywork()" "$comp_dir/_easywork"

# .zshrc (if created) must NOT use the broken fpath approach
if [[ -f "${TEST_HOME}/.zshrc" ]]; then
! grep -q "fpath=" "${TEST_HOME}/.zshrc" 2> /dev/null || true
fi
}

@test "completions: injects source line into both bash and zsh rc files" {
# Create both rc files ahead of time
touch "${TEST_HOME}/.bashrc"
touch "${TEST_HOME}/.zshrc"
touch "${TEST_HOME}/.bash_profile"

run "${EASYWORK_ROOT}/bin/easywork" install --yes

# Both should have the EasyWork completion marker
grep -qF "# EasyWork completion" "${TEST_HOME}/.bashrc"
grep -qF "# EasyWork completion" "${TEST_HOME}/.zshrc"
}

@test "completions: removal cleans up from all rc files and preserves user content" {
# Pre-create rc files so _detect_completion_rc_files includes them on all platforms
touch "${TEST_HOME}/.bashrc"
touch "${TEST_HOME}/.zshrc"

# First install to set up manifest and completions
run "${EASYWORK_ROOT}/bin/easywork" install --yes
[[ "$status" -eq 0 ]]

# Append user content to rc files after completion lines were injected
echo "# my bash alias" >> "${TEST_HOME}/.bashrc"
echo "# my zsh setting" >> "${TEST_HOME}/.zshrc"

# Verify completion lines exist before uninstall
grep -qF "# EasyWork completion" "${TEST_HOME}/.bashrc"
grep -qF "# EasyWork completion" "${TEST_HOME}/.zshrc"

# Run uninstall to trigger _remove_completions via _finalize_uninstall
run "${EASYWORK_ROOT}/bin/easywork" uninstall --yes
[[ "$status" -eq 0 ]]

# Completion lines must be gone
! grep -qF "# EasyWork completion" "${TEST_HOME}/.bashrc" 2> /dev/null || true
! grep -qF "# EasyWork completion" "${TEST_HOME}/.zshrc" 2> /dev/null || true

# User content must be preserved
grep -q "my bash alias" "${TEST_HOME}/.bashrc"
grep -q "my zsh setting" "${TEST_HOME}/.zshrc"
}

@test "completions: idempotent injection does not duplicate lines" {
touch "${TEST_HOME}/.bashrc"

# First install
run "${EASYWORK_ROOT}/bin/easywork" install --yes
local count1
count1=$(grep -cF "# EasyWork completion" "${TEST_HOME}/.bashrc" || echo "0")

# Second install (manifest will already exist, triggering same_version mode)
run "${EASYWORK_ROOT}/bin/easywork" install --yes
local count2
count2=$(grep -cF "# EasyWork completion" "${TEST_HOME}/.bashrc" || echo "0")

[[ "$count1" -eq 1 ]]
[[ "$count2" -eq 1 ]]
}
Loading