#!/usr/bin/env bash # # JetBrains IDE plugin installer for macOS, Linux (Ubuntu Desktop) and WSL2. # # Detects every locally installed JetBrains IDE (Toolbox + standalone) and, # on Linux, any JetBrains Gateway remote-dev-server.sh distributions. Lets # you multi-select IDE(s) and plugin(s) interactively, then installs each # plugin into each selected IDE. # # Picker UX: # - Uses fzf with --multi when fzf is on PATH (TAB to toggle, Enter to confirm). # - Otherwise prints a numbered list and accepts space-separated indexes # (e.g. "1 3 5"), exclusions (e.g. "!1 !4" to select all except 1 and 4), # or "a" for all. # # Usage: # ./install-jetbrains.sh # interactive # ./install-jetbrains.sh --help set -euo pipefail IFS=$'\n\t' # ------------------------------------------------------------------ # Canonical marketplace plugin list (xmlId | display label). # Both columns separated by a literal pipe; the label is for the picker. # ------------------------------------------------------------------ readonly PLUGIN_CATALOG=( "com.intellij.ml.llm|JetBrains AI Assistant" "izhangzhihao.rainbow.brackets|Rainbow Brackets" "IdeaVIM|IdeaVim" "org.sonarlint.idea|SonarQube for IDE (SonarLint)" "Key Promoter X|Key Promoter X" "net.ashald.envfile|EnvFile" "org.intellij.qodana|Qodana" ) # Known JetBrains IDE launcher basenames (Toolbox shims + Linux .sh names). readonly KNOWN_IDE_NAMES=( idea idea.sh pycharm pycharm.sh webstorm webstorm.sh goland goland.sh rubymine rubymine.sh clion clion.sh phpstorm phpstorm.sh datagrip datagrip.sh rustrover rustrover.sh rider rider.sh studio studio.sh fleet ) usage() { cat <&2; usage >&2; exit 2 ;; esac done # ------------------------------------------------------------------ # OS detection # ------------------------------------------------------------------ OS_KIND="" case "$(uname -s)" in Darwin) OS_KIND=macos ;; Linux) if grep -qi microsoft /proc/version 2>/dev/null; then OS_KIND=wsl2 else OS_KIND=linux fi ;; *) echo "Error: unsupported OS '$(uname -s)'. Use install-jetbrains.ps1 on Windows." >&2 exit 1 ;; esac # ------------------------------------------------------------------ # Helpers # ------------------------------------------------------------------ # is_known_ide_name -> 0 if it matches a known JetBrains launcher. is_known_ide_name() { local n="$1" local k for k in "${KNOWN_IDE_NAMES[@]}"; do [[ "$n" == "$k" ]] && return 0 done return 1 } # pretty_from_path /path/to/idea.sh -> "IntelliJ IDEA (~/.local/share/.../idea.sh)" # Builds a short, readable label for the picker. pretty_label() { local path="$1" base base="$(basename "$path")" base="${base%.sh}" base="${base%.cmd}" base="${base%.exe}" case "$base" in idea|idea64) printf 'IntelliJ IDEA' ;; pycharm|pycharm64) printf 'PyCharm' ;; webstorm|webstorm64) printf 'WebStorm' ;; goland|goland64) printf 'GoLand' ;; rubymine|rubymine64) printf 'RubyMine' ;; clion|clion64) printf 'CLion' ;; phpstorm|phpstorm64) printf 'PhpStorm' ;; datagrip|datagrip64) printf 'DataGrip' ;; rustrover|rustrover64) printf 'RustRover' ;; rider|rider64) printf 'Rider' ;; studio|studio64) printf 'Android Studio' ;; fleet) printf 'Fleet' ;; *) printf '%s' "$base" ;; esac printf ' (%s)' "$path" } # Translate a Linux/WSL home into the Windows-side user dir under /mnt/c. win_user_dir() { local winuser winuser="$(cmd.exe /c 'echo %USERNAME%' 2>/dev/null | tr -d '\r\n' || true)" [[ -z "$winuser" ]] && winuser="$USER" printf '/mnt/c/Users/%s' "$winuser" } # ------------------------------------------------------------------ # IDE candidate scan # Each candidate is recorded as a single line: # ||