#!/usr/bin/env bash set -euo pipefail DOTFILES_REPO="https://github.com/tylerkeyes/dotfiles.git" DOTFILES_NVIM_PATH="shared/.config/nvim" INSTALL_ROOT="${HOME}/.local/opt/nvim" BIN_DIR="${HOME}/.local/bin" CONFIG_DIR="${HOME}/.config" NVIM_CONFIG_DIR="${CONFIG_DIR}/nvim" log() { printf '[nvim-installer] %s\n' "$*" } fail() { printf '[nvim-installer] ERROR: %s\n' "$*" >&2 exit 1 } require_cmd() { local cmd="$1" command -v "${cmd}" >/dev/null 2>&1 || fail "Missing required command: ${cmd}" } detect_os() { case "$(uname -s)" in Darwin) echo "macos" ;; Linux) echo "linux" ;; *) fail "Unsupported OS. Only macOS and Linux are supported." ;; esac } detect_arch() { case "$(uname -m)" in x86_64|amd64) echo "x86_64" ;; arm64|aarch64) echo "arm64" ;; *) fail "Unsupported architecture: $(uname -m)" ;; esac } latest_nvim_tag() { local latest_url latest_url="$(curl -fsSL -o /dev/null -w '%{url_effective}' "https://github.com/neovim/neovim/releases/latest")" [[ -n "${latest_url}" ]] || fail "Failed to resolve latest Neovim release." echo "${latest_url##*/}" } asset_candidates() { local os="$1" local arch="$2" if [[ "${os}" == "macos" && "${arch}" == "x86_64" ]]; then echo "nvim-macos-x86_64.tar.gz nvim-macos.tar.gz" return fi if [[ "${os}" == "macos" && "${arch}" == "arm64" ]]; then echo "nvim-macos-arm64.tar.gz nvim-macos.tar.gz" return fi if [[ "${os}" == "linux" && "${arch}" == "x86_64" ]]; then echo "nvim-linux-x86_64.tar.gz nvim-linux64.tar.gz" return fi if [[ "${os}" == "linux" && "${arch}" == "arm64" ]]; then echo "nvim-linux-arm64.tar.gz" return fi fail "No download asset mapping found for ${os}/${arch}" } download_nvim_archive() { local tag="$1" local os="$2" local arch="$3" local out_file="$4" local candidates candidate url candidates="$(asset_candidates "${os}" "${arch}")" for candidate in ${candidates}; do url="https://github.com/neovim/neovim/releases/download/${tag}/${candidate}" log "Trying ${candidate}" if curl -fL "${url}" -o "${out_file}"; then log "Downloaded ${candidate}" return 0 fi done fail "Could not download a Neovim release asset for ${os}/${arch} from tag ${tag}." } install_nvim() { local tag="$1" local archive="$2" local unpack_dir install_dir extracted_dir unpack_dir="$(mktemp -d)" tar -xzf "${archive}" -C "${unpack_dir}" extracted_dir="$(find "${unpack_dir}" -mindepth 1 -maxdepth 1 -type d | head -n1)" [[ -d "${extracted_dir}" ]] || fail "Unable to find extracted Neovim directory." install_dir="${INSTALL_ROOT}/${tag}" mkdir -p "${INSTALL_ROOT}" "${BIN_DIR}" rm -rf "${install_dir}" mv "${extracted_dir}" "${install_dir}" ln -sfn "${install_dir}" "${INSTALL_ROOT}/current" ln -sfn "${INSTALL_ROOT}/current/bin/nvim" "${BIN_DIR}/nvim" ln -sfn "${INSTALL_ROOT}/current/bin/nvim" "${BIN_DIR}/vim" ln -sfn "${INSTALL_ROOT}/current/bin/nvim" "${BIN_DIR}/vi" log "Installed Neovim ${tag} at ${install_dir}" rm -rf "${unpack_dir}" } install_config() { local tmp_root tmp_repo src timestamp backup_dir tmp_root="$(mktemp -d)" tmp_repo="${tmp_root}/dotfiles" git clone --depth=1 --filter=blob:none --sparse "${DOTFILES_REPO}" "${tmp_repo}" >/dev/null ( cd "${tmp_repo}" git sparse-checkout set "${DOTFILES_NVIM_PATH}" ) src="${tmp_repo}/${DOTFILES_NVIM_PATH}" [[ -d "${src}" ]] || fail "Expected Neovim config path not found in repo: ${DOTFILES_NVIM_PATH}" mkdir -p "${CONFIG_DIR}" if [[ -e "${NVIM_CONFIG_DIR}" ]]; then timestamp="$(date +%Y%m%d%H%M%S)" backup_dir="${NVIM_CONFIG_DIR}.backup.${timestamp}" mv "${NVIM_CONFIG_DIR}" "${backup_dir}" log "Backed up existing config to ${backup_dir}" fi cp -R "${src}" "${NVIM_CONFIG_DIR}" log "Installed Neovim config to ${NVIM_CONFIG_DIR}" rm -rf "${tmp_root}" } print_path_hint() { case ":${PATH}:" in *":${BIN_DIR}:"*) return 0 ;; esac log "${BIN_DIR} is not on your PATH." log "Add this line to your shell profile (~/.zshrc, ~/.bashrc, etc.):" printf 'export PATH="%s:$PATH"\n' "${BIN_DIR}" } main() { require_cmd curl require_cmd tar require_cmd git require_cmd uname require_cmd find local os arch tag tmp_archive os="$(detect_os)" arch="$(detect_arch)" tag="$(latest_nvim_tag)" tmp_archive="$(mktemp)" trap 'rm -f "${tmp_archive}"' EXIT log "Detected ${os}/${arch}" log "Latest Neovim release tag: ${tag}" download_nvim_archive "${tag}" "${os}" "${arch}" "${tmp_archive}" install_nvim "${tag}" "${tmp_archive}" install_config print_path_hint log "Done. Run 'nvim --version' to verify." } main "$@"