#!/bin/sh set -u SCRIPT_DIR=$(CDPATH=; cd "$(dirname "$0")" && pwd) || exit 1 MY_PI_LIB=$SCRIPT_DIR/scripts/lib.sh if [ ! -f "$MY_PI_LIB" ]; then printf '错误: 找不到共享辅助脚本:%s\n' "$MY_PI_LIB" >&2 exit 1 fi # shellcheck source=scripts/lib.sh . "$MY_PI_LIB" PACKAGE_SOURCE=${PI_PACKAGE_SOURCE:-git:git@bitbucket.org:siakitem/my-pi.git} POWERLEVEL10K_PRESET_FILE=$SCRIPT_DIR/config/p10k.zsh CODEGRAPH_INSTALL_URL=https://raw.githubusercontent.com/colbymchenry/codegraph/main/install.sh KITTY_INSTALL_URL=https://sw.kovidgoyal.net/kitty/installer.sh OH_MY_ZSH_INSTALL_URL=https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh POWERLEVEL10K_REPOSITORY=https://github.com/romkatv/powerlevel10k.git ZSH_AUTOSUGGESTIONS_REPOSITORY=https://github.com/zsh-users/zsh-autosuggestions.git ZSH_SYNTAX_HIGHLIGHTING_REPOSITORY=https://github.com/zsh-users/zsh-syntax-highlighting.git install_failed=0 ask_yes_no() { prompt=$1 printf '%s [y/N] ' "$prompt" if ! IFS= read -r answer; then answer=n fi case $answer in y|Y|yes|YES|Yes) return 0 ;; *) return 1 ;; esac } ask_to_install() { ask_yes_no "未检测到 $1。是否现在安装?" } ask_to_enable() { ask_yes_no "尚未启用 $1。是否现在安装所需文件并启用?" } require_brew() { if command -v brew >/dev/null 2>&1; then return 0 fi warn "自动安装该依赖需要 Homebrew,但当前 PATH 中没有 brew。" warn "请先安装 Homebrew(https://brew.sh/),然后重新运行本脚本。" return 1 } record_install_failure() { warn "$1 安装失败;其余依赖仍会继续检查。" install_failed=1 } java_major_version() { java_version=$(java -version 2>&1 | awk -F '"' 'NR == 1 { print $2 }') case $java_version in 1.*) java_version=${java_version#1.} ;; esac java_major=${java_version%%.*} case $java_major in ''|*[!0-9]*) return 1 ;; *) printf '%s\n' "$java_major" ;; esac } install_codegraph() { if ! command -v curl >/dev/null 2>&1; then warn "安装 CodeGraph 需要 curl。" return 1 fi codegraph_installer=$(mktemp "${TMPDIR:-/tmp}/my-pi-codegraph.XXXXXX") || return 1 if ! curl -fsSL "$CODEGRAPH_INSTALL_URL" -o "$codegraph_installer" || \ ! sh "$codegraph_installer"; then rm -f "$codegraph_installer" return 1 fi rm -f "$codegraph_installer" # 官方安装器默认使用 ~/.local/bin;让本次检查立即看到新命令。 if [ -d "$HOME/.local/bin" ]; then PATH="$HOME/.local/bin:$PATH" export PATH fi command -v codegraph >/dev/null 2>&1 } install_kotlin_lsp() { require_brew || return 1 HOMEBREW_NO_ASK=1 brew install JetBrains/utils/kotlin-lsp || return 1 command -v kotlin-lsp >/dev/null 2>&1 } install_java_21() { require_brew || return 1 HOMEBREW_NO_ASK=1 brew install openjdk@21 || return 1 java_home=$(brew --prefix openjdk@21 2>/dev/null) || return 1 PATH="$java_home/bin:$PATH" export PATH installed_java_major=$(java_major_version || true) if [ -z "$installed_java_major" ] || [ "$installed_java_major" -lt 21 ]; then return 1 fi say "已在本次安装流程中使用 $java_home/bin。若新终端仍找不到 Java,请把该目录加入 PATH。" } install_jdtls() { require_brew || return 1 HOMEBREW_NO_ASK=1 brew install jdtls || return 1 command -v jdtls >/dev/null 2>&1 } install_kitty() { case $(uname -s) in Darwin) require_brew || return 1 HOMEBREW_NO_ASK=1 brew install --cask kitty || return 1 ;; *) if ! command -v curl >/dev/null 2>&1; then warn "安装 Kitty 需要 curl。" return 1 fi installer_file=$(mktemp "${TMPDIR:-/tmp}/my-pi-kitty.XXXXXX") || return 1 if ! curl -fsSL "$KITTY_INSTALL_URL" -o "$installer_file" || \ ! sh "$installer_file"; then rm -f "$installer_file" return 1 fi rm -f "$installer_file" ;; esac detect_kitty } kitty_solarized_dark_configured() { resolve_kitty_config_dir [ -f "$kitty_config_dir/current-theme.conf" ] || return 1 [ -f "$kitty_config_dir/kitty.conf" ] || return 1 grep -F "Solarized Dark" "$kitty_config_dir/current-theme.conf" >/dev/null 2>&1 || return 1 grep -E '^[[:space:]]*include[[:space:]]+([^#[:space:]]*/)?current-theme\.conf([[:space:]]*(#.*)?)?$' \ "$kitty_config_dir/kitty.conf" >/dev/null 2>&1 } configure_kitty_solarized_dark() { "$kitty_bin" +kitten themes --reload-in=none "Solarized Dark" } oh_my_zsh_installed() { resolve_zsh_paths [ -f "$oh_my_zsh_dir/oh-my-zsh.sh" ] } install_oh_my_zsh() { if ! command -v curl >/dev/null 2>&1; then warn "安装 Oh My Zsh 需要 curl。" return 1 fi if ! command -v git >/dev/null 2>&1; then warn "安装 Oh My Zsh 需要 git。" return 1 fi resolve_zsh_paths oh_my_zsh_installer=$(curl -fsSL "$OH_MY_ZSH_INSTALL_URL") || return 1 RUNZSH=no CHSH=no KEEP_ZSHRC=yes ZSH="$oh_my_zsh_dir" \ sh -c "$oh_my_zsh_installer" "" --unattended --keep-zshrc || return 1 oh_my_zsh_installed } prepare_zshrc() { resolve_zsh_paths zshrc_dir=${zshrc_file%/*} [ "$zshrc_dir" = "$zshrc_file" ] && zshrc_dir=. mkdir -p "$zshrc_dir" || return 1 [ -f "$zshrc_file" ] || : > "$zshrc_file" || return 1 } zshrc_has_oh_my_zsh_loader() { [ -f "$zshrc_file" ] || return 1 grep -E '^[[:space:]]*(source|\.)[[:space:]].*oh-my-zsh\.sh' "$zshrc_file" >/dev/null 2>&1 } ensure_oh_my_zsh_loader() { prepare_zshrc || return 1 if zshrc_has_oh_my_zsh_loader; then return 0 fi replace_zsh_managed_block "oh-my-zsh-loader" "append" "" \ "export ZSH=\"$oh_my_zsh_dir\"" \ "source \"\$ZSH/oh-my-zsh.sh\"" } set_powerlevel10k_theme() { prepare_zshrc || return 1 replace_zsh_managed_block "powerlevel10k-theme" "before" \ "$MY_PI_ZSH_LOADER_ANCHOR" \ 'ZSH_THEME="powerlevel10k/powerlevel10k"' } p10k_config_sourced() { my_pi_managed_block_exists "$zshrc_file" "powerlevel10k-config" } ensure_p10k_config_source() { prepare_zshrc || return 1 replace_zsh_managed_block "powerlevel10k-config" "append" "" \ "[[ ! -f \"$p10k_managed_config_file\" ]] || source \"$p10k_managed_config_file\"" } configure_powerlevel10k() { resolve_zsh_paths powerlevel10k_dir=$zsh_custom_dir/themes/powerlevel10k if [ ! -f "$powerlevel10k_dir/powerlevel10k.zsh-theme" ]; then if ! command -v git >/dev/null 2>&1; then warn "安装 Powerlevel10k 需要 git。" return 1 fi mkdir -p "$zsh_custom_dir/themes" || return 1 git clone --depth=1 "$POWERLEVEL10K_REPOSITORY" "$powerlevel10k_dir" || return 1 fi my_pi_sync_managed_file "$POWERLEVEL10K_PRESET_FILE" "$p10k_managed_config_file" || return 1 set_powerlevel10k_theme || return 1 ensure_oh_my_zsh_loader || return 1 ensure_p10k_config_source || return 1 say "已应用仓库内置的 Powerlevel10k Rainbow/ASCII 受管配置。" } zsh_plugin_enabled() { plugin=$1 [ -f "$zshrc_file" ] || return 1 awk -v target="$plugin" ' { line = $0 sub(/[[:space:]]*#.*/, "", line) if (line ~ /^[[:space:]]*plugins[[:space:]]*[+]?=/) active = 1 if (active) { gsub(/[()=+]/, " ", line) count = split(line, fields, /[[:space:]]+/) for (i = 1; i <= count; i++) if (fields[i] == target) found = 1 if ($0 ~ /\)/) active = 0 } } END { exit(found ? 0 : 1) } ' "$zshrc_file" } add_zsh_plugin() { plugin=$1 prepare_zshrc || return 1 ensure_oh_my_zsh_loader || return 1 replace_zsh_managed_block "zsh-plugin-$plugin" "before" \ "$MY_PI_ZSH_LOADER_ANCHOR" \ "plugins+=($plugin)" } install_and_enable_zsh_plugin() { plugin=$1 repository=${2:-} if [ -n "$repository" ] && [ ! -d "$zsh_custom_dir/plugins/$plugin" ]; then if ! command -v git >/dev/null 2>&1; then warn "安装 $plugin 需要 git。" return 1 fi mkdir -p "$zsh_custom_dir/plugins" || return 1 git clone --depth=1 "$repository" "$zsh_custom_dir/plugins/$plugin" || return 1 fi add_zsh_plugin "$plugin" } check_zsh_plugin() { plugin=$1 description=$2 repository=${3:-} if zsh_plugin_enabled "$plugin"; then say "✓ $plugin 已启用" elif ask_to_enable "$description"; then install_and_enable_zsh_plugin "$plugin" "$repository" || record_install_failure "$plugin" else say "- 已跳过 ${plugin}。" fi } if ! command -v pi >/dev/null 2>&1; then warn "找不到 pi,无法安装组合包。请先安装 Pi 并确保 pi 位于 PATH。" exit 127 fi say "正在安装 Pi 组合包:$PACKAGE_SOURCE" if ! pi install "$PACKAGE_SOURCE"; then warn "pi install 执行失败,已停止后续依赖检查。" exit 1 fi say "Pi 组合包安装完成。" say "开始检查终端环境和机器级 CLI 依赖。" kitty_ok=0 if detect_kitty; then kitty_ok=1 say "✓ Kitty 已安装(${kitty_bin})" elif ask_to_install "Kitty 终端"; then if install_kitty; then kitty_ok=1 say "✓ Kitty 安装完成" else record_install_failure "Kitty" fi else say "- 已跳过 Kitty;不会检查 Solarized Dark 主题。" fi if [ "$kitty_ok" -eq 1 ]; then if kitty_solarized_dark_configured; then say "✓ Kitty 已配置 Solarized Dark" elif ask_yes_no "Kitty 尚未配置 Solarized Dark。是否现在配置?"; then configure_kitty_solarized_dark || record_install_failure "Kitty Solarized Dark 主题" else say "- 已跳过 Kitty Solarized Dark 主题。" fi fi oh_my_zsh_ok=0 if oh_my_zsh_installed; then oh_my_zsh_ok=1 say "✓ Oh My Zsh 已安装(${oh_my_zsh_dir})" elif ask_to_install "Oh My Zsh"; then if install_oh_my_zsh; then oh_my_zsh_ok=1 say "✓ Oh My Zsh 安装完成" else record_install_failure "Oh My Zsh" fi else say "- 已跳过 Oh My Zsh;不会检查 Powerlevel10k 和 Zsh 插件。" fi if [ "$oh_my_zsh_ok" -eq 1 ]; then resolve_zsh_paths powerlevel10k_dir=$zsh_custom_dir/themes/powerlevel10k if [ -f "$powerlevel10k_dir/powerlevel10k.zsh-theme" ] && \ my_pi_managed_block_exists "$zshrc_file" "powerlevel10k-theme" && \ [ -f "$p10k_managed_config_file" ] && \ cmp -s "$POWERLEVEL10K_PRESET_FILE" "$p10k_managed_config_file" && p10k_config_sourced; then say "✓ Powerlevel10k 已安装并配置" elif ask_yes_no "Powerlevel10k 尚未完整配置。是否安装并应用仓库内置的 Rainbow/ASCII 配置?"; then configure_powerlevel10k || record_install_failure "Powerlevel10k" else say "- 已跳过 Powerlevel10k。" fi check_zsh_plugin "git" "git 插件" "" check_zsh_plugin "zsh-autosuggestions" "zsh-autosuggestions(命令自动建议)" \ "$ZSH_AUTOSUGGESTIONS_REPOSITORY" check_zsh_plugin "zsh-syntax-highlighting" "zsh-syntax-highlighting(语法高亮)" \ "$ZSH_SYNTAX_HIGHLIGHTING_REPOSITORY" fi if command -v codegraph >/dev/null 2>&1; then say "✓ codegraph 已安装" elif ask_to_install "CodeGraph(提供 codegraph_explore)"; then install_codegraph || record_install_failure "CodeGraph" else say "- 已跳过 CodeGraph;codegraph_explore 将不可用。" fi if command -v kotlin-lsp >/dev/null 2>&1; then say "✓ kotlin-lsp 已安装" elif ask_to_install "kotlin-lsp(Kotlin LSP)"; then install_kotlin_lsp || record_install_failure "kotlin-lsp" else say "- 已跳过 kotlin-lsp;Kotlin LSP 功能将不可用。" fi java_ok=0 if command -v java >/dev/null 2>&1; then java_major=$(java_major_version || true) if [ -n "$java_major" ] && [ "$java_major" -ge 21 ]; then java_ok=1 say "✓ Java $java_major 已安装(满足 Java 21+)" fi fi if [ "$java_ok" -eq 0 ]; then if ask_to_install "Java 21+(JDT LS 运行时)"; then if install_java_21; then java_ok=1 else record_install_failure "Java 21+" fi else say "- 已跳过 Java 21+;JDT LS 可能无法启动。" fi fi if command -v jdtls >/dev/null 2>&1; then say "✓ jdtls 已安装" elif ask_to_install "jdtls(Java LSP)"; then install_jdtls || record_install_failure "jdtls" else say "- 已跳过 jdtls;Java LSP 功能将不可用。" fi say "终端环境与依赖检查完成。RTK 命令改写默认关闭,因此未检查可选的 rtk CLI。" exit "$install_failed"